如何把一次充满不确定性的 Agent 执行,转化为可查询、可回放、可评测、可归因,并最终能够进入回归集与后训练流程的轨迹数据。
大模型应用刚进入工程现场时,很多团队沿用了传统微服务的监控思路:记录请求是否成功、接口耗时多少、模型消耗了多少 Token,再配上一块错误率和 P99 延迟大盘。
这套方法能回答“系统有没有挂”,却很难回答“Agent 为什么做错”。
假设用户对智能导购 Agent 说:
帮我挑一款 1000 元以内、适合地铁通勤、非入耳式的降噪耳机。
Agent 最后推荐了一款售价 1299 元、当前无货的商品。传统日志里可能只有这些信息:
POST /agent/chat 200LLM request successProduct search successInventory API successTotal latency: 4.8s所有调用都成功了,答案却失败了。工程师仍然不知道:
- 预算约束是否被正确提取;
- 商品检索时是否带上了价格过滤条件;
- 实时价格和库存结果有没有进入模型上下文;
- 上下文压缩是否丢失了关键约束;
- Agent 是否跳过了最终校验步骤;
- 推荐错误来自模型、检索、工具、编排,还是业务数据本身;
- 用户最终有没有点击、加购、下单,或者重新描述需求。
这正是 Agent 观测工程和传统 APM 的分水岭:
传统可观测性主要解释服务是否正常,Agent 可观测性还必须解释它看到了什么、采取了什么动作、环境返回了什么,以及任务是否真正完成。

图 1:Agent 观测、采样、评测与数据闭环总览。
1. 为什么传统日志解释不了 Agent
传统服务的执行路径通常由代码预先确定。一次 HTTP 请求进入系统后,会沿着相对稳定的控制流访问 RPC、数据库和消息队列。即使内部发生异常,工程师通常也能根据调用栈、错误码和依赖链路定位问题。
Agent 的控制流则由模型、上下文和环境反馈共同决定。相同输入在不同模型版本、提示词版本、工具结果甚至随机种子下,可能走出不同路径:
用户输入 ↓理解目标 ↓选择工具 ↓构造参数 ↓读取工具结果 ↓决定继续调用、转交其他 Agent,还是结束因此,Agent 的故障不只包括“程序抛异常”,还包括大量语义和行为层面的失败:
| 失败类型 | 示例 |
|---|---|
| 目标理解失败 | 把“非入耳式”理解成“半入耳式也可以” |
| 工具选择失败 | 没有调用实时库存工具 |
| 参数生成失败 | 商品检索漏传 max_price=1000 |
| 轨迹编排失败 | 查询价格后没有执行约束校验 |
| Observation 使用失败 | 工具已返回“无货”,模型仍然推荐 |
| 提前终止 | 只检索一次就直接回答,没有补充必要信息 |
| 循环调用 | 重复查询同一批商品,成本持续攀升 |
| 业务结果失败 | 文本推荐看似合理,但用户完全不点击或频繁纠正 |
这意味着生产系统至少要区分三种“成功”:
接口调用成功 ≠Agent 行为正确 ≠业务任务完成商品接口返回 HTTP 200,只能说明调用层面成功;它不代表结果满足预算和库存约束,更不代表用户最终完成了购买。

