文章类型技术长文 所属专栏Agent 观测 预计阅读49 分钟 文档状态已发布
返回

第 3 篇:Agent 内部组件应该记录什么——模型、工具、RAG、记忆与 MCP

逐层梳理模型、工具、RAG、Memory、MCP、上下文压缩、多 Agent 与 HITL 所需观测字段,并把字段连接为可解释的因果证据链。

开始阅读全文9716 字 · 49 分钟 查看系列目录Agent 观测
关键词 Agent可观测性RAGMemoryMCP
栏目 AgentObservability;专栏 Agent 观测;标签 Agent、可观测性、RAG、Memory、MCP

文章目标#

Agent 可观测性真正困难的部分,不是“有没有 Trace”,而是:

每一种组件到底应该记录什么,才能在任务失败后回答“错误从哪里开始、经过哪些节点传播、最终为什么变成这个结果”?

只记录模型耗时、工具名称和最终回答,通常只能看到一条执行流水账。要做因果分析、故障定位、回放、评测和数据回流,还必须把以下信息结构化保存:

  • 模型实际请求了哪个 Provider、哪个模型、哪套 Prompt 和哪组采样参数;
  • 模型看到的上下文由哪些部分组成,各自占用了多少 Token;
  • 工具是如何被发现、描述、筛选、校验、审批和执行的;
  • 检索系统召回了什么、过滤了什么、最终把什么注入模型;
  • 记忆从召回到注入、更新和删除经历了哪些策略;
  • MCP Client 和 Server 如何发现能力、传播 Trace、读取资源和调用工具;
  • 上下文压缩前后保留了什么、丢失了什么;
  • 子 Agent 接收了什么任务 Contract,结果如何回到父 Agent;
  • 人工审批修改了什么参数,拒绝后系统走了哪条替代路径;
  • 某条模型输出、检索结果、记忆或工具返回,究竟只是“存在于上下文”,还是确实影响了后续行为。

本文沿用一个完整任务作为示例:

用户要求 Coding Agent 修复仓库中的折扣计算 Bug,约束是“只能修改订单模块,不得改动支付模块,并且所有相关测试必须通过”。

Agent 需要:

  1. 读取用户目标和仓库状态;
  2. 调用模型制定计划;
  3. 搜索代码和文档;
  4. 读取历史记忆;
  5. 通过 MCP 获取 Issue、仓库资源或工具能力;
  6. 选择编辑和测试工具;
  7. 必要时请求人工审批;
  8. 压缩上下文并恢复任务;
  9. 可能将测试、审查等子任务委派给子 Agent;
  10. 验证最终环境 Outcome。

本文只讨论可观测字段和因果证据,不记录或推断模型不可见的隐藏思维过程。对于“为什么选择某个动作”,应记录可验证的输入、候选、策略版本、结构化决策摘要和后续行为,不应把隐藏 Chain-of-Thought 当作必需的遥测数据。


标准状态与设计原则#

截至 2026 年 8 月 5 日,OpenTelemetry 的 GenAI Semantic Conventions 已覆盖模型推理、检索、记忆、工具执行和 Agent 等语义,但相关文档仍标记为 Development。因此,生产系统应锁定所采用的语义约定版本,自定义字段使用独立命名空间,并记录遥测 Schema 版本。OpenTelemetry GenAI Spans

本文采用以下字段策略:

  1. 标准已经定义的字段优先使用 gen_ai.*error.typeserver.* 等标准名称;
  2. 标准尚未稳定覆盖的 Agent 领域字段放在 app.agent.*
  3. 原始 Provider 字段和跨 Provider 归一化字段同时保留;
  4. “原始值”和“归一化值”分开,例如 provider_stop_reasonnormalized_stop_reason
  5. “逻辑操作”和“物理 Attempt”分开;
  6. “内容是否可见”和“内容是否真的产生影响”分开;
  7. 默认记录 Shape、Hash、ID、版本和引用,不默认复制完整 Prompt、工具参数、检索文档和记忆原文;
  8. 所有可变对象都记录来源和版本,使同一 Trace 能够被复现或解释。

OpenTelemetry 也明确将完整输入消息、输出消息、System Instructions 和 Tool Definitions 设为 Opt-in,并建议在生产环境将大文本或敏感内容保存到受控外部存储,仅在 Span 中保存引用。OpenTelemetry GenAI Spans


1. 模型调用观测#

Agent 内部组件观测字段全景

模型调用是 Agent 的决策入口,但“模型请求成功”并不等于“模型行为可解释”。一条可用的 Model Span 至少要回答:

  • 请求发给了谁;
  • 实际由谁执行;
  • 使用了什么模型和配置;
  • 模型看到了什么上下文;
  • 输出为何停止;
  • 是否发生解析、修复、重试和 Fallback;
  • 这次输出生成了文本、结构化结果还是 Tool Call。

OpenTelemetry 推荐将一次推理 Span 建模为调用方观察到的完整逻辑操作,并覆盖自动重试;如果需要分析每次物理请求,则应在逻辑操作下面增加 Attempt Span,而不是让重试覆盖原始错误。OpenTelemetry GenAI Spans

1.1 Provider、模型与版本#

最少应同时记录“请求目标”和“实际响应来源”。

字段含义示例
gen_ai.provider.name客户端观察到的 Provideropenai
gen_ai.request.model请求中指定的精确模型名model-x-2026-07-15
gen_ai.response.modelProvider 返回的实际模型名model-x-2026-07-15-r2
server.address实际请求地址或网关地址llm-gateway.example.com
app.agent.model.route内部模型路由名reasoning-primary
app.agent.model.gateway.version网关版本gateway-3.8.1
app.agent.model.deployment.id部署或 Endpoint IDdep_apne1_04
app.agent.model.adapter.versionProvider Adapter 版本openai-adapter-2.4

为什么要同时记录 Request Model 和 Response Model?

因为实际系统可能经过:

Agent
→ 企业 LLM Gateway
→ 区域路由
→ Provider
→ 具体模型 Revision

用户配置的只是逻辑别名,网关可能做:

  • 灰度路由;
  • 区域切换;
  • 模型升级;
  • 容量 Fallback;
  • Provider 兼容转换。

如果只记录 model=reasoning-primary,就无法判断一次回归到底来自 Prompt、模型 Revision,还是网关路由变化。

OpenTelemetry 也区分 gen_ai.request.modelgen_ai.response.model,并要求在可用时记录实际生成响应的精确模型名称。OpenTelemetry GenAI Spans

推荐字段示例#

{
"gen_ai.provider.name": "openai",
"gen_ai.request.model": "model-x",
"gen_ai.response.model": "model-x-2026-07-15",
"server.address": "llm-gateway.example.com",
"app.agent.model.route": "reasoning-primary",
"app.agent.model.gateway.version": "3.8.1",
"app.agent.model.adapter.version": "2.4.0"
}

不要把模型版本写成模糊标签#

以下值不利于复现:

latest
default
smart-model
production-model

可以保留逻辑路由名,但必须额外记录最终解析后的模型或部署 Revision。


1.2 Temperature、Top-p 与最大输出长度#

生成参数会影响模型输出稳定性、长度和工具选择,至少应记录:

gen_ai.request.temperature
gen_ai.request.top_p
gen_ai.request.top_k
gen_ai.request.max_tokens
gen_ai.request.seed
gen_ai.request.stop_sequences
gen_ai.request.reasoning.level

需要注意三点。

第一,记录实际发送值,不只记录应用默认值#

应用配置可能写:

temperature: 0.2

但 Provider Adapter 可能:

  • 删除不支持的参数;
  • max_tokens 转成另一字段;
  • 使用 Provider 默认值;
  • 根据模型类型动态覆盖。

因此最好区分:

app.agent.model.configured.temperature
gen_ai.request.temperature

前者是应用希望发送的值,后者是最终请求值。

第二,未发送不等于零#

