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

第 1 篇:Agent 可观测性全景与统一数据模型

从 Agent 执行链、四类核心证据、统一对象模型、标识符关系到环境副作用,建立可追踪、可评估、可审计的观测数据底座。

开始阅读全文10933 字 · 55 分钟 查看系列目录Agent 观测
关键词 Agent可观测性Trace数据模型OpenTelemetry
栏目 AgentObservability;专栏 Agent 观测;标签 Agent、可观测性、Trace、数据模型、OpenTelemetry

文章目标#

传统应用可观测性通常围绕一次请求展开:请求从哪个入口进入,经过哪些服务,在哪里变慢,最终返回了什么状态码。Agent 系统却不只是在“处理请求”,而是在围绕用户目标持续决策并改变环境。一次任务可能跨越多轮模型调用、检索、记忆读取、工具执行、人工审批、重试、上下文压缩和任务恢复,最终还可能修改代码、数据库、浏览器页面或外部业务系统。

因此,Agent 可观测性的核心问题不再只是“接口是否正常”,而是:

如何把用户目标、模型决策、工具行为、状态变化、失败恢复和最终结果组织成一条可追溯、可分析、可评测的因果执行链?

本文围绕这个问题完成三件事:

  1. 说明传统日志和 APM 为什么无法单独承担 Agent 观测;
  2. 建立 Session、Task、Run、Trial、Turn、Step、Trace、Span 等统一对象模型;
  3. 给出一份可用于工程落地的最小 Agent Trace 数据契约。

全文使用一个 Coding Agent 任务作为贯穿案例:

用户提交一个 GitHub Issue,要求 Agent 修复仓库中的 failing test。Agent 需要读取仓库、搜索代码、运行测试、修改文件、重新验证,并在网络中断、工具超时或权限受限时尝试恢复。

标准边界说明:OpenTelemetry 已经定义了 Trace、Span、Event、Link、Metric、Log 等底层遥测概念,W3C Trace Context 定义了跨服务传播 traceparenttracestate 的标准格式;但本文中的 Task、Run、Trial、Step、Artifact、State Snapshot 等属于面向 Agent 的应用层建模,不应被误解为 OpenTelemetry 的统一标准。OpenTelemetry 的 GenAI 语义约定也仍在独立仓库中持续演进,因此工程实现需要保留 schema_version 和自定义字段命名空间。1234


1. 为什么传统日志和 APM 不足以观测 Agent#

Agent 任务的典型执行链

1.1 HTTP 请求成功不等于 Agent 任务成功#

传统 Web 服务通常将一次请求作为基本运行单元:

HTTP Request → Service → Database / Cache → HTTP Response

如果接口返回 200 OK,通常说明这次请求在协议和服务层面被成功处理。但 Agent 的任务生命周期可能远长于入口请求:

提交任务 → 接受任务 → 多步执行 → 修改环境 → 验证结果 → 返回 Outcome

例如,用户调用接口提交“修复测试失败”的任务。服务立即返回 200,只说明任务已进入队列,并不能证明:

  • Agent 找到了正确的失败原因;
  • Agent 修改了正确文件;
  • 测试已经通过;
  • 修改没有破坏其他模块;
  • 任务没有在后台超时或卡死。

因此至少要区分三种状态:

状态层级回答的问题示例
Transport Status请求是否被系统接收HTTP 200、消息成功入队
Execution StatusAgent 是否仍在正常执行running、waiting approval、retrying
Task Outcome用户目标是否真正完成tests passed、patch valid、no unexpected changes

最常见的错误设计,是直接把 HTTP 状态码映射成任务状态。这会让监控面板显示“成功率 99.9%”,而真实任务完成率可能只有 70%。

1.2 最终回答正确不等于执行过程正确#

只评估最终输出,会漏掉三类关键问题。

第一类:偶然正确。

Agent 读取了错误文件,又根据经验生成了一个补丁,恰好通过不完整测试。结果看起来正确,但路径并不稳定;换一个仓库版本或稍微修改输入,任务就可能失败。

第二类:结果正确但过程高风险。

Agent 最终修复了问题,却在过程中:

  • 读取了与任务无关的敏感文件;
  • 使用了超出任务需要的高权限工具;
  • 删除了临时目录之外的文件;
  • 多次重复执行有副作用的操作;
  • 绕过了人工审批。

这种运行不能因为最终结果正确就被判定为完全成功。

第三类:最终错误只是错误传播的终点。

最终回答错误,根因可能早已出现在上游:

错误检索结果
错误上下文
错误工具选择
错误文件修改
最终测试失败

如果系统只保存最终回答,就只能知道“错了”,却无法知道“从哪里开始错”。Agent 观测必须保存足够的中间证据,用于区分:

  • 模型理解错误;
  • 规划错误;
  • 检索或记忆污染;
  • 工具参数错误;
  • 工具执行失败;
  • 环境状态与预期不一致;
  • 恢复策略错误。

1.3 Agent 可能产生真实的外部环境副作用#

问答模型的主要产物是文本,而 Agent 的关键能力是行动。它可能:

  • 修改代码和配置;
  • 创建 Git commit 或 Pull Request;
  • 写入数据库;
  • 操作浏览器并提交表单;
  • 发送邮件或消息;
  • 创建、更新或关闭工单;
  • 调用支付、部署、删除等高风险接口。

这些行为会改变真实环境。只记录“模型生成了一个工具调用”并不够,因为生成意图、实际执行和环境结果是三个不同事实

模型提出调用工具
编排器校验并批准参数
工具实际执行
外部环境发生变化
系统验证变化是否符合目标

例如,模型生成了 edit_file(path="src/order.py"),但工具可能因为路径权限失败;也可能返回成功,却只写入了临时副本;还可能写入成功,但修改了错误函数。完整观测至少要回答:

  • 模型想做什么;
  • 系统允许它做什么;
  • 工具实际做了什么;
  • 环境最终变成什么;
  • 变化是否与任务目标一致。

1.4 同一个错误可能来自模型、工具、编排、状态或环境#

Agent 是多组件系统。同一个表面故障,可能来自完全不同的层。

表面现象可能根因
Agent 没找到目标文件模型查询词错误、检索索引过期、权限不足、工具返回被截断
工具调用参数错误模型生成错误、Tool Schema 不清晰、Schema 版本不一致、参数修复器出错
测试一直失败代码修复错误、测试环境异常、依赖版本变化、旧进程未重启
Agent 重复调用同一工具结果未回填、状态未持久化、模型忽略结果、循环终止条件失效
恢复后重复提交幂等键缺失、Checkpoint 过旧、恢复逻辑没有读取已完成动作
最终回答声称成功Outcome 依赖 Agent 自述,没有验证真实环境状态

这意味着排障不能只搜索错误日志,而要跨层关联:

模型决策 → 编排状态 → 工具执行 → 外部环境 → 结果验证

如果缺少任意一层,就可能把根因误判到下游。例如,工具返回空结果并不一定是工具故障,也可能是检索权限、参数构造或上游状态错误。

1.5 Agent 需要同时观测“说了什么、做了什么、造成了什么”#

Agent 的最小完整证据链可以概括为三个问题:

观测维度关注内容典型对象
说了什么模型输出了什么决策、计划和最终回答Model Span、Message、Event
做了什么Agent 实际调用了哪些工具、执行了哪些动作Tool Span、Approval、Subagent Span
造成了什么外部环境发生了什么变化,任务是否完成State Snapshot、State Diff、Outcome

三者不能互相替代:

  • 只有模型输出,没有真实执行证据,无法证明动作发生;
  • 只有工具日志,没有模型和状态上下文,无法解释为什么调用;
  • 只有最终环境状态,没有执行轨迹,无法定位是谁造成变化;
  • 只有最终回答,没有 Outcome 验证,无法证明任务完成。

所以,Agent 可观测性的基本单位不是一条日志,而是一条可关联的因果链:

Goal
→ Context
→ Decision
→ Action
→ Observation
→ State Change
→ Next Decision
→ Outcome
→ Score

2. Agent 可观测性的四类核心证据#

Agent 可观测性的四类核心证据

把所有数据都塞进 Trace 并不会自动获得可观测性。更合理的方式,是按它们回答的问题分成四类证据。

证据类型核心问题最小内容主要用途
执行证据Agent 做了什么模型、规划、工具、控制流调试、轨迹分析
状态证据Agent 当时看到了什么上下文、记忆、环境快照根因定位、回放
结果证据任务真正完成了吗Outcome、State Diff、产物评测、验收
运行证据花了多少时间和成本延迟、Token、重试、资源SLO、成本治理

2.1 执行证据:模型、规划、工具与控制流#

执行证据描述 Agent 的行为轨迹,至少包括:

  • 模型调用的开始、结束、模型版本和停止原因;
  • 每个 Step 的目标或动作类型;
  • 工具名称、参数、校验结果和返回状态;
  • 分支、循环、并行、重试和回退;
  • 人工审批、权限决策和子 Agent 委派;
  • Tool Result 被回填到哪个后续 Step。

以 Coding Agent 为例,一条可分析的执行轨迹可能是:

Step 1 读取 Issue
Step 2 搜索失败测试
Step 3 读取目标文件
Step 4 运行测试并确认复现
Step 5 修改代码
Step 6 运行测试失败
Step 7 分析新错误
Step 8 二次修改
Step 9 测试通过
Step 10 输出总结

这里不能只保存步骤名称,还要保存步骤之间的关联。比如第二次修改是因为哪次测试失败触发的,测试结果是否被模型实际看到,最终总结引用的是哪份测试报告。

执行证据重点记录行为事实,而不是强制保存模型隐藏推理。工程上更适合记录结构化决策摘要、动作类型、输入证据和结果,而不是把不可控的内部思维文本当成观测核心。

2.2 状态证据:上下文、记忆和环境状态#

状态证据用于回答:Agent 在做出某个决策时,实际掌握了哪些信息?

它可以分成三层。

上下文状态

  • 当前 system prompt 和 prompt 版本;
  • 当前会话消息;
  • 注入的检索片段;
  • 当前任务约束;
  • 上下文压缩后的摘要;
  • 模型实际可见的工具定义。

记忆状态

  • 召回了哪些候选记忆;
  • 哪些候选被过滤;
  • 哪些内容进入上下文;
  • 本次运行是否写入、更新或删除记忆;
  • 记忆版本和来源。

环境状态

  • 当前仓库 revision;
  • 工作区是否存在未提交修改;
  • 关键文件的内容哈希;
  • 数据库关键记录;
  • 浏览器 URL、DOM 摘要和登录状态;
  • 外部服务或 MCP Server 的 capability。

状态证据不等于“把整个环境复制进 Trace”。更稳妥的设计是保存:

  1. 小型结构化摘要;
  2. 不可变内容哈希;
  3. 指向外部 Artifact 或 Snapshot Store 的引用。

这样既能控制 Trace 体积,也能在需要时恢复原始证据。

2.3 结果证据:任务 Outcome 与真实环境变化#

结果证据必须独立于 Agent 的自然语言自述。

例如,Agent 最后说“测试已经全部通过”,不能直接作为成功依据。系统应读取真实测试结果,并形成结构化 Outcome:

{
"task_status": "success",
"tests_passed": true,
"modified_files": ["src/order.py", "tests/test_order.py"],
"unexpected_changes": false,
"verification_artifact_id": "artifact_test_report_01"
}

一个完整 Outcome 通常包含:

  • 任务状态:success、failure、partial、cancelled;
  • 最终输出;
  • 环境状态是否达到目标;
  • 关键产物;
  • 预期副作用;
  • 非预期副作用;
  • 完成或终止原因;
  • 验证证据引用。

对于不同 Agent,Outcome 的验证方式不同:

Agent 类型主要 Outcome 证据
Coding Agent测试结果、编译结果、Git Diff、静态检查
Browser Agent最终页面状态、DOM、截图、业务记录
数据库 Agent事务结果、目标记录、约束校验、审计日志
客服 Agent工单状态、回复内容、政策遵循、用户确认
Workflow Agent各阶段状态、外部系统回执、补偿结果

2.4 运行证据:延迟、费用、重试和资源消耗#

运行证据不回答“结果是否正确”,而回答“系统以什么代价得到结果”。

最少应记录:

  • 端到端时延;
  • 模型首 Token 延迟和生成时间;
  • 工具、检索、记忆和审批耗时;
  • 输入、输出和缓存 Token;
  • 模型费用和外部工具费用;
  • 重试次数、退避时间和失败原因;
  • 并行分支的关键路径;
  • CPU、内存、浏览器或沙箱使用情况。

Agent 特别容易出现“成功但低效”的情况:

  • 工具振荡导致重复查询;
  • 长上下文不断增长,费用逐步放大;
  • 网络重试导致尾延迟恶化;
  • 子 Agent 大量并发,却没有缩短关键路径;
  • 人工审批等待被错误统计为模型推理时间。

因此,比“每次请求成本”更有意义的指标通常是:

Cost per Successful Task

即完成一个真实成功任务所消耗的平均成本。


3. 统一对象模型#

Agent 统一对象模型

本文采用的对象模型由两部分组成:

  • 标准追踪层:Trace、Span、Event、Link,尽量与 OpenTelemetry 兼容;
  • Agent 领域层:Session、Task、Run、Trial、Turn、Step、Artifact、State Snapshot、Score。

Langfuse 等平台已经使用 Session、Trace、Observation 和 Score 组织 LLM/Agent 运行数据,说明“会话—执行链—局部观察—评价”是较常见的工程分层;但不同平台对 Trace 粒度的定义并不完全相同,因此内部系统必须明确自己的基数和边界。56

推荐关系如下:

Session 1 ── N Task
Task 1 ── N Run
Eval Case 1 ── N Trial
Trial 1 ── 1 Run
Run 1 ── 1..N Trace Segment
Turn 1 ── N Step
Trace 1 ── N Span
Trace / Step / Span ── N Event / Artifact / Snapshot / Score

3.1 Session:连续交互会话#

Session 表示用户与 Agent 的连续交互容器,通常对应聊天线程、CLI 会话或长时间工作区。

一个 Session 可以包含多个 Task。例如用户先要求修复 Bug,随后又要求补充测试和更新 README。它们共享会话上下文,但目标和验收条件不同。