图 2:传统 APM 主要回答服务是否正常;Agent 可观测性还需解释目标、动作、Observation 与业务结果。
2. Agent 中的“采样”到底是什么
Agent 语境中的“采样”很容易产生歧义。至少有三种完全不同的采样问题。
2.1 模型解码采样
这是模型生成阶段的 temperature、top_p、top_k、随机种子和多候选生成。它决定输出的随机性和多样性,但不是本文重点。
2.2 观测数据采样
它回答的是:
每天产生海量 Agent 运行时,哪些运行需要保存完整 Prompt、上下文、工具参数、工具结果和状态快照?
这属于可观测性管道中的采样,典型机制包括 Head Sampling、Tail Sampling、概率采样、属性采样和自适应采样。
2.3 轨迹样本采样
它回答的是:
已经保存下来的执行轨迹中,哪些应该进入人工审核、离线评测、回归测试、SFT、偏好学习或 Agentic RL?
这一步考虑的不再只是存储成本,还包括代表性、长尾覆盖、正负样本平衡、重复样本、隐私授权和标签质量。
完整工程里,采样不是一个开关,而是四道连续的闸门:
是否产生详细遥测 ↓是否保留整条 Trace ↓是否保存敏感或大体积内容 ↓是否晋升为评测或训练样本如果把四层混成一个 sampled=true/false,后续几乎无法同时处理成本、隐私、分析偏差和训练数据质量。
3. Agent 可观测性的对象发生了什么变化
传统系统主要观测 HTTP、RPC、数据库、消息队列、CPU、内存、线程池、错误码和延迟。Agent 在这些对象之上,又新增了一整层“认知执行面”:
- Session、Run、Turn 和 Step;
- 上下文组装、历史读取、记忆读取和上下文压缩;
- 模型请求、模型响应和 Token 消耗;
- 检索、重排和知识版本;
- 工具选择、工具参数、工具执行和工具返回;
- Handoff、子 Agent 调用和人工介入;
- Guardrail、结果校验和业务提交;
- 用户点击、加购、购买、拒绝和重新提问。
这里需要澄清一个经常被混淆的问题:Agent 执行轨迹不等于模型的私有思维链。
生产观测应该记录可审计的外部事实,例如模型接收了哪些经过治理的输入、生成了什么工具调用、工具返回了什么、状态如何变化、最终输出是什么。它不应依赖保存模型不可验证的隐藏思维过程。模型生成的决策摘要只能作为可见输出留档,不能被当成真实因果解释;由代码或规则引擎产生的规则命中,才是可复核的执行事实。
更稳妥的观测对象是:
可见输入可执行动作外部环境反馈结构化状态变化模型生成的决策摘要与代码规则命中最终输出真实业务结果例如,可以记录由过滤代码计算出的“候选商品共 20 个,因预算规则淘汰 12 个”,而不是要求模型吐出一大段未经验证的内心推理。关于模型解释与真实影响因素可能不一致的风险,可参考 Anthropic 的相关研究。
4. Session、Run、Turn、Step、Span、Event、Log 与 Metric
要建立可查询的 Agent 观测系统,首先需要约定一套逻辑执行层级。下面是本文采用的工程模型,并不是 OpenTelemetry 强制规定的唯一层级:通常一条 Trace 对应一次 Run,Span 可以表示一个 Step,也可以表示 Step 内的模型、工具或检索子操作,Event 则通常附着在 Span 上记录瞬时事实。
Session└── Run └── Turn └── Step ├── Span └── Event4.1 Session
Session 表示一段连续会话。用户先说“想买通勤耳机”,随后补充“我用安卓手机,而且不喜欢入耳式”,这些信息属于同一个会话上下文。
4.2 Run
Run 表示一次完整任务执行。一个 Turn 通常触发一个 Run,但在重试、后台任务或多阶段审批场景中,两者也可能不是一一对应。
4.3 Turn
Turn 表示一轮用户输入和 Agent 输出。多轮任务由多个 Turn 组成。
4.4 Step
Step 是 Agent Loop 中的一次状态迁移:
读取当前状态→ 模型决定下一动作→ 执行动作→ 获得 Observation→ 更新状态4.5 Trace 与 Span
Trace 是一次端到端执行的完整因果链。Span 是其中一段有明确开始和结束时间的操作,例如:
model.generatecatalog.searchinventory.queryprice.queryrecommendation.validate
Span 负责回答“这一步何时开始、何时结束、耗时多久、是否出错、父操作是谁”。
4.6 Event
Event 是某个瞬间发生的结构化事实,例如:
TOOL_CALL_STARTEDTOOL_CALL_COMPLETEDCONTEXT_COMPRESSEDHUMAN_APPROVAL_REQUIREDFINAL_RESPONSE_CREATEDBUSINESS_OUTCOME_RECORDED
Event 可以挂在 Span 内,也可以作为独立事件进入事件流和数据湖。
4.7 Log
Log 更适合承载诊断文本和异常上下文,例如“库存接口第 2 次重试仍然超时”。它可以是结构化的,但通常不天然表达父子关系和持续时间。
4.8 Metric
Metric 是聚合后的数值时间序列,例如错误率、P95 延迟、平均 Step 数、单位成功任务成本。它适合告警和趋势分析,但无法单独还原某一次具体执行。
可以把几者理解为:
| 对象 | 回答的问题 |
|---|---|
| Trace | 这次任务完整经历了什么 |
| Span | 某个阶段做了什么、耗时多久 |
| Event | 某个时刻发生了什么事实 |
| Log | 当时还有哪些诊断细节 |
| Metric | 一段时间内整体表现如何 |

