この記事では、AWS Cloud Development Kit(AWS CDK)を使用して、Managed Amazon Bedrock Knowledge Base 上にエンタープライズ向けのエージェント型検索を構築します。それが構築する価値のあるものである理由を理解するには、通常のRetrieval Augmented Generation(RAG)がどこで破綻するかから見ていくと分かりやすくなります。ほとんどのRAGシステムは同じように動き始めます。つまり、質問を埋め込みベクトル化し、1つのインデックスから最も近いチャンクを取得し、それらをモデルに渡して、回答を返します。検索は1回、生成は1回です。多くの種類の質問にとって、それが適切な設計であり、それ以上複雑なものを構築する理由はありません。
それは認識可能な形で失敗する。答えが2つのコーパスにまたがる質問をすると、単一の検索がモデルに必要な情報の半分しか返さず、何も返さない場合よりひどいことになる。なぜなら、答えは流暢でありながら不完全な形で返ってくるからだ。 翻訳:適切な情報源は問いによって異なるまた、単一のインデックスには選択する仕組みがありません。最初の検索結果を見た後にコーパスをもう一度見直す必要がある質問をしても、その2回目の検索を行う場所がありません。
エンタープライズ・エージェント型リトリーバルは、推論エージェントをループ内に置くことでこの3つすべてに答えます。エージェントは、検索するかどうか、どのナレッジベースを参照するか、そして回答を作成する前に追加情報を取りに行くかどうかを判断します。これが次の方向性です Amazon Bedrock Knowledge Bases 進化しました。Managed Knowledge Base の登場により、検索はもはや単一の embed-and-fetch 呼び出しではなくなりました。それはエージェント型の機能、つまりマルチターンの計画、ツール駆動の検索、そして根拠に基づいた引用付きの回答へと変わったのです。AgenticRetrieveStream API は、これを自分で組み立てるものではなく、第一級のサービス機能として提供します。それは機能しますが、同時にはるかに難しい運用上の問題も引き起こします。単発のパイプラインは、コードを読めば理解できる関数です。一方、推論と検索をループで行うエージェントは、質問ごとに異なる経路をたどります。デフォルトでは、その経路も、生成された回答が良いものかどうかも、どちらも見ることができません。この場合、オブザーバビリティは「あれば良いもの」ではなく、システムが正しく動作していることを知る唯一の手段となります。
2024年8月に、 Knowledge Bases for Amazon BedrockとAWS CDKを使用してエンドツーエンドのRAGソリューションを構築する 単発(シングルショット)設計をCDKアプリケーションとしてデプロイしました: Amazon Simple Storage Service (Amazon S3) コーパス、そして Amazon OpenSearch Serverless コレクションとインデックス、ナレッジベース、そしてデータソースで、手作業で順序を定めた CDK スタックとしてプロビジョニングされます。公開から現在までに、3つのことが変化しました。
- Amazon Bedrock マネージド型ナレッジベースを提供するようになりました (タイプ: MANAGED)は、取り込み、ストレージ、インデックス作成、取得そのものを所有します。post がプロビジョニングする OpenSearch Serverless のコレクションおよびインデックスは、OpenSearch Compute Units (OCUs) を使って作成、スケール、支払いを行う必要がなくなりました。
- Amazon Bedrock AgentCore エージェント側の機能を提供する「各ナレッジベースをModel Context Protocol(MCP)ツールとして公開するGateway、推論エージェントをホストするRuntime、そして内部でリランクを行い引用付きの統合回答を返すAgenticRetrieveStream。アジェンティック検索は利用可能です」 のみ Managed Knowledge Base上で。
- オブザーバビリティと評価の仕組みは、今やプロビジョニングできるものになりました。、後付けで追加するものではありません。このソリューションには2つの Amazon CloudWatch 7層のテレメトリーにわたるダッシュボードに加え、同じデプロイメントからオンデマンドと継続の2種類の評価を提供します。
この記事ではそのシステムを構築します。2つのナレッジベースを横断してルーティングし、Gatewayを経由して検索を行い、根拠のある引用付きの回答を返すエージェントであり、その動作のすべての階層に計測を施したものを、単一の AWS CDK Python のアプリケーションです。1つのコマンドですべてをデプロイでき、CDK が順序を導き出します。
前回の 2024年の記事をお読みであれば、Amazon S3 バケットからナレッジベースへの経路は見覚えがあるでしょう。その後の内容はすべて新しくなっています。ベクトルストアは廃止され、検索はエージェント型になり、アプリがプロビジョニングする内容の約半分は、エージェントが何をしたかを伝えるために存在しています。お読みでない方にも、この記事はそれ単体で成立する内容です。
処理に関わるサービスと、1つの質問がたどる経路を示します。エージェントは検索するかどうか、どのナレッジベースを参照するかを判断し、Gateway は各ナレッジベースを呼び出し可能なツールとして公開し、Managed Knowledge Bases が検索と引用を行い、このループが質問に必要な回数だけ繰り返されます。エージェントがその過程で行うすべては Amazon CloudWatch に記録されます。
図 1: 関係するサービスと、1つの質問がたどる経路
背景
ウォークスルーの前に、3つの点を確認しておくと役立ちます。検索をアジェンティックにする要素とは何か、なぜ Managed Knowledge Base がその適切な基盤なのか、そしてなぜオブザーバビリティと評価を後付けではなく設計に組み込むべきなのかです。
RAG からエンタープライズアジェンティック検索へ
従来の検索拡張生成(RAG)は、検索を1回、生成を1回行います。 エンタープライズアジェンティック検索 では推論エージェントをループに組み込みます。エージェントは 検索するかどうか 、および 何 を検索するかを判断し、精度を高めるために複数回検索を行い、 どの ナレッジベースが関連するかを選択(セマンティックルーティング)し、その後で引用付きの根拠ある回答を生成します。Managed Knowledge Base では、これはファーストクラスのサービス機能であり、AgenticRetrieveStream API がマルチターンのプランニングを実行し、検索を実行し、引用付きの回答を返します。これは生のベクトル検索の上に構築・運用するオーケストレーションレイヤーではありません。その結果、複雑な質問に対してより良い回答が得られますが、同時に運用がより複雑なシステムにもなります。だからこそ、オブザーバビリティと評価を最初から組み込む必要があるのです。
マネージドと DIY ナレッジベースの比較
Amazon Bedrock は現在、 Managed Knowledge Base (Type: MANAGED)を提供しています。Amazon Bedrock が 取り込み、ストレージ、インデックス作成、検索 を代行し、デフォルトでサービス管理のモデルによる埋め込み(エンベディング)とリランキングを含むため、 プロビジョニング、スケーリング、パッチ適用が必要なベクトルデータベースはありません。これは、ベクトルストア(例:Amazon OpenSearch Serverless)を自分で選択して運用する カスタマーマネージド (DIY)ナレッジベースとは異なります。
このソリューションで最も重要な2つの違いは次のとおりです:
| 機能 | Amazon Bedrock マネージド | カスタマーマネージド(DIY) |
| アジェンティック検索(AgenticRetrieveStream) | サポートあり | 非対応 |
| AgentCore Gateway 統合 | 対応 | サポートされていません |
| データストア | Amazon Bedrock によって完全に管理されるオートスケーリング | プロビジョニング、スケーリング、メンテナンスはご自身で行います |
| 埋め込み + リランキング | 組み込みの管理対象モデル、追加費用なし | これらはユーザーが設定します |
| 管理対象のインフラストラクチャ | None | ベクターDB+その他 |
エージェント型検索および AgentCore Gateway コネクタはマネージド型ナレッジベースでのみ利用可能であるため、マネージド型ナレッジベースがエンタープライズ向けエージェント型検索の自然な基盤となります。これはまた、ストレージとインデックス作成を Amazon Bedrock が管理することを意味し、この設計ではプロビジョニングが必要な OpenSearch Serverless コレクションや OCU 容量はありません。既存の OpenSearch Serverless コレクションの再利用や、データレジデンシー、チューニング、コンプライアンスに関する特定の要件を満たすために、自分でベクターストアを選択して運用したい場合には、カスタマーマネージド型のオプションが依然として最適な選択肢です。エンタープライズ向けエージェント型検索において、この機能を可能にするのはマネージド型ナレッジベースです。
なぜオブザーバビリティと評価が重要なのか
エージェント型システムは 回答を返します は本番環境には不十分です。システムがどのように振る舞うか(レイテンシ、呼び出し回数、トークン使用量)、検索と回答の質がどの程度かを確認する必要があり、それらのシグナルを継続的に得る必要があります。このソリューション 船舶 7層のテレメトリにまたがる2つのCloudWatchダッシュボードに加え、2種類の評価(オンデマンドと継続的)がすべて同一のCDKアプリケーションによってプロビジョニングされます。グラウンディング、引用、評価の各レイヤーは責任ある利用も支えています。引用付きの回答はソースのパッセージまで追跡できるため回答の監査可能性が保たれ、忠実性と正確性のスコアによって、本番環境で問題が判明する前にソースから乖離した回答を検知できます。
なぜこの特定のソリューションにCDKを選ぶのか
これらのリソースは、どのinfrastructure-as-codeツールでも作成できます。CDKがここで採用される理由は、このアーキテクチャ特有のものです。 4つのスタックは緊密に相互参照されている. Gatewayは両方のナレッジベースidを必要とします。エージェントにはGatewayのURLとARNが必要です。ダッシュボードにはKB id、Gateway ARN、 および エージェントのランタイム id、スタック境界をまたぐ7つの値、それぞれがメトリクスの1ディメンション、あるいはコネクタ上の1ターゲットである。
生のテンプレートとして書くと、それは手作業で整合性を保つスタック間のエクスポートとクロススタック参照一式に、正しい順序でデプロイし出力を後段に渡すスクリプトが加わったものであり、何かをリネームした瞬間に両方がずれてしまいます。CDKではコンストラクトを渡すだけで、エクスポートと依存エッジがその渡すという一つの行為から導出されます。2024年のソリューションは3つのスタックと、ほぼ線形の依存チェーンを持っていました。4つのスタックで7つの値がそれらの間を行き交うになると、その違いはもはやスタイルの問題ではなくなります。
ソリューション概要
以下は、一つの質問がシステムをエンドツーエンドでたどる経路です。この投稿の残りの部分ではこれを左から右へ順に解説していくため、じっくり見ておく価値があります。
次の図は、エンドツーエンドのエージェント型検索ワークフローを示しています。ユーザーの質問はAgentCore Runtime上のエージェントに入力され、AgentCore Gatewayを経由して2つのAgenticRetrieveStreamツールのいずれかにルーティングされます。ツールは管理対象ナレッジベースごとに1つずつ、財務用と天気用があり、それぞれAmazon S3から取り込まれています。選択されたツールは根拠に基づく引用付きの回答を返し、すべてのステップはAmazon CloudWatchとAWS X-Rayにストリーミングされます。番号付きのバッジは後述のウォークスルーに対応しています。
図2: 単一の質問に対するエンドツーエンドのエージェント型検索ワークフロー
図中の番号付きステップは次のワークフローに対応しています。
ステップ0 — 一度だけ、デプロイ時に実行。 質問が行われる前に、Amazon S3内の各コーパスはそれぞれの管理対象ナレッジベースに取り込まれます。Amazon Bedrockが解析、チャンク分割、埋め込み、インデックス化を自動的に行います。これはスタックのデプロイ時に一度だけ発生し、質問ごとのパスには含まれないため、破線の矢印で描かれステップ0と示されています。その後、すべての質問はステップ1〜6に従います:
- ユーザーがエージェントに質問を送信します。エージェントは Amazon Bedrock AgentCore Runtime上でホストされています。RuntimeはOpenTelemetry (OTEL) スパンによって推論と行動のループ全体を自動計測するため、最初の呼び出しからシステムを観測できます。
- エージェントの推論モデルが計画を立て、ナレッジベース間をルーティングします。 各ナレッジベースが独自の検索ツールとして公開されているため、エージェントは1つの統合インデックスを検索するのではなく、質問のトピック、財務または天気、に一致するツールを選択します。
- 選択されたツール呼び出しはAgentCore GatewayによってMCP経由で仲介され、エージェントのIAMタスクロールによって認可されSigV4で署名された後、管理対象ナレッジベース上のそのナレッジベースのAgenticRetrieveStream APIを呼び出します。
- AgenticRetrieveStreamがナレッジベース内の処理を行います: 質問をサブクエリに分解し、Amazon Bedrockが管理するデータストア(Amazon S3のコーパスから取り込み)から反復的に検索し、根拠に基づく引用付きの回答を合成してGateway経由でストリーミング返却します。
- エージェントは返されたコンテキストが十分かどうかを確認します。 十分でなければループして再度ルーティングと検索を行い、その後に最終的な回答を構成します。十分であれば、引用付きの回答をユーザーに返します。
- この間ずっと、Runtimeはスパン、トークン数、メトリクスを Amazon CloudWatchとAWS X-Rayに出力し、7つのオブザーバビリティレイヤーを満たすとともに、オンデマンド評価と継続的評価の両方のスコアに供給します。
内部的には、このワークフローは1つのCDKアプリ app.pyであり、4つのスタックを宣言しています。順序を自分で選ぶ必要はなく、CDKが渡されたコンストラクト参照から導出します:
kb_stack = KnowledgeBasesStack(app, f"{project}-kb", env=env)
gateway_stack = AgenticGatewayStack(
app, f"{project}-gateway", env=env,
financial_kb=kb_stack.financial_kb, # ← the reference creates the dependency
weather_kb=kb_stack.weather_kb,
)
agent_stack = AgentRuntimeStack(
app, f"{project}-agent", env=env,
gateway=gateway_stack.gateway,
)
DashboardsStack(
app, f"{project}-dashboards", env=env,
financial_kb=kb_stack.financial_kb,
weather_kb=kb_stack.weather_kb,
gateway=gateway_stack.gateway,
agent_runtime=agent_stack.agent_runtime,
)
これが接続のすべてです。これら4つのコンストラクタ呼び出しが、エクスポート/インポートの管理とデプロイスクリプトを置き換えるものです。
| # | スタック | 作成するもの | 備考 |
| 1 | -kb | S3バケット、コーパスのアップロード、Type: MANAGEDのKB 2つ、データソース、IAM、取り込み | KBがACTIVEになり、コーパスが取り込まれる |
| 2 | -gateway | Gateway (AWS_IAM / MCP)、KBごとのAgenticRetrieveStreamターゲット | Cognitoは不要 |
| 3 | -agent | ECR、CodeBuild、Runtime上での計測済みStrands agent、提供されるログ/トレース、オンライン評価 | 約8~10分 (イメージビルド) |
| 4 | -dashboards | agentic-observability + kb-observability ダッシュボード | トラフィックを流すまでは空の状態 |
エージェントは推論と行動のループを実行します。つまり、思考し(LLM(大規模言語モデル)の呼び出し)、どのKBツールを呼び出すかを決め、Gatewayを通じて返ってきた内容を読み、最終的な引用付きの回答を組み立てる前に再取得することがよくあります。すべてのステップはRuntimeによって自動で計測されるため、7つのオブザーバビリティレイヤーは実際のトラフィックからデータが入ります。
7つのレイヤー。 L1 KBネイティブメトリクス、L2 インジェスト、L3 エージェンティック検索品質、L4 Gateway/MCPメトリクス、L5 OTELスパンツリー、L6 トークン使用量、そしてL7 評価スコアです。レイヤー1、4、5は自動的に出力されます。レイヤー3、6、7はドライバーノートブックによってカスタムメトリクスとして公開されます。
Anything tunable resides in a single file. config.py:
| 設定 | 意味 | デフォルト |
EnvSettings.PROJECT_NAME |
の名前プレフィックス あらゆる スタックとリソース | bmkb-obs-cdk |
EnvSettings.ACCOUNT_REGION |
デプロイ先のAWSリージョン | us-west-2(または使用するプロファイルのリージョン) |
KbConfig.EMBEDDING_MODEL_TYPE |
MANAGED (バンドル版) または CUSTOM (ARN持ち込み) |
MANAGED |
AgentConfig.MODEL_ID |
エージェントの推論モデル、Layer 6 の支出 | Claude Haiku 4.5 |
EvalConfig.SAMPLING_PERCENTAGE |
オンライン評価のサンプリング率、 免責事項をご覧ください | 100 |
変化する PROJECT_NAME ソリューション全体に独立した名前のコピーを与えるもので、2つのデプロイメントが1つのアカウント内で共存できます。ドライバーノートブックは同じ定数を読み込むため、名前変更に自動的に追従し、手動で同期を保つべきスタック名はありません。
データセットを説明してください。
このソリューションには、リポジトリの data/ 以下にバンドルされた2つの小さなコーパスが付属しています:
- 金融 — Octank Financialの合成10-K(octank_financial_10K.pdf、約198 KB)。
- 天気 — A 実際に一般公開されている 米国議会調査局の竜巻に関する報告書(「IF12695, tornadoes_report.pdf、約560 KB)。
データについて: その金融コーパスは 合成Octankは架空の企業であり、10-Kはモデル生成のサンプルデータ(CC0ライセンス)です。これはn’t 実際の金融データです。気象コーパスは実際の米国政府のパブリックドメイン作品であり、議会調査局(Congressional Research Service)の報告書で、以下で公開されています。 sgp.fas.org/crs/misc/IF12695.pdf (合衆国政府の著作物は著作権の対象となりません)。自分のデータを使用するには、data/ の下にファイルを置き、そこにあるプレフィックスを調整してください。 config.py、その他の変更は不要です。
2つのコーパスは意図的に区別されており、エージェントは各質問を適切なナレッジベースにルーティングする必要があります。これがセマンティックルーティングの仕組みです。これらはManaged Knowledge Baseであるため、チャンキング、埋め込み、インデックスの設定は不要です。取り込み時に、Amazon Bedrockが各PDFを解析、チャンク分割し、マネージドモデルで埋め込みを行い、自動的にインデックス化します。
私たちは2つの独立したナレッジベースを使用し、それぞれをGateway経由で個別の検索ツールとして公開しています。両方のコーパスを1つのナレッジベースに格納するのではなく、あえてそうしています。エージェントに2つの名前付きツールを与えることで、ルーティングが明示的な推論の行為になり、それを観察できるようになります。スパンツリーには次のように表示されます: どの 特定の質問に対してエージェントが選択したツールが明確になり、レイヤー 3 の検索品質メトリクスはナレッジベースごとに分離されたままになるため、金融と天気の精度が単一の数値に混ざり合うことはありません。両方のコーパスをまたぐ単一のツールでは、この区別が崩れ、ルーティングの判断が一つのインデックスのランキング内部に移動して見えなくなり、すべての品質シグナルが無関係な2つのドメインにわたる平均値になってしまいます。コーパスとそのツールを分けておくことこそが、エージェントの挙動を把握可能にするのであり、これがこの取り組みの根本的な狙いです。
CDKでは、コーパスをS3に格納するのはBucketDeploymentです。
s3deploy.BucketDeployment(
self, "FinancialCorpus",
sources=[s3deploy.Source.asset(str(ROOT / "data" / "financial"))],
destination_bucket=self.source_bucket,
destination_key_prefix=KbConfig.FINANCIAL_PREFIX,
)
BucketDeployment について知っておく価値があるのは、まさにその代替手段が魅力的に見えるからです: これがないと、取り込み(ingestion)をトリガーするカスタムリソースにアップロード処理が混在して成長しがちになり、1 つのリソースが無関係な 2 つの仕事を担うことになります。ここでは、そのリソースは次のことだけを行えばよいのです。 取り込み.
管理型インジェストは、各ドキュメントを自動でスキャン、チャンク分割、埋め込み、インデックス化します。ナレッジベースごとに1件のドキュメントがインデックス化され、失敗はゼロです。
図3: 管理対象インジェストの結果、ナレッジベースごとに1件のドキュメントがインデックスされ、失敗はゼロ
ソリューションをデプロイする
このセクションでは、1つのコマンドでソリューション全体をデプロイする手順を説明します。前提条件が満たされていることを確認した後、 cdk deploy --all CDK の派生依存順序で 4 つのスタックすべてをプロビジョニングし、スタック間のすべての値を自動的に引き回します。
前提条件
- Amazon Bedrock、AgentCore(Runtime + Gateway)の権限を持つ AWS アカウント、 AWS Identity and Access Management (IAM)、CloudWatch、 AWS X-Ray, Amazon Elastic Container Registry (Amazon ECR), AWS CodeBuild、Amazon S3、 AWS Lambda、および AWS CloudFormation.
- アカウントで利用可能なエージェントのモデル(デフォルト
us.anthropic.claude-haiku-4-5-20251001-v1:0). - CloudWatch Transaction Search が有効化されていること。これにより、OTEL スパンがレイヤー 5〜7 のために aws/spans に書き込まれます。
- Node.js 18+ および CDK CLI(
npm install -g aws-cdk)、さらに Python 3.13+。 - CDK 用にアカウントとリージョンがブートストラップ済みであること(cdk bootstrap)。アカウント/リージョンごとに 1 回だけ必要です。
- ローカルの Docker は不要です。エージェントイメージは CodeBuild によりビルドされます(ARM64)。
1 つのコマンド
このソリューションをデプロイするには、リポジトリをクローンし、仮想環境を作成し、依存関係をインストールして、 cdk deploy --allを実行します。ターミナルから以下のコマンドを実行してください:
git clone https://github.com/aws-samples/amazon-bedrock-samples.git
cd rag/managed-knowledge-bases/07-IaaC/managed-kb-observability-cdk/
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cdk bootstrap # once per account/region
cdk deploy --all
最初の 2 つのコマンドはサンプルをクローンしてプロジェクトディレクトリに移動します。次の 2 つのコマンドは Python 仮想環境を作成して有効化し、CDK アプリの依存関係をインストールします。 cdk bootstrap はアカウントとリージョンを CDK 用に準備し(アカウント/リージョンごとに 1 回だけ実行)、 cdk deploy --all は 4 つのスタックすべてをプロビジョニングします。
アカウントとリージョンは AWS プロファイル(CDK_DEFAULT_ACCOUNT / CDK_DEFAULT_REGION)から取得されるため、編集なしでアプリをデプロイできます。
リージョン利用可能性: マネージドナレッジベース、エージェントリトリーバル(AgenticRetrieveStream)、Amazon Bedrock AgentCore(Runtime および Gateway)は、すべての AWS リージョンで利用できるわけではありません。デプロイ前に、対象リージョンでこれらすべてが提供されていることを確認し、 ACCOUNT_REGION 内の config.py を適宜設定してください。デフォルトは us-west-2 です。最新の利用可能性については、 Amazon Bedrock および Amazon Bedrock AgentCore のドキュメントと、 AWS Regional Services List をご参照ください。.
このバージョンにはデプロイスクリプトはなく、それこそがポイントです: cdk deploy --all は、 app.pyから導出した依存グラフをたどり、4つのスタックを順番にデプロイし、すべてのスタック間の値を自動的に渡します。ターミナルには各スタックの完了が報告され、bmkb-obs-cdk-kb [1/4](デフォルトのPROJECT_NAME)、次にgateway [2/4]、次にagent [3/4]、そしてdashboards [4/4]という順序は、宣言されたものではなく導出されたものです。スタック間のコンストラクト参照から自然に導かれるため、既存の出力を利用する5つ目のスタックを追加すると、デプロイ順序が自動的に並べ替えられます。
Stack -agentはCodeBuildでエージェントコンテナをビルドするため、この段階には8〜10分程度を見込んでください。他の3つは短時間で完了します。各スタックは完了時に出力を表示し、そこでナレッジベースID、Gateway URL、ランタイムARN、両方のダッシュボードURLを確認できます。デプロイが完了すると、4つのスタックすべてが完了状態になります。次のスクリーンショットは、成功した cdk deploy --all.
Figure 4: デプロイ成功後のAWS CloudFormationコンソール。4つのスタックすべてがCREATE_COMPLETEになっています
コンソールでは、kb、gateway、agent、dashboardsの4つのスタックすべてがCREATE_COMPLETEと表示され、それぞれの説明に導出された依存順序における [n/4] の位置が示されています。
実行時間の長いリソースに関する注意
このソリューションには、AWS Lambda関数の15分の上限に対して単純なポーリングでは到底間に合わない処理が2つあります。KBの取り込み同期とCodeBuildのイメージビルドです。AWS CDKのProviderフレームワークは、これを on_event / is_complete の分割で処理します。1つのハンドラーが処理を開始し、完了が報告されるまで2つ目のハンドラーがポーリングされます。
cr.Provider(
self, "IngestSyncProvider",
on_event_handler=on_event, # StartIngestionJob, return the job id
is_complete_handler=is_complete, # GetIngestionJob, report IsComplete
query_interval=Duration.seconds(30),
total_timeout=Duration.minutes(30),
)
処理がブロックされることはなく、CloudFormationへの応答プロトコルは各ハンドラーに手書きされるのではなくフレームワークが所有し、30分の時間枠はプロパティとして設定でき、回避策を実装する必要はありません。
起動とテスト
notebooks/01-drive-and-observe.ipynb を開き、最初から最後まで実行します(pip install -r requirements-notebook.txt を先に実行)。このノートブックはスタックの出力を読み込み、デプロイされたエージェントにトピック別のプロンプトを送信します。 KBごとに1セッションであるため、回答とセマンティックルーティングの両方を1か所で確認できます。財務に関する質問にはOctankの10-Kから、竜巻に関する質問には竜巻レポートから回答が得られ、それぞれ自動的に対応するAgenticRetrieveStreamツールにルーティングされます。
このノートブックはスタックの出力からすべてを検出するため、手動で貼り付ける項目はありません:
# Stack names are derived from the same PROJECT_NAME the CDK app uses, so renaming the
# project in config.py cannot leave this notebook pointing at stale stacks.
from config import EnvSettings
PROJECT = EnvSettings.PROJECT_NAME
STACKS = {suffix: f'{PROJECT}-{suffix}'
for suffix in ('kb', 'gateway', 'agent', 'dashboards')}
def outputs(stack_name):
s = cfn.describe_stacks(StackName=stack_name)['Stacks'][0]
return {o['OutputKey']: o['OutputValue'] for o in s.get('Outputs', [])}
このノートブックは、アプリと 同じ config.py ものをインポートするため、スタック名について両者が食い違うことは決してありません。
ドライバーは、KBごとのプロンプトをデプロイされたエージェントを通じて送信します。財務に関する質問は財務KBに、天気に関する質問は天気KBにルーティングされ、それぞれ根拠に基づき引用付きの回答が返されます。
Figure 5: KBごとのプロンプトが対応するナレッジベースにルーティングされ、それぞれ根拠に基づく引用付きの回答が返される様子
このノートブックはさらに、エージェントが実際に取得・引用した内容に基づいて、参照情報不要のLayer 3検索品質シグナル(正解データ不要、追加のジャッジ不要)を算出し、CloudWatchに発行します。
Figure 6: ナレッジベースごとの参照情報不要のLayer 3検索品質シグナル
観察:7層のダッシュボード
2つのダッシュボードは、7つのレイヤーが可視化される場所です。スタックが両方のダッシュボードを作成し、トラフィックを流すことでデータが埋められていきます。次のスクリーンショットは、CloudWatchコンソールに表示される2つのダッシュボードを示しています。
図7: CloudWatchコンソールに表示される2つのダッシュボード、エンドツーエンドのエージェント観測性とKBごとの観測性
両方のダッシュボードがCloudWatchコンソールに表示されます。1つはエンドツーエンドのエージェント観測性、もう1つはKBごとの観測性(BMKB、Bedrock Managed Knowledge Base)のものです。このセクションの残りの部分では、各レイヤーに何が表示されるかを説明します。
このバージョンでは、ダッシュボードはJSON文字列ではなく型付きオブジェクトから構築されます。メトリクスはMetric、ウィジェットはGraphWidget、レイアウトはRowsのリストです。
cw.GraphWidget(
title="L4 — Gateway metrics (MCP)",
width=12, height=6, period=PERIOD, statistic="Sum",
left=[
self._gateway_metric("Invocations", gateway_arn, "tools/call Invocations"),
self._gateway_metric("SystemErrors", gateway_arn, "SystemErrors"),
self._gateway_metric("UserErrors", gateway_arn, "UserErrors"),
self._gateway_metric("Throttles", gateway_arn, "Throttles"),
],
)
実用上の利点は行数が少ないことではなく、 dashboards_stack.py までの400行になり、同等のDashboardBodyのJSONよりも長くなります。それよりも、プロパティのスペルミスや無効なウィジェットの組み合わせが cdk synth時点、つまりラップトップ上でおよそ1秒で検出されることです。同じミスがDashboardBody文字列の中にあれば、デプロイは成功し、AWS Management Console上で壊れたウィジェットとして表示され、見て初めて気づくことになります。
Rowは任意のウィジェットを受け入れるため、ドキュメントは陳腐化するWikiページではなくレイアウトの一部になります。各ダッシュボードにはTextWidgetがグラフと交互に配置されています:
[cw.TextWidget(width=24, height=7, markdown=self._l3_explainer())],
[
self._quality_widget("L3 · Financial KB — agentic quality (reference-free, 0–1)", fin_id),
self._quality_widget("L3 · Weather KB — agentic quality (reference-free, 0–1)", wx_id),
],
_l3_explainer()は、各比率の定義と読み方を示すmarkdownテーブルを返します。そのため、grounded_coverageを説明するウィジェットは、それをプロットするウィジェットと同じコミットで提供され、ダッシュボードを初めて開いた読者は、リファレンスフリーのメトリクスが何かを推測する必要がありません。
ダッシュボードA — エージェント観測性(エンドツーエンド)。 このボードは、各レイヤーが異なるものをカウントすることの説明で始まります( N 件のクエリに対して、約N 回のエージェント呼び出し、約2N 回の検索、約3N 回のLLM呼び出し、約5N 回のGateway MCPオペレーション、つまり可視化されたエージェントループ)。その後、KBメトリクス(L1)、Gatewayのメトリクスとレイテンシー(L4)、トークン使用量(L6)、そしてエージェントがAgenticRetrieveStreamを使用しており、これはチャンクごとのスコアではなく引用付きの合成された回答を返すため、インラインで定義されKBごとにプロットされるリファレンスフリーのLayer 3品質シグナルが表示されます。
ダッシュボードAでは、Layer 3のリファレンスフリー品質シグナルがナレッジベースごとにプロットされます。まずは金融KBです。次のチャートは、retrieval_utilization、grounded_coverage、およびduplicate_rateをプロットしています。
図8: 金融KBのLayer 3品質。検索利用率、グラウンデッドカバレッジ、重複率をプロット
天気KBも同じ3つのシグナルを示しますが、2つのドメインが1つの数字に混ざることがないよう、個別に追跡されます。
図9: 天気KBのLayer 3品質。金融KBとは別に追跡
この2つのチャートは、金融KB(左)と天気KB(右)のretrieval_utilization、grounded_coverage、およびduplicate_rateをプロットしており、各ナレッジベースの検索品質が1つの数字に平均化されず、分離されたまま保たれます。
ダッシュボードB — KBごとの(BMKB)観測性。 コストにも影響する運用シグナルをKBごとに表示:インデックスサイズ(S3ソースバイトから算出)、検索ボリューム、エージェントのツール呼び出し、セッショントークン使用量、モデル別の生成トークン使用量で、それぞれにインラインの説明が付いています。
次のスクリーンショットは、実行後に入力されたダッシュボードBの4つのメトリクスウィジェットを示しています。最初のウィジェットは、S3ソースバイトから算出されたナレッジベースごとのインデックスサイズを報告します。
図10: ダッシュボードBのナレッジベースごとのインデックスサイズ。Amazon S3のソースバイトから算出
2番目のウィジェットは、ナレッジベースごとのretrieve呼び出し回数をカウントします。
図11: ダッシュボードBのナレッジベースごとのretrieve呼び出し回数
3つ目のウィジェットは、Gatewayを経由するエージェントのツール呼び出しをカウントします。これは、分割ではなく両方のナレッジベースで共有されるものです。
図12: AgentCore Gatewayを経由するダッシュボードBのエージェントツール呼び出し回数。両方のナレッジベースで共有
4つ目のウィジェットは、ナレッジベースごとのセッショントークン使用量を報告します。
図13: ダッシュボードBのナレッジベースごとのセッショントークン使用量
4つのウィジェットは、インデックスサイズ(S3ソースのバイト数から算出)、retrieve呼び出しボリューム、Gatewayを経由するエージェントツール呼び出し、およびセッショントークン使用量を表示します。それぞれ、両方で共有されるGatewayのツール呼び出し回数を除き、ナレッジベースごとに区分されています。
呼び出し可能な型付きAPIでは検出できないため、指摘しておくべきディメンションの詳細が1つあります。それは、 AWS/Bedrock/KnowledgeBases namespaceは接頭辞付きのidを想定しているのに対し、 knowledge-base/<id>、一方ソリューション自身の BMKB/* namespaceは裸のidを使用することです。CDKの観点では、どちらも単なる文字列です。
# L1 — AWS/Bedrock/KnowledgeBases wants the prefixed form.
dimensions_map={"Operation": "Retrieve", "KnowledgeBaseId": f"knowledge-base/{kb_id}"}
# BMKB/* — our own namespaces use the bare id.
dimensions_map={"KnowledgeBaseId": kb_id}
レイヤー5: スパンツリー
レイヤー5はaws/spans内の生のOTELスパンツリーであり、エージェントのループを直接表示するレイヤーです。ネストが自明ではないため、注意深く読む価値があります。1回のLLM呼び出しで3つのスパンが生成されるのです。Strandsの invoke_agent スパンがターンをラップし、内部の chat スパンがモデル呼び出しをラップし、そしてkindが chat <model> の CLIENT スパンが実際の呼び出しであり、3つすべてが同じ gen_ai.usage.* トークン属性を繰り返します。
次のスクリーンショットは、aws/spans内のスパンツリーを示したもので、上位の行に3つのスパンのネストが見えます。
図14: aws/spans内のレイヤー5のスパンツリー。1回のLLM呼び出しにおける3つのスパンのネストを表示
上位3行を読むと、invoke_agentのStrands Agentsアグリゲート、次に内部のchatスパン、そしてCLIENTのchatスパンと続き、すべてが同一の7,299入力トークンと268出力トークンを報告しています。これは同じLLM呼び出しがツリーの3つのレベルに出現しているものであり、集計には注意が必要な理由です。
この構造は、集計を行う瞬間に問題になります。各LLM呼び出しを1回だけカウントするには、 CLIENT リーフにフィルタリングします。
fields attributes.gen_ai.request.model as model,
attributes.gen_ai.usage.input_tokens as in_tok,
attributes.gen_ai.usage.output_tokens as out_tok
| filter ispresent(in_tok) and kind = 'CLIENT' and name like /^chat /
| stats sum(in_tok) as input_tokens, sum(out_tok) as output_tokens,
count(*) as llm_calls by model
このフィルタがないと、同じクエリが3倍のトークンと3倍の呼び出し回数を報告してしまいます。
そのCLIENT-leafフィルタを適用すると、次のスクリーンショットは、各LLM呼び出しを1回としてカウントしたモデル別の生成トークン使用量を示しています。
図15:モデル別の生成トークン使用量。各LLM呼び出しを1回としてカウント
フィルターを適用した状態での実行では、合計60,472トークンの入力と2,709トークンの出力を伴う14回のLLM呼び出しが記録されます。フィルターなしでは、同じクエリで3倍の呼び出し数と3倍のトークン数が報告されることになります。
評価: オンデマンドおよび継続的
品質は2つの方法で測定され、そのどちらもアプリによって提供されます。
オンデマンド: ドライバーノートブックはAgentCoreを呼び出します。このバージョンにはデプロイスクリプトが含まれていませんが、それこそがポイントです。(LLM-as-judge) を各セッションのスパンに対して組み込み評価器である Correctness、Faithfulness、Tool-Selection Accuracy について実行し、スコアを CloudWatch に発行します。そこでスコアは Dashboard A の Layer 7 として表示されます。
次のスクリーンショットは、ダッシュボードA上のLayer 7を示しています。ナレッジベースごとのオンデマンド評価スコアと、同じダッシュボード上のLayer 5のOTELスパンテーブルが表示されています。
図16:レイヤー7のナレッジベースごとのオンデマンド評価スコア、レイヤー5のスパンテーブルの隣
レイヤー7では、ナレッジベースごとのオンデマンド評価スコアが表示され、その隣にレイヤー5のOTELスパンテーブルが並ぶため、スコアとそれを生み出したスパンを1つのボード上で並べて確認できます。
継続的(オンライン): エージェントスタックはさらに、次のものもプロビジョニングします。 AWS::BedrockAgentCore::OnlineEvaluationConfig ライブセッションをサンプリングし、ノートブックの実行を必要とせずに自動でスコアリングするものです。この設定自体は、に表示されています。 「Bedrock AgentCoreコンソールの Assess → Evaluations → Evaluation configurations」. 結果はロググループに配信されます /aws/bedrock-agentcore/evaluations/results/<config-id>、さらにすべてのスコアは Bedrock-AgentCore/Evaluations 評価者名と設定IDをキーとする名前空間に保存されるため、ログを解析することなく、スコアをグラフ化したりアラームを設定したりできます。
次のスクリーンショットは、AgentCoreコンソールのオンライン評価の設定を示しています。「Assess」、「Evaluations」、「Evaluation configurations」の順に移動します。
Figure 17: AgentCoreコンソールにおけるオンライン評価の設定画面。組み込みの4つのエバリュエーターが100パーセントのサンプリング率で構成されている
設定には、Tool-Selection Accuracy、Faithfulness、Correctness、Response Relevance の4つの組み込み評価器が記載されており、いずれもサンプリング率 100 パーセントに設定され、ACTIVE かつ有効と表示されています。
スコアは通常の CloudWatch メトリクスなので、他のメトリクスと同じようにグラフ化でき、ログの解析は不要で、同じウィジェットにアラームを持たせることもできます。次のスクリーンショットは、それら 4 つの評価スコアが通常の CloudWatch メトリクスとしてグラフ化された様子を示しています。
図18: 4つの連続評価スコアを通常のCloudWatchメトリクスとしてグラフ化したもの
4つの評価者スコアは、次のメトリクスとして表示されます: Bedrock-AgentCore/Evaluations 名前空間に、評価設定 ID とエージェントのサービス名ごとにキー付けされて保存されるため、ログを解析することなく、それらをグラフ化したりアラートを設定したりできます。
オンラインの結果は、セッションが終了した後にのみ届きます実行セッションに対してスコアリングが行われるため、トラフィックを流した後も、セッションアイドルタイムアウト(約30分)の間は結果ロググループが空のままになります。実行直後にグループが空であるのは想定された動作であり、設定ミスではありません。各結果イベントには session.id、そのため他のすべての層と結合します。ノートブックのオンデマンド経路にはこのような待ち時間がないため、両方がここにあります。
この実行の4つの組み込み評価器すべてにおいて、連続パスはCorrectness、Faithfulness、Response Relevance、Tool-Selection Accuracyで1.00を記録しました。各評価器につき5つのトレースを採点し、各結果には書面による説明が付いています。
サンプリングとコスト。 このアプリは設定します SAMPLING_PERCENTAGE = 100 完全にブログ実験のためのみそのため、すべてのセッションが採点され、結果はすぐに確認できます。 これは本番環境での推奨ではありません。 オンライン評価ではサンプリングされたセッションごとに LLM-as-judge が呼び出されるため、コストはサンプリング率とトラフィック量に比例して増加します。実際の運用では、品質モニタリングのニーズと予算に合ったサンプリング率を選択し、有効化する前に、組織独自のポリシーおよびコストガバナンスの要件に合わせて設定を調整してください。この率は単一の定数です。 EvalConfig.SAMPLING_PERCENTAGE、における config.py.
クリーンアップ
作業が終わったら、課金が発生し続けないようソリューションを削除してください。保存されたKBデータ、ECR内のエージェントイメージ、およびまだ稼働中のオンライン評価は、スタックがアイドル状態の間も費用がかかります。1つのコマンドですべてを削除できます:
cdk destroy --all
CDKはスタックを依存関係の逆順で自動的に削除します。ダッシュボード、次にエージェント、次にゲートウェイ、次にkbという順序で、デプロイ時に導出したのと同じグラフを逆にたどります。デプロイスクリプトと整合性を保つべきテアダウンスクリプトを別途管理する必要がなく、これこそが依存関係を一度だけモデル化することの、地味ながら非常に価値のある性質のひとつです。
知っておく価値のある2つの削除動作があります。どちらも手書きのクリーンアップコードを置き換えるものだからです。
self.source_bucket = s3.Bucket(
self, "SourceBucket",
removal_policy=RemovalPolicy.DESTROY,
auto_delete_objects=True, # replaces a list_object_versions delete loop
)
self.repository = ecr.Repository(
self, "AgentImageRepo",
removal_policy=RemovalPolicy.DESTROY,
empty_on_delete=True, # replaces an image-delete loop
)
どちらもサンプル衛生管理用に設定されていますが、重要なデータを扱う場合はバケットを以下に切り替えてください RemovalPolicy.RETAIN.
CDKブートストラップスタック(CDKToolkit)とそのステージングバケットは削除されません。これらは他のCDKアプリケーションが依存する可能性がある、アカウント全体で共有されるインフラストラクチャです。
単発実行のソリューションから何が変わったのか
本記事と 2024年のソリューション はどちらもPythonで書かれたCDKアプリケーションで、 cdk deployでデプロイされます。オーサリングツールはここでは共通で、変わったのはその基盤となるアーキテクチャであり、その変更の大部分は削減です:
| 関心事項 | 2024年:単発実行のRAG | 本ソリューション |
| ベクターストア | プロビジョニング、スケーリング、OCUの支払いが必要なOpenSearch Serverlessのコレクションとインデックス | なし。タイプ: MANAGED、Amazon Bedrock がストレージとインデックスを所有 |
| インデックスのライフサイクル | カスタムリソースがインデックスを作成し、検索可能になるまで待機する | 作成するものはなし |
| 埋め込みモデル | ユーザーが選択し、モデルアクセスを付与する | デフォルトでサービス管理 |
| スタック | 3: KB ロール → OpenSearch インフラ → KB + データソース | 4: kb → gateway → agent → dashboards |
| 検索 | 1 回の Retrieve、ベクトル類似度による上位 k 個のチャンク | AgenticRetrieveStream、内部でリランクされ、引用付きの統合回答を返す |
| ナレッジベースの選択 | 該当なし、インデックスは 1 つ | エージェントが 2 つを意味的に振り分ける |
| コーパスの再確認 | 1 回では不可能 | エージェントが回答前に再度検索する |
| テレメトリー | プロビジョニングなし | 7 つのレイヤー、2 つのダッシュボード |
| 評価 | 手動: 同期、モデル選択、質問 | オンデマンドと継続的、両方ともプロビジョニング済み |
| アイドル時のコスト下限 | クエリの有無にかかわらず OCU 料金が発生 | ストレージのみ |
このトレードオフは双方に実際に存在します。単一のインデックスで単発の質問に答える設計は、クエリあたりのコストが安く、考察もしやすく、1 つのコーパスに答えがある場合は十分すぎるほどです。そうでない場合にこの設計を選びましょう。
処理を担う CDK コンストラクト
このアプリでは 4 つのコンストラクトが大部分の処理を担っています。それぞれが、通常なら人々が手書きする何かを置き換えるものであるため、名前を挙げる価値があります:
- Provider – AWS Lambda の15分という上限を超えて存続するカスタムリソース向けです。この
on_event/is_complete分割により、ポーリングループで何もスリープせずに済み、CloudFormation へのレスポンスプロトコルは、各ハンドラーに同梱されたurllibコールバックではなくフレームワークの担当になります。 - BucketDeployment – コーパスをアップロードします。そのため、ingestionカスタムリソースは取り込みのみを行います。
- auto_delete_objects=True および empty_on_delete=True、手書きの
list_object_versions翻訳対象: さらに、スタックがそもそも破棄可能であるために必要となる image-delete ループも含まれます。 - repository.grant_pull_push(project) – 8人に一言
ecr:*CodeBuild に必要なアクションを、構築によって正しいスペルで指定します。
「セクションから型付きダッシュボード。DashboardBody の JSON 文字列ではなく Metric / GraphWidget / Row オブジェクトを使用するもので、これにより無効なウィジェットの組み合わせが「コンソール上で壊れて表示される」から「"], cdk synth.”
CDKが変えない2つのことがあり、始める前にどちらも知っておく価値があります:
- 自由形式のJSONはcamelCaseのままです.
connector_parametersおよびparameter_valuesはそのまま渡されるため、AWS CDKのsnake_caseからPascalCaseへの変換はその内部には適用されません。例えば次のようなキーです。bucketNameおよびinclusionPrefixesAPIが期待する通りの正確な綴りにする必要があり、リソースが失敗するまでその誤りを指摘してくれるものは何もありません。 - 文字列型の値は依然として文字列です。 この
knowledge-base/<id>ベア(素腸)との比較<id>Observeセクションとの次元の違いは型システムからは見えません。型付き構成要素はチェックします 構造ではなく、 意味論.
どちらも一方のツールを他方より優先する理由にはなりません。これらは「型付け」がもたらす恩恵の限界を示すものであり、その限界がどこにあるのかを知っていることこそが、CDK を驚きではなく快適なものにしている最大の要因です。
結論
Managed Amazon Bedrock Knowledge Base と AgentCore 上に、マルチKBセマンティックルーティング、推論エージェント、7層のオブザーバビリティ、オンデマンドと継続的の両方の評価を備えた完全なエンタープライズ向けエージェンティック検索ソリューションを構築し、そのすべてを単一の AWS CDK アプリケーションとしてデプロイしました。
重要なポイントは2つある。マネージド型ナレッジベースは機能を追加するのではなく、インフラを削減した。プロビジョニングすべきベクトルストアはなく、作成して待つインデックスもなく、システムがアイドル状態の間に積み上がるOCUの請求項目もない。また、これはエージェンティック検索とAgentCore Gatewayコネクタをサポートするナレッジベースタイプでもある。次のように記述されている: Amazon Bedrock ドキュメント。「そして、検索がエージェント化されると、オブザーバビリティはもはやオプションではなくなりました。このアプリがプロビジョニングするものの約半分は、エージェントが実際に何をしたのか、その回答が良かったのかを知るために存在しており、それはエージェントシステムにとってのオーバーヘッドではありません。運用可能にする部分なのです。」
CDKのオーサリングモデルは、その継ぎ目にこそ存在価値を発揮します。このアーキテクチャでは7つの値がスタックの境界を越えて受け渡されますが、そのすべてがexport/importのペアではなくconstruct参照であり、だからこそデプロイスクリプトも、テアダウンスクリプトも、覚えておくべき固定の順序も存在しないのです。
ここから、データソースを自分のコーパスに向けること、エージェントを仮想プライベートクラウド(VPC)に配置すること、オンライン評価のサンプリング率を予算やポリシーに合わせて調整すること、あるいはルーターにさらにナレッジベースを追加することができます。詳細については、以下をご覧ください。 Amazon Bedrock 詳細ページおよび Amazon Bedrock ドキュメント。アプリ、ドライバーノートブック、自己完結型ユーティリティはすべて accompanying GitHub リポジトリに含まれているので、クローンして1つのコマンドで自分用のコピーをデプロイできます。