Session 适合承载:

  • 用户或租户信息引用;
  • 会话开始和结束时间;
  • 多轮交互关系;
  • Session 级标签和汇总评分;
  • 会话恢复信息。

不应把 Session 直接作为任务成功率的唯一统计单位,因为一个 Session 可能同时包含成功和失败任务。

3.2 Task:用户希望完成的目标#

Task 是用户目标的规范化表达,是 Outcome 评估的主要对象。

一个 Task 最好包含:

  • 原始用户请求;
  • 规范化目标;
  • 约束条件;
  • 成功标准;
  • 禁止行为;
  • 可使用工具和权限边界。

例如“修复测试失败”过于模糊,规范化后可以是:

目标:修复 tests/test_discount.py::test_coupon_stack
成功标准:目标测试与完整测试套件通过
约束:不得修改公共 API;不得删除现有测试

Task 应尽量稳定,不应因为一次 Run 的执行结果而被覆盖。

3.3 Run:某个 Agent 版本的一次执行#

Run 表示某个确定的 Agent 配置对一个 Task 的一次实际执行。

它应绑定“有效运行版本”,包括:

  • Agent 代码 revision;
  • 模型和参数;
  • Prompt 版本;
  • Tool Schema 版本;
  • 环境 revision;
  • 权限策略。

同一个 Task 可以有多个 Run:

  • 用户重新执行;
  • Agent 升级后重跑;
  • Prompt 变更后重跑;
  • 线上失败后离线复现。

Run 是连接“业务任务”和“遥测 Trace”的关键对象。

3.4 Trial:评测场景中的一次独立尝试#

Trial 只在评测语境中使用,表示同一个评测样本的一次独立尝试。

例如,为了测量随机性,对同一个 Coding Task 运行 5 次:

Eval Case A
├── Trial 1 → Run 1
├── Trial 2 → Run 2
├── Trial 3 → Run 3
├── Trial 4 → Run 4
└── Trial 5 → Run 5

这样可以统计:

  • 单次成功率;
  • 多次运行稳定性;
  • pass@k;
  • 成本和延迟分布;
  • 失败模式方差。

不要把 Trial 当成生产运行的必需层。没有评测语义时,trial_id 可以为空。

3.5 Turn:一次用户—Agent 交互轮次#

Turn 描述用户与 Agent 的交互结构。

一个典型 Turn 可以是:

用户输入 → Agent 内部多步执行 → Agent 回复

在一个 Turn 内,Agent 可能调用模型和工具很多次。因此 Turn 不等于 Model Call,也不等于 Step。

Turn 主要用于:

  • 多轮会话展示;
  • 用户反馈挂载;
  • 会话级上下文重建;
  • 分析用户补充信息如何改变后续执行。

3.6 Step:Agent Loop 中的一次决策或动作#

Step 是 Agent 领域中的逻辑执行单元,通常对应一次“观察—决策—行动”推进。

例如:

Step 4
输入:测试失败结果
决策:读取 discount 计算函数
动作:调用 read_file
输出:文件内容进入下一步上下文

一个 Step 可以包含多个底层 Span。例如一次“验证修复”Step,可能同时启动单元测试、静态检查和类型检查三个工具 Span。

Step 的价值在于把底层遥测重新映射为可理解的 Agent 行为,而不是让分析者直接面对数百个技术 Span。

3.7 Trace:一次任务的完整执行链#

OpenTelemetry 将 Trace 理解为由共享 trace_id 的 Span 组成的端到端路径。2 在 Agent 系统中,推荐根据任务生命周期选择两种映射方式:

短生命周期任务:

1 Run = 1 Root Trace

适用于同步 API、短时 CLI 命令和一次性工作流。

长生命周期或可恢复任务:

1 Run = N Trace Segments

例如任务暂停等待人工审批,数小时后在另一个进程恢复。此时不应强行维持一个永不结束的 Span,而可以使用相同 run_id 关联多个 Trace,并用 Span Link 表示恢复关系。

因此,Trace 是技术执行链,而 Run 是业务执行实例;二者通常接近,但不应强制永久一一对应。

3.8 Span:模型、工具、检索等局部操作#

Span 表示具有明确开始和结束时间的一次局部操作。OpenTelemetry Span 通常包含:

  • Trace ID 和 Span ID;
  • Parent Span ID;
  • 开始和结束时间;
  • Attributes;
  • Events;
  • Links;
  • Status。2

Agent 常见 Span 类型包括:

  • agent:Agent 或工作流运行;
  • model:模型调用;
  • tool:工具执行;
  • retrieval:检索;
  • memory:记忆召回、过滤或写入;
  • approval:人工审批;
  • subagent:子 Agent;
  • system:编排器内部操作。

Span 应记录“操作”,而不是承载所有业务对象。大文件、补丁和截图应使用 Artifact 引用,避免把 Trace 变成不可查询的大对象仓库。

3.9 Event:错误、审批、压缩和恢复等离散事件#

Event 表示某个有明确时间点、但不一定具有持续时间的事实,例如:

  • 收到 429;
  • 工具参数校验失败;
  • 用户批准高风险操作;
  • 上下文压缩发生;
  • Checkpoint 创建;
  • Run 被暂停或恢复;
  • 模型流被用户取消。

判断使用 Span 还是 Event 的简单规则是:

  • 有开始、结束和持续时间的过程,优先用 Span;
  • 强调某个时间点发生的事实,优先用 Event。

3.10 Artifact:代码补丁、截图、文件和测试报告#

Artifact 是执行过程产生或消费的独立内容对象,例如:

  • Git Diff;
  • 测试报告;
  • 修改后的文件;
  • Shell 完整输出;
  • 浏览器截图;
  • DOM 快照;
  • 生成的文档。

推荐 Artifact 至少包含:

{
"artifact_id": "artifact_diff_01",
"type": "git_diff",
"uri": "s3://agent-artifacts/run_01/fix.patch",
"sha256": "...",
"media_type": "text/x-diff",
"created_by_span_id": "span_edit_01"
}

sha256 或等价内容哈希很重要,它能证明后续评测读取的内容与运行时产物一致。

3.11 State Snapshot:执行前后的环境状态#

State Snapshot 表示关键时间点的环境状态投影。

它不必总是完整快照,可以有三种形态:

  1. 完整快照:容器镜像、数据库副本、工作区归档;
  2. 结构化投影:关键字段、Git HEAD、测试列表;
  3. 内容引用:指向外部 Snapshot Store 的 URI 和哈希。

常见 Snapshot 类型:

  • before:操作前;
  • after:操作后;
  • checkpoint:可恢复状态点;
  • verification:用于 Outcome 验证的状态。

3.12 Score:对结果、步骤或轨迹的评价#

Score 是评价对象,不属于原始运行事实。它可以挂载到:

  • Trace / Run:整个任务质量;
  • Span / Step:局部动作质量;
  • Session:多轮会话整体质量;
  • Dataset Run:实验汇总结果。

Langfuse 的 Score 模型也允许评分关联 Trace、Observation、Session 或 Dataset Run,并区分数值、分类、布尔和文本类型。6

Score 至少需要:

  • 被评价对象;
  • 指标名称;
  • 分值或标签;
  • Grader 类型和版本;
  • Rubric 版本;
  • 证据引用;
  • 评分时间。