图 3:本文建议的 Agent 执行层级。为便于阅读,图中省略了作为整条因果链容器的 Trace;Span 可对应 Step 或其子操作,Event 通常附着在 Span 上。
OpenTelemetry 的 Semantic Conventions 为 Span 名称、属性、Metric 和 Log 提供统一命名方式,避免同一概念在 Java、Python、模型网关和工具服务中各说各话。Agent 专属字段还应持续关注独立演进的 GenAI Semantic Conventions。
5. 建立统一的 Agent Trace 树
一次智能导购任务可以被组织为下面的 Trace 树:
shopping_agent.run├── context.build│ ├── session_history.load│ ├── user_profile.load│ └── preference.extract│├── product.retrieve│ ├── query.rewrite│ ├── catalog.search│ └── result.rerank│├── product.enrich│ ├── realtime_price.query│ ├── inventory.query│ ├── promotion.query│ └── review_summary.query│├── model.generate_recommendation│├── recommendation.validate│ ├── budget.validate│ ├── inventory.validate│ ├── attribute.validate│ └── factuality.validate│├── response.stream│└── recommendation.publish
图 4:智能导购 Agent 的 Trace 与业务结果关联视图。右侧点击、加购和购买是在线 Trace 结束后的业务事实,不是同一在线瀑布中的连续 Span;生产系统应通过 recommendation_id 等稳定业务键进行离线关联。
这里有两个容易被忽略的工程细节。
5.1 Trace 应延伸到业务发布点,但不应无限延长
推荐结果发布给前端时,本次在线 Trace 就可以结束。用户可能几分钟后点击,几小时后下单,甚至几天后退货。没有必要让一个 Span 打开数天等待业务结果。
更合理的做法是为每次推荐生成稳定的 recommendation_id:
trace_id ──产生──> recommendation_id │ ├── product_click ├── add_to_cart ├── purchase └── return_or_refund业务事件稍后通过 recommendation_id 与 Agent Trace 做离线关联。Trace 描述在线执行,Outcome 表描述延迟到达的业务结果。
5.2 业务事实不能由模型自己宣布
模型说“这款商品有货”不能作为库存事实。库存状态应来自库存服务;模型说“推荐很成功”也不能作为转化事实。业务结果必须由正式业务系统、埋点事件或事务记录提供。
6. 一次 Agent 执行应该采集哪些数据
一份可用于调试、评测和回放的 Trace,至少需要六组字段。
6.1 关联标识
trace_idspan_idparent_span_idsession_idrun_idturn_idstep_idattempt_idtool_call_idrecommendation_idattempt_id 很重要。没有它,模型或工具重试会在轨迹中叠成一团,工程师很难区分首次调用和重试调用。
6.2 版本标识
agent_versionprompt_versionmodel_providermodel_namemodel_revisiontool_schema_versionretrieval_config_versionknowledge_base_versionguardrail_versioncontext_compression_versionsampling_policy_versionAgent 行为由多种版本共同决定。只记录模型名称而不记录 Prompt、工具 Schema 和知识库版本,后续几乎无法复现。
6.3 执行字段
event_typestatusstart_timeend_timelatency_msretry_counterror_typeerror_codecancellation_reason6.4 模型字段
input_tokensoutput_tokenscached_tokenstemperaturetop_pfinish_reasontime_to_first_token_msestimated_cost6.5 Agent 行为字段
selected_tooltool_arguments_reftool_result_refobservation_summarystep_counthandoff_targetcontext_lengthcompression_countcandidate_countvalidation_result6.6 采样、隐私与保留字段
sampling_probabilitysampling_reasoncapture_payloadpii_tagsredaction_policyretention_tierdata_usage_scope一个简化后的结构化事件可以写成:
{ "trace_id": "trace_01J...", "span_id": "span_8F...", "parent_span_id": "span_71...", "session_id": "session_1024", "run_id": "run_2048", "turn_id": "turn_03", "step_id": "step_07", "attempt_id": "attempt_02",
"event_type": "TOOL_CALL_COMPLETED", "timestamp": "2026-08-30T12:00:00Z", "status": "SUCCESS",
"agent_name": "shopping_agent", "agent_version": "v17", "prompt_version": "shopping_prompt_v8", "model_name": "model_name",
"tool_name": "query_inventory", "tool_call_id": "call_55A", "latency_ms": 428,
"input_ref": "object://agent-trace/input/55A", "output_ref": "object://agent-trace/output/55A",
"sampling_probability": 1.0, "sampling_reason": ["BUSINESS_CONSTRAINT_VIOLATION"], "capture_payload": true, "pii_tags": [], "retention_tier": "FULL_TRACE_30D"}注意,这个结构没有把完整 Prompt 和工具返回直接塞入索引字段,而是保存对象引用。这是下一节的关键。
7. 元数据、内容与业务事实的分层存储
Agent Trace 可能包含长上下文、网页、商品详情、图片、用户画像和工具返回。把所有内容都写进一张宽表,会迅速制造一只吞噬查询性能的“数据河马”。
更合理的方案是三层存储。
7.1 Trace 索引层
保存轻量且高频查询的字段:
- ID、父子关系和时间;
- 状态、耗时、Token 和成本;
- Agent、模型、Prompt 和工具版本;
- 工具名称、错误分类和采样原因;
- 内容哈希、摘要和对象引用。
这一层适合 Trace Backend、ClickHouse、BigQuery 或其他列式分析存储。
7.2 内容对象层
保存大体积或敏感内容:
- Prompt 与上下文快照;
- 检索候选和重排结果;
- Tool Input、Tool Output;
- 模型完整响应;
- 图片、音频和文件。
这一层通常进入加密对象存储,并通过短期凭证、字段级权限和生命周期策略控制访问。
7.3 业务事实层
保存正式业务状态:
- 商品实时价格和库存;
- 推荐展示、点击和加购;
- 订单、支付、退款和退货;
- 用户主动反馈和人工客服结果。
业务事实层不应该被观测系统取代。特别是审计、支付、订单状态等强一致数据,应进入正式业务数据库或可靠消息链路,而不是只依赖“尽力而为”的遥测写入。