如果应用没有发送 temperature,不要记录为 0
0 是明确配置,缺失表示使用 Provider 或模型默认值。

第三,不同 Provider 的参数不一定可直接比较#

例如同名 temperaturetop_p 在不同 Provider 或模型上的实现可能不同。跨 Provider 分析时,应把 Provider 和模型作为必要分组字段。

诊断示例#

假设模型开始频繁输出超长解释而没有调用工具,可以检查:

  • max_tokens 是否被错误放大;
  • Stop Sequence 是否丢失;
  • Reasoning Level 是否变化;
  • Temperature 或 Top-p 是否在发布后改变;
  • 请求参数是否被网关过滤。

1.3 Prompt Template 与 System Prompt 版本#

Prompt 是 Agent 程序的一部分,不能只记录渲染后的大文本。推荐把 Prompt 拆成:

模板身份
模板版本
模板 Hash
变量集合
渲染结果 Hash
System Instructions 版本
工具说明版本
策略片段版本

OpenTelemetry 已定义:

gen_ai.prompt.name
gen_ai.prompt.version
gen_ai.prompt.variable.*
gen_ai.system_instructions

其中完整变量和 System Instructions 属于 Opt-in 内容。OpenTelemetry GenAI Spans

推荐字段:

字段说明
gen_ai.prompt.namePrompt 模板稳定名称
gen_ai.prompt.versionSemVer、日期或平台版本
app.agent.prompt.template_hash模板内容 Hash
app.agent.prompt.rendered_hash渲染结果 Hash
app.agent.system_prompt.versionSystem Prompt 版本
app.agent.system_prompt.hashSystem Prompt 内容 Hash
app.agent.prompt.variable_names变量名列表,不含敏感值
app.agent.prompt.registry.idPrompt 管理平台记录 ID

为什么 Hash 和版本都要保留#

版本是人类可读的发布标识,Hash 用于验证内容是否真的一致。

可能出现:

version = planner-v3

但生产平台中的 planner-v3 被原地修改。此时只有 Hash 能暴露“同版本不同内容”。

System Prompt 不应默认全文写入 Trace#

System Prompt 可能包含:

  • 内部策略;
  • 安全规则;
  • 工具权限;
  • 商业逻辑;
  • 用户或租户定制信息。

生产环境更适合保存:

{
"app.agent.system_prompt.version": "2026-08-01",
"app.agent.system_prompt.hash": "sha256:...",
"app.agent.system_prompt.artifact_ref": "artifact://prompts/system/2026-08-01"
}

1.4 上下文组成#

模型调用必须记录“最终上下文由什么组成”,而不是只记录总 Token。

建议将上下文分为以下类别:

system_instructions
user_messages
assistant_history
tool_definitions
tool_results
retrieval_chunks
memory_records
subagent_results
compaction_summary
environment_state
reserved_output_budget

推荐字段:

{
"app.agent.context.message_count": 18,
"app.agent.context.component_count": 8,
"app.agent.context.components": [
{
"type": "system_instructions",
"item_count": 2,
"token_count": 1430,
"content_hash": "sha256:..."
},
{
"type": "retrieval_chunks",
"item_count": 6,
"token_count": 3180,
"source_ref": "retrieval_run://ret_009"
},
{
"type": "memory_records",
"item_count": 3,
"token_count": 680,
"source_ref": "memory_injection://meminj_004"
}
]
}

完整数组如果过大,可以保存为 Artifact,只在 Span 中保留:

app.agent.context.manifest_ref
app.agent.context.manifest_hash

上下文组成是因果分析的入口#

当模型选错工具时,可以先问:

  • 工具定义是否真正进入上下文;
  • 检索内容是否挤掉了关键约束;
  • 记忆是否覆盖了用户当前目标;
  • Tool Result 是否被截断;
  • Compaction Summary 是否遗漏“禁止修改支付模块”。

没有 Context Manifest,就只能凭最终 Prompt 猜测。


1.5 各类上下文 Token 占比#

总输入 Token 只能回答成本问题,不能回答“上下文预算被谁占用”。

建议记录:

app.agent.context.tokens.system
app.agent.context.tokens.user
app.agent.context.tokens.history
app.agent.context.tokens.tools
app.agent.context.tokens.tool_results
app.agent.context.tokens.retrieval
app.agent.context.tokens.memory
app.agent.context.tokens.subagents
app.agent.context.tokens.compaction
app.agent.context.tokens.total
app.agent.context.tokens.reserved_output

同时计算比例:

retrieval_ratio = retrieval_tokens / total_input_tokens
memory_ratio = memory_tokens / total_input_tokens
tool_result_ratio = tool_result_tokens / total_input_tokens

一个典型异常#

总上下文:120,000 Token
工具结果:76,000 Token
用户目标:180 Token
System Prompt:2,300 Token
检索与记忆:8,500 Token

此时模型“忘记任务目标”并不神秘:大量未经裁剪的测试日志和文件内容占据了注意力预算。

Token 估算和 Provider Usage 要分开#

  • Context Builder 在请求前给出的通常是估算值;
  • Provider 返回的是实际计费或实际消费值;
  • 两者应分别记录,不能混用。
app.agent.context.estimated_input_tokens
gen_ai.usage.input_tokens

1.6 Cache 命中与复用#

模型缓存至少有两种:

  1. 应用侧 Prompt / Response Cache;
  2. Provider 管理的 Prompt Cache。

OpenTelemetry 已定义:

gen_ai.usage.cache_creation.input_tokens
gen_ai.usage.cache_read.input_tokens

并规定这些 Token 应包含在总输入 Token 中。OpenTelemetry GenAI Spans

Anthropic 的 Prompt Caching 文档也分别返回 cache creation 与 cache read Token,可用于判断稳定前缀是否被复用。Anthropic Prompt Caching

建议记录:

app.agent.cache.type
app.agent.cache.key_hash
app.agent.cache.scope
app.agent.cache.hit
app.agent.cache.entry_age_ms
app.agent.cache.prefix_hash
app.agent.cache.breakpoint_count
app.agent.cache.eviction_reason
gen_ai.usage.cache_creation.input_tokens
gen_ai.usage.cache_read.input_tokens

Cache 命中不能只看布尔值#

应同时知道:

  • 命中了哪一段;
  • 命中多少 Token;
  • Cache Key 是否包含模型和 Prompt 版本;
  • 是否跨租户共享;
  • Entry 是否过期;
  • 一次发布是否改变了稳定前缀。

推荐派生指标:

cache_read_ratio =
cache_read_input_tokens / input_tokens

缓存污染诊断#

如果旧工具描述被缓存,模型可能持续选择已经下线的工具。应检查:

tool_schema_set_hash
prompt_prefix_hash
cache_key_hash
cache_entry_created_at

不能仅靠清空全部缓存解决,而应定位 Cache Key 缺少了哪个版本维度。


1.7 Structured Output Schema#

结构化输出不是“返回 JSON 就算成功”,必须记录:

  • Schema 身份;
  • Schema 版本;
  • Schema Hash;
  • Strict 模式;
  • Validator 版本;
  • Provider 原生结构化能力;
  • 实际响应类型。

OpenAI 官方 Structured Outputs 文档说明,结构化输出用于让模型结果遵守给定 JSON Schema;Function Calling 的 Strict 模式要求对象禁用额外属性,并明确必填字段。OpenAI Structured Outputs OpenAI Function Calling

推荐字段:

app.agent.output.schema.id
app.agent.output.schema.version
app.agent.output.schema.hash
app.agent.output.schema.dialect
app.agent.output.schema.strict
app.agent.output.validator.name
app.agent.output.validator.version
app.agent.output.validation.status
app.agent.output.validation.error_count

示例:

{
"app.agent.output.schema.id": "tool-plan",
"app.agent.output.schema.version": "2.3.0",
"app.agent.output.schema.hash": "sha256:...",
"app.agent.output.schema.dialect": "json-schema-2020-12",
"app.agent.output.schema.strict": true,
"app.agent.output.validator.name": "jsonschema",
"app.agent.output.validator.version": "4.25.0",
"app.agent.output.validation.status": "valid"
}

1.8 解析失败和修复#

必须把以下阶段分开:

Provider 返回完成
→ 内容提取
→ JSON 解析
→ Schema 校验
→ 业务规则校验
→ 修复
→ 重新校验

常见状态:

provider_refusal
empty_output
json_syntax_error
schema_validation_error
business_validation_error
repair_succeeded
repair_failed

推荐字段:

app.agent.output.parse.status
app.agent.output.parse.error.type
app.agent.output.parse.error.path
app.agent.output.repair.strategy
app.agent.output.repair.attempt_count
app.agent.output.repair.model
app.agent.output.repair.result

修复不能覆盖原始失败#

如果模型第一次输出非法 JSON,第二次修复成功,最终状态可以是:

logical_outcome = success
recovery_status = repaired

但第一次解析失败必须保留。否则无法发现:

  • 某模型版本结构化输出退化;
  • 某 Schema 过于复杂;
  • 某 Prompt 更容易触发非法字段;
  • 修复带来了额外成本和延迟。

1.9 Stop Reason#

Stop Reason 表示模型为什么停止生成,不等于 HTTP 状态。

OpenTelemetry 提供 gen_ai.response.finish_reasons。Anthropic 官方文档也明确区分正常停止原因和 API 错误,并列出 tool_usemax_tokensmodel_context_window_exceededpause_turnrefusal 等停止原因。Anthropic Stop Reasons

推荐同时记录:

app.agent.model.provider_stop_reason
app.agent.model.normalized_stop_reason
app.agent.model.stop_details_ref

统一枚举可以设计为:

completed
tool_call
length_limit
context_limit
refusal
content_filter
pause
cancelled
unknown

为什么保留原始值#

跨 Provider 的统一枚举便于聚合,但会丢失细节。
因此应保存:

{
"app.agent.model.provider_stop_reason": "model_context_window_exceeded",
"app.agent.model.normalized_stop_reason": "context_limit"
}

1.10 Fallback Model#

Fallback 是独立的决策链,不能把最终成功归到最初模型。

推荐结构:

model.operation op_001
├── attempt 1: primary-model → 429
├── fallback.decision
└── attempt 2: backup-model → success

字段:

app.agent.model.operation_id
app.agent.model.attempt_id
app.agent.model.attempt_number
app.agent.model.fallback.from
app.agent.model.fallback.to
app.agent.model.fallback.reason
app.agent.model.fallback.policy.version
app.agent.model.fallback.semantic_compatibility

需要判断:

  • 备用模型是否支持相同 Tool Schema;
  • Structured Output 能力是否一致;
  • Context Window 是否足够;
  • Reasoning 参数是否可映射;
  • 成本和隐私边界是否改变;
  • Provider 切换是否跨区域或跨合规边界。

2. 工具调用观测#

模型到工具结果回填的字段链路

工具调用的完整生命周期不是:

tool_name → result

而是:

发现
→ 暴露给模型
→ 模型选择
→ 参数拼装
→ 参数校验
→ 权限审批
→ 执行
→ 结果规范化
→ 回填
→ 后续消费
→ 环境副作用验证

任何一个阶段都可能是根因。

2.1 Tool Discovery#

模型只能选择它看到的工具,所以工具发现过程必须可观测。

建议记录:

app.agent.tool.discovery.id
app.agent.tool.discovery.source
app.agent.tool.discovery.started_at
app.agent.tool.discovery.duration_ms
app.agent.tool.discovery.available_count
app.agent.tool.discovery.exposed_count
app.agent.tool.discovery.filtered_count
app.agent.tool.discovery.snapshot_hash
app.agent.tool.discovery.filter_policy.version

每个工具候选至少有:

tool_name
server_id
schema_version
description_version
eligibility
filtered_reason

错误工具选择的第一问#

不是“模型为什么选错”,而是:

正确工具当时是否真的在 Tool List 中,并且是否被暴露给模型?

可能原因:

  • MCP Server 尚未发现;
  • Capability Cache 过期;
  • 权限策略过滤;
  • Tool List 截断;
  • 同名工具冲突;
  • Tool Description 版本错误。

2.2 Tool Name 与 Schema 版本#

OpenTelemetry Tool Span 建议记录:

gen_ai.operation.name = execute_tool
gen_ai.tool.name
gen_ai.tool.call.id

并支持记录 Tool Description、Type 和 Arguments 等信息。OpenTelemetry GenAI Spans

生产系统还应补充:

app.agent.tool.namespace
app.agent.tool.provider
app.agent.tool.schema.version
app.agent.tool.schema.hash
app.agent.tool.description.version
app.agent.tool.description.hash
app.agent.tool.implementation.version

工具名称应具有稳定命名空间:

github.create_pull_request
filesystem.read_file
shell.run
orders.update_discount

不要仅使用:

run
execute
search
update

同名和描述歧义会直接增加错误选择概率。


2.3 Tool Arguments#

完整参数可能包含代码、Secret、用户信息和数据库值,因此默认记录:

参数字段名
参数字节数
参数 Hash
内容引用
敏感字段数量

建议字段:

app.agent.tool.arguments.raw_hash
app.agent.tool.arguments.parsed_hash
app.agent.tool.arguments.byte_count
app.agent.tool.arguments.field_count
app.agent.tool.arguments.sensitive_field_count
app.agent.tool.arguments.artifact_ref

如果内容采集被授权,应在进入遥测系统前完成脱敏。

原始参数和执行参数必须分开#

模型输出:

{"path":"src/order.py","line":"42"}

Validator 可能转换为:

{"path":"src/order.py","line":42}

审批人可能再改成:

{"path":"src/order/service.py","line":42}

必须保留三份 Hash 或 Diff:

raw_arguments
validated_arguments
approved_arguments

否则无法判断错误由模型、校验器还是审批人引入。


2.4 参数校验#

校验至少分三层:

  1. JSON / 协议解析;
  2. Schema 校验;
  3. 业务规则和权限校验。

推荐字段:

app.agent.tool.validation.schema_status
app.agent.tool.validation.business_status
app.agent.tool.validation.error_count
app.agent.tool.validation.error_paths
app.agent.tool.validation.defaulted_fields
app.agent.tool.validation.coerced_fields
app.agent.tool.validation.repair_strategy

Schema 通过不代表业务合法。例如:

{"branch":"main","force":true}

可能完全符合 JSON Schema,但违反“不得直接推送主分支”的业务策略。


2.5 权限模式与 Approval#

工具审批应记录:

app.agent.approval.request_id
app.agent.approval.required
app.agent.approval.policy.version
app.agent.approval.risk_level
app.agent.approval.decision
app.agent.approval.actor.id
app.agent.approval.actor.role
app.agent.approval.wait_ms
app.agent.approval.arguments_modified

OpenAI Agents SDK 的 Human-in-the-loop 流程会暂停 Run,将待审批工具调用作为 interruption 保存,并允许在恢复时按调用 ID 批准或拒绝。OpenAI Agents HITL

关键点是:

  • 审批对象必须绑定到具体 tool_call_id
  • 决定不能只写 approved=true
  • 参数被修改时要保存前后 Diff;
  • 等待时间必须与工具执行时间分开;
  • 审批拒绝不一定是技术错误。

2.6 Tool Execution#

工具执行至少记录:

app.agent.tool.operation_id
app.agent.tool.attempt_id
app.agent.tool.runtime
app.agent.tool.sandbox.id
app.agent.tool.timeout_ms
app.agent.tool.started_at
app.agent.tool.duration_ms
app.agent.tool.exit_code
app.agent.tool.external_request_id
app.agent.tool.execution.status

执行状态应区分:

started
completed
cancelled
timed_out
crashed
permission_denied
transport_failed