没有 evidence_refs 的分数很难审计,也无法解释为什么某个 Trace 得到低分。


4. 标识符和关联关系设计#

标识符体系与关联关系设计

标识符设计的目标不是“看起来整齐”,而是保证跨服务、跨进程、跨存储和跨评测系统的关联稳定。

建议遵循五条规则:

  1. 全局唯一:不同节点并行生成时不冲突;
  2. 稳定不变:对象创建后不因重试或迁移改变;
  3. 不包含业务语义:不要把用户名、邮箱或路径编码进 ID;
  4. 身份与尝试分离:逻辑操作 ID 和物理 Attempt ID 不混用;
  5. 可跨系统传播:Trace 相关 ID 尽量兼容 OpenTelemetry/W3C。

W3C Trace Context 明确要求 traceparent 使用标准化的 Trace ID 和 Parent ID 传播链路,并强调这些字段不应包含个人敏感信息。3

4.1 session_id#

作用域是一次连续交互会话。

推荐用途:

  • 聚合多轮 Trace;
  • 恢复会话;
  • 统计 Session 级用户体验;
  • 关联 Session 级 Score。

session_id 不应直接使用用户 ID,否则既会造成隐私风险,也无法表达同一用户的多个并行会话。

4.2 task_id#

作用域是一个稳定用户目标。

Task 被重新运行时,task_id 保持不变,新的执行生成新的 run_id。这样才能比较同一目标在不同版本上的表现。

4.3 run_idtrial_id#

  • run_id:一次实际执行;
  • trial_id:一次评测尝试。

评测场景中通常是一一对应:

trial_id → run_id

但 Run 仍应独立存在,因为它承载真实运行版本和生命周期,而 Trial 还要关联 Dataset、Experiment 和重复次数。

4.4 trace_idspan_idparent_span_id#

这三个 ID 构成标准追踪骨架:

  • trace_id 标识一条 Trace;
  • span_id 标识一个局部操作;
  • parent_span_id 表示直接调用父节点。

如果系统跨 HTTP、RPC、MCP Gateway 或多个观测后端传播,优先使用 OpenTelemetry SDK 和 W3C Trace Context,而不是手工拼接 Trace ID。traceparent 负责标准位置传播,tracestate 可以承载供应商特定追踪信息。3

4.5 turn_idstep_id#

  • turn_id 关联用户交互轮次;
  • step_id 关联 Agent 逻辑步骤。

一个 Step 的多个 Span 应共享 step_id,这样 UI 可以同时提供两种视图:

  • 技术视图:Span Timeline;
  • Agent 视图:Step Trajectory。

4.6 tool_call_id#

tool_call_id 标识一次逻辑工具调用。需要与以下对象区分:

  • operation_id:更通用的逻辑操作 ID;
  • attempt_id:某次物理尝试;
  • idempotency_key:用于外部系统去重的幂等键。

例如一次工具调用遇到 429 后重试三次:

tool_call_id = tool_01
├── attempt_id = attempt_01
├── attempt_id = attempt_02
└── attempt_id = attempt_03

如果每次重试都生成新的 tool_call_id,系统会误以为 Agent 主动选择了三次相同工具,而不是同一调用的三个 Attempt。

4.7 artifact_id#

用于唯一标识补丁、截图、测试报告等产物。

Artifact ID 应保持稳定,内容变化时生成新 Artifact,而不是覆盖旧内容。这样评测和审计才能准确引用运行时版本。

4.8 evaluation_id#

标识一次评分或评价行为。

同一个 Trace 可以拥有多个 evaluation_id

  • 单元测试 Grader;
  • 规则 Grader;
  • LLM Judge;
  • 人工复核;
  • 用户反馈。

每个 Evaluation 都应记录 Grader、Rubric 和证据版本。

4.9 逻辑父节点与物理父节点#

parent_span_id 只表示一个直接父节点,因此天然形成树。Agent 的业务因果关系却可能是多源的,例如一个汇总 Step 同时依赖三个并行子 Agent 结果。

工程上可以采用两层关系:

物理父关系

parent_span_id

表示调用和上下文继承,兼容标准 Trace Tree。

逻辑因果关系

links[] / logical_parent_refs[]

表示异步触发、恢复、汇合或证据依赖。OpenTelemetry Span Link 正是用于关联存在因果关系、但不适合直接父子关系的 Span。2

推荐优先使用标准 links,只有在表达 Agent 特有业务语义时,再增加命名空间字段,例如:

{
"links": [
{"trace_id": "trace_sub_a", "span_id": "span_result_a"},
{"trace_id": "trace_sub_b", "span_id": "span_result_b"}
],
"agent.logical_relation": "fan_in"
}

5. Agent Trace 为什么不只是普通树结构#

5.1 并行工具调用#

Agent 可能同时读取多个文件、查询多个数据源或运行多个检查器:

Planning Span
├── Read File A
├── Read File B
├── Run Unit Test
└── Run Static Check
Aggregate Result

四个工具 Span 可以共享同一个物理父 Span,而汇总 Span 通过 Links 引用四个结果。这样既保留树形调用关系,也表达 fan-in 因果依赖。

5.2 子 Agent 扇出与汇合#

主 Agent 可以委派多个子任务:

  • 子 Agent A 分析仓库结构;
  • 子 Agent B 复现 Bug;
  • 子 Agent C 搜索相关历史 Issue。

如果子 Agent 与主 Agent 生命周期一致且在同一进程内,可以作为子 Span;如果独立部署、异步运行或拥有自己的复杂轨迹,更适合使用独立 Trace,并通过 run_idsubtask_id 和 Span Link 连接。

选择标准不是“单 Agent 还是多 Agent”,而是:

  • 是否需要独立采样;
  • 是否跨进程;
  • 是否独立重试;
  • 是否独立恢复;
  • 是否拥有独立 Outcome。

5.3 异步后台任务#

长时间编译、远程测试和消息队列任务可能在父 Span 结束后才开始。此时强行保持父 Span 开启会造成:

  • Trace 持续数小时;
  • Span 泄漏;
  • 尾采样和存储困难;
  • 进程重启后上下文丢失。

更合理的方式是:

  1. 生产者记录任务提交 Span;
  2. 后台消费者创建新 Trace 或新 Span;
  3. 使用传播上下文或 Link 关联;
  4. 统一使用 run_idoperation_id 聚合。

5.4 人工审批与长时间暂停#

高风险操作经常需要人工批准。一次审批可能等待几秒,也可能等待几小时。

建议拆成:

Approval Requested Event
Approval Waiting Span / 状态记录
Approval Decision Event
Resume Trace Segment

审批信息至少要包含:

  • 请求操作;
  • 风险级别;
  • 请求者;
  • 审批者;
  • 决策;
  • 修改后的参数;
  • 等待时间;
  • 恢复位置。

恢复后的 Trace 应通过 Link 指向暂停前的最后一个有效 Span,而不是伪造跨数小时的直接同步调用。

5.5 MCP 跨进程调用#

MCP 调用可能跨越 Agent、MCP Client、Gateway、MCP Server 和实际工具实现:

Agent
→ MCP Client
→ Transport / Gateway
→ MCP Server
→ Tool Implementation

