这篇解决什么困惑

很多 Agent Demo 的“可观测性”是把 Prompt、模型原文、工具返回和完整日志全部打印出来。调试时很方便,上线后却可能泄漏用户问题、内部证据、异常细节和 API Key。

真正的生产 Trace 要解决两个同时存在的问题:出了什么事?哪些数据绝对不能被记录?

Trace 和日志不是一回事

日志适合排查瞬时故障,Trace 是可按运行 ID 查询的结构化历史。它应该能告诉我们某次运行经历了哪些 Step、调用了哪个模型和工具、每次调用处于什么状态、是否发生过重放,以及用量数据是否完整。

但 Trace 不是恢复状态的第二个来源。Checkpoint 决定从哪里继续;Trace 只回答之前发生了什么。恢复逻辑不能因为 Trace 看起来“像成功”就擅自修复业务状态。

当前 Trace 记录什么

项目把生产 Trace 拆成三类记录:Step、模型调用和工具调用。每个逻辑调用都有确定性 ID,并按照 Step、attempt 和 ordinal 排序。

一条模型调用可以有这些状态:开始、完成、失败或不确定;用量则明确区分 RECORDEDUNAVAILABLE。如果供应商没有真实 Token 元数据,系统不会把框架里的 0/0 当成真实零消耗,而是标记为不可用。查询聚合只累计真实记录,并通过 usageComplete 告诉调用者结果是否完整。

这比简单记录 inputTokens: 0 更诚实:不知道就是不知道,不能为了让报表好看而伪造精确度。

一次调用如何留下证据

获得有效租约
  → 创建 Step / Model / Tool Trace
  → 执行外部边界
  → 校验结果与调用身份
  → Trace 终态 + Checkpoint 推进原子提交
  → 按 runId 查询有序历史

模型或工具结果、Step 终态和 Checkpoint CAS 被放进同一事务。这样不会出现“Checkpoint 已经前进,但对应的完成 Trace 没写进去”的单边状态;如果 CAS 或租约守卫失败,Trace 也不会独自提交。

外部调用本身无法和数据库事务共享原子边界,所以恢复时仍可能遇到调用已发出、结果却未落盘的窗口。系统把遗留调用标为 UNCERTAIN,使用同一逻辑调用 ID 重放,并记录 attempt_countuncertainty_observed。这不是 exactly-once 的幻觉,而是对不确定性的可查询表达。

什么数据不能进 Trace

当前策略明确禁止把下面内容写入 Trace 表:

  • 用户原始问题和完整搜索词;
  • Prompt、模型原始响应和思维链;
  • 完整 Evidence 正文与引用片段;
  • 异常消息、堆栈和 Worker 输出;
  • API Key、伪密钥和其他凭据。

需要关联时,系统保存经过限制的 Claim ID、允许的安全名称,或对参数做 SHA-256 摘要。摘要能帮助判断两次调用是否针对同一输入,却不会把输入本身变成数据库资产。

这不是“少记录一点”的审美选择,而是 Harness 的安全边界:可观测性不能反过来扩大模型、数据库或运维人员的访问权限。

测试如何证明它没有泄漏

测试不只检查“能不能查到一条 Trace”,还对数据库所有字段做审计,使用问题文本、原始搜索词、Prompt、证据片段、异常消息和伪密钥哨兵逐一搜索。当前审计结果为零命中。

恢复测试验证了模型和工具调用在接管后都使用同一逻辑 ID,尝试次数变为 2,并且数据库不会出现两个逻辑调用。事务测试则验证陈旧版本和失效租约不会留下孤立 Trace。

这些是结构性证据,不是“我看了一眼日志觉得没问题”。

Trace 能回答什么,不能回答什么

它能回答:某次运行走了几步、调用了什么边界、何时失败、是否重放、用量是否完整、最终状态是什么。

它不能回答:模型“真正想了什么”、一段完整 Prompt 是否安全、某个调用是否在外部系统成功执行,或者业务状态应该如何自动修复。

把不能回答的问题也写进设计,反而能防止团队把 Trace 当成万能录像机。

当前边界与下一步

生产 Trace 已经落地,但 M3 还没有结束。真实 Token/费用预算扣减、一般模型和工具异常的分类恢复、对外运行与恢复 API 仍待完成。Trace 也不会自动赋予工具更多权限,更不会把只读证据搜索变成可以执行任意副作用的 Agent。

下一步值得做的不是“记录更多”,而是把 Trace 接到明确的运行查询 API、成本预算和告警策略上,并继续保持最小化数据原则。

一个可靠 Agent 的可观测性,最终不是让人看到所有东西,而是让人看到足够做出正确判断的东西

更新时间:2026-08-30