业务失败和执行失败分开#

测试工具正常执行,但存在失败用例:

execution_status = completed
business_outcome = tests_failed

测试进程无法启动:

execution_status = crashed
error.type = process_spawn_error

这两类问题的修复方式完全不同。


2.7 Tool Result#

建议记录:

app.agent.tool.result.type
app.agent.tool.result.byte_count
app.agent.tool.result.line_count
app.agent.tool.result.content_hash
app.agent.tool.result.artifact_ref
app.agent.tool.result.truncated
app.agent.tool.result.redacted
app.agent.tool.result.summary_applied
app.agent.tool.result.business_outcome

大结果应进入 Artifact Store,Trace 仅保存引用。

Tool Result 的四个状态#

raw_result
normalized_result
context_ready_result
injected_result

例如 Shell 输出可能经过:

  • ANSI 清理;
  • Secret 脱敏;
  • 过长行截断;
  • 摘要;
  • Token 预算裁剪。

如果不记录转换链,就无法解释模型为什么没有看到原始错误。


2.8 错误类型与重试#

建议同时记录低基数错误类别和原始错误引用:

error.type
app.agent.tool.error.category
app.agent.tool.error.retryable
app.agent.tool.error.provider_code
app.agent.tool.error.artifact_ref
app.agent.tool.retry.count
app.agent.tool.recovery.status

错误类别示例:

schema_invalid
permission_denied
rate_limit
connect_timeout
read_timeout
process_crashed
non_zero_exit
resource_not_found
conflict
unknown_outcome

一次逻辑操作包含多个 Attempt 时,中间失败不能被最终成功覆盖。


2.9 环境副作用#

工具观测必须说明它改变了什么。

推荐字段:

app.agent.tool.side_effect.class
app.agent.tool.idempotency_key
app.agent.state.before_ref
app.agent.state.after_ref
app.agent.state.diff_ref
app.agent.rollback.available
app.agent.rollback.point_id

副作用分类:

read_only
reversible_write
conditionally_idempotent
irreversible
unknown

代码编辑工具至少应保存:

  • 修改文件列表;
  • Git Diff Artifact;
  • 修改前后 Commit / Working Tree 状态;
  • 是否触及禁止目录;
  • 是否可以回滚。

2.10 Tool Result 是否被后续步骤实际使用#

这是工具观测中最容易被忽略的一层。

Tool Result 产生后,存在四个不同事实:

  1. available:结果已产生;
  2. attached:结果已加入 Agent 状态;
  3. injected:结果实际进入下一次模型请求;
  4. used:后续动作确实依赖该结果。

前三项可以直接观测,第四项通常只能通过证据推断或实验验证。

推荐字段:

app.agent.tool_result.available
app.agent.tool_result.attached_to_step_id
app.agent.tool_result.injected_into_model_span_id
app.agent.tool_result.injected_token_count
app.agent.tool_result.truncated_before_injection
app.agent.tool_result.summary_ref
app.agent.tool_result.downstream_action_ids
app.agent.tool_result.usage_evidence

usage_evidence 可以是:

explicit_reference
field_copied
citation
decision_dependency
counterfactual_verified
unknown

不能把“在上下文里”直接写成“造成了行为”#

要证明某个 Tool Result 导致了后续动作,最好使用:

  • Replay;
  • 去掉该结果后的对照运行;
  • 替换结果后的敏感性测试;
  • 结构化字段引用;
  • 后续 Tool Arguments 中的值传播。

可观测性给出因果证据,但不自动等于因果证明。


3. RAG 与检索观测#

OpenTelemetry 已定义 Retrieval Span,推荐记录 gen_ai.operation.name=retrieval、数据源 ID、Provider、模型、Top-k,并将完整 Query 和 Documents 作为 Opt-in 内容。OpenTelemetry GenAI Spans

RAG 的核心观测链是:

用户问题
→ Query Rewrite
→ Retriever
→ 候选集合
→ Filter
→ Rerank
→ Context Selection
→ Claim / Citation
→ 最终回答

3.1 原始 Query#

建议记录:

app.agent.retrieval.query_id
app.agent.retrieval.query_hash
app.agent.retrieval.query_length
app.agent.retrieval.query_language
app.agent.retrieval.query_artifact_ref

原始 Query 可能包含用户隐私,默认不应全文写入 Metric 或普通 Trace Attribute。


3.2 Query Rewrite#

Query Rewrite 不能覆盖原始 Query。

推荐记录:

app.agent.retrieval.rewrite.id
app.agent.retrieval.rewrite.strategy
app.agent.retrieval.rewrite.model
app.agent.retrieval.rewrite.prompt.version
app.agent.retrieval.rewrite.input_hash
app.agent.retrieval.rewrite.output_hash
app.agent.retrieval.rewrite.changed_terms

如果一次任务生成多个子查询,还应记录:

parent_query_id
subquery_ids
subquery_order
parallel

常见故障#

原始目标是:

修复订单折扣计算,不能修改支付模块

Rewrite 只保留:

discount calculation bug

“禁止修改支付模块”的约束被丢失。
这不是 Retriever 错误,而是 Rewrite 错误。


3.3 检索源与索引版本#

至少记录:

gen_ai.data_source.id
app.agent.retrieval.corpus.id
app.agent.retrieval.corpus.version
app.agent.retrieval.index.id
app.agent.retrieval.index.version
app.agent.retrieval.index.built_at
app.agent.retrieval.embedding.model
app.agent.retrieval.embedding.version
app.agent.retrieval.retriever.version

如果索引基于代码仓库,还应记录:

repository_id
commit_sha
branch
index_snapshot_id

否则无法判断“没有召回目标代码”是模型问题,还是索引落后于仓库。


3.4 召回候选#

每个 Candidate 至少应包含:

candidate_id
document_id
chunk_id
source_revision
retriever
raw_score
rank
acl_status
content_hash

推荐候选清单保存为 Artifact:

{
"retrieval_run_id": "ret_009",
"candidates": [
{
"chunk_id": "chunk_order_014",
"retriever": "bm25",
"raw_score": 12.6,
"rank": 1,
"source_revision": "commit_abc123"
}
]
}

不同 Retriever 的 Raw Score 不能直接比较#

BM25、余弦相似度、内积和混合检索分数的尺度不同。
应记录:

score_type
score_direction
score_normalization

不要把来自不同系统的 0.82 当作同一种意义。


3.5 相似度、Rank 与过滤原因#

Candidate 可能因为以下原因被过滤:

acl_denied
stale_revision
duplicate
below_threshold
wrong_language
wrong_tenant
unsafe_content
token_budget
metadata_mismatch

推荐字段:

app.agent.retrieval.candidate.rank_before_filter
app.agent.retrieval.candidate.rank_after_filter
app.agent.retrieval.candidate.filter_decision
app.agent.retrieval.candidate.filter_reason
app.agent.retrieval.filter.policy.version

“未进入上下文”必须能够区分:

  • 没被召回;
  • 被召回但低于阈值;
  • 被 ACL 拒绝;
  • 被去重;
  • 被 Token Budget 淘汰。

3.6 Rerank 前后变化#

Rerank 要记录:

app.agent.rerank.model
app.agent.rerank.version
app.agent.rerank.input_count
app.agent.rerank.output_count
app.agent.rerank.duration_ms
app.agent.rerank.score_type

每个 Candidate 保存:

pre_rank
post_rank
pre_score
post_score
rank_delta

为什么重要#

如果目标 Chunk 召回排名第 2,但 Rerank 后掉到第 20,故障在 Reranker。
如果目标 Chunk 根本不在 Candidate 中,故障在 Recall。


3.7 最终进入上下文的 Chunk#

最终注入阶段记录:

app.agent.retrieval.selection.id
app.agent.retrieval.selected_count
app.agent.retrieval.selected_token_count
app.agent.retrieval.selection.policy.version
app.agent.retrieval.selection.position
app.agent.retrieval.selection.truncated