图 5:三层存储是逻辑职责分层,并不代表由同一进程同时写入。业务事实应由正式业务系统持久化,再通过 recommendation_id 等业务键与 Trace 离线关联。
8. Runtime 埋点、Callback、Plugin、Middleware 与自动采集
观测数据不是凭空出现的。它必须在 Agent 生命周期的关键位置被结构化地产生。
8.1 Runtime 原生埋点
最理想的采集点在 Agent Runtime 内部,因为 Runtime 知道什么时候发生了:
- Agent 启动和结束;
- 模型请求和响应;
- 工具调用和 Observation;
- 子 Agent 转交;
- 状态检查点;
- Guardrail 命中;
- 最终响应生成。
它比单纯代理 HTTP Client 更懂 Agent 语义。
8.2 Callback 与 Plugin
Callback 适合在单个 Agent 或单个工具的生命周期点插入逻辑;Plugin 更适合把一组观测、治理和策略能力一次注册到整个 Runner。
典型挂点包括:
on_user_messagebefore_run / after_runbefore_agent / after_agentbefore_model / after_model / on_model_errorbefore_tool / after_tool / on_tool_erroron_eventGoogle ADK 的 Plugin 就建立在 Callback 机制之上。单个 Plugin 注册到 Runner 后,可以对该 Runner 管理的 Agent、工具和模型调用全局生效,适合统一日志、Trace、指标和策略治理。参见 Google ADK Plugins。
8.3 Middleware
Middleware 适合做横切逻辑:
- 注入和传播 Trace Context;
- 补充租户、环境和版本字段;
- 统一错误分类;
- 计算 Token、成本和延迟;
- 脱敏与内容截断;
- 决定是否捕获完整 Payload。
8.4 自动 Instrumentation
模型客户端、HTTP、RPC、数据库、缓存和向量库可以通过自动 Instrumentation 生成基础 Span。它能快速补齐依赖链路,但无法替代 Runtime 语义埋点。
例如,自动埋点能告诉你调用了 /inventory/query,却未必知道这个调用在 Agent 语义上属于“最终推荐前的库存校验”。两者需要结合。
8.5 观测代码不要侵入每个业务节点
不推荐在每个工具函数中手写十几行日志。更可维护的方式是让 Runtime、Plugin 和中间件统一生成通用事件,再由业务节点补充少量领域属性,例如:
shopping.constraint.max_price = 1000shopping.candidate.count = 20shopping.validation.budget_passed = false9. Agent Event Stream 与观测 Trace 的关系
如果需要先理解前端实时执行链路,可阅读站内文章 从 Token Streaming 到 Agent Event Stream;本节聚焦同一组运行时事实如何进一步投影为可查询、可采样的观测数据。
现代 Agent 通常一边执行,一边向前端发送流式事件:
MESSAGE_DELTATOOL_STARTEDTOOL_PROGRESSTOOL_COMPLETEDAPPROVAL_REQUIREDRUN_COMPLETED这条 Event Stream 和观测 Trace 会描述同一次运行,但服务对象不同。
| 维度 | Event Stream | Trace |
|---|---|---|
| 主要消费者 | 前端、控制器、HITL | 工程师、分析系统、评测系统 |
| 时效性 | 毫秒级、边执行边消费 | 可实时,也可异步汇总 |
| 关注点 | 展示、进度、中断、审批 | 因果链、耗时、错误、归因 |
| 数据形态 | 有序事件流 | 父子 Span 和结构化事件 |
| 容错方式 | 断线重连、游标、补发 | 批量导出、重试、采样、落湖 |
最佳实践不是让前端直接消费内部 OTel Span,也不是拿 SSE 协议充当永久观测 Schema,而是从统一事实源分叉:
Agent 内部结构化事件 │ ├── 转换为前端 SSE / WebSocket 事件 └── 转换为 Trace、Log、Metric 和离线事件
图 6:同一内部 Agent Event 向实时交互和离线观测做两种投影;其中 OTLP 是传输协议,Logs 与 Traces 是遥测信号类型。
还有一个常见误区:不要为每个 Token 或每个流式 Chunk 创建 Span。这样会制造海量低价值 Span。更合理的做法是把一次模型流式生成作为一个 Span,并记录:
- TTFT;
- Chunk 数;
- 总输出 Token;
- 流中断原因;
- 首包和末包时间;
- 必要的关键事件。
10. 生产级观测采集管道与背压
一个典型生产管道可以分成四层:
Agent Runtime ↓进程内队列与 Batch Processor ↓OpenTelemetry Collector / 事件采集服务 ↓Trace Backend、Metrics Store、Data Lake、对象存储OpenTelemetry Collector 提供与厂商无关的接收、处理和导出能力,Processor 可以在遥测流经管道时完成过滤、补充属性、采样和路由。参见 OpenTelemetry Collector。