只在 Agent 侧记录“调用某 MCP Tool”无法区分:

  • Client 参数构造失败;
  • Transport 连接失败;
  • Server capability 变化;
  • 工具内部报错;
  • Tool Result 在返回途中丢失。

因此应尽量传播标准 Trace Context,并在不能传播时至少保存:

  • client trace/span;
  • server request ID;
  • MCP session / connection ID;
  • tool call ID;
  • server revision;
  • correlation link。

OpenTelemetry 的 GenAI 语义约定仓库已经将 MCP、GenAI Client、Provider-specific conventions 纳入同一演进方向,为跨组件字段统一提供了基础。4

5.6 从 Span Tree 到因果 DAG#

必须澄清:DAG 并不意味着一个 Span 拥有多个 parent_span_id

标准实现仍保持:

每个 Span 最多一个直接 Parent

在此基础上,通过以下关系构成完整因果图:

  • Parent-child:直接调用与上下文继承;
  • Span Links:异步、汇合、恢复;
  • tool_call_id:工具请求与结果;
  • artifact_id:操作与产物;
  • snapshot_id:动作与状态;
  • step_id:技术 Span 与 Agent Step;
  • evidence_refs:评分与证据。

因此更准确的模型是:

Trace Tree
+ Span Links
+ Domain References
= Agent Causal DAG

这个 DAG 才是后续根因分析、回放和轨迹评测真正需要的数据结构。


6. 环境状态和副作用建模#

环境状态与副作用建模

6.1 state_before#

state_before 描述动作执行前的环境状态。它用于证明 Agent 在什么前提下执行操作,并为冲突检测和回滚提供依据。

代码修改前可以记录:

{
"git_head": "a1b2c3d",
"workspace_dirty": false,
"target_file_sha256": "...",
"failing_tests": ["tests/test_discount.py::test_coupon_stack"]
}

数据库更新前可以记录:

  • 主键;
  • 旧版本号;
  • 关键字段;
  • 事务或锁信息;
  • 前置条件。

6.2 state_after#

state_after 使用与 state_before 相同的观察口径记录动作后状态。

关键原则是可比较。如果 before 记录文件哈希,after 也应记录同一文件哈希;如果 before 记录数据库版本号,after 也应记录更新后的版本号。

6.3 State Diff#

State Diff 是前后状态的结构化差异,而不是一段“修改成功”的文本。

一个通用 State Diff 可以包含:

{
"target_type": "file",
"target_id": "src/order.py",
"operation": "update",
"before_ref": "snapshot_before_01",
"after_ref": "snapshot_after_01",
"changed_fields": ["content"],
"verification": "passed",
"reversible": true
}

Diff 应区分:

  • 预期变化;
  • 非预期变化;
  • 未验证变化;
  • 无法获取的变化。

6.4 文件修改与 Git Diff#

对 Coding Agent,Git 是天然的状态版本系统。建议至少记录:

  • 基准 commit;
  • 工作区初始状态;
  • 修改文件列表;
  • Git Diff Artifact;
  • 新增和删除行数;
  • 最终测试 Artifact;
  • 是否产生 commit / branch / PR。

Git Diff 应作为独立 Artifact,而不是直接塞进 Span Attribute,因为补丁可能很大,也可能包含敏感代码。

6.5 数据库、浏览器和外部系统状态#

并非所有系统都能完整快照,应为不同环境定义领域投影。

数据库:

  • 目标记录;
  • 版本号;
  • 事务结果;
  • 约束检查;
  • 受影响行数。

浏览器:

  • URL;
  • 页面 title;
  • 关键 DOM 节点;
  • 表单值;
  • 截图 Artifact;
  • Cookie 或认证状态只保存安全引用,不保存敏感原文。

外部业务系统:

  • 外部对象 ID;
  • 请求幂等键;
  • 业务状态;
  • 服务端回执;
  • 后续查询验证结果。

6.6 可逆操作与不可逆操作#

建议为工具动作标注副作用等级:

等级含义示例
none不改变环境搜索、读取
reversible可自动恢复修改工作区文件、更新草稿
compensatable不能直接回滚,但可补偿创建工单、发送可撤回消息
irreversible无法可靠恢复支付、外发邮件、永久删除

不可逆操作应额外要求:

  • 明确审批;
  • 参数摘要;
  • 幂等键;
  • 业务回执;
  • 审计事件;
  • 失败后的人工或补偿路径。

6.7 Checkpoint 与 Rollback Point#

Checkpoint 是可恢复执行所需的状态锚点;Rollback Point 是允许环境回退的位置。二者相关但不完全相同。

例如:

  • Agent 在修改代码前创建 Git Checkpoint;
  • 修改完成后运行测试;
  • 测试失败,可以回滚文件;
  • 但如果此前已经发送外部通知,通知无法通过 Git 回滚。

因此 Checkpoint 应记录覆盖范围:

{
"checkpoint_id": "checkpoint_01",
"covers": ["workspace", "agent_state"],
"does_not_cover": ["external_email"],
"snapshot_refs": ["snapshot_workspace_01", "snapshot_agent_01"]
}

不要把“存在 Checkpoint”误解为“所有副作用都可回滚”。


7. 版本与数据血缘#

可观测数据如果缺少版本信息,通常只能用于观看,不能用于可靠比较。Agent 的行为由模型、Prompt、工具、编排和环境共同决定,因此必须记录有效运行配置

推荐在 Trace 根对象保存 Provenance Manifest,并在局部 Span 发生覆盖时记录局部版本。

7.1 Agent 代码版本#

至少记录:

  • repository;
  • commit SHA;
  • release version;
  • container image digest;
  • build ID。

只记录 agent_version = latest 没有复现价值。版本必须能解析到不可变 revision。

7.2 模型和 Provider 版本#

记录:

  • provider;
  • model name;
  • provider 返回的 resolved model / revision(若有);
  • endpoint 或 region;
  • 推理参数;
  • fallback model。

模型别名可能指向变化中的后端,因此应同时记录用户配置名称和运行时实际解析结果。

7.3 Prompt 与 System Prompt 版本#

Prompt 是 Agent 行为逻辑的一部分,应记录:

  • prompt ID;
  • prompt version;
  • 内容哈希;
  • 渲染后的模板变量摘要;
  • system prompt version;
  • planner / tool / evaluator prompt 的独立版本。

为了隐私和成本,可以只在 Trace 中保存内容引用与哈希,原文保存在权限更严格的 Prompt Store。

7.4 Tool Schema 与 MCP Server 版本#

模型生成工具参数时,真正依赖的是它看到的 Tool Schema。因此应记录:

  • tool name;
  • schema version;
  • schema hash;
  • implementation revision;
  • MCP Server name 和 version;
  • capability snapshot。

只记录工具名称无法解释“为什么旧版本能调用,新版本却参数错误”。

7.5 环境镜像和依赖版本#

包括:

  • 容器或虚拟机镜像 digest;
  • 操作系统;
  • 依赖锁文件;
  • 浏览器版本;
  • 数据库 snapshot revision;
  • 代码仓库基准 commit;
  • 时区和关键 feature flag。

很多“模型回归”实际上来自环境漂移,因此环境 revision 必须与 Agent revision 一起比较。

7.6 Evaluator、Rubric 与 Dataset 版本#