每个 Chunk 记录:

chunk_id
source_revision
content_hash
token_count
context_position
truncated_bytes

这一步决定模型实际看到了什么,不能只保存 Top-k Candidate。


3.8 引用和最终答案之间的证据关系#

建议建立 Claim—Evidence 图:

claim_id
→ retrieval_chunk_id
→ document_id
→ source_revision
→ quote / offset
→ verification_status

Anthropic 的 Citations 功能会把回答中的引用关联到输入文档中的具体来源文本,这说明“答案—来源”关系可以被结构化记录,而不必只保留一段自然语言参考资料列表。Anthropic Citations

推荐字段:

app.agent.claim.id
app.agent.claim.text_hash
app.agent.claim.evidence_chunk_ids
app.agent.claim.support_status
app.agent.claim.verifier.version

支持状态:

supported
partially_supported
unsupported
contradicted
not_checked

3.9 检索失败、遗漏与错误召回#

建议将故障分类为:

empty_retrieval
low_recall
irrelevant_high_rank
stale_index
acl_overfilter
duplicate_domination
rerank_regression
context_selection_drop
context_truncation
unsupported_claim

每个失败都应指向具体阶段:

query
rewrite
retrieval
filter
rerank
selection
generation
citation

4. Agent Memory 观测#

RAG 与 Memory 的证据和血缘链

OpenTelemetry 已为 Memory 提供 search_memorycreate_memoryupdate_memoryupsert_memorydelete_memory 等操作语义,并建议记录 Memory Store、Record ID 和 Record Count;Query 和 Record 内容属于 Opt-in。OpenTelemetry GenAI Spans

Memory 可观测性不能只记录“召回了三条记忆”,而应完整覆盖:

Recall
→ Candidate
→ Filter
→ Injection
→ Downstream Use
→ Update / Delete
→ Lineage

4.1 Memory Recall#

记录:

app.agent.memory.recall.id
app.agent.memory.store.id
app.agent.memory.store.version
app.agent.memory.query_hash
app.agent.memory.query_type
app.agent.memory.top_k
app.agent.memory.duration_ms

还应记录 Recall 的触发原因:

task_start
user_identity
similar_task
tool_failure
explicit_request
periodic_refresh

4.2 Memory Candidate#

每条 Candidate 至少包含:

memory_id
memory_version
memory_type
source_type
source_task_id
created_at
updated_at
valid_from
expires_at
confidence
retrieval_score
trust_level
content_hash

Memory Type 可以是:

user_preference
task_fact
procedural_hint
tool_history
environment_fact
summary
policy

来源和信任级别非常关键。模型生成的摘要、用户明确声明和工具验证事实,不能视为同等可信。


4.3 Memory Filter#

过滤决策应记录:

app.agent.memory.filter.policy.version
app.agent.memory.filter.decision
app.agent.memory.filter.reason
app.agent.memory.filter.risk_score
app.agent.memory.filter.conflict_set_id

过滤原因:

expired
scope_mismatch
identity_mismatch
task_mismatch
conflict
low_confidence
privacy_restricted
stale_environment
duplicate
unsafe

只记录“未注入”没有诊断价值。


4.4 Memory Injection#

记录:

app.agent.memory.injection.id
app.agent.memory.injected_count
app.agent.memory.injected_token_count
app.agent.memory.injection.position
app.agent.memory.injection.format
app.agent.memory.injection.policy.version
app.agent.memory.injection.model_span_id

每条记忆还应记录:

memory_id
version
content_hash
token_count
context_position
injection_mode

Injection Mode:

full
summary
metadata_only
style_only
constraint_only

这样才能解释“记忆原文是否真正进入模型”。


4.5 Memory Update 与删除#

写操作必须记录:

app.agent.memory.mutation.id
app.agent.memory.operation
app.agent.memory.record.id
app.agent.memory.previous_version
app.agent.memory.new_version
app.agent.memory.reason
app.agent.memory.actor
app.agent.memory.provenance_ref
app.agent.memory.tombstone

删除最好使用 Tombstone 或 Audit Record,避免“记忆消失后无法解释历史 Trace”。


4.6 原文、摘要和压缩链#

一个 Memory 可能经历:

原始用户消息
→ 会话摘要
→ 长期记忆
→ 二次压缩
→ 当前上下文注入

必须保存 Lineage:

source_record_ids
derived_from
transform_type
transform_model
transform_prompt_version
transform_hash
compression_level

不要只保留最终摘要,否则无法判断错误来自原始信息,还是摘要转换。

示例:

{
"memory_id": "mem_041",
"version": 4,
"content_hash": "sha256:...",
"derived_from": ["msg_003", "msg_008"],
"transform_type": "summary",
"transform_model": "model-s",
"transform_prompt_version": "memory-summary-v2"
}

4.7 哪条记忆影响了哪个后续动作#

建议建立:

memory_injection_id
→ model_span_id
→ selected_tool_call_id
→ environment_action_id

证据级别可以设计为:

present_in_context
explicitly_referenced
value_propagated
decision_changed_in_replay
counterfactual_verified

不要写:

memory mem_041 caused tool selection

除非有更强证据。更准确的写法是:

mem_041 was injected into model_span_12;
the downstream tool arguments copied its repository path;
the memory-disabled replay selected a different repository.

4.8 记忆污染、冲突和错误继承#

需要检测:

stale_memory
cross_user_leakage
scope_leakage
conflicting_memories
incorrect_summary
unsupported_inference
overdominant_memory
duplicate_reinforcement

推荐记录 Conflict Set:

{
"conflict_set_id": "conf_007",
"memory_ids": ["mem_041", "mem_052"],
"conflict_type": "environment_version",
"resolution_policy": "prefer_newer_verified",
"selected_memory_id": "mem_052"
}

对于“记忆压制新方案搜索”,至少应观察:

  • 记忆被注入后是否跳过检索;
  • Planner 是否提前锁定方案;
  • 是否存在候选搜索数量下降;
  • Memory-disabled Replay 是否选择了不同路径;
  • 最终 Outcome 是否改善或恶化。

5. MCP 观测#

MCP 的观测设计必须先记录协议 Revision,因为 2025 与 2026 版本的生命周期模型差异很大。

官方 2026-07-28 Release Candidate 将 MCP 核心改为无状态协议,移除初始化握手和协议级 Session;协议版本、客户端信息和能力按请求携带,并通过 server/discover、缓存元数据和显式 State Handle 支撑能力发现与有状态应用。SEP-2567 已是 Final,并明确移除 Mcp-Session-Id,用显式 State Handle 替代隐式 Session。MCP 2026-07-28 RC SEP-2567

但生产系统仍可能连接旧 Revision,所以观测层应支持两条兼容路径:

Legacy:
connect → initialize → capability negotiation → session
2026-07-28 line:
request metadata → server/discover → cacheable capabilities
→ explicit state handles when needed

5.1 Server Discovery#

记录:

app.agent.mcp.server.id
app.agent.mcp.server.name
app.agent.mcp.server.version
app.agent.mcp.server.endpoint
app.agent.mcp.transport
app.agent.mcp.protocol.revision
app.agent.mcp.discovery.mode
app.agent.mcp.discovery.source
app.agent.mcp.discovery.duration_ms

Discovery Mode:

static_config
registry
server_discover
legacy_initialize

还应保存:

server_identity_hash
authorization_principal
tenant_scope

5.2 Connection 与 Handshake#

对于旧 Revision,记录:

connect_started_at
connect_duration_ms
initialize_request_id
negotiated_protocol_version
session_id_hash
initialized_at

对于无状态 Revision,不应伪造 Session 或 Handshake,应记录每次请求的:

protocol_revision
client_info
server_info
transport
request_metadata_hash

为什么不能统一成一个 connected=true#