图 7:观测管道必须通过有界队列、批处理与降级顺序保护 Agent 主线程;图中的 queue_usage 等名称是示意指标,不是 OpenTelemetry 固定字段。
10.1 Agent 主线程不能同步等待遥测落库
如果每生成一个 Event 都同步写 BigQuery、ClickHouse 或远程 Trace Backend,观测系统就会反过来拖慢业务系统。
常见实现是:
- 主线程只做轻量序列化并写入有界内存队列;
- 后台线程或异步任务批量导出;
- 按批次大小和刷新间隔触发 Flush;
- 进程退出时执行有限时间的优雅关闭。
10.2 队列必须有界
无界队列在下游故障时会把内存变成缓慢引爆的气球。队列需要设置:
queue_max_sizebatch_sizeflush_intervalexport_timeoutretry_budgetshutdown_timeout当队列达到高水位,可以依次采取:
- 提前 Flush;
- 降低 Payload 捕获级别;
- 丢弃低优先级成功 Trace;
- 保留错误、审计和约束违规事件;
- 将关键审计事实转入独立可靠通道。
10.3 可接受有限丢失,但不能静默丢失
普通诊断遥测通常是 best-effort。真正危险的不是丢掉少量低价值数据,而是系统已经大量丢数,大盘却依然一片祥和。
采集系统自身必须暴露:
telemetry_queue_usage_ratioexport_latency_msexport_failure_totaldropped_span_totaldropped_event_totalserialization_failure_totalretry_exhausted_totalGoogle ADK 的 BigQuery Agent Analytics Plugin 就显式提供 dropped-event 统计。当前 Python 和 Java 实现允许宿主轮询这些计数进行告警,但 drop_reason 名称因语言而异;Kotlin 实现暂不提供该统计。参见 BigQuery Agent Analytics Plugin for ADK。
10.4 Tail Sampling 是有状态组件
Tail Sampling 需要等待整条或大部分 Trace 到达后再决策,因此采样器必须暂存 Span。横向扩容时,如果同一 trace_id 的 Span 被随机分发到不同 Collector,每个实例都会看到一条残缺 Trace。
因此需要在 Tail Sampler 前按 trace_id 做一致性路由,确保同一 Trace 的 Span 落到同一个有状态采样实例。OpenTelemetry 的 Collector Scaling 指南 也特别指出了这个问题。
11. Agent 的指标体系
只看 Token 和延迟,会把 Agent 看成一台昂贵的文本打印机。生产指标至少应分成五层。
11.1 系统可靠性
run_success_ratemodel_error_ratetool_error_ratetimeout_rateretry_ratecancellation_ratecollector_drop_ratecheckpoint_resume_success_rate11.2 性能与成本
time_to_first_tokenend_to_end_latencymodel_latencyretrieval_latencytool_latencyinput_tokensoutput_tokenscost_per_runcost_per_successful_runcost_per_successful_run 比单纯 cost_per_run 更有意义。一个 Agent 看起来每次只花 0.05 美元,但如果一半任务都失败,真实有效成本已经翻倍。
11.3 Agent 行为
average_step_counttool_call_countrepeated_tool_call_rateinvalid_tool_argument_rateagent_loop_ratepremature_termination_ratecontext_compression_rateempty_observation_ratehandoff_depth11.4 智能导购质量
budget_constraint_match_rateinventory_validity_rateattribute_match_rateprice_freshness_raterecommendation_diversityunsupported_claim_rateuser_rephrase_raterecommendation_rejection_rate11.5 业务结果
product_click_rateadd_to_cart_ratepurchase_conversion_rateaverage_order_valuereturn_ratepost_recommendation_exit_rate离线质量指标和在线业务指标不能互相替代。点击率高不一定意味着答案更准确,价格、促销、品牌知名度和页面位置都会影响点击。购买也只能作为弱反馈,不能直接等价为“模型推理正确”。
比较稳妥的做法是:
规则与轨迹评测负责判断“是否正确”在线业务指标负责判断“是否有用”人工反馈负责解释两者冲突12. 为什么不能全量保存所有 Agent 内容
完整 Trace 可能包含系统 Prompt、会话历史、用户画像、地址、手机号、检索文档、商品详情、工具凭证、图片和音频。全量保存不仅昂贵,还会把观测系统变成隐私与权限风险的集中仓库。
成本主要来自:
- 序列化和网络传输;
- Collector 内存与 CPU;
- 索引膨胀;
- 对象存储和扫描费用;
- 数据脱敏、权限和审计;
- 数据保留与删除请求;
- 训练数据授权和用途限制。
因此推荐使用“双通道”:
通道 A:策略允许范围内的高覆盖轻量元数据
每次 Run 都保留:
版本、状态、延迟、Token、成本、Step 数、工具数、错误分类、业务结果引用这些字段支撑总体指标和告警。即使是 Session、用户或业务引用,也可能构成个人数据,因此仍需使用伪名化标识、最小保留周期和用途隔离。
通道 B:选择性完整轨迹
只有命中采样策略时,才保存:
Prompt、上下文快照、工具参数、工具结果、完整模型输出、状态变化和事件序列内容捕获最好与 Trace 是否保留分开决策。可以保留一条 Trace 的元数据和 Span 结构,但不保存敏感 Payload。访问令牌、API Key、密码、Cookie 和连接串不能等到 Tail Sampling 之后再处理,必须在进入进程内队列、Collector、临时缓冲或任何持久层之前直接剔除或不可逆脱敏;其他 PII 也应遵循最小化采集原则。参见 OpenTelemetry 敏感数据处理指南。
如果 Payload 是否长期保存要等 Tail Sampling 结束后才能决定,系统只能把已获授权、已剔除凭证且经过必要脱敏的内容放进短 TTL、受访问控制的临时缓冲区。已经在 Head 阶段彻底丢弃的内容,尾部采样器无法凭空取回。
Google ADK 的结构化 GenAI 日志默认省略 Prompt 内容,需要显式配置才捕获,这种“内容默认关闭、按需开启”的思路很值得借鉴。参见 Google ADK Logging。
13. Head Sampling、Tail Sampling 与分层采样
13.1 Head Sampling
Head Sampling 在根 Span 创建时就决定是否采样。
它适合:
- 固定比例随机样本;
- 测试用户和内部流量;
- 新模型、新 Prompt 和灰度版本;
- 特定租户或高风险业务线。
优点是决策早、成本可控、容易传播;缺点是此时还不知道任务最终是否错误、超时或触发约束违规。
13.2 Tail Sampling
Tail Sampling 会观察整条或大部分 Trace,再根据错误、总延迟和 Span 属性做保留决策。OpenTelemetry 的 Sampling 文档 将“始终保留错误 Trace”“按整体延迟采样”“按新部署服务属性提高采样率”列为典型用途。
智能导购场景中,下面这些 Trace 应优先完整保留:
ERROR 或 TIMEOUT预算、库存、属性约束违规工具参数非法重复工具调用或循环高延迟、高 Token、高成本用户连续改写需求用户明确拒绝推荐新版本上线后的异常罕见意图或长尾品类人工投诉关联 Trace13.3 随机基线样本必须始终存在
只保存失败和慢请求,会得到一个“全世界都在燃烧”的数据集。它适合故障诊断,却不能代表真实线上分布。
推荐组合是:
随机基线样本+异常全量样本+高价值业务失败样本+长尾分层样本+新版本重点样本+人工指定样本随机基线用于回答“总体发生了什么”;异常样本用于回答“问题为什么发生”。
13.4 采样概率和原因必须落盘
每条样本至少记录:
sampling_probabilitysampling_reasonsampling_policy_version对随机样本,可以通过采样概率做加权估计;对规则强制保留的失败样本,主要用于诊断,不应直接拿来计算总体失败率。
更稳健的原则是:
总体比率以策略允许范围内的高覆盖 Metric 流为准,采样 Trace 用来解释分布背后的原因。
13.5 一套可落地的采样决策
def decide_trace_policy(run): reasons = []
# 无偏随机基线 if random_sample(probability=0.02): reasons.append("RANDOM_BASELINE")
# 确定性异常保留 if run.status in {"ERROR", "TIMEOUT"}: reasons.append("EXECUTION_FAILURE")
if run.constraint_violation: reasons.append("BUSINESS_CONSTRAINT_VIOLATION")
if run.latency_ms > run.dynamic_p99_latency: reasons.append("HIGH_LATENCY")
if run.cost > run.dynamic_p99_cost: reasons.append("HIGH_COST")
if run.agent_version in run.newly_released_versions: reasons.append("NEW_VERSION")
if run.intent_cluster in run.rare_intent_clusters: reasons.append("RARE_INTENT")
keep_trace = bool(reasons) capture_payload = keep_trace and run.data_policy_allows_payload
return { "keep_trace": keep_trace, "capture_payload": capture_payload, "sampling_reason": reasons, "retention_tier": choose_retention(reasons) }
图 8:四级采样闸门在实现上应分别记录决策原因;图中的串行箭头表示处理顺序,不代表四个策略必须共享同一个开关。
在极高流量系统中,可以先用 Head Sampling 保护采集管道,再在剩余 Trace 上做 Tail Sampling。但这意味着被 Head 阶段丢弃的 Trace 已无法在尾部“复活”,策略设计必须明确这一代价。参见 OpenTelemetry Sampling。
14. 从 Trace 构建标准 Agent Trajectory
原始 Trace 是为观测而生的,它包含网络 Span、重试、序列化事件和基础设施噪声,不能直接拿去评测或训练。
需要经过一条轨迹加工流水线:
Trace 读取→ Span 与 Event 关联→ 重试归一化→ 无关基础设施 Span 过滤→ Prompt 与 Payload 脱敏→ Observation 对齐→ 状态快照补全→ 业务结果绑定→ 标签与质量门禁→ Episode 输出标准 Episode 可以表示为:
{ "episode_id": "episode_001", "trace_id": "trace_01J...", "user_goal": { "category": "headphones", "max_price": 1000, "commute": true, "fit": "non_in_ear", "noise_cancellation": true }, "initial_context": { "session_summary_ref": "object://...", "profile_version": "profile_v12" }, "steps": [ { "step_id": "step_01", "observation": "用户给出预算和佩戴约束", "action": "search_products", "tool_arguments": {"max_price": 1000}, "tool_result_ref": "object://...", "state_update": {"candidate_count": 20} }, { "step_id": "step_02", "action": "query_price_and_inventory", "state_update": {"valid_candidate_count": 5} } ], "final_response_ref": "object://...", "business_outcome": { "clicked_product_id": "sku_02", "added_to_cart": true }, "labels": { "constraint_pass": true, "trajectory_quality": 0.92, "final_answer_quality": 0.88 }}14.1 重试不能简单删除
重试会影响成本、延迟和可靠性,不能从 Trace 中彻底抹去。更合适的方式是在 Episode 主轨迹中折叠重复尝试,同时保留:
attempt_countfirst_error_typerecovery_strategyfinal_attempt_status14.2 Golden Trajectory 不应永远等于唯一固定序列
复杂 Agent 往往存在多条有效路径。智能导购可以先查价格再查库存,也可以并行查询。若评测只接受一条完全相同的工具序列,会误伤正确轨迹。
可以将预期轨迹表示为三类约束:
- 必做动作:推荐前必须验证实时价格和库存;
- 禁止动作:不得调用未授权的用户画像接口;
- 偏序关系:
validate_constraints必须发生在generate_final_response之前。
这样比死板的字符串数组更接近真实生产系统。
15. 评测、失败归因、回归集与后训练闭环
Agent 评测不能只评最终一句话。一个 Agent 可能碰巧给出正确商品,却在过程中调用了错误工具、泄露敏感数据、产生十倍成本,或者依赖已经过期的缓存。
15.1 四层评测
第一层:确定性规则
智能导购可以直接检查:
- 推荐价格是否小于等于预算;
- 商品是否有实时库存;
- 商品 ID 是否真实存在;
- 关键属性是否符合用户约束;
- 输出 JSON 是否符合 Schema;
- 是否出现未授权工具调用。
规则评测便宜、稳定,应作为第一道门。
第二层:轨迹评测
检查 Agent 是否:
- 选择了正确工具;
- 正确构造参数;
- 在回答前完成必要校验;
- 出现重复、无效或危险调用;
- 以合理步数完成任务。
Google ADK 的评测文档也明确把 Agent 评测拆为“轨迹与工具使用”和“最终响应”两部分,并提供精确工具轨迹、Rubric 工具质量、多轮轨迹质量等标准。参见 Google ADK Evaluation 与 Evaluation Criteria。
第三层:LLM-as-a-Judge
适合评价:
- 推荐理由是否完整;
- 商品差异是否解释清楚;
- 是否真正回应用户偏好;
- 是否存在无依据的营销式断言;
- 多轮对话是否完成整体目标。
Judge 不应成为唯一真相。它需要版本化、校准,并与规则和人工标注对照。
第四层:业务结果与人工反馈
业务结果提供真实效用信号,人工评测负责高风险、分歧和长尾样本。
15.2 失败归因从“第一处分叉”开始
推荐失败后,不要只给整条 Trace 打一个 MODEL_ERROR。更有效的方法是寻找实际轨迹与预期轨迹的第一处分叉:
用户预算提取正确 ↓商品检索漏传 max_price ← 第一处分叉 ↓返回超预算候选 ↓最终校验又被跳过 ↓生成错误推荐根因可以分为:
ModelContextRetrievalToolOrchestrationBusiness DataInfrastructure15.3 用受控回放做更强的归因
把工具结果和上下文快照冻结,只替换一个变量:
原 Prompt + 原模型 + 冻结工具结果 → 失败新 Prompt + 原模型 + 冻结工具结果 → 成功这比“感觉是 Prompt 的问题”更接近因果证据。
还可以分别替换:
- 模型版本;
- Prompt 版本;
- 工具 Schema;
- 检索结果;
- 上下文压缩策略;
- Agent 编排规则。
15.4 从线上失败进入回归集
异常发现→ 同类 Trace 聚类→ 去重与根因确认→ 固化输入和工具快照→ 定义预期结果与轨迹约束→ 加入回归数据集→ 新版本批量重放回归集至少应覆盖:
- Prompt Regression;
- Model Regression;
- Tool Schema Regression;
- Workflow Regression;
- Safety Regression;
- Cost Regression。
15.5 从轨迹进入后训练
成功且经业务事实和质量门禁验证的轨迹,只是后训练的候选原料,不能原样同时充当所有训练格式:
- SFT 需要经过验证的目标输出或工具调用序列;
- 偏好学习和 Reward Model 需要同一上下文下的成对或排序候选及偏好标签;
- Agentic RL / RFT 需要任务与环境快照、可验证的 Grader 或 Reward,并在训练阶段采样新的 Rollout;历史 Trace 更适合作为任务种子、示范或经过治理的离策略数据。
去重与聚类后,还必须按 Session 或 Task Family 分组划分训练集、验证集与不可变 Holdout / 回归集,禁止同一条或近重复轨迹同时进入训练和回归测试。
但在进入训练前,必须检查:
用户授权与用途范围PII 和凭证脱敏Prompt Injection 污染工具结果可信度标签一致性重复样本测试集污染版权与数据许可
图 9:Trace 是候选数据原料;训练集、验证集与不可变回归集必须按 Session 或 Task Family 隔离,不能让同一或近重复轨迹跨集合泄漏。
16. 大厂开源参考:Google ADK 的观测闭环
Google ADK 是一个开源、代码优先的 Agent 开发框架,可作为本文架构的现实参照。以下能力核对截至 2026 年 9 月 2 日;具体能力及最低版本会随不同语言 SDK 演进。参见 Google ADK Python GitHub Repository。
它的观测能力可以理解为两条互补链路,而不是一条强行串联的单线管道:
ADK Runner / Agent Runtime │ ├── Python 实现的 Logging、Metrics、Traces │ └── OpenTelemetry / 观测后端 │ └── BigQuery Agent Analytics Plugin(Python / Java) └── BigQuery Storage Write API └── BigQuery 事件表 └── Agent Analytics SDK └── 轨迹重建、可视化、离线评测以当前 ADK Python 实现为例,框架可通过 OpenTelemetry 输出结构化 GenAI 日志、指标和 Trace;具体能力及最低版本因语言 SDK 而异,参见 ADK Observability。Plugin 可以覆盖 Runner、Agent、Model、Tool 和 Event 等生命周期阶段。在 Python 和 Java 实现中,BigQuery Agent Analytics Plugin 通过异步 Storage Write API 捕获用户消息、Invocation、模型、工具、HITL 等事件;丢弃原因及部分工作流事件的支持因语言和版本而异。Kotlin 当前通过同步写入仅记录 Invocation 起止事件,不具备上述完整能力。
职责边界上,Plugin 负责采集、写表、Tool Provenance、查询视图和丢弃统计;独立的 BigQuery Agent Analytics SDK 再消费这些数据,完成轨迹重建与可视化、评测和 Golden Trajectory 匹配。实现细节参见 BigQuery Agent Analytics Plugin。
这套设计最值得借鉴的不是某个云产品,而是职责分层:
Runtime 产生语义事件Plugin 统一采集和治理OTel 处理在线 Trace 与指标事件数据湖支撑离线分析Evaluation 评轨迹与最终结果
图 10:图示以 Python / Java 插件链路为主;Plugin 负责采集与写表,轨迹可视化、Golden Trajectory 和离线评测属于事件表之后的 Analytics SDK 消费层。图中的回调名为简写,正式 API 通常带 _callback 后缀。
17. 从 Google ADK 映射到智能导购 Agent
假设导购 Agent 使用四个工具:
search_productsquery_realtime_pricequery_inventorysummarize_reviewsPlugin 在 Model 和 Tool 生命周期挂点产生结构化事件;OpenTelemetry Trace 用于线上定位延迟和错误;BigQuery 事件表用于长期分析、轨迹重建和回归样本筛选。
某次请求出现超预算推荐,Trace 还原为:
| 阶段 | 观测结果 |
|---|---|
preference.extract | 正确提取 max_price=1000 |
search_products | 工具参数中 max_price=null |
query_realtime_price | 返回真实价格 1299 元 |
recommendation.validate | 节点未执行 |
generate_recommendation | 推荐了该商品 |
这条轨迹同时暴露两个问题:工具参数构造漏传预算,最终编排又跳过了约束校验。它命中 BUSINESS_CONSTRAINT_VIOLATION Tail Sampling 规则,被完整保留。
随后进入闭环:
BigQuery 中筛选同类轨迹→ 聚类确认共性根因→ 把 max_price 设为工具必填参数→ 在最终响应前强制执行 validate_constraints→ 固化本次工具返回作为回归夹具→ 使用 ADK 轨迹与最终答案评测重新验证修复后的 Golden 约束不必规定价格查询和库存查询的绝对先后顺序,但必须满足:
price_checked = trueinventory_checked = truebudget_passed = truevalidate_constraints happens before final_response这就把第 16 节的大厂开源能力,映射成了一条可落地的智能导购数据闭环。

