Agent 的一次运行不是按时间排列的一串日志,而是由模型调用、工具执行、子任务和重试组成的因果树。可观测性的核心不是收集更多文本,而是保留“哪个动作由谁触发、耗时和结果是什么”的父子关系。
日志回答事件,Trace 回答因果
并发工具和子 Agent 会让多条日志交错。只凭时间戳,很难确定某次模型调用触发了哪些工具、某个重试属于哪条分支,以及总延迟消耗在哪里。
每段操作可以记录为一个 Span:
Agent Run├─ Model Call├─ Tool Call: search├─ Tool Call: read└─ Sub Agent ├─ Model Call └─ Tool Call: testSpan 包含稳定的 trace_id、自身标识、父节点、名称、起止时间、状态和结构化属性。完整运行由这些父子关系组成 Trace。指标用于回答整体成功率和延迟是否异常,Trace 用于解释某一次异常如何发生,日志则补充具体事件文本。
Checkpoint 与 Trace 不能互相替代
Checkpoint 服务于恢复,需要保存下一步执行所需状态;Trace 服务于解释,记录已经发生的操作与耗时。
把完整 Prompt、工具结果和状态全部复制到每个 Span,会产生存储膨胀与隐私风险。默认应记录模型、Token、耗时、工具名称、退出状态和引用标识,具体内容只在失败采样或受控调试中保存。
同样,Trace 不能作为恢复来源。观测数据可能采样、延迟或丢失,而恢复状态必须满足一致性要求。
是否采用 OpenTelemetry 取决于传播复杂度
单进程、同步、层次很浅的系统,用结构化日志加父标识即可满足需求。OpenTelemetry 的主要价值在于提供跨线程、异步任务和跨服务的上下文传播,以及统一的导出协议。
SDK 只负责生成和发送数据,不负责长期存储与查询;仍需要后端接收 Trace。若系统尚未明确要回答的问题,直接引入完整平台只会增加部署与字段治理成本。
我会按以下顺序落地:
- 先定义运行、模型、工具和子任务四类节点;
- 记录父子关系、耗时、状态与关键资源用量;
- 用真实故障验证能否定位慢点和失败点;
- 出现跨服务传播需求后再接入 OpenTelemetry;
- 最后根据隐私和成本决定内容采样策略。
我的选择:默认记录结构,按需记录内容
所有运行都记录因果结构、耗时、模型和工具结果状态;完整 Prompt、输出和文件内容默认不进入可观测系统,只在失败样本、授权调试或脱敏后采集。
这种做法牺牲了随时回看全部上下文的便利,但降低了敏感数据扩散和平方级存储增长。若系统只有单进程短链路,我会保持轻量实现;当异步、并发和跨服务成为常态时,再使用标准 Trace 上下文避免自行维护传播协议。
下一篇继续讨论跨运行状态:四类记忆为什么应该统一接口,而不是统一存储。