本文使用 AWS Cloud Development Kit(AWS CDK)在托管式 Amazon Bedrock Knowledge Base 上构建企业级代理式检索。要理解为什么值得构建它,不妨先看看普通的检索增强生成(RAG)在哪些地方会失效。大多数 RAG 系统的起步方式都一样:将问题嵌入,从单一索引中获取最相近的文本块,交给模型,然后返回答案。一次检索,一次生成。对于一大类问题而言,这就是正确的设计,没有理由构建更复杂的方案。
它以一种可识别的方式失效。问一个答案横跨两个语料库的问题,单次检索只会返回模型所需内容的一半,这比什么都返回不了更糟,因为返回的答案流利却不完整。问一个 正确来源取决于问题本身的问题,单一索引没有任何机制来做出选择。问一个需要在看到第一个结果之后再回看语料库的问题,而系统没有任何地方可以进行第二次查看。
企业级代理式检索通过在流程中引入一个具备推理能力的代理来解决这三个问题。该代理决定是否检索、查询哪个知识库,以及是否在组织答案之前再进行一轮检索。这正是 Amazon Bedrock Knowledge Bases 的发展方向。随着托管式知识库的推出,检索不再是一次简单的嵌入加获取调用,而是成为一种代理式能力:多轮规划、工具驱动的检索,以及有依据、带引用的答案。AgenticRetrieveStream API 将这一能力作为一等服务特性提供,而无需您自行组装。这确实可行,但同时也带来了一个真正更难的运维问题。单次执行的管道是一个读一遍代码就能推理的函数;而一个在循环中推理和检索的代理,对每个问题都会走出不同的路径。默认情况下,您既看不到它走过的路径,也无法判断它给出的答案是否可靠。可观测性不再是锦上添花,而成为了解系统是否正常工作的唯一途径。
2024年8月, 使用 Knowledge Bases for Amazon Bedrock 和 AWS CDK 构建端到端 RAG 解决方案 一文以 CDK 应用程序的形式部署了单次执行的设计:一个 Amazon Simple Storage Service (Amazon S3) 语料库、一个 Amazon OpenSearch Serverless 集合和索引、一个知识库和一个数据源,均以 CDK 堆栈的形式预置,其顺序需要您手动编排。自那篇文章发布以来,有三件事发生了变化。
- Amazon Bedrock 现在提供托管式知识库 (类型:MANAGED),它负责数据的摄取、存储、索引和检索本身。那篇文章所预置的 OpenSearch Serverless 集合和索引不再是您需要创建、扩展或按 OpenSearch Compute Units(OCU)付费的内容。
- Amazon Bedrock AgentCore 提供了代理式的部分:一个将每个知识库暴露为 Model Context Protocol(MCP)工具的 Gateway,一个托管推理代理的 Runtime,以及 AgenticRetrieveStream,它在内部进行重排序并返回综合的、带引用的答案。代理式检索 仅 在托管式知识库上可用。
- 可观测性和评估能力现在是可以预置的内容,而非事后附加的东西。本方案附带两个 Amazon CloudWatch 仪表板,覆盖七层遥测数据,并提供两种评估方式(按需和持续),全部来自同一次部署。
本文构建的就是这样一个系统:一个可在两个知识库之间路由、通过 Gateway 检索并返回有依据、带引用答案的代理,其行为的每一层都经过插桩,作为一个使用 Python 编写的单一 AWS CDK 应用程序。一条命令即可部署全部内容,顺序由 CDK 自动推导。
如果你读过 2024年的那篇文章,那么从Amazon S3存储桶到知识库的路径你不会陌生,而其后的所有内容都是新的:向量存储没有了,检索变成了代理式的,而且应用所预置的内容中大约有一半的存在意义是告诉你这个代理做了什么。如果你没读过,这篇文章本身也是独立成立的。
涉及的服务以及一个问题在其中经历的路径:agent 决定是否检索以及查询哪个知识库,Gateway 将每个知识库暴露为它可调用的工具,托管知识库进行搜索并给出引用,然后循环根据问题的需要重复任意次数。agent 在此过程中所做的一切都会记录到 Amazon CloudWatch 中。
图 1:所涉及的服务以及单个问题在其中经历的路径
背景
在演练之前,最好先明确三点:什么使检索具有 agentic 特性、为什么托管知识库是其正确的基石,以及为什么可观测性和评估应属于设计本身而不是事后附加。
从 RAG 到企业级 agentic 检索
经典的检索增强生成(RAG)只做一次检索和一次生成。 企业级 agentic 检索 将一个推理 agent 置于循环中:它决定 是否 以及 检索什么 ,可以多次检索以进行细化,选择 哪个 知识库是相关的(语义路由),然后才编写带有引用的有据可依的答案。借助托管知识库,这是一项一流的服务能力,AgenticRetrieveStream API 执行多轮规划、执行检索并返回带引用的答案,而不是需要你在原始向量搜索之上构建和维护的编排层。这在复杂问题上能产生更好的答案,但也产生了一个运营上更复杂的系统,这正是从一开始就内置可观测性和评估的原因。
托管式与自建知识库的对比
Amazon Bedrock 现在提供一个 托管知识库 (类型:MANAGED):Amazon Bedrock 为你管理 摄取、存储、索引和检索 ,默认情况下包括使用服务托管模型进行的嵌入和重排序,因此 无需预置、扩展或修补向量数据库。这不同于 客户自管 (DIY)知识库,在那种模式下你需要自己选择并运营向量存储(例如 Amazon OpenSearch Serverless)。
对本解决方案而言最重要的两个区别:
| 能力 | Amazon Bedrock 托管 | 客户自管(DIY) |
| Agentic 检索(AgenticRetrieveStream) | ✅ 支持 | ❌ 不支持 |
| AgentCore Gateway 集成 | ✅ 支持 | ❌ 不支持 |
| 数据存储 | 自动扩展,由 Amazon Bedrock 全托管 | 由你自行配置、扩展和维护 |
| 嵌入 + 重排序 | 内置托管模型,无额外成本 | 由你自行配置 |
| 需要管理的基础设施 | 无 | 向量数据库 + 更多 |
由于代理式检索和 AgentCore Gateway 连接器仅在托管知识库上可用,它自然成为企业级代理式检索的基础。这也意味着 Amazon Bedrock 会为你管理存储和索引,因此在此设计中无需配置 OpenSearch Serverless 集合或 OCU 容量。当你想选择并运营自己的向量存储时,例如复用现有的 OpenSearch Serverless 集合,或满足特定的数据驻留、调优或合规要求时,客户自管选项仍然是正确的选择。对于企业级代理式检索而言,托管知识库正是实现该能力的关键。
为什么需要可观测性与评估
一个返回答案的 代理系统 不足以投入生产。你需要观察它的行为(延迟、调用量、token 使用量),评估检索和答案的质量,并且需要持续获得这些信号。本解决方案 提供 两个 CloudWatch 仪表板,覆盖七层遥测数据,外加两种评估方式(按需和持续),全部由同一个 CDK 应用程序配置。接地、引用和评估层也支持负责任的使用。带引用的答案可追溯至其来源段落,从而保持响应可审计;忠实度和正确性评分让你能及时发现答案偏离其来源的情况,而不是在生产环境中才察觉。
为什么此方案选用 CDK
任何基础设施即代码工具都可以创建这些资源。CDK 在此架构中有其独特的立足理由: 这四个堆栈之间存在密集的交叉引用。Gateway 需要两个知识库 id。agent 需要 Gateway 的 URL 和 ARN。仪表板需要 KB id、Gateway ARN、 以及 agent 运行时 id,共七个跨越堆栈边界的值,每一个都是某个指标上的维度或某个连接器上的目标。
如果写成原始模板,那就是一组手动保持一致的堆栈导出和跨堆栈查询,再加上一个按正确顺序部署并向前传递输出的脚本,而一旦有任何东西被重命名,两者都会发生漂移。在 CDK 中,你传递的是 construct,导出和依赖边都从这一个传递动作中派生出来。2024 年的方案有三个堆栈和一条基本线性的依赖链。当有四个堆栈和七个在它们之间传递的值时,这种差异就不再是风格问题了。
解决方案概述
以下是一个问题在系统中端到端所经过的路径。值得慢慢细看,因为本文的其余部分将按从左到右的顺序逐步讲解。
下图展示了端到端的智能体检索工作流程。用户的问题通过 AgentCore Runtime 进入智能体,随后经由 AgentCore Gateway 路由到两个 AgenticRetrieveStream 工具之一,每个受管知识库对应一个工具,分别为金融和天气,两者均从 Amazon S3 摄取数据。所选工具返回带有依据和引用的答案,并且每个步骤都会流式传输到 Amazon CloudWatch 和 AWS X-Ray。编号徽章对应下方的操作演示。
图2:针对单个问题的端到端智能体检索工作流程
图中的编号步骤对应以下工作流程。
第 0 步——一次性操作,在部署时执行。 在提出任何问题之前,Amazon S3 中的每个语料库都会被摄取到各自的 Managed Knowledge Base 中:Amazon Bedrock 会自动对其进行解析、分块、嵌入和索引。这一步只在堆栈部署时执行一次,并不属于每次提问的路径,因此绘制为虚线箭头并标记为步骤 0。之后的每个问题都会遵循步骤 1–6:
- 用户向智能体发送一个问题 托管在 Amazon Bedrock AgentCore Runtime 上。该 Runtime 使用 OpenTelemetry(OTEL)span 自动对整个“推理-行动”循环进行插桩,因此系统从第一次调用起就是可观测的。
- 该智能体的推理模型会在知识库之间进行规划与路由。 因为每个知识库都作为自己的检索工具暴露,智能体会选择其主题与问题相匹配的工具(财务或天气),而不是搜索一个混合索引。
- 所选的工具调用由 AgentCore Gateway 通过 MCP 进行代理, 由代理的 IAM 任务角色授权并使用 SigV4 签名,该角色调用 Managed Knowledge Base 上该知识库的 AgenticRetrieveStream API。
- AgenticRetrieveStream 负责知识库内部的工作: 它将问题分解为子查询,从 Amazon Bedrock 管理的数据存储(由 Amazon S3 中的语料库摄入)中迭代检索,并综合出一个有依据、带引用的答案,通过 Gateway 流式传回。
- 代理检查返回的上下文是否足够。 如果不足,它会循环,再次进行路由和检索,然后撰写最终答案。如果足够,它将带引用的答案返回给用户。
- 在整个过程中,Runtime 会发出 span、token 计数和指标 发送到 Amazon CloudWatch 和 AWS X-Ray,填充七层可观测性层,并为按需评估和持续评估评分提供数据。
在内部,该工作流是一个 CDK 应用, app.py它声明了四个栈。你无需选择顺序,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,
)
这就是全部的接线。这四个构造函数调用取代了导出/导入的簿记工作和部署脚本。
| # | 栈 | 它创建的内容 | 备注 |
| 1 | -kb | S3 存储桶、语料库上传、两个 Type: MANAGED 知识库、数据源、IAM、摄入 | 知识库达到 ACTIVE 状态,语料库已摄入 |
| 2 | -gateway | Gateway(AWS_IAM / MCP)、每个知识库的 AgenticRetrieveStream 目标 | 无需 Cognito |
| 3 | -agent | ECR、CodeBuild、在 Runtime 上插桩的 Strands agent、交付的日志/追踪、在线评估 | ~8–10 分钟 (镜像构建) |
| 4 | -dashboards | agentic-observability + kb-observability 仪表板 | 在你产生流量之前为空 |
该 agent 运行一个推理-行动循环:它进行思考(一次大语言模型(LLM)调用)、决定调用哪个 KB 工具、读取通过 Gateway 返回的内容,并经常在生成最终的、带引用的答案之前再次检索。每一步都由 Runtime 自动插桩,因此七层可观测性由真实流量填充。
七层。 L1 KB 原生指标、L2 摄取、L3 agentic 检索质量、L4 Gateway/MCP 指标、L5 OTEL span 树、L6 token 用量,以及 L7 评估分数。第 1、4 和 5 层自动生成。第 3、6 和 7 层由驱动 notebook 作为自定义指标发布。
所有可调节的内容都集中在一个文件中, 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 会为你提供整个解决方案的独立命名副本,因此两个部署可以在同一账户中共存。驱动笔记本读取同一个常量,因此它会自动跟随重命名,无需手动保持堆栈名称同步。
描述数据集
该解决方案附带两个小型语料库,捆绑在仓库的 data/ 目录下:
- 金融 — 一份合成的 Octank Financial 10-K(octank_financial_10K.pdf,约 198 KB)。
- 天气 — 一份 真实、公开可用的 美国国会研究服务处关于龙卷风的报告(IF12695,tornadoes_report.pdf,约 560 KB)。
⚠️ 关于数据: 金融语料库是 合成的。Octank 是一家虚构的公司,该 10-K 是模型生成的示例数据(CC0 许可)。它不是 真实金融数据。该天气语料库是一份真实的、属于公有领域的美国政府作品,是一份国会研究服务处报告,公开可获取于 sgp.fas.org/crs/misc/IF12695.pdf (美国政府的作品不受版权保护)。要使用您自己的数据,请将文件放在 data/ 目录下并调整其中的前缀设置 config.py, 无需其他更改。
这两个语料库是有意区分的,因此代理必须将每个问题路由到正确的知识库,即语义路由方案。由于这些是托管知识库(Managed Knowledge Bases),我们无需配置分块、嵌入或索引:在摄取时,Amazon Bedrock 会解析每个 PDF,进行分块,使用其托管模型进行嵌入,并自动建立索引。
我们使用两个独立的知识库,各自通过 Gateway 暴露为单独的检索工具,而不是用一个知识库同时存放两个语料库——这一选择是刻意的。给 agent 两个命名工具使路由成为一种可观察的显式推理行为:span 树显示 其中 它为给定问题选择的工具,以及第3层中的检索质量指标在每个知识库之间保持可分离,因此金融和天气的精确度从不会混为一个数字。若用一个工具同时覆盖两个语料库,这种区分就会被打破,路由决策将移入单一索引的排序内部、变得不可见,而每个质量信号都会成为跨两个不相关领域的平均值。保持语料库及其工具相互分离,正是让智能体的行为清晰可读的关键,这也是整个练习的核心目的。
在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值得了解,恰恰因为替代方案太诱人了:如果没有它,触发摄入的自定义资源往往会增加一个上传步骤,于是这一个资源就要承担两个不相关的工作。在这里,它只需要负责 ingest.
托管式摄入会自动扫描、分块、嵌入并索引每份文档:每个知识库索引一份文档,零失败。
图3:托管式摄入结果,每个知识库索引一份文档且零失败
部署该解决方案
本节将演示如何用一条命令部署整个解决方案。在满足前提条件后, cdk deploy --all 会按照CDK推导出的依赖顺序部署全部四个技术栈,并自动传递每一个跨技术栈的值。
前提条件
- 一个具有 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 span 落入 aws/spans,以支持第 5–7 层。
- Node.js 18+ 和 CDK CLI(
npm install -g aws-cdk),以及 Python 3.13+。 - 账户和区域已为 CDK 引导(cdk bootstrap),每个账户/区域执行一次。
- 无需本地 Docker,智能体镜像由 CodeBuild(ARM64)构建。
一条命令
要部署该解决方案,请克隆仓库、创建虚拟环境、安装依赖项,然后运行 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
前两条命令克隆示例并进入项目目录。接下来两条命令创建并激活 Python 虚拟环境,并安装 CDK 应用的依赖项。 cdk bootstrap 为账户和区域准备 CDK(每个账户/区域只需执行一次),而 cdk deploy --all 会预置全部四个堆栈。
账户和区域来自您的 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 区域服务列表.
这个版本中没有部署脚本,而这正是重点所在: cdk deploy --all 会遍历它从 app.py推导出的依赖图,按顺序部署四个堆栈,并自动传递每个跨堆栈的值。终端会随着每个堆栈的完成进行报告:首先是 bmkb-obs-cdk-kb [1/4](默认的 PROJECT_NAME),然后是 gateway [2/4],接着是 agent [3/4],最后是 dashboards [4/4],这个顺序是推导出来的,而不是声明的:它由堆栈之间的构造引用自然得出,因此添加一个消费现有输出的第五个堆栈会自行重新排序部署。
Stack -agent 使用 CodeBuild 构建 agent 容器,因此该阶段大约需要 8–10 分钟。其他三个堆栈很快。每个堆栈完成后都会打印其输出,你可以在那里获取知识库 ID、Gateway URL、runtime ARN 以及两个仪表板 URL。部署完成后,所有四个堆栈都处于完成状态。以下截图显示部署成功后的 AWS CloudFormation 控制台。 cdk deploy --all.
图 4:部署成功后的 AWS CloudFormation 控制台,四个堆栈均为 CREATE_COMPLETE
在控制台上,全部四个堆栈(kb、gateway、agent 和 dashboards)都显示 CREATE_COMPLETE,每个堆栈的描述中都显示了它在推导出的依赖顺序中的 [n/4] 位置。
关于长时间运行资源的一点说明
此解决方案中有两件事远远超过了 AWS Lambda 函数 15 分钟上限所能应付的天真轮询方式:KB 摄取同步和 CodeBuild 镜像构建。AWS CDK Provider 框架通过一个 on_event / is_complete 拆分来处理这个问题:一个处理程序启动工作,另一个处理程序被轮询直到报告完成。
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 先运行)。它会读取堆栈输出,然后向已部署的 agent 发送特定主题的提示, 每个 KB 一个会话,这样你可以在一个地方同时看到答案和语义路由:财务问题由 Octank 10-K 回答,天气问题由龙卷风报告回答,每个问题都自动路由到匹配的 AgenticRetrieveStream 工具。
Notebook 从堆栈输出中发现所有内容,因此无需手动粘贴任何内容:
# 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', [])}
该 notebook 导入与应用 相同 config.py 的配置,因此两者对堆栈名称的理解永远不会不一致。
驱动程序通过已部署的 agent 发送针对每个 KB 的提示。财务问题路由到财务 KB,天气问题路由到天气 KB,每个都返回有依据、带引用的答案。
图 5:针对每个 KB 的提示被路由到匹配的知识库,每个都返回有依据、带引用的答案
该 notebook 还根据 agent 实际检索和引用的内容计算出无参考的 Layer 3 检索质量信号——无需 ground truth,无需额外的评判器——并将其发布到 CloudWatch。
图 6:每个知识库的无参考 Layer 3 检索质量信号
观察:七层仪表板
两个仪表板是七层变为可见之处。技术栈会创建这两个仪表板,而驱动流量则使其填充数据。以下截图显示了这两个仪表板在 CloudWatch 控制台中的样子。
图 7:CloudWatch 控制台中的两个仪表板——端到端智能体可观测性和每个 KB 的可观测性
两个仪表板都出现在 CloudWatch 控制台中:一个用于端到端智能体可观测性,一个用于每个 KB 的可观测性(BMKB,Bedrock Managed Knowledge Base)。本节其余部分将逐一介绍每一层展示的内容。
在此版本中,仪表板由类型化对象构建,而不是 JSON 字符串。一个指标是 Metric,一个部件是 GraphWidget,布局是一个 Row 列表:
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时、在你的笔记本电脑上、大约一秒内失败。同样的错误若发生在 DashboardBody 字符串中,会成功部署并在 AWS 管理控制台上渲染为损坏的部件,你只能通过目视才能发现问题。
因为 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)、token 用量(L6),并且由于智能体使用 AgenticRetrieveStream——它返回合成的、带引用的答案而非逐 chunk 的分数——因此展示无参考的 Layer 3 质量信号,内联定义并按 KB 绘制。
在仪表板 A 上,Layer 3 无参考质量信号按知识库分别绘制。首先是金融 KB。下图绘制了它的 retrieval_utilization、grounded_coverage 和 duplicate_rate。
图 8:金融 KB 的 Layer 3 质量,绘制检索利用率、grounded coverage 和重复率
天气 KB 显示同样的三个信号,但分别跟踪,因此两个领域不会模糊成一个数字。
图 9:天气 KB 的 Layer 3 质量,与金融 KB 分开跟踪
这两张图分别绘制金融 KB(左)和天气 KB(右)的 retrieval_utilization、grounded_coverage 和 duplicate_rate,因此每个知识库的检索质量保持可区分,而不是被平均成一个数字。
仪表板 B——每个 KB(BMKB)的可观测性。 同样决定支出的运营信号,按 KB 展示:索引大小(来自 S3 源字节)、retrieve 调用量、智能体工具调用、会话 token 用量,以及按模型划分的生成 token 用量,每一项都附有内联说明。
以下截图显示仪表板 B 的四个指标部件,均为一次运行后填充的数据。第一个部件报告索引大小,按知识库划分,由 S3 源字节推导而来。
图 10:仪表板 B 的每个知识库索引大小,由 Amazon S3 源字节推导
第二个部件统计每个知识库的 retrieve 调用次数。
图11:仪表板 B 每个知识库的检索调用次数
第三个小组件统计通过网关(Gateway)进行的代理工具调用。这一项在两个知识库(KB)之间共享,而不是分开统计。
图12:仪表盘B通过AgentCore Gateway进行的代理工具调用次数,两个知识库共用
第四个小组件报告每个知识库的会话令牌使用情况。
图 13:Dashboard B 中每个知识库的会话令牌使用情况
这四个小部件分别显示索引大小(来自 S3 源字节)、检索调用量、通过 Gateway 的代理工具调用次数以及会话令牌使用量。除 Gateway 工具调用次数为两者共享外,其余均按知识库分别列出。
有一个尺寸细节值得特别指出,因为类型化 API 不会为你捕捉到它: AWS/Bedrock/KnowledgeBases 命名空间需要一个带前缀的 id, knowledge-base/<id>,而该解决方案自身的 BMKB/* 命名空间使用裸 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 层:跨度树
Layer 5 是 aws/spans 中原始的 OTEL span 树,它是直接向你展示代理循环的层。值得仔细阅读,因为嵌套结构并不直观:一次 LLM 调用会产生三个 span。The Strands invoke_agent span 包裹该轮次,一个内部 chat span 包裹了模型调用,并且一个 chat <model> 类别的跨度 CLIENT 是实际调用,三者都重复了相同的 gen_ai.usage.* token 属性。
以下截图展示了 aws/spans 中的 span 树,顶部几行可见三个 span 的嵌套结构。
图14:aws/spans 中的第 5 层 span 树,展示单次 LLM 调用的三个 span 嵌套
阅读最上面三行:一个 invoke_agent Strands Agents 聚合,然后一个内部 chat span,再一个 CLIENT chat span,三者都报告完全相同的 7,299 个输入和 268 个输出 token。这是同一次 LLM 调用在树的三个层级上出现,这正是聚合需要谨慎处理的原因。
当你进行聚合时,这种结构就变得重要起来。为了将每次 LLM 调用只计数一次,请筛选到 CLIENT leaf:
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
如果没有该过滤器,同一查询将报告三倍的 token 数和三倍的调用次数。
应用该 CLIENT-leaf 过滤器后,以下截图显示了按模型统计的生成 token 用量,其中每次 LLM 调用只计一次。
图 15:按模型统计的生成 token 用量,每次 LLM 调用只计一次
启用该过滤器后,此次运行显示 14 次 LLM 调用,共计 60,472 个输入 token 和 2,709 个输出 token。如果没有它,同一个查询将报告三倍的调用次数和三倍的 token 数量。
评估:按需与持续
质量通过两种方式衡量,两者均由该应用提供。
按需评估: 驱动笔记本会调用 AgentCore。此版本中没有部署脚本,而这正是关键所在:(LLM-as-judge) 会针对每个会话的 span 运行内置评估器,即 Correctness、Faithfulness 和 Tool-Selection Accuracy,并将分数发布到 CloudWatch,在仪表板 A 上显示为第 7 层。
以下截图显示了仪表板 A 上的第 7 层:按知识库统计的按需评估分数,以及同一仪表板上的第 5 层 OTEL span 表。
图 16:第 7 层按知识库统计的按需评估分数,旁边是第 5 层 span 表
第 7 层显示按知识库统计的按需评估分数,第 5 层 OTEL span 表并排显示,因此分数和产生这些分数的 span 可以在同一块看板上一起查看。
持续(在线)评估: 代理栈还会提供一个 AWS::BedrockAgentCore::OnlineEvaluationConfig 它会采样实时会话并自动为其打分,无需运行笔记本。配置本身可以在 Bedrock AgentCore 控制台的 Assess → Evaluations → Evaluation configurations中查看。结果会落在日志组 /aws/bedrock-agentcore/evaluations/results/<config-id>中,并且每个分数还会作为 CloudWatch 指标发布到该 Bedrock-AgentCore/Evaluations 命名空间,以评估器名称和配置 id 作为键,因此无需解析日志即可绘制分数图表或对其设置告警。
以下截图显示了 AgentCore 控制台中的在线评估配置,路径为 Assess,然后是 Evaluations,再是 Evaluation configurations。
图 17:AgentCore 控制台中的在线评估配置,四个内置评估器的采样率为 100%
该配置列出了四个内置评估器:Tool-Selection Accuracy、Faithfulness、Correctness 和 Response Relevance,均设置为 100% 采样,并显示为 ACTIVE 且已启用。
由于这些分数是普通的 CloudWatch 指标,它们可以像其他任何指标一样绘制图表,无需解析日志,同一个组件也可以配置告警。以下截图显示了这四个评估器分数作为普通 CloudWatch 指标绘制的图表。
图 18:作为普通 CloudWatch 指标绘制的四个持续评估器分数
这四个评估器分数作为指标显示在该 Bedrock-AgentCore/Evaluations 命名空间中,每个分数都以评估配置 id 和代理的服务名称为键,因此无需解析日志即可绘制图表或对其设置告警。
在线结果只会在会话关闭后到达。评分是在已完成的会话上运行的,因此在您产生流量后,结果日志组会在大约会话空闲超时时间(约 30 分钟)内保持为空。运行后日志组为空是预期行为,并非配置错误。每个结果事件都带有 session.id,因此它可以与其他每一层关联。笔记本中的按需路径没有这种等待,这就是两者并存于此的原因。
在此次运行的四个内置评估器中,持续路径在 Correctness、Faithfulness、Response Relevance 和 Tool-Selection Accuracy 上均得分为 1.00,每个评估器有五条被评分的追踪记录,每条结果都附带书面说明。
⚠️ 采样与成本。 此应用设置 SAMPLING_PERCENTAGE = 100 纯粹是为了这次博客实验, 因此每个会话都会被评分,结果立即可见。 这并不是一份生产环境推荐。 在线评估会对每个采样的会话调用一次 LLM-as-judge,因此成本随采样率和流量大小而扩展。对于真实部署,请选择符合您的质量监控需求和预算的采样百分比,并在启用之前使配置与您所在组织自身的策略和成本治理要求保持一致。该比率是一个单一常量, EvalConfig.SAMPLING_PERCENTAGE,在 config.py.
清理
完成后,请删除该解决方案以停止产生费用,因为存储的KB数据、ECR中的代理镜像以及仍处于活跃状态的在线评估在堆栈闲置期间都会产生费用。一条命令即可全部删除:
cdk destroy --all
CDK 会自行按相反的依赖顺序删除堆栈:先是 dashboard,然后是 agent,再是 gateway,最后是 kb——也就是把为部署推导出的同一张依赖图反向走一遍。不需要再维护一个必须与部署脚本保持一致的卸载脚本,这正是将依赖关系只建模一次所带来的较为不显眼却很有价值的特性之一。
有两种移除行为值得了解,因为它们都可以替代手写的清理代码:
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 → 网关 → 代理 → 仪表板 |
| 检索 | 单次 Retrieve,按向量相似度返回前 k 个文本块 | AgenticRetrieveStream,内部进行重排序,返回综合整理并带引用的答案 |
| 选择知识库 | 不适用,只有一个索引 | 代理在两个之间进行语义路由 |
| 对语料库的二次查看 | 无法一次性完成 | 代理在回答前会再次检索 |
| 遥测 | 未配置任何内容 | 七层,两个仪表板 |
| 评估 | 手动:同步、选择模型、提问 | 按需和持续,均已配置 |
| 空闲时的成本下限 | 无论是否查询,OCU 费用都会持续累积 | 仅存储费用 |
这种权衡在两个方向上都真实存在。用单一索引回答单次提问的方式每次查询成本更低、推理更简单,并且在答案就在一个语料库中时完全足够。当情况并非如此时,才选择那种设计。
承担核心工作的 CDK 构造
四个构造承担了此应用的大部分工作,值得逐一指出,因为它们各自替代了人们通常需要手写的内容:
- 提供方 – 适用于生命周期超过 AWS Lambda 15分钟上限的自定义资源。
on_event/is_complete拆分意味着轮询循环中没有任何进程会休眠,而返回 CloudFormation 的响应协议由框架负责,而不是在每个处理程序中内置一个urllib回调。 - BucketDeployment – 上传语料库,因此摄取自定义资源只需负责摄取。
- auto_delete_objects=True 和 empty_on_delete=True取代了手写的
list_object_versions以及删除镜像的循环,否则一个堆栈甚至根本无法被销毁。 - repository.grant_pull_push(project) – 一行代码覆盖 CodeBuild 所需的八个
ecr:*操作,而且拼写正确是天然保证的。
还有来自 Observe 部分的类型化仪表板:用 Metric / GraphWidget / Row 对象代替 DashboardBody JSON 字符串,这使得无效的组件组合从“在控制台上渲染出错”变成“在 cdk synth.”
有两点是 CDK 不会改变的,开始之前都值得了解:
- 自由格式的 JSON 保持 camelCase.
connector_parameters和parameter_values会原样传递,因此 AWS CDK 从 snake_case 到 PascalCase 的转换不适用于它们内部。像bucketName和inclusionPrefixes这样的键必须完全按照 API 期望的方式拼写,否则在资源失败之前没有任何东西会提醒你。 - 字符串类型的值仍然是字符串。 来自 Observe 部分的带引号
knowledge-base/<id>与裸<id>的维度区别对类型系统是不可见的。类型化构造检查的是 结构,而不是 语义.
这两点都不足以成为偏好某个工具的理由。它们是“类型化”所能带来收益的边界,而知道这个边界在哪里,正是让 CDK 用起来令人愉悦而非惊讶的关键所在。
结论
我们基于托管式 Amazon Bedrock Knowledge Base 和 AgentCore 构建了一个完整的企业级智能体检索解决方案:多知识库语义路由、一个推理智能体、七层可观测性,以及按需和持续评估,并将全部内容部署为单个 AWS CDK 应用程序。
有两点值得记取。托管式知识库是移除了基础设施而非增加基础设施:无需预置向量存储,无需创建并等待索引,也没有在系统闲置时不断累积的 OCU 账单项。它也是支持智能体检索和 AgentCore Gateway 连接器的知识库类型,如 Amazon Bedrock 文档中所述。而一旦检索变成了智能体式的,可观测性就不再是可选的。此应用程序所预置的内容中大约有一半是为了回答智能体到底做了什么以及答案是否好用,这并不是智能体系统的额外开销,而是让它可运营的关键部分。
CDK 的编写模型在接缝处证明了自己的价值。在此架构中,有七个值跨越堆栈边界传递,而每一个都是构造引用而非导出/导入对,这也是为什么没有部署脚本、没有拆除脚本,也没有需要记住的固定顺序。
从这里开始,你可以将数据源指向你自己的语料库,把智能体放置在虚拟私有云(VPC)中,根据你的预算和策略调整在线评估的采样率,或者向路由器添加更多知识库。要了解更多信息,请参阅 Amazon Bedrock 详情页以及 Amazon Bedrock 文档。该应用程序、驱动程序笔记本以及自包含的实用工具均位于 随附的 GitHub 仓库中,因此您可以克隆它并使用一条命令部署自己的副本。