因为:

  • stdio 是本地进程通道;
  • Streamable HTTP 是请求级传输;
  • 某些客户端保持连接池;
  • 无状态协议不等于没有网络连接;
  • 应用级状态可能通过显式 State Handle 延续。

5.3 Capability Negotiation#

旧版本在 Initialize 阶段交换 Capability;新路径通过每请求元数据和发现结果表达。

统一观测字段:

app.agent.mcp.capability.snapshot_id
app.agent.mcp.capability.snapshot_hash
app.agent.mcp.capability.source
app.agent.mcp.capability.cache_ttl_ms
app.agent.mcp.capability.cache_scope
app.agent.mcp.capability.changed

Capability 变更时保存 Diff:

tools_added
tools_removed
resources_added
resources_removed
extensions_added
extensions_removed

5.4 Tool List 与 Resource List#

记录:

app.agent.mcp.tools_list.request_id
app.agent.mcp.tools_list.count
app.agent.mcp.tools_list.hash
app.agent.mcp.tools_list.ttl_ms
app.agent.mcp.tools_list.cache_scope
app.agent.mcp.resources_list.count
app.agent.mcp.resources_list.hash

每个 Tool 保存:

name
schema_hash
description_hash
annotations_hash
server_id

每个 Resource 保存:

uri_template
mime_type
revision
etag
cache_policy

2026-07-28 线路为 List / Resource Read 引入 ttlMscacheScope,使客户端能明确判断 Tool List 的新鲜度。MCP 2026-07-28 RC


5.5 Tool Call#

记录:

app.agent.mcp.request.id
app.agent.mcp.method
app.agent.mcp.name
gen_ai.tool.call.id
gen_ai.tool.name
app.agent.mcp.server.id
app.agent.mcp.request.state_handle_hash
app.agent.mcp.response.state_handle_hash

远程 Streamable HTTP 还可记录:

Mcp-Method
Mcp-Name
http.response.status_code
server.address

参数、Schema、审批和副作用字段继续复用第 2 节 Tool 观测体系。


5.6 Resource Read#

记录:

app.agent.mcp.resource.uri_hash
app.agent.mcp.resource.revision
app.agent.mcp.resource.mime_type
app.agent.mcp.resource.byte_count
app.agent.mcp.resource.content_hash
app.agent.mcp.resource.cache_hit
app.agent.mcp.resource.ttl_ms
app.agent.mcp.resource.artifact_ref

Resource URI 可能包含用户路径、Token 或业务 ID,不应直接作为 Metric Label。


5.7 Client 与 Server Trace 关联#

2026-07-28 线路明确使用 _meta 中的 W3C Trace Context 键:

traceparent
tracestate
baggage

使 Host、MCP Client、Server 和下游服务的 Trace 可以关联。MCP 2026-07-28 RC

建议记录:

app.agent.mcp.trace.injected
app.agent.mcp.trace.extracted
app.agent.mcp.trace.propagation_error
app.agent.mcp.client_span_id
app.agent.mcp.server_span_id

不要把敏感用户数据放进 Baggage,因为它可能传播到所有下游。


5.8 连接断开、重连与能力变化#

旧状态型连接应记录:

disconnect_reason
reconnect_count
reconnect_delay_ms
session_recovered
capability_refreshed

无状态 HTTP 应更关注:

request_failure
connection_pool_reset
retry_attempt
state_handle_rejected
capability_cache_expired
server_revision_changed

当 Tool List 发生变化时,必须记录:

old_snapshot_hash
new_snapshot_hash
change_reason
affected_model_calls

否则模型选择了“已经消失的工具”时无法追溯。


6. 上下文压缩与任务恢复#

MCP、上下文恢复与多 Agent Trace 关系

上下文压缩不是普通文本摘要,而是一次会影响后续决策的状态转换。

OpenTelemetry 提供 gen_ai.conversation.compacted=true 作为正向指示,只在系统能够可靠确认发生压缩时设置。OpenAI 的 Compaction 文档也将压缩描述为在缩减上下文的同时保留后续任务所需状态,并支持用压缩 Item 或前序 Response 继续运行。OpenTelemetry GenAI Spans OpenAI Compaction

6.1 Context Window 使用率#

记录:

app.agent.context.window.capacity_tokens
app.agent.context.window.used_tokens
app.agent.context.window.reserved_output_tokens
app.agent.context.window.utilization
app.agent.context.window.estimated_overflow_tokens

同时记录组件占比,见 1.5。


6.2 Compaction 触发点#

触发原因:

token_threshold
provider_context_limit
latency_budget
cost_budget
manual
checkpoint_policy
tool_result_growth

字段:

app.agent.compaction.id
app.agent.compaction.trigger
app.agent.compaction.threshold_tokens
app.agent.compaction.started_at
app.agent.compaction.model
app.agent.compaction.prompt.version

6.3 压缩输入与输出#

输入记录:

source_message_ids
source_tool_result_ids
source_memory_ids
input_token_count
input_manifest_hash

输出记录:

summary_hash
summary_token_count
opaque_item_ref
output_manifest_hash
compression_ratio

如果 Provider 返回不可读的压缩 Item,应把它当作受控状态对象保存,不应假装能从中人工解释所有内容。


6.4 被保留和被丢弃的信息#

必须有 Retention Manifest:

retained
dropped
summarized
externalized

关键类别:

user_goal
hard_constraints
pending_tool_calls
completed_actions
environment_state
unresolved_errors
approval_state
subagent_state
citations

对于本文案例,压缩后必须检查:

只能修改订单模块
不得改动支付模块
必须运行相关测试
当前失败测试名称
已经修改的文件
尚未完成的步骤

6.5 Checkpoint#

Checkpoint 至少包含:

checkpoint_id
run_id
state_version
created_at
last_completed_step_id
pending_tool_call_ids
environment_revision
message_manifest_hash
memory_snapshot_id
approval_state

LangGraph 官方 Persistence 机制会在步骤边界保存 Checkpoint,用于 Human-in-the-loop、容错、恢复和 Time Travel。LangGraph Persistence


6.6 Resume#

记录:

app.agent.resume.id
app.agent.resume.source_checkpoint_id
app.agent.resume.reason
app.agent.resume.started_at
app.agent.resume.previous_trace_id
app.agent.resume.new_trace_id
app.agent.resume.previous_response_id
app.agent.resume.state_validation.status

长时间暂停后恢复时,不必强行延长原 Trace。可以创建新 Trace Segment,并使用:

run_id
checkpoint_id
Span Link
logical_parent_step_id

保持逻辑连续性。


6.7 恢复后的逻辑连续性#

恢复前后应验证:

goal_hash
constraint_set_hash
tool_schema_set_hash
environment_revision
pending_actions
already_applied_side_effects

尤其要防止:

  • 重复执行已经完成的写操作;
  • 恢复到错误仓库 Revision;
  • 使用过期 Tool List;
  • 丢失等待审批的具体 Tool Call;
  • 将旧 Tool Result 回填到新的调用。

6.8 压缩后目标漂移检测#

建议保存一份稳定 Task Contract:

{
"goal": "修复折扣计算 Bug",
"must": ["相关测试通过"],
"must_not": ["修改支付模块"],
"allowed_scope": ["orders/**", "tests/orders/**"]
}

压缩后重新提取 Contract,并比较:

goal_similarity
hard_constraint_recall
forbidden_constraint_recall
scope_change
pending_action_consistency

最重要的不是普通语义相似度,而是硬约束是否完整保留


7. 多 Agent 与子 Agent 观测#

多 Agent 系统中,“谁做了什么”很容易丢失。必须区分 Delegation 和 Handoff。

7.1 Delegation#

Delegation 表示父 Agent 保留控制权,把子任务交给子 Agent 或 Agent-as-Tool:

parent agent
├── delegate test analysis
├── delegate code review
└── merge results

父 Agent负责最终决策。

记录:

app.agent.delegation.id
app.agent.delegation.parent_agent
app.agent.delegation.child_agent
app.agent.delegation.reason
app.agent.delegation.contract_ref

7.2 Handoff#

Handoff 表示控制权转移到另一个 Agent。OpenAI Agents SDK 将 Handoff 表现为一种工具,并提供输入类型、输入过滤和源 / 目标 Agent 信息。OpenAI Handoffs

记录:

app.agent.handoff.id
app.agent.handoff.from_agent
app.agent.handoff.to_agent
app.agent.handoff.reason
app.agent.handoff.context_filter.version
app.agent.handoff.accepted

Delegation 和 Handoff 不应混为同一枚举,否则无法知道谁对最终 Outcome 负责。


7.3 子任务 Contract#

每个子任务必须有 Contract:

goal
input_refs
allowed_tools
forbidden_tools
environment_scope
budget
deadline
expected_output_schema
acceptance_criteria

示例:

{
"subtask_id": "sub_02",
"goal": "分析 failing test 的根因,不修改文件",
"allowed_tools": ["read_file", "search_code", "run_tests"],
"forbidden_tools": ["edit_file", "git_push"],
"budget": {
"max_model_calls": 4,
"max_tokens": 20000
},
"expected_output_schema": "root-cause-report-v1"
}

7.4 父 Trace 与子 Trace#

短同步子任务可以作为父 Trace 的子 Span。
长时间、跨服务或独立生命周期的子 Agent 可以创建独立 Trace,并通过 Link 关联:

parent_run_id
delegation_id
parent_trace_id
child_trace_id
parent_span_id

不要只依赖嵌套 Span,因为子 Agent 可能在父 Span 结束后继续运行。


7.5 子 Agent 并行执行#

记录:

fanout_id
parallel_child_count
concurrency_limit
queue_wait_ms
child_started_at
child_completed_at

并行子任务应是兄弟节点,而不是按完成顺序串成父子关系。


7.6 汇合和结果选择#

Fan-in 阶段记录:

app.agent.merge.id
app.agent.merge.input_result_ids
app.agent.merge.policy.version
app.agent.merge.selected_result_ids
app.agent.merge.rejected_result_ids
app.agent.merge.conflict_count
app.agent.merge.output_hash

结果选择策略:

all_required
first_success
majority
judge_selected
rule_based
manager_agent_selected

7.7 子任务失败传播#

失败类型:

child_failed_local
child_timed_out
child_budget_exhausted
child_cancelled
contract_violated
result_schema_invalid
merge_conflict
parent_cancelled

记录父 Agent 的处理动作:

retry_child
replace_child
degrade
continue_without
handoff_human
fail_parent

7.8 多 Agent 费用和关键路径#

总成本通常是所有子任务成本之和:

total_cost = Σ child_cost + manager_cost

墙钟时间则由关键路径决定,不是所有 Span Duration 之和。

建议记录:

app.agent.multi_agent.total_cost
app.agent.multi_agent.total_tokens
app.agent.multi_agent.critical_path_ms
app.agent.multi_agent.parallelism_ratio
app.agent.multi_agent.wasted_work_cost

“被取消的子 Agent”也可能已经消耗 Token 和工具费用,不能从成本中删除。


8. 人工审批与 Human-in-the-loop#

8.1 审批请求#

记录:

approval_request_id
tool_call_id
requested_action
risk_level
policy_version
arguments_hash
side_effect_class
requested_at

审批 UI 展示的内容也要版本化,因为“用户看到了什么”决定审批是否有效。


8.2 等待时间#

审批等待应使用独立 Span:

approval.wait

记录:

queue_wait_ms
human_wait_ms
decision_processing_ms
total_wait_ms

不要把等待时间算进 Tool Execution,否则性能分析会错误地认为工具很慢。


8.3 审批人和审批结果#

记录:

actor_id
actor_role
actor_tenant
authentication_method
decision
decision_reason_code
decided_at

结果:

approved
rejected
expired
cancelled
modified_and_approved
escalated

Actor ID 可能敏感,不应进入 Metric Label。


8.4 修改后的工具参数#

保存:

original_arguments_hash
approved_arguments_hash
changed_fields
change_reason
modifier_actor_id

必要时保存结构化 Diff:

{
"changed_fields": [
{
"path": "$.branch",
"before": "main",
"after": "fix/discount-bug"
}
]
}

这能区分:

  • 模型参数错误;
  • 审批人纠正参数;
  • 审批 UI 序列化错误;
  • 工具执行器使用了旧参数。

8.5 拒绝后的替代路径#

拒绝不是链路终点。应记录:

rejection_result_attached
planner_reinvoked
alternative_tool_selected
scope_reduced
handoff_to_human
task_cancelled

例如用户拒绝直接修改文件后,Agent 可以:

  • 只生成 Patch;
  • 输出修改建议;
  • 创建草稿;
  • 将任务交给人工;
  • 请求更低权限工具。

替代路径必须关联原审批请求,才能评价 Agent 是否“安全地继续完成任务”。


9. 从字段到因果解释#

Agent 错误的因果诊断图

可观测字段的最终价值,是把“模型表现不好”拆成可验证的问题。

建议建立统一 Causal Worksheet:

现象
→ 最早异常节点
→ 输入证据
→ 候选与过滤
→ 决策
→ 动作
→ 状态变化
→ Outcome
→ 对照实验

9.1 Agent 为什么选择错误工具#

检查链路:

Tool Discovery Snapshot
→ Exposed Tool Definitions
→ Model Context Manifest
→ Model Output Tool Call
→ Argument Validation
→ Approval
→ Execution

需要回答:

  1. 正确工具是否存在;
  2. 是否被权限策略过滤;
  3. 工具描述是否含糊;
  4. Schema 是否错误;
  5. 同名工具是否冲突;
  6. 模型是否收到完整 Tool List;
  7. Fallback Model 是否支持相同工具;
  8. Context Compaction 是否删掉了工具使用约束。

9.2 错误来自模型还是工具描述#

可用以下对照:

同一模型 + 同一上下文 + 工具描述 v1
同一模型 + 同一上下文 + 工具描述 v2

观察:

  • 工具选择率;
  • 参数合法率;
  • 任务成功率;
  • 错误类型。

如果仅修改 Tool Description 就显著改善,根因更可能是接口描述。
如果多个清晰描述下仍持续选错,才更支持模型决策问题。

必须保存:

tool_description_hash
tool_schema_hash
model_revision
prompt_hash
random_seed / trial_id

9.3 RAG 是否提供了误导证据#

检查:

Query Rewrite
→ Candidate
→ Filter
→ Rerank
→ Selected Chunk
→ Claim Evidence

需要区分:

  • 没召回正确内容;
  • 正确内容被 Rerank 降级;
  • 错误内容被高排名注入;
  • 正确内容进入上下文但模型误读;
  • 引用与结论不一致。

更强的验证方式是:

  • 删除误导 Chunk 重跑;
  • 只保留正确 Chunk 重跑;
  • 固定模型输出随机性做多 Trial;
  • 比较 Claim 支持状态。

9.4 记忆是否压制了新的方案搜索#

观察:

Memory Injection
→ Planner Decision
→ Retrieval / Search Count
→ Tool Candidate Diversity
→ Final Outcome

证据:

  • 记忆注入后直接复用旧方案;
  • 没有执行当前环境检索;
  • 候选工具数量下降;
  • 旧仓库路径被复制到 Tool Arguments;
  • Memory-disabled Replay 选择新方案并成功。

不要仅凭“记忆出现在 Prompt 中”就声称它造成了失败。


9.5 Tool Result 是否被模型忽略或误读#

按四层检查:

Result Produced
→ Result Normalized
→ Result Injected
→ Result Used

可能根因:

  • 工具返回为空;
  • Result 被截断;
  • Summary 删除关键字段;
  • 回填到错误 tool_call_id
  • 下一次模型请求没有包含 Result;
  • 模型看到了但误读;
  • 后续动作使用了旧状态。