Score 不是绝对事实,它取决于评测器和规则。必须记录:

  • evaluator name / version;
  • judge model;
  • judge prompt version;
  • rubric version;
  • dataset version;
  • environment revision;
  • score config。

否则两个分数无法比较:变化可能来自 Agent,也可能来自评分标准。

一条可靠血缘链应能回答:

哪个 Task
由哪个 Agent 版本
在什么环境中
使用什么模型、Prompt 和工具
产生了哪个 Trace 和 Artifact
又由哪个 Evaluator 与 Rubric
得到什么 Score

8. Observability、Evaluation 与 Audit 的边界#

三者共享数据基础,但解决的问题不同。

系统核心问题主要对象典型使用者
Observability系统发生了什么,为什么失败或变慢Trace、Span、Event、Metric、Artifact研发、SRE、Agent 工程师
Evaluation结果和轨迹是否正确、有效、安全Task、Outcome、Score、Rubric评测、算法、产品
Audit谁在何时以什么权限做了什么Actor、Approval、Tool Action、Side Effect安全、合规、管理员

8.1 Observability 负责保存运行证据#

Observability 应保存:

  • 调用和执行链;
  • 状态变化;
  • 错误和恢复;
  • 延迟和成本;
  • 调试所需证据引用。

它不负责单独定义“答案是否正确”,但必须为判断提供证据。

8.2 Evaluation 负责判断完成质量#

Evaluation 读取运行证据和环境 Outcome,输出:

  • 成功/失败;
  • 数值评分;
  • 分类标签;
  • 失败类型;
  • 评价依据。

评价结果应作为独立 Score 写回,而不是覆盖原始 Trace。这样才能保留“事实”和“判断”的边界。

8.3 Audit 负责回答谁在何时执行了什么操作#

Audit 重点记录:

  • 发起者;
  • Agent 身份;
  • 权限策略;
  • 审批者;
  • 高风险参数摘要;
  • 外部对象;
  • 操作结果;
  • 不可逆副作用。

Audit 数据通常有更严格的完整性、保留周期和访问控制要求,不应简单等同于调试日志。

8.4 为什么三者必须共享标识符和数据血缘#

虽然存储和权限可以分离,但三者必须共享:

  • session_id
  • task_id
  • run_id
  • trace_id
  • span_id
  • tool_call_id
  • artifact_id
  • evaluation_id
  • Provenance Manifest。

这样才能实现:

低分 Evaluation
→ 定位 Trace
→ 找到错误 Step
→ 找到 Tool Call
→ 查看 State Diff
→ 检查 Approval 与 Audit
→ 生成回归样本

共享标识符不代表共享所有内容。Prompt 原文、敏感 Tool Result 和审计身份信息可以存放在不同权限域,通过引用关联。


9. 最小 Agent Trace 数据契约#

这份契约的目标不是替代 OTLP,也不是一次覆盖所有 Agent,而是定义一层稳定的 Agent Envelope:

  • 底层 Trace/Span 可由 OpenTelemetry 采集;
  • Agent Envelope 补充 Task、Run、Step、Artifact、State 和 Score;
  • 大内容使用引用,不直接塞入遥测字段;
  • 所有对象都有版本和证据链。

9.1 Trace 顶层字段#

字段必需说明
schema_version数据契约版本
trace_id当前 Trace 标识
session_id所属会话
task_id所属任务
run_id所属实际执行
trial_id评测尝试
agentAgent 名称和 revision
started_at / ended_at是/否生命周期时间
statusrunning、success、failure、partial、cancelled
root_goal规范化任务目标或引用
spansSpan 列表
eventsTrace 级事件
artifacts产物引用
state_snapshots状态快照
outcome结束时最终任务结果
scores评价结果
provenance版本和数据血缘

9.2 Span 通用字段#

字段说明
span_idSpan 标识
trace_id所属 Trace
parent_span_id直接父 Span
links额外因果关联
turn_id / step_idAgent 领域关联
operation_id逻辑操作
attempt_id本次物理尝试
kindmodel、tool、retrieval、memory 等
name操作名称
started_at / ended_at开始和结束时间
statusunset、ok、error、cancelled
attributes小型、可查询元数据
content_refsPrompt、结果等大内容引用
eventsSpan 内离散事件
error结构化错误
retry重试信息
recovery恢复动作
details类型专属字段

9.3 Model、Tool、Retrieval 和 Memory 扩展字段#

Model:

{
"provider": "provider-name",
"model": "model-name",
"model_revision": "resolved-revision",
"prompt_version": "planner-v3",
"input_tokens": 1800,
"output_tokens": 220,
"cached_tokens": 900,
"stop_reason": "tool_call"
}

Tool:

{
"tool_call_id": "tool_01",
"tool_name": "edit_file",
"schema_version": "2.1.0",
"arguments_ref": "artifact_tool_args_01",
"result_ref": "artifact_tool_result_01",
"side_effect_class": "reversible",
"approval_required": false
}

Retrieval:

{
"query_ref": "artifact_query_01",
"index_revision": "index_2026_08_05",
"top_k": 10,
"candidate_refs": ["chunk_1", "chunk_2"],
"selected_refs": ["chunk_2"]
}

Memory:

{
"memory_query_ref": "artifact_memory_query_01",
"recalled_ids": ["memory_7", "memory_9"],
"filtered_ids": ["memory_9"],
"injected_ids": ["memory_7"],
"write_actions": []
}

9.4 Error、Retry 与 Recovery 字段#

错误和重试应建模为结构化状态,而不是只写一条日志。

Error:

  • type:timeout、rate_limit、validation、permission、provider_error 等;
  • code:供应商或系统错误码;
  • source:model、tool、orchestrator、environment;
  • message:脱敏后的错误摘要;
  • retryable:是否允许重试。

Retry:

  • operation_id
  • attempt_id
  • attempt_index
  • max_attempts
  • reason
  • backoff_ms
  • idempotency_key

Recovery:

  • strategy:retry、fallback_model、fallback_tool、resume_checkpoint、human_escalation、abort;
  • checkpoint_id
  • fallback_target
  • resume_from_span_id
  • result

必须保留失败 Attempt,即使后续重试成功。否则无法分析限流、网络抖动和恢复成本。

9.5 Outcome 和 Score 字段#

Outcome:

{
"task_status": "success",
"completion_reason": "verification_passed",
"final_output_ref": "artifact_final_answer_01",
"environment_result": {
"tests_passed": true,
"modified_files": ["src/order.py"]
},
"expected_side_effects": ["source_file_updated"],
"unexpected_side_effects": [],
"evidence_refs": ["snapshot_after_01", "artifact_test_report_01"]
}

Score:

{
"evaluation_id": "eval_01",
"target_type": "trace",
"target_id": "trace_01",
"name": "task_success",
"data_type": "boolean",
"value": true,
"grader_type": "rule_based",
"grader_version": "test-grader-2.0",
"rubric_version": "coding-success-1.1",
"evidence_refs": ["artifact_test_report_01"]
}

9.6 JSON Schema 示例#

