您的多智能体运行刚刚返回了一个完美的答案。摘要清晰,资源正确,没有错误。您的 APM 仪表盘(您已经在运行的应用性能监控:正常运行时间、延迟、错误率)显示 200 OK,延迟正常,一切绿色。
而且您被静默地多收取了大约应付费用的 1.4 倍。
那部分是没有人会向您展示的。嵌套追踪和每个智能体的成本正变得普遍;原始构建块现在很容易找到。如今仍然罕见的是一种数据模型,它能让您 act 在这些数据上:捕获那些看似完全成功但实际上正在烧钱的运行。论文《为什么多智能体 LLM 系统会失败?》(MAST, arXiv:2503.13657)对 7 种最先进的多智能体系统的 150 条追踪进行了人工标注,标注者间一致性达到 kappa=0.88,并测得故障率在 41% 到 86.7% 之间。令人不安的发现:许多这些故障并不会导致崩溃。它们完成了运行。它们看起来一切正常。
在本文中,我构建了一个小型只读的 "AWS Account Investigator" 团队,将真实成本嵌入到每个追踪跨度中,然后用真实的 Amazon Nova Pro 美元重现三种静默浪费模式。您可以在本地以 0 美元运行整个过程。您的 AWS 账户不会被创建、修改或删除任何内容。
如果您只有两分钟,请直接跳转到 独特部分:捕捉静默浪费。虽然前期铺垫很重要,但这一节才是收获。
我在真实的 AWS 账户上花费了大约一周时间:在信任它之前,我花了几天时间探测 SDK 的行为;随后又花了几天时间构建团队,观察追踪设计两次失效;在文档用尽时,我阅读了 SDK 源码。以下内容基于此经验而非快速入门。这些伤痕是有意保留的,因为它们正是能为您节省一周时间的部分。
这篇文章面向已经在构建 AI 智能体但尚未在其下方放置真实可观测性层的人员。您已经了解智能体、工具和团队。当追踪术语(跨度、追踪、OpenTelemetry)对您来说是新内容时,我会在首次出现时进行定义。
目录
- 为什么智能体可观测性是一个不同的问题
- 选择插桩
- 先决条件
- 将 Traccia 添加到您的代码
- 技术栈:原生 AWS 且只读
- 成本桥梁及一个陷阱
- 在追踪中建模多智能体团队
- 独特部分:捕捉静默浪费
- 观察其发生:实时控制面板
为什么代理可观测性是一个不同的问题
传统的应用监控回答三个问题:它是否正在运行,速度是否够快,是否出错。对于 CRUD 服务来说这就足够了,因为工作是确定的,故障模式也很明显。AI 代理打破了这三个假设。它在运行时决定自己的控制流,它调用工具的顺序不是你硬编码的,并且它为每个推理步骤按 token 付费。一次运行可能表现为正常、快速且无错误,但实际上在做错的工作量:重复读取相同数据,在步骤之间拖着臃肿的上下文,在稳定前多循环一次。这些情况不会表现为 500 错误或慢速跨度。它们会出现在账单上,到那时已经是趋势而非单一事件。
因此,代理可观测性必须记录经典 APM 从未需要的内容:代理进行了多少推理循环,它调用了哪些工具,与它被允许调用的工具相比,每一步的 token 数量和美元成本,以及在多代理团队中哪个代理做了什么。这些属性使得不可见的回归变得可见。
这并不是边缘观点。AWS 自己的 Well-Architected Agentic AI Lens 将基线状态(其“Level 1”)定义为正是这个问题:代理成本仅在账户层面可见,Cost Explorer 无法区分代理或工作流,且“团队只能在事后对账单惊喜做出反应,因为缺少按代理和按推理阶段的归因。”以下内容的重点是摆脱 Level 1:使支出能够“在推理循环、代理、工作流和租户层面进行归因,而不仅仅是在账户层面”,这正是 AWS 对目标的描述。
以下是快速的词汇锚点,因为后文依赖此概念。span 是一个带有属性的计时步骤(一次 LLM 调用、一次工具调用、一次 AWS 读取)。trace 是一次工作单元的 span 树。经典 APM 也会记录 span,但仅记录显眼的属性(状态、延迟)。代理可观测性使用相同的 trace 结构,携带代理特定的属性:循环次数、token 数、成本以及哪个代理拥有该步骤。这就是整个想法;下面的内容只是在正确的 span 上添加正确的属性。
在一次运行中,1.4x 的超支只是四舍五入的误差。在企业规模下,这成为预算项和治理问题,并以四种具体方式体现:
- 成本控制。 每次运行的静默超支在 1.03 倍到 1.4 倍之间(这是我在三种浪费模式中测得的真实范围),乘以每天数千次运行和数十个代理后,就是无声泄漏的真实金额,却没有任何报警。在追踪中按代理、按工具记录成本,是归因和控制的唯一方法。
- 责任追究。 当团队出现失误时,需要能够回答“哪个代理、属于哪个团队、花费了多少”。追踪层级的所有权元数据将模糊事件转化为可路由的工单。
- 回归检测。 当提示、模型或工具发生变化时,代理的行为也会随之改变。已知良好的基线加上每次运行的增量,能够在财务部门发现之前,捕捉到提示微调悄然使令牌使用量翻倍的那天。
- 可审计性。 在受监管的环境中,你需要记录代理读取了什么、做出了什么决策以及产生了什么成本。追踪正是这份记录。
-
Python 3.10+ 和 SDK(
strands-agents,strands-agents-tools,traccia,boto3)。仓库在requirements.txt中锁定了确切的已测试版本。 -
AWS 凭证 具有对团队所需服务(Cost Explorer、EC2、CloudWatch、S3、Lambda、IAM、GuardDuty)的只读权限,并额外包含
bedrock:InvokeModel,以便代理能够实际调用模型。该仓库提供了一个可直接使用的策略文件iam/read-only-policy.json;AWS 托管的SecurityAudit+ViewOnlyAccess已覆盖读取权限,但您仍需在这些策略之上添加bedrock:InvokeModel。 -
Amazon Nova Pro 包含两个独立步骤:(a) 在 Bedrock 控制台(
us-east-1,amazon.nova-pro-v1:0)的 Model access 中一次性启用模型访问;(b) 在您的 IAM 策略中允许bedrock:InvokeModel。控制台授予不是 IAM 权限,因此您需要同时具备两者。 - 它是 OpenTelemetry 原生的。 Span、处理器和资源属性在底层都是标准的 OTel,因此数据模型不是专有的,您不会被锁定。
-
它默认情况下运行成本为 $0 且离线。 未设置 API key 时,它会写入本地文件导出器;设置
TRACCIA_API_KEY后,相同的 span 会推送到托管仪表盘。无论哪种方式,span 都是相同的,这使得本地开发和 CI 变得毫不痛苦。 - 它提供的远不止是一个追踪器。 有一个真实的成本引擎,附带一份捆绑的定价快照(涵盖 Nova 和 Claude 等),当该快照过时时会发出过时警告,并为 LangChain、CrewAI、OpenAI Agents 以及原始 OpenAI/Anthropic/Gemini 客户端提供自动插装。如果您使用这些技术栈之一,我手动配置的许多功能本来就会免费获得。
-
span 级别的 agent 身份模型是最佳部分。 span 级别的
agent.id和agent.name会覆盖进程默认值,这正是让单进程团队呈现为四个代理的舰队并实现真实 per-agent 成本的关键。这是一个深思熟虑的设计决定,而非偶然。 - 目前尚未集成 Strands,并且它不直接连接原始 Bedrock。 对于此技术栈,成本桥梁是手动的。这没问题,并且它给予您控制权,但 Strands 集成将消除 AWS 原生开发者设置中的最大部分。
-
成本处理器会静默失败(撰写时)。 如果
llm.model缺失或span.type不是LLM,它会在不报错的情况下跳过该 span。这种宽容行为是可以理解的,但沉默导致我花费了一个下午来调试一个全零的仪表盘。在跳过时添加调试日志可以直接解决此问题,这正是早期工具通常会快速解决的小瑕疵。 -
一些尖锐的边缘仅在源码中可见(撰写时)。
span_scope(parent=None)仍然继承当前上下文(因此,除非你先 detach,否则不同的 agent 轨迹会悄悄合并),且span_scope不是一个上下文管理器(你需要自己调用.end())。如今的文档中这两点都不明显。 -
文档才是真正的缺口。 我通过阅读 SDK(而非文档)了解了身份优先级、
llm.model的要求以及 context-detach 行为。对于一个刚起步、早期版本的产品来说这是可以理解的,而且 SDK 本身足够易读,才使得这成为可能。但更好的文档会让一天的探索变成一小时。
贯穿始终的主题是:看似正确的答案并不代表运行健康。证据存在于追踪中,即你刻意放置的属性上。下面用一句话展示前后对比。之前,典型的演示只会给出整个运行的一个总令牌数,低效运行与高效运行在表现上完全相同。之后,每一步推理和每次 AWS 读取都成为一个携带真实成本、令牌、周期数以及所属代理身份的 span,因此即使两次运行都返回正确答案且都显示 200 OK,只要其中一次的成本高出 43%,它们就不再无法区分。
选择检测手段
当你明确需要在每个 span 上记录每个代理的成本、周期数以及工具调用属性时,接下来的问题是用什么来记录这些信息。你可以使用原始 OpenTelemetry 来完成大部分工作,我本来也打算这么做。我没有这么做的原因是,代理需要一套原生 OpenTelemetry 所不具备的词汇:将令牌数转换为美元、span 级别的代理身份(以便单个进程能够呈现为真实的车队)、所有权元数据,以及按会话分组查看每个代理成本的方式。要么你自己构建这些功能,要么找一个已经支持这些特性的 SDK。
这里有几个选择:LangSmith、Langfuse 和 Arize Phoenix 都能进行 LLM 追踪,根据你的技术栈,每个都值得一看。我在代码库中寻找一个我能信任的工具,对我而言这就意味着两个硬性要求:我能读到源码,并且不会被锁定。Traccia 已清晰满足这两点。SDK 是 开源,采用 Apache-2.0 许可证,并且基于 OpenTelemetry(OTel,供应商中立的开放标准,用于追踪和指标,这就是你不会被锁定在单一后端的原因)。它生成的 span 是标准 OTel,文件导出器无需账号和网络即可工作,我在提交之前就能读到它对我的数据的确切操作(我确实这样做了,而本文后面的源码依据批判正是由此得出的)。它在本地运行成本为 $0;位于 app.traccia.ai 的托管仪表盘是可选的,只有在你需要可视化时才会启用。一个开放且可检查的 SDK,配有可选的商业后端,是我愿意采用的方案,因为 instrumentation 不会把我束缚住。
这就是它被纳入此构建的真正原因:我希望使用原生代理的管道,而不想手动编写;我可以审计的源码;以及真正的 $0 离线路径。它也有一些尖锐的边缘,我遇到了几个;这些在文末有完整记录,而不是被草率带过。
为什么不使用 Amazon Bedrock AgentCore Observability 或 Langfuse,这两个明显的 AWS 原生替代方案?两者都很好,对于许多团队来说,任选其一都是正确的选择。AgentCore Observability 将追踪导出到 CloudWatch,如果你的代理在 AgentCore 运行时运行,这是自然的选择;但 AWS 自身的 Well-Architected 框架在成本差距上直言不讳:“成本报告仅停留在 AWS 账户层面,因此团队无法将主管开销与工作负载执行分开。”按代理计算的美元仍然需要你自行拼凑。Langfuse 是强大的开源现有方案,我很乐意使用它;但这里并未要求我对其进行深入评估。此构建的重点不是 “Traccia 胜过它们”。而是无论你选择哪种追踪器,按代理成本属性和基线-增量检测都是你有意识地连接的内容,而本文将展示具体做法。
先决条件
没有什么特别的。要自己运行这个示例,需要以下三项:
无需 Traccia 账户。未提供 API key 时,它会将追踪写入本地文件,这就是本文中一直使用的 $0 路径。
运行它只需四条命令:
git clone https://github.com/simplynadaf/ai-agent-observability-aws.git
cd ai-agent-observability-aws
python3 -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt
先用您自己的管理员凭据创建一次最小权限策略,然后将其附加到运行该 crew 的人员:
aws iam create-policy \
--policy-name AgentObservabilityReadOnly \
--policy-document file://iam/read-only-policy.json
将 Traccia 添加到您的代码
在介绍团队之前,这里是“将成本写入 span”实际含义的最小版本,因为这是唯一不明显的一步。Traccia 会自动为 LangChain、CrewAI 以及 OpenAI/Anthropic/Gemini 客户端添加检测,因此在这些技术栈上您可以免费获得大部分功能。它尚未对 Strands 或原始 Bedrock 进行挂钩,因此您需要自行记录成本。这是一个简短的函数,而一个属性名称可能会让您栽跟头(下文将详细说明)。
def stamp_llm_cost(span, result, model_id="amazon.nova-pro-v1:0"):
usage = result.metrics.accumulated_usage
in_tok, out_tok = usage["inputTokens"], usage["outputTokens"]
cost = (in_tok / 1000 * 0.0008) + (out_tok / 1000 * 0.0032) # Nova Pro, us-east-1
span.set_attribute("llm.model", model_id) # REQUIRED. wrong key = silently zero
span.set_attribute("span.type", "LLM")
span.set_attribute("llm.usage.prompt_tokens", in_tok)
span.set_attribute("llm.usage.completion_tokens", out_tok)
span.set_attribute("llm.cost.usd", round(cost, 6))
这就是整个想法:读取 SDK 已经给你的 token 用量,根据实际定价转换为美元,并将其附加到 span 上。本文其余部分是在多智能体团队中应用同样的操作,然后读回这些数字。完整的布线(初始化、每个智能体的身份、工具 span)位于仓库中的 src/crew.py。
技术栈:AWS 原生且只读
团队通过 AWS Strands Agents 使用 agents-as-tools 模式在 Amazon Nova Pro (amazon.nova-pro-v1:0) 上运行。名为 investigation_run 的主管将任务委托给三个专门的子智能体。每个智能体都是职责的真正分离,拥有若干只读工具,并且每个工具都会打开自己的实时 span,因此在仪表盘中你可以看到智能体,然后在其下方嵌套显示每个 AWS 读取操作及其真实持续时间。以下是完整的舰队以及每个智能体的确切职责。
1. AWS 账户调查员(主管,investigation_run)
编排器。它不会直接触碰 AWS;它读取用户的问题,决定哪些专家在范围内,将任务委托给他们,并综合生成一份报告。在其追踪 span 上,它记录 agent.delegated_to(本次运行调用了哪些专家),并盖上共享的 session.id,将整个调查串联起来。
委托是基于意图路由的,而不是广播一切。主管的指令非常严格:仅调用用户实际询问领域对应的专家。仅询问成本时,它只会委托给成本分析师,而健康与运维以及安全审计师永不运行。仅询问安全时,只有安全审计师会被触发。只有完整的账户问题(“正在运行什么?有哪些风险?我的花费去向何处?”)才会点亮全部三个。这对追踪和账单很重要:agent.delegated_to 精确显示了哪些专家运行了,并且范围化的问题只需花费全量扫描的一小部分,因为你不需要的智能体根本不会消耗任何 token。你可以在控制面板实时观察这一点:仅成本的提示会点亮一个智能体,而其他两个保持空闲。
| 任务 | 工具 | AWS 只读 API |
|---|---|---|
| 计划 + 委托 |
cost_analyst, health_ops, security_ops
|
直接无(委托) |
![]()
在 Traccia 中,主管的追踪记录了哪些专家参与了本次运行(通过 agent.delegated_to),而共享的 session.id 将四个代理关联为一次调查。
2. 成本分析师 (cost_analyst)
一个只读的 FinOps 专家。它利用三种工具构建完整的支出图景,所有三种工具在其追踪中均表现为独立的工具跨度。
| 任务 | 工具 | AWS 只读 API |
|---|---|---|
| 月度累计总额、月末预测、前五大服务 | cost_forecast |
ce:GetCostAndUsage, ce:GetCostForecast
|
| 上个月的总额,用于环比变化 | last_month_cost |
ce:GetCostAndUsage |
| 每日成本序列,以捕捉峰值 | daily_cost_trend |
ce:GetCostAndUsage |
它在实际运行中报告:实际的 MTD 支出、账户的月末预测总额、按支出排序的前五大服务、环比方向及大致百分比,以及与日均相比的单日最高支出(可能的峰值)。
![]()
成本分析师的追踪:三个 Cost Explorer 工具跨度嵌套在代理下方,每个都有其真实的读取持续时间以及代理自身的每代理成本。这一每代理成本数字正是将一次 "successful" 的运行转变为后来被发现的超支的关键。
3. 健康与运营 (health_ops)
一个只读的 SRE 专家。它用五种工具对账户进行清点并读取健康信号,因而通常是输入 token 消耗最大的代理(因为它链式调用了最多的读取)。
| 任务 | 工具 | AWS 只读 API |
|---|---|---|
| 列出正在运行的 EC2 实例 | running_instances |
ec2:DescribeInstances |
| 读取每个实例的 CPU 利用率 | cpu_utilization |
cloudwatch:GetMetricStatistics |
| 查找未挂载(空闲)的 EBS 卷 | list_volumes |
ec2:DescribeVolumes |
| 清点 Lambda 函数 | list_functions |
lambda:ListFunctions |
| 清点 S3 存储桶 | list_buckets |
s3:ListAllMyBuckets |
它报告的内容包括:实例及其 CPU、卷、函数和存储桶的清单,以及任何显著的健康发现,例如未挂载的 EBS 卷。
![]()
健康 & 运营追踪:五个只读工具跨度,在此次运行中,由于它链式调用了最多的读取,舰队的 token 数量最高(4,772)。
4. 安全审计员(security_ops)
一个只读的安全专家。它运行四项独立检查,每项检查对应一个工具跨度。
| 任务 | 工具 | AWS 只读 API |
|---|---|---|
| 面向互联网(0.0.0.0/0)开放的安全组 | open_security_groups |
ec2:DescribeSecurityGroups |
| 根账户和 IAM 用户的 MFA 缺口 | mfa_findings |
iam:GetAccountSummary, iam:ListUsers, iam:ListMFADevices
|
| 缺少公共访问阻止的 S3 存储桶 | public_s3_buckets |
s3:ListAllMyBuckets, s3:GetPublicAccessBlock
|
| GuardDuty 是否已启用 | guardduty_enabled |
guardduty:ListDetectors |
它会直白地说明每个发现及其风险,并在检查结果正常时明确指出。
![]()
安全审计追踪:四个独立的只读检查,每个对应一个具有实际持续时间的工具跨度。
每个专家在其追踪跨度上标记自身身份,于是一次团队运行后,仪表盘展示出四个各自具有独立令牌和成本档案的不同代理,而不是同一个代理被记录四次。每个代理作为自身的顶级追踪运行,通过共享的 session.id 与其他代理关联,并从目录文件中继承生产所有权(类型、所有者、团队)。每次调用都是 describe 或 get,不涉及 create、modify、delete。该代理可能造成的最坏后果是读取过多,正如你将看到的,这正是我们想要捕捉的浪费。
可观测性来自 Traccia,这是一个原生于 OpenTelemetry 的代理可观测性 SDK。
pip install traccia
默认使用本地文件导出器运行,成本为 $0。如果设置了 TRACCIA_API_KEY,则会将跨度推送到 app.traccia.ai。无密钥,无网络,无费用。(仓库在 requirements.txt 中锁定了确切的测试版本;文本部分保持未锁定,以免随时间过时。)
Nova Pro 定价,实时从 AWS Price List API 拉取(生效日期 2026-08-01,us-east-1):
| 令牌类型 | 每 1K 价格 |
|---|---|
| 输入 | $0.0008 |
| 输出 | $0.0032 |
下面的每个美元数字均是根据实际令牌数针对这两个数字计算得出的。
成本桥梁与一个陷阱
这是大多数教程会跳过的部分。Traccia 开箱即用地自动插装若干技术栈(包括带有 BedrockChat 的 LangChain、CrewAI、OpenAI Agents 以及原始 OpenAI/Anthropic/Gemini),并随附一个成本引擎,内含覆盖 Nova 和 Claude 的捆绑定价快照。目前尚未提供 Strands 集成,并且它不会挂钩原始 Bedrock converse 调用。对于此特定技术栈,Strands 以工具形式的代理直接调用 Bedrock,您需要自行将成本接入。这相当于受支持框架免费提供的价值主张的一部分,而对于不受支持的框架则是真正的手动工作。
Strands 在运行后会将令牌使用情况交给您。您读取它,计算成本,并将其戳印到跨度上,这正是之前提到的 stamp_llm_cost 函数。处理完所有四个代理和工具跨度后大约是 40 行代码;完整版本位于 src/crew.py。
这个陷阱让我困惑了一个下午,阅读 SDK 源码后才彻底明白原因。Traccia 的成本标注处理器只有在以下三个条件全部满足时才会为跨度计算成本:span.type 是 LLM(或未设置),存在 llm.model 属性,并且两个令牌计数均已设置。只要其中任一条件不满足,处理器就会直接返回,且不会报错也没有警告。我最初将 llm.request.model(感觉更符合语义)设置为模型,而不是 llm.model,因此处理器静默地跳过了每个跨度,而“LLM 调用”和“总令牌”磁贴显示为零,尽管我的跨度明确包含令牌。设置 llm.model 后,磁贴会亮起。这种宽容的失败是合理的;问题在于它是不可见的,这才是陷阱。一行调试日志(“跳过成本:无 llm.model”)本可以节省下午的时间。
跨代理不重复计数
经常被提出的另一个问题是:如果主管调用两个子代理,而我将所有人的令牌数相加,我是否在重复计算子代理的令牌?
我编写了 probes/probe_doublecount.py 来进行验证,而不是猜测。Strands 为每个子代理分配独立的事件循环和对应的 metrics 对象。主管的 accumulated_usage 不包含其子代理的 token 消耗。因此计算非常直接:
crew total = supervisor + sum(sub-agents)
没有减法,没有重叠,没有重复计数。已验证,而非假设。
Modeling a multi-agent crew in traces
桥梁建好后,你可以获得每个代理的成本。但这里有一个值得明确说明的设计决策,因为大多数演示会草率带过:你如何在追踪中建模主管及其专家?
你有两种合理的选择。你可以把所有内容嵌套在一个追踪下(主管为根,子代理为子跨度)。或者你可以给 每个代理分配一个顶级追踪,并通过共享的 session.id 将它们关联起来。我选择了第二种,因为这正是真实生产 fleet 的样子:成本分析师、健康与运维以及安全审计员是独立拥有、独立运营的服务。在追踪页面上,它们会以各自的执行呈现,各自拥有自己的成本、令牌和时长;当你需要全局视图时,“按 session 分组”会把它们折叠回一个调查中。
session 4f4c1ade... (one investigation, four independent traces)
investigation_run AWS Account Investigator $0.006 delegated -> 3
cost_analyst Cost Analyst $0.004
health_ops Health & Ops $0.005 (highest total tokens: 4,772)
security_ops Security Auditor $0.005
crew total $0.021
(这些是上面追踪截图中导出运行所示的每个代理的实际数字,已按照仪表盘自身的成本瓷砖进行四舍五入;它们会随着令牌使用量在不同运行之间变化。团队总计是 investigation_workflow 汇总跨度,等于主管自身的合成加上三个子代理,不重复计算。Health & Ops 由于链式读取最多而具有最高的令牌数,而主管的成本大约相同,因为它撰写了较长的最终合成。文章后面出现的 CLEAN 和 CONTEXT BLOAT 数字来源于单独的、已标记的运行,因此不要期望它们能与此次运行对应。)
每个代理的自身追踪仍然在其下方嵌套其工具(agent -> tool:running_instances -> 实际的 boto3 调用),因此您可以保持钻取而不必假装四个独立的服务是一个调用栈。在“追踪”页面,“按会话分组”会将一次运行中的四个代理折叠回一个单独的调查,这样您可以在舰队视图和每个代理视图之间切换而不会丢失任何信息。
(这是与后文使用的 CLEAN 基线不同的一次运行;令牌数量以及因此的美元费用会在不同运行之间变化。重点在于每个代理的细分,而不是绝对数值。)
好。有用。单独的每个代理成本正变得普遍。它在这里之所以重要,不是因为数字本身,而是其底层的数据模型:一旦每一步都携带成本、令牌、周期数以及所属代理,您就能构建一种仍然罕见的能力——在表面看来健康的运行中捕捉超支情况。这就是这些原语让您接下来能够构建的东西。
这里有些细节很容易出错,我在 trace 设计最终确定前的几天里大致按以下顺序遇到了这些问题。首先,所有四个代理都来源于同一个 single 进程中的 single 次 crew 运行。Traccia 在初始化时将代理身份烘焙进 OpenTelemetry 资源,这是进程级别的,因此我的最初版本为每个追踪都贴上了同一个代理名称:在仪表盘中出现了三个完全相同的 “AWS Account Investigator” 行。阅读 SDK 的 enrichment 处理器后发现,跨度级别的 agent.id / agent.name 属性优先于进程默认值,于是给每个代理的跨度贴上其自身身份的戳记,使其显示为独立的代理。静态所有权(类型、所有者、团队、组织)来源于 SDK 自动发现的 agent_config.json 目录,因此仪表盘展示的是一个真实的、带有所有者和团队的舰队,而不是四行匿名条目。不需要额外的进程,也不需要虚假的代理。
其次,独立的追踪需要一个真实的关联键,否则它们会显得脱节。每个代理都会在运行上盖上 session.id 的戳记,而主管还会额外记录 agent.delegated_to(即本次运行它调用了哪些专家)。这是显式的关联,使得四条独立的追踪能够被读取为一次协调的调查。
第三,'每个代理都是其自身的追踪'这一说法并不是凭空想象的,而且这让我付出了重新构建的代价。Traccia 的 span_scope(parent=None) 仍会继承当前活跃的 当前 span(如果存在的话),因此我的代理在每个代理的 span 开始之前,我会先分离 OpenTelemetry 上下文,否则它们会悄悄地全部合并回一个追踪。通过导出的 span 中统计不同 trace ID 的数量来验证,这一小助手起到了作用。
第四,我第一次查看时间线时,每个工具 span 都是 0ms,这是因为我在事后从 metrics 对象重构了工具 span。解决办法是在它运行时将真实的 boto3 调用包装在一个活跃的 span 中,这样时间线就能显示每次 AWS 读取的真实持续时间。0ms 的条正是会让观众对整个追踪产生怀疑的问题,值得深入排查。随后又有一个更隐蔽的后续问题困扰着我:这些活跃的工具 span 继承了进程级的默认身份,导致所有工具都被归入主管下,专家们的追踪显得非常单薄。我不得不为每个工具 span 添上其调用代理的身份标记。文档中没有提到这一点;我通过解析导出的 traces.jsonl 并注意到 agent.id 错误而发现了这个问题。
此外,这一细节让人感觉像是生产环境而非演示:每个代理都会记录 agent.tools_available(其被授予的完整工具集)和 agent.tools_called(本次运行中模型实际使用的工具)。在此次运行中,Cost Analyst 拥有两个可用工具(month_to_date_cost 和 cost_forecast),但只使用了其中一个;Health & Ops 拥有五个工具,全部都被使用了。这种差异不是需要掩盖的错误,而是真实的信息。"拥有两个,使用了一个"正是在决定一个代理是否过度配置时希望可见的信息。
独特之处:捕捉静默浪费
以下是我的免责声明。我在搭建团队的过程中,在真实运行中看到了这三种情况的各种版本,随后我在 src/waste_demo.py 中实现了它们,以便可靠地触发,这样你就可以按需观察,而不必等待出现问题的运行。这种复刻是有意而为之的。LLM 的输出具有非确定性,因此在生产环境中,同样的模式会自行出现,只是没有你可以演示的固定时间表。更重要的是:这里的检测基于相对于基准的增量(delta-vs-baseline),而不是神奇的绝对阈值。这就是真实的回归检测的工作方式。你先捕获一个已知良好的运行,然后标记出偏离的运行。下面的每个数字都是来自一次代表性运行的真实 Nova Pro 令牌使用量。你的数字会有所不同;但这些比例是成立的。
首先,干净的基线。这是我用来对比一切的‘已知良好’基线。
CLEAN baseline (one exported run): crew total ~ $0.0083
health_ops: 2,274 input / 199 output / 3 cycles
场景 1:失控循环
智能体卡在反复推理和重新阅读相同内容的循环中。
RUNAWAY LOOP: ~1.2x baseline ($0.0100 vs $0.0083)
health_ops ran 4 cycles (baseline: 3)
re-read cpu_utilization twice (baseline: once)
health_ops input tokens ~1.6x (3,557 vs 2,274)
最终答案相同。APM 跨度为 200 OK。导致这一点的是:agent.cycle_count 和 tool.call_count。代理循环次数超过了其基准,并重复调用了同一个工具。没有单个数字是 “错误” 的,错误在于增量。
场景 2:冗余工具调用
较为温和,更具隐蔽性。代理调用了它已经知道答案的工具。
REDUNDANT TOOL CALLS: ~1.03x baseline ($0.0085 vs $0.0083)
running_instances called 3x (baseline: 1x)
一次运行中只有几百分比的差异,你根本不会注意到。把它放大到每天成千上万次的运行中,它就会成为一项开支。信号:tool.call_count 对 running_instances 从 1 跳升到 3。这仅在逐工具、逐代理的层面可见,正因为单次运行中的美元差异极小,所以很容易被忽略。
场景 3:上下文膨胀(代价高昂的一种)
代理会把过多的上下文拖入其提示中。每多一个输入的 token 都会产生费用,并且这种开销会层层叠加。
CONTEXT BLOAT: ~1.4x baseline ($0.0119 vs $0.0083)
health_ops input tokens elevated, output nearly 4x (803 vs 199)
supervisor synthesis cost rises too, bloat cascades
这是最阴险的一个,因为它会层层叠加。子代理的膨胀会把更大的一坨上下文喂给监督者,监督者自身的综合成本也随之水涨船高(在这次运行中,监督者的开销从 $0.0032 跳到了 $0.0044)。要盯的信号:每个代理的 llm.usage.prompt_tokens 和 llm.cost.usd。你会看到 prompt token 在工作量根本没变的地方悄悄往上爬。
同样的答案,不同的账单
src/compare.py 把 CLEAN 和 BLOAT 并排放在一起。
| CLEAN | CONTEXT BLOAT | |
|---|---|---|
| 最终答案 | 正确 | 正确 |
| Crew 总成本 | $0.0083 | $0.0119 |
| 差值 | - | +43%(约 1.4 倍) |
| APM 状态 | 200 OK | 200 OK |
两次运行,都返回了正确答案,在任何延迟与错误率面板上都一片绿。多出来的 43% 唯一会露面的地方,是 trace 里你自己打上的那个每代理成本属性。在 Traccia 面板里,这正是这套工具证明自身价值的时刻:两次运行并排摆着,都显示"成功",而每代理成本那一列,就是臃肿的那次现形的地方。Agent 原生可观测性的全部论据,就浓缩在这一张表里。
检测逻辑并不高明,就是一个差值检查:对每个代理,把本次运行和基线做比较,然后标记三种情况。循环次数超过基线,可能是失控循环;prompt token 超过基线的 1.25 倍,可能是上下文膨胀;某个工具的调用次数超过基线,可能是冗余调用。整个检测器就这些,在 src/compare.py 里大约十五行代码。
真正关键的,是手里有基线,还有可供对比的每代理属性。而 trace,正是让这些属性得以存在的东西。
构建过程中,这个检测器坏过一次,倒是个说明埋点与检测如何互相耦合的好例子。当我把工具改成每次调用实时输出一个 span(就是上面那个 0ms 修复)之后,冗余调用检查失灵了——因为它原来是从一个单独重组出来的 span 上读取 call_count 属性,而那个 span 已经不存在了。我只好改成按工具名统计 span 出现的次数。这件事让我记住了一条:记录方式一变,检测方式可能悄悄就坏了。基线抓住了这个问题,这正是它存在的意义。
看着它发生:实时控制面板
这是你在上面视频中看到的面板。轨迹是真相的来源,但一堆 span JSON 并不是向队友展示团队的方式。因此,仓库附带了一个小型实时控制面板:一个单页 UI,它运行真实的团队并随着调查进行进行动画展示。
你在命令控制台中输入提示,点击 Investigate,视图会向下滚动到团队的图形。主管位于顶部,三位专家在其下方展开,通过导线相连。随着运行流式传输,每个代理就像交通信号灯一样亮起:空闲,然后运行(带有实时活动线,“Reading Cost Explorer”、“Scanning security groups”),然后完成,报告在底部揭晓。每个代理卡片以小芯片形式显示其触及的 AWS 服务,使观众能够一眼看出:Cost Analyst 读取 Cost Explorer 和预测,Health & Ops 读取 EC2/EBS/Lambda/S3/CloudWatch,而 Security Auditor 检查安全组、IAM、S3 和 GuardDuty。
面板有两种模式。Live 运行真实的团队:真实的 Nova Pro 调用、真实的只读 AWS 读取、追踪上的真实费用,约十三秒。Replay 从已提交的追踪文件中确定性地、免费地动画化保存的运行,这样您可以随意多次排练可视化而不消耗令牌。两者从同一事件流驱动完全相同的 UI;唯一的区别在于事件是来自全新的 Bedrock 运行还是来自录制的运行。
后端是一个小型 FastAPI 应用,它以 Server-Sent Events 的形式流式传输团队的生命周期。关键在于 UI 是一个薄层查看器,基于追踪记录的同一遥测数据;它不是第二个、手动维护的真相来源。图形展示的正是团队所做的。
零成本且只读地自行构建
尝试此功能不需要付费计划或实时 AWS 账单。整个系统可在本地使用文件导出器和只读 AWS 凭据运行。
权限范围刻意保持很小:所有操作都是 Get、List 或 Describe,涵盖 Cost Explorer、EC2、CloudWatch、S3、Lambda、IAM 和 GuardDuty。工具集中任何地方都没有创建、修改或删除操作。完整的策略 JSON 在仓库的 README 中;如果您不想手动编写它,AWS 的托管 SecurityAudit 和 ViewOnlyAccess 策略涵盖相同的权限集。附加该策略,通过 Strands 调用 Nova Pro,您将获得一个只能观察而不能修改的团队。要将追踪发送到托管仪表板,请设置 TRACCIA_API_KEY;保持未设置则一切都会写入本地文件。无论哪种方式,追踪范围都是相同的。
只读形状对每个工具都是相同的:在实时 span 中包裹真实的 boto3 describe/get/list 调用,使其在 trace 中的持续时间等于真实的 AWS 读取时间,返回所需字段,不做其他操作。仓库中的 src/tools.py 包含了全部七个;它们都采用这种形状。
对 Traccia 的诚实评价
我曾用真实团队对抗此 SDK 并阅读其源码以理解行为,因此这里的评估基于此,而非营销页面。
真正的优点:
它让我费力的地方以及可以改进的地方:
Net: 对于受支持的框架,你可以免费获得很多功能;即使偏离常规路径,OTel 基础设施和成本/身份模型也很可靠。功能已经具备;缺失的打磨主要是文档以及少数开发者体验上的小瑕疵,这正是你对这个阶段产品所能期待的。
坦率的警告
我想直言不讳地谈论局限,因为这就是这篇文章的核心目的。
- 我首先在真实运行中看到了三种浪费场景的版本;
src/waste_demo.py中的版本只是让它们在提示时触发。LLM 输出具有非确定性。在实际生活中,这些模式会自行出现,只是不会按照你可以演示的时间表出现。 - 检测基于相对于基准的增量,而不是固定的魔法阈值。你需要一个已知良好的运行来进行比较,这与任何回归系统一样。
- 每一美元都对应着经过验证的 us-east-1 定价下的真实 Nova Pro token 使用量,但具体数字会随运行而变化。不要把任何单一数字视为常量。要把这种关系(我测量的最糟糕模式大约是 1.4 倍)当作教训,而不是 exact 的小数。
- Traccia 不会自动插桩 Strands 或 Bedrock。成本桥梁大约是你自己编写并拥有的 40 行代码。这是一项功能:你可以精确控制 span 中包含的内容。
- 我将每个 agent 建模为独立的 trace,以
session.id进行分组。这是一个有意的选择,以匹配真实机群的所有权和运营方式。如果你更倾向于每次运行只有一个嵌套 trace,则保持 supervisor 作为父级,而不是 detach 上下文。两种方式都是有效的;选择与你的团队对系统的理解方式相匹配的那一种。 - 该 agent 通过 IAM 策略是只读的,而非出于希望。
收获不是“购买一个可观测性工具”。而是一个看似正确的答案并不能告诉你运行是否高效,而真相仅存在于 trace 中,你必须刻意放置在那些属性上。
常见问题
什么是 AI 代理可观测性,它与 LLM 监控有何不同?
LLM 监控通常只关注单次模型调用:延迟、错误,也许还有 token 数。而代理可观测性则观察整个推理会话:代理经历了多少轮循环,调用了哪些工具,每一步的成本是多少,在多代理团队中,哪个代理做了什么。代理的故障往往体现在多步骤链路上,而不是单次调用,因此需要完整的追踪才能发现它们。
如何在 Amazon Bedrock 上跟踪每个代理的成本?
Bedrock 在每次调用后会返回 token 使用量。将输入和输出 token 数分别乘以模型的每 1K token 价格(例如 us-east-1 区域的 Nova Pro,输入 $0.0008,输出 $0.0032),得到的美元金额附加到发起该调用的代理对应的 trace span 上。这就是本文中的 stamp_llm_cost 函数。AWS 自带的基于标签的成本分配在 Cost Explorer 中仅作用于账户和标签层面;而按代理、按推理循环进行归因则是 trace 额外提供的信息。
AWS Cost Explorer 能否自行显示每个代理的成本?
不能单独实现。根据 AWS Well-Architected Agentic AI Lens,默认情况下成本仅在账户层面可见,Cost Explorer 无法区分不同的代理或工作流。标签分配加上 AgentCore Observability 能有所改善,但按代理、按推理阶段的归因仍需通过插装 trace 来实现——这就是本次构建所做的。
为什么我的多代理应用即使运行正常也会超出预期成本?
因为正确的答案并不一定便宜。代理可能会多跑一次推理循环,重复调用已经得到答案的工具,或者在各步骤之间携带臃肿的上下文。这些操作不会产生错误,只会增加 token 消耗。超支会体现在账单上,而不是延迟和错误的仪表盘中——这就是本文所讨论的“隐形浪费”。
尝试此方案是否需要付费工具或 AWS 账户?
不需要。整个构建在本地运行成本为 $0:Traccia SDK 将追踪写入本地文件,无需 API key;AWS 读取使用只读凭证(或已提交的重放运行,此时根本不需要 AWS 访问权限)。只有在您希望在自己的账户中运行实时代理团队时,才需要申请 Bedrock 模型的使用权限。
Traccia 是否开源?
SDK(traccia-py) 采用 Apache-2.0 许可证开源,并基于 OpenTelemetry 构建,因此其 span 遵循 OTel 标准,您不会被供应商锁定。托管的仪表盘位于 traccia.ai,为可选的商业组件;只有在您需要可视化时才会使用它。
Traccia 是否开箱即用支持 AWS Strands Agents?
撰写本文时,Traccia 尚未原生支持。它会自动插桩 LangChain、CrewAI 以及 OpenAI/Anthropic/Gemini 客户端,但不包括 Strands 或原始 Bedrock converse,因此在这套技术栈上,您需要自行在 span 上添加成本(大约 40 行代码)。在受支持的框架上,大部分工作会自动完成。
试用
完整代码(包括 crew、工具、废弃演示、实时控制面板、对比视图以及双计数探针)已在 GitHub 上提供:ai-agent-observability-aws。此外,您还可以在浏览器中点击查看一次运行的实时回放:https://simplynadaf.github.io/ai-agent-observability-aws/。克隆该仓库,使用本地导出运行 python -m src.waste_demo,您将看到一个完美答案的成本约为 1.4 倍。然后,在您的账单产生费用之前,为您自己的代理添加插桩。
要将 Traccia 集成到您自己的代理中,上手过程故意设计得简短且免费:
-
安装 SDK(开源,Apache-2.0):
pip install traccia。未提供 API key 时,它会将追踪写入本地文件,因此您在注册任何服务之前就可以看到 $0 的 span。源码和文档:github.com/traccia-ai/traccia-py。 -
在您的 span 上添加成本使用上面约 40 行的
stamp_llm_cost模式(或者如果您使用 LangChain、CrewAI 或 OpenAI/Anthropic/Gemini 客户端,Traccia 会自动插桩,则可免费获得)。 -
在仪表盘中查看当您想要可视化的每个代理成本以及并排运行比较时:设置
TRACCIA_API_KEY,并将相同的 span 推送到 traccia.ai。无论哪种方式,span 都是相同的,因此您的插桩不会有任何变化。
如果您使用它构建了某些东西,请告诉我您发现的静默浪费。这就是有趣的部分。
欢迎关注我,获取更多关于 AWS 架构、DevOps 和 AI 基础设施的内容:
作品集 | LinkedIn | Dev.to | YouTube | 邮箱 | AWS Builder Center | X