可以检查:

result_hash
injected_hash
injected_token_count
downstream_model_span_id
value_propagation

9.6 Context Compaction 是否造成任务目标丢失#

比较压缩前后:

Task Contract
Hard Constraints
Pending Actions
Applied Side Effects
Environment Revision

本文案例中最关键的三个不变量:

目标:修复折扣计算
禁止:修改支付模块
完成标准:相关测试通过

如果压缩后:

  • “禁止修改支付模块”消失;
  • 已完成文件编辑未被记录;
  • Pending Tool Call 丢失;
  • 恢复后重复写入;

就可以把故障定位到 Compaction / Resume,而不是笼统归因给模型。

因果解释的最后一道边界#

Trace 能证明:

A 发生在 B 之前
B 的输入包含 A
B 的输出引用了 A

但“如果没有 A,B 就不会发生”通常需要 Replay、A/B 或 Counterfactual 测试。

因此,一份诚实的根因报告应该区分:

observed
strongly_supported
counterfactual_verified
unknown

统一字段合同示例#

下面给出一条简化的组件观测记录。完整内容应拆入多个 Span 和 Artifact,这里仅展示字段关系。

{
"telemetry_schema_version": "1.0.0",
"run_id": "run_20260805_001",
"step_id": "step_007",
"model": {
"provider": "openai",
"request_model": "model-x",
"response_model": "model-x-2026-07-15",
"prompt": {
"name": "coding-planner",
"version": "3.4.0",
"template_hash": "sha256:..."
},
"context": {
"manifest_ref": "artifact://contexts/ctx_007",
"input_tokens": 42100,
"retrieval_tokens": 6200,
"memory_tokens": 740,
"tool_result_tokens": 18300,
"compacted": true
},
"output": {
"schema_id": "tool-plan-v2",
"validation_status": "valid",
"provider_stop_reason": "tool_use",
"normalized_stop_reason": "tool_call"
}
},
"tool": {
"discovery_snapshot_id": "toolsnap_041",
"tool_call_id": "call_091",
"name": "filesystem.edit_file",
"schema_version": "2.1.0",
"raw_arguments_hash": "sha256:...",
"validated_arguments_hash": "sha256:...",
"approval": {
"required": true,
"decision": "modified_and_approved",
"wait_ms": 18340
},
"execution": {
"status": "completed",
"attempt_count": 1,
"side_effect_class": "reversible_write",
"state_diff_ref": "artifact://diffs/diff_019"
},
"result": {
"content_hash": "sha256:...",
"artifact_ref": "artifact://tool-results/result_091",
"injected_into_model_span_id": "span_model_008",
"injected_token_count": 820
}
},
"retrieval": {
"run_id": "ret_014",
"index_version": "repo-abc123",
"candidate_manifest_ref": "artifact://retrieval/ret_014",
"selected_chunk_ids": ["chunk_14", "chunk_22"]
},
"memory": {
"recall_id": "memrec_006",
"candidate_count": 5,
"injected_memory_ids": ["mem_041"],
"injection_id": "meminj_004"
},
"mcp": {
"protocol_revision": "2026-07-28",
"server_id": "github-mcp",
"discovery_mode": "server_discover",
"capability_snapshot_hash": "sha256:...",
"trace_context_injected": true
},
"resume": {
"checkpoint_id": "cp_012",
"previous_trace_id": "trace_old",
"goal_invariants_verified": true
}
}

落地检查表#

模型#

  • Request Provider、Request Model 和 Response Model 分开记录
  • Prompt Template、System Prompt、Tool Definitions 都有版本和 Hash
  • Context Manifest 能解释每类 Token 的来源
  • Cache Read / Creation Token 可见
  • Structured Output Schema 与 Validator 可追溯
  • Parse Failure 和 Repair 不会被最终成功覆盖
  • Provider Stop Reason 和统一 Stop Reason 同时保存
  • Fallback Attempt 保留原始错误

工具#

  • Tool Discovery Snapshot 可复现
  • Tool Schema、Description、Implementation 独立版本化
  • Raw、Validated、Approved Arguments 可比较
  • Approval 与执行耗时分开
  • 执行失败和业务失败分开
  • Tool Result 转换链可追踪
  • State Before / After / Diff 可用
  • 能判断 Result 是否实际进入后续模型上下文

RAG#

  • 原始 Query 与 Rewrite 分开
  • Index、Embedding、Retriever 和 Reranker 都有版本
  • Candidate、Filter、Rerank、Selection 四层不混写
  • Raw Score 带 Score Type
  • 最终注入 Chunk 与 Token 位置可见
  • Claim 可以关联到来源 Chunk
  • 检索失败能定位到具体阶段

Memory#

  • Recall、Filter、Injection、Mutation 独立
  • 每条记忆有来源、版本、有效期和信任级别
  • 原文、摘要和压缩链可追溯
  • 冲突记忆有 Conflict Set
  • 不把“存在于上下文”直接等同于“造成行为”

MCP#

  • 记录协议 Revision
  • 区分旧 Initialize / Session 路径和无状态路径
  • Capability Snapshot 与 Tool / Resource List 可追溯
  • Client / Server Trace Context 可关联
  • 断连、Retry、State Handle 和 Capability 变化可见

压缩、多 Agent 与 HITL#

  • Compaction 前后有 Retention Manifest
  • Checkpoint 和 Resume 验证目标不变量
  • Delegation 与 Handoff 分开
  • 子任务有 Contract
  • 并行子 Agent 有 Fan-out / Fan-in 和 Critical Path
  • 审批对象绑定具体 Tool Call
  • 审批参数修改有 Diff
  • 拒绝后的替代路径可追踪

结语#

Agent 内部组件观测的核心,不是收集更多日志,而是建立一套能够回答以下问题的证据体系:

模型看到了什么
→ 为什么出现这个候选动作
→ 哪个动作被执行
→ 工具和环境真实发生了什么
→ 哪些信息进入了下一步
→ 哪条链路把局部错误传播成最终失败

一套合格的 Agent 观测模型,应同时具备:

  • 身份:对象和调用都有稳定 ID;
  • 版本:模型、Prompt、工具、索引、记忆和协议可追溯;
  • 内容边界:默认记录 Shape,不默认复制敏感 Content;
  • 血缘:检索、记忆、摘要和 Artifact 有来源链;
  • 状态差异:环境变化通过 Before / After / Diff 表达;
  • 因果边:输入、选择、执行、回填和后续动作可以关联;
  • 证据等级:观察事实、强支持和反事实验证分开;
  • 恢复记录:Retry、Fallback、Compaction 和 Resume 不抹掉中间失败。

只有做到这一层,Trace 才不再是一张“调用耗时树”,而会成为 Agent 调试、评测、审计和数据闭环真正可用的运行证据。


参考资料#

  1. OpenTelemetry:Semantic conventions for generative AI spans
  2. OpenTelemetry:Semantic conventions for generative AI agent spans
  3. OpenTelemetry:GenAI attribute registry
  4. OpenAI API:Structured model outputs
  5. OpenAI API:Function calling
  6. OpenAI API:Compaction
  7. OpenAI Agents SDK:Human-in-the-loop
  8. OpenAI Agents SDK:Handoffs
  9. Anthropic:Prompt caching
  10. Anthropic:Handling stop reasons
  11. Anthropic:Citations
  12. Model Context Protocol:2026-07-28 Specification Release Candidate
  13. Model Context Protocol:SEP-2567 Sessionless MCP via Explicit State Handles
  14. LangGraph:Persistence
  15. LangGraph:Interrupts
第 3 篇:Agent 内部组件应该记录什么——模型、工具、RAG、记忆与 MCP
https://jupiter-ws.cn/posts/agent-observability/03-agent-component-observability/
作者
Jupiter
发布于
2026-08-05
许可协议
CC BY-NC-SA 4.0