下面是一份基于 JSON Schema Draft 2020-12 的最小示例。它用于验证 Agent Envelope,不等同于 OTLP Schema;生产系统可以将各 $defs 拆成独立文件。

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.org/schemas/agent-trace/1.0.0.json",
"title": "Agent Trace Envelope",
"type": "object",
"additionalProperties": false,
"required": [
"schema_version",
"trace_id",
"task_id",
"run_id",
"agent",
"started_at",
"status",
"root_goal",
"spans",
"provenance"
],
"properties": {
"schema_version": {
"const": "1.0.0"
},
"trace_id": {
"$ref": "#/$defs/id"
},
"session_id": {
"$ref": "#/$defs/nullableId"
},
"task_id": {
"$ref": "#/$defs/id"
},
"run_id": {
"$ref": "#/$defs/id"
},
"trial_id": {
"$ref": "#/$defs/nullableId"
},
"agent": {
"$ref": "#/$defs/agent"
},
"started_at": {
"$ref": "#/$defs/timestamp"
},
"ended_at": {
"$ref": "#/$defs/nullableTimestamp"
},
"status": {
"enum": ["running", "success", "failure", "partial", "cancelled"]
},
"root_goal": {
"type": "object",
"additionalProperties": false,
"required": ["summary"],
"properties": {
"summary": {
"type": "string",
"minLength": 1,
"maxLength": 4096
},
"content_ref": {
"$ref": "#/$defs/nullableId"
}
}
},
"spans": {
"type": "array",
"items": {
"$ref": "#/$defs/span"
}
},
"events": {
"type": "array",
"items": {
"$ref": "#/$defs/event"
},
"default": []
},
"artifacts": {
"type": "array",
"items": {
"$ref": "#/$defs/artifact"
},
"default": []
},
"state_snapshots": {
"type": "array",
"items": {
"$ref": "#/$defs/stateSnapshot"
},
"default": []
},
"outcome": {
"oneOf": [
{"$ref": "#/$defs/outcome"},
{"type": "null"}
],
"default": null
},
"scores": {
"type": "array",
"items": {
"$ref": "#/$defs/score"
},
"default": []
},
"provenance": {
"$ref": "#/$defs/provenance"
}
},
"$defs": {
"id": {
"type": "string",
"minLength": 1,
"maxLength": 128,
"pattern": "^[A-Za-z0-9._:-]+$"
},
"nullableId": {
"oneOf": [
{"$ref": "#/$defs/id"},
{"type": "null"}
]
},
"timestamp": {
"type": "string",
"format": "date-time"
},
"nullableTimestamp": {
"oneOf": [
{"$ref": "#/$defs/timestamp"},
{"type": "null"}
]
},
"agent": {
"type": "object",
"additionalProperties": false,
"required": ["name", "revision"],
"properties": {
"name": {"type": "string", "minLength": 1},
"revision": {"type": "string", "minLength": 1},
"release": {"type": ["string", "null"]},
"instance_id": {"$ref": "#/$defs/nullableId"}
}
},
"link": {
"type": "object",
"additionalProperties": false,
"required": ["trace_id", "span_id"],
"properties": {
"trace_id": {"$ref": "#/$defs/id"},
"span_id": {"$ref": "#/$defs/id"},
"relation": {
"type": "string",
"enum": ["caused_by", "fan_in", "resume_from", "derived_from", "related"]
},
"attributes": {
"type": "object",
"additionalProperties": true
}
}
},
"error": {
"type": "object",
"additionalProperties": false,
"required": ["type", "source", "retryable"],
"properties": {
"type": {"type": "string"},
"code": {"type": ["string", "null"]},
"source": {
"enum": ["model", "tool", "retrieval", "memory", "orchestrator", "environment", "unknown"]
},
"message": {"type": ["string", "null"], "maxLength": 4096},
"retryable": {"type": "boolean"}
}
},
"retry": {
"type": "object",
"additionalProperties": false,
"required": ["operation_id", "attempt_id", "attempt_index"],
"properties": {
"operation_id": {"$ref": "#/$defs/id"},
"attempt_id": {"$ref": "#/$defs/id"},
"attempt_index": {"type": "integer", "minimum": 0},
"max_attempts": {"type": ["integer", "null"], "minimum": 1},
"reason": {"type": ["string", "null"]},
"backoff_ms": {"type": ["integer", "null"], "minimum": 0},
"idempotency_key": {"type": ["string", "null"]}
}
},
"recovery": {
"type": "object",
"additionalProperties": false,
"required": ["strategy"],
"properties": {
"strategy": {
"enum": [
"retry",
"fallback_model",
"fallback_tool",
"resume_checkpoint",
"human_escalation",
"abort"
]
},
"checkpoint_id": {"$ref": "#/$defs/nullableId"},
"fallback_target": {"type": ["string", "null"]},
"resume_from_span_id": {"$ref": "#/$defs/nullableId"},
"result": {"type": ["string", "null"]}
}
},
"modelDetails": {
"type": "object",
"additionalProperties": false,
"required": ["provider", "model"],
"properties": {
"provider": {"type": "string"},
"model": {"type": "string"},
"model_revision": {"type": ["string", "null"]},
"prompt_version": {"type": ["string", "null"]},
"input_tokens": {"type": ["integer", "null"], "minimum": 0},
"output_tokens": {"type": ["integer", "null"], "minimum": 0},
"cached_tokens": {"type": ["integer", "null"], "minimum": 0},
"stop_reason": {"type": ["string", "null"]}
}
},
"toolDetails": {
"type": "object",
"additionalProperties": false,
"required": ["tool_call_id", "tool_name"],
"properties": {
"tool_call_id": {"$ref": "#/$defs/id"},
"tool_name": {"type": "string"},
"schema_version": {"type": ["string", "null"]},
"arguments_ref": {"$ref": "#/$defs/nullableId"},
"result_ref": {"$ref": "#/$defs/nullableId"},
"side_effect_class": {
"enum": ["none", "reversible", "compensatable", "irreversible"]
},
"approval_required": {"type": "boolean"}
}
},
"retrievalDetails": {
"type": "object",
"additionalProperties": false,
"required": ["top_k"],
"properties": {
"query_ref": {"$ref": "#/$defs/nullableId"},
"index_revision": {"type": ["string", "null"]},
"top_k": {"type": "integer", "minimum": 1},
"candidate_refs": {
"type": "array",
"items": {"$ref": "#/$defs/id"}
},
"selected_refs": {
"type": "array",
"items": {"$ref": "#/$defs/id"}
}
}
},
"memoryDetails": {
"type": "object",
"additionalProperties": false,
"properties": {
"memory_query_ref": {"$ref": "#/$defs/nullableId"},
"recalled_ids": {
"type": "array",
"items": {"$ref": "#/$defs/id"}
},
"filtered_ids": {
"type": "array",
"items": {"$ref": "#/$defs/id"}
},
"injected_ids": {
"type": "array",
"items": {"$ref": "#/$defs/id"}
},
"write_actions": {
"type": "array",
"items": {"type": "object", "additionalProperties": true}
}
}
},
"details": {
"type": "object",
"additionalProperties": false,
"maxProperties": 1,
"properties": {
"model": {"$ref": "#/$defs/modelDetails"},
"tool": {"$ref": "#/$defs/toolDetails"},
"retrieval": {"$ref": "#/$defs/retrievalDetails"},
"memory": {"$ref": "#/$defs/memoryDetails"}
}
},
"event": {
"type": "object",
"additionalProperties": false,
"required": ["event_id", "name", "time"],
"properties": {
"event_id": {"$ref": "#/$defs/id"},
"name": {"type": "string", "minLength": 1},
"time": {"$ref": "#/$defs/timestamp"},
"target_span_id": {"$ref": "#/$defs/nullableId"},
"attributes": {"type": "object", "additionalProperties": true}
}
},
"span": {
"type": "object",
"additionalProperties": false,
"required": [
"span_id",
"trace_id",
"kind",
"name",
"started_at",
"status",
"attributes"
],
"properties": {
"span_id": {"$ref": "#/$defs/id"},
"trace_id": {"$ref": "#/$defs/id"},
"parent_span_id": {"$ref": "#/$defs/nullableId"},
"links": {
"type": "array",
"items": {"$ref": "#/$defs/link"},
"default": []
},
"turn_id": {"$ref": "#/$defs/nullableId"},
"step_id": {"$ref": "#/$defs/nullableId"},
"operation_id": {"$ref": "#/$defs/nullableId"},
"attempt_id": {"$ref": "#/$defs/nullableId"},
"kind": {
"enum": ["agent", "model", "tool", "retrieval", "memory", "approval", "subagent", "system"]
},
"name": {"type": "string", "minLength": 1},
"started_at": {"$ref": "#/$defs/timestamp"},
"ended_at": {"$ref": "#/$defs/nullableTimestamp"},
"status": {"enum": ["unset", "ok", "error", "cancelled"]},
"attributes": {"type": "object", "additionalProperties": true},
"content_refs": {
"type": "array",
"items": {"$ref": "#/$defs/id"},
"default": []
},
"events": {
"type": "array",
"items": {"$ref": "#/$defs/event"},
"default": []
},
"error": {
"oneOf": [
{"$ref": "#/$defs/error"},
{"type": "null"}
],
"default": null
},
"retry": {
"oneOf": [
{"$ref": "#/$defs/retry"},
{"type": "null"}
],
"default": null
},
"recovery": {
"oneOf": [
{"$ref": "#/$defs/recovery"},
{"type": "null"}
],
"default": null
},
"details": {"$ref": "#/$defs/details"}
}
},
"artifact": {
"type": "object",
"additionalProperties": false,
"required": ["artifact_id", "type", "uri", "sha256"],
"properties": {
"artifact_id": {"$ref": "#/$defs/id"},
"type": {"type": "string"},
"uri": {"type": "string", "format": "uri"},
"sha256": {"type": "string", "pattern": "^[a-fA-F0-9]{64}$"},
"media_type": {"type": ["string", "null"]},
"created_by_span_id": {"$ref": "#/$defs/nullableId"}
}
},
"stateSnapshot": {
"type": "object",
"additionalProperties": false,
"required": ["snapshot_id", "kind", "time"],
"properties": {
"snapshot_id": {"$ref": "#/$defs/id"},
"kind": {"enum": ["before", "after", "checkpoint", "verification"]},
"time": {"$ref": "#/$defs/timestamp"},
"content_ref": {"$ref": "#/$defs/nullableId"},
"content_hash": {"type": ["string", "null"]},
"summary": {"type": "object", "additionalProperties": true}
}
},
"outcome": {
"type": "object",
"additionalProperties": false,
"required": ["task_status", "completion_reason", "evidence_refs"],
"properties": {
"task_status": {"enum": ["success", "failure", "partial", "cancelled"]},
"completion_reason": {"type": "string"},
"final_output_ref": {"$ref": "#/$defs/nullableId"},
"environment_result": {"type": "object", "additionalProperties": true},
"expected_side_effects": {"type": "array", "items": {"type": "string"}},
"unexpected_side_effects": {"type": "array", "items": {"type": "string"}},
"evidence_refs": {"type": "array", "items": {"$ref": "#/$defs/id"}}
}
},
"score": {
"type": "object",
"additionalProperties": false,
"required": [
"evaluation_id",
"target_type",
"target_id",
"name",
"data_type",
"grader_type",
"grader_version",
"evidence_refs"
],
"properties": {
"evaluation_id": {"$ref": "#/$defs/id"},
"target_type": {"enum": ["session", "task", "run", "trace", "step", "span", "artifact"]},
"target_id": {"$ref": "#/$defs/id"},
"name": {"type": "string"},
"data_type": {"enum": ["numeric", "categorical", "boolean", "text"]},
"value": {},
"grader_type": {"enum": ["rule_based", "test", "llm_judge", "human", "user_feedback"]},
"grader_version": {"type": "string"},
"rubric_version": {"type": ["string", "null"]},
"evidence_refs": {"type": "array", "items": {"$ref": "#/$defs/id"}}
}
},
"provenance": {
"type": "object",
"additionalProperties": false,
"required": ["agent_revision", "environment_revision"],
"properties": {
"agent_revision": {"type": "string"},
"agent_image_digest": {"type": ["string", "null"]},
"environment_revision": {"type": "string"},
"model_revisions": {"type": "array", "items": {"type": "string"}},
"prompt_revisions": {"type": "array", "items": {"type": "string"}},
"tool_revisions": {"type": "array", "items": {"type": "string"}},
"dataset_version": {"type": ["string", "null"]},
"evaluator_versions": {"type": "array", "items": {"type": "string"}}
}
}
}
}