图 11:完整保留 Trace 与 Payload 的前提,是内容已获授权、凭证已在入队前剔除,并符合脱敏和保留策略。
18. 常见误区与工程陷阱
这一部分可以浓缩成八条生产守则:
- 不要只保存失败 Trace:必须保留随机基线,否则无法了解真实分布。
- 不要把 HTTP 200 当成任务成功:调用成功、行为正确、业务成功是三件事。
- 不要只评最终答案:轨迹可能包含危险工具调用、循环和高成本。
- 不要默认保存所有 Prompt:内容采集要经过脱敏、授权和保留策略。
- 不要把点击和购买直接当正确标签:它们受到价格、促销和页面位置等混杂因素影响。
- 不要让 Tail Sampler 随机分片:同一 Trace 的 Span 必须汇聚到同一有状态实例。
- 不要让采集系统静默丢数:队列、导出、序列化和丢弃原因都要被监控。
- 不要遗漏版本信息:没有 Agent、Prompt、模型、工具和知识版本,就没有可靠复现。
19. 总结:把 Agent 运行过程沉淀成数据资产
一套成熟的 Agent 观测系统,不应止步于“能看到模型调用了几次工具”。它需要形成完整闭环:
线上执行→ 结构化 Trace、Span 与 Event→ 高覆盖轻指标和选择性完整轨迹→ 规则、轨迹、Judge 与业务评测→ 失败归因和受控回放→ 回归数据集与高质量训练样本→ 新版本灰度发布→ 再次观测其中最关键的工程判断有四个:
- 观测的对象是 Agent 的外部执行事实,不是不可验证的隐藏思维;
- 总体趋势依赖高覆盖 Metric,深度解释依赖采样 Trace;
- Trace 是否保留、Payload 是否捕获、数据保留多久、能否进入训练集,应分别决策;
- 最终成功必须与真实业务事实绑定,而不是由模型自己宣布。
Agent 观测工程的终点,不是一块塞满折线图的监控大盘。它真正的价值,是把一次次稍纵即逝的 Agent 执行,沉淀成可解释、可验证、可复用的数据资产,让系统能够从失败中留下证据,从证据中形成评测,再从评测中获得下一次改进。
参考资料
- OpenTelemetry Semantic Conventions
- Google ADK Plugins
- OpenTelemetry Collector
- BigQuery Agent Analytics Plugin for ADK
- OpenTelemetry Collector Scaling
- Google ADK Logging
- OpenTelemetry Sampling
- Google ADK Evaluation
- Google ADK Python GitHub Repository
- Google ADK Observability
- Google ADK Evaluation Criteria
- BigQuery Agent Analytics SDK
- OpenTelemetry Handling Sensitive Data
- OpenTelemetry GenAI Semantic Conventions
- Anthropic: Reasoning Models Don’t Always Say What They Think