这份 Schema 体现了几个关键设计选择:

  1. Task、Run 与 Trace 分离;
  2. Parent 关系与 Links 分离;
  3. 逻辑操作与 Attempt 分离;
  4. 大内容使用 Artifact / Content Reference;
  5. Outcome 独立于 Agent 最终自述;
  6. Score 独立于原始运行事实;
  7. 版本血缘是必需字段,而不是附加备注。

结语#

Agent 可观测性不是“给模型 API 加一层日志”,而是建立一个任务级证据系统。

这套系统必须同时描述:

用户想完成什么
Agent 当时看到了什么
Agent 为什么选择某个动作
工具实际上做了什么
环境发生了什么变化
失败后如何恢复
最终目标是否完成
这个判断基于哪些证据

本文提出的统一模型可以压缩成一句话:

以 Task 和 Run 表达业务执行,以 Trace、Span、Event 和 Link 表达技术因果链,以 Artifact、State Snapshot 和 Outcome 表达真实环境结果,再以 Score 和 Provenance 连接评测与数据闭环。

下一篇将进入工程实现:如何使用 OpenTelemetry、异步上下文传播、流式模型埋点、并行工具追踪和 Collector,将这套数据模型真正嵌入 Agent Loop。


参考资料#

Footnotes#

  1. OpenTelemetry:Signals

  2. OpenTelemetry:Traces、Spans、Events 与 Span Links 2 3 4

  3. W3C Trace Context Recommendation 2 3

  4. OpenTelemetry GenAI Semantic Conventions Repository 2

  5. Langfuse Observability Data Model:Observations、Traces 与 Sessions

  6. Langfuse Scores Data Model 2

第 1 篇:Agent 可观测性全景与统一数据模型
https://jupiter-ws.cn/posts/agent-observability/01-agent-observability-unified-data-model/
作者
Jupiter
发布于
2026-08-05
许可协议
CC BY-NC-SA 4.0