KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA

06. 工具轨迹为什么不能随便压缩? — keel 龙骨

普通对话删掉一条旧消息,最多让语义变差;工具轨迹删错一条,下一次请求可能直接违反供应商消息协议。

普通对话删掉一条旧消息,最多让语义变差;工具轨迹删错一条,下一次请求可能直接违反供应商消息协议。

支付事故里,模型曾发出 search_incident,工具返回 INC-42,随后模型依据这个结果判断近期存在超时。如果 compaction 只保留工具结果、不保留对应 call ID,结果会变成孤儿;只保留调用、不保留结果,模型又像一直在等待一个没有返回的工具。

因此工具轨迹首先是结构化协议,其次才是可摘要文本。

1. 一条完整工具回合

内部消息可以表示为:

assistant a1:
  tool_call id=c1 name=search_incident args={query: payment}

tool t1:
  tool_call_id=c1
  result={ok: true, evidence_id: INC-42}

assistant a2:
  INC-42 显示近期有超时。

关键不变量:

不同供应商对 role、顺序、并行调用和消息字段的要求不同。课程使用内部协议,真正发送前必须经过 provider adapter 校验。

2. 先运行合法轨迹压缩

python courses/foundation/context-engineering/course/project/examples/04_compact_tool_trace.py

输入包含系统规则、用户请求、一次 call/result、模型结论和最新用户要求。当前输出:

strategy: tool-trace-summary
sources: ['u1', 'a1', 't1']
messages: [
  ('system', 's'),
  ('system', 'compaction-summary'),
  ('assistant', 'a2'),
  ('user', 'u2')
]

旧的完整工具回合被转成带来源的历史摘要;最近模型结论和用户请求保留。sources 证明摘要由 u1/a1/t1 派生,但原始工具事实仍应保存在事件或证据存储中,以便按 ID 回查。

3. Validator 实际检查什么

项目的 validate_tool_trace() 维护两个集合:出现过的 calls 和已完成 results。

if message.role == "tool":
    if not message.tool_call_id or message.tool_call_id not in calls:
        raise ValueError("orphan tool result")
    if message.tool_call_id in completed:
        raise ValueError("duplicate tool result")
    completed.add(message.tool_call_id)

missing = set(calls) - completed
if missing:
    raise ValueError("tool calls without results")

这个 validator 是 provider-neutral 的最小不变量,不等价于某家 API 的完整 schema 校验。生产适配器还要验证消息顺序、允许的 content 类型、并行调用约束、流式参数拼接和响应字段。

4. 为什么“保留最近 N 条”不安全

假设最近两条是:

tool result c1
user: 继续

机械切片会从 tool result 开始,丢掉前面的 assistant call。项目中的 _recent_tool_safe() 会向前扩展后缀,直到把对应调用一起带上。空间不足时,再以完整工具回合为单位移除,而不是逐条 pop。

对于并行调用,还要把同一 assistant 消息里的多个 call 与各自结果作为一组处理。流式调用则必须等参数和终态收束后,才能形成可压缩的稳定记录。

5. 工具结果怎样缩短而不伪造事实

不要让摘要器自由改写副作用结果。可以按风险分层:

结果类型 推荐保留
只读查询 状态、关键字段、evidence ID、来源版本
写操作成功 业务对象 ID、幂等键、参数摘要、确认状态
超时/未知 unknown 状态、查询句柄、下一次对账动作
权限拒绝 policy code、主体/资源引用,不暴露敏感规则全文
大日志 统计、异常片段、时间范围、原始 chunk IDs

最危险的错误是把 outcome_unknown 压成“执行完成”。摘要更流畅,但恢复和重试策略会被破坏,甚至重复产生外部副作用。

6. 压缩后的证据边界

模型可以依据摘要继续推理,但用户要求精确证据时,系统应该通过 evidence ID 回查原始工具输出。摘要是导航,不是新的权威来源。

原始结果的保存还要满足数据最小化、租户隔离、加密和保留期限。不能因为“未来可能回查”就永久保存全部敏感工具响应。

7. 失败注入

  1. 只传 assistant call,不传 tool result,确认 validator 报 missing;
  2. 只传 tool result,确认报 orphan;
  3. 同一 call ID 放两个 result,确认报 duplicate;
  4. 让最近后缀从 tool result 开始,确认 compaction 自动向前包含 call;
  5. 构造一个 outcome_unknown,验证摘要和恢复状态不会把它改成 success。

运行确定性测试:

python -m unittest courses.foundation.context-engineering.course.project.tests.test_compaction -v

这些测试不需要真实模型,因为消息结构是否合法不是生成质量问题。

8. 本章验收

你应能画出一次工具回合的消息关系;解释为什么时间截断会产生 orphan;说明什么时候可以把旧工具回合转成证据摘要;并证明压缩前后 provider adapter 仍能接受消息序列。

下一章:Compaction 何时触发,重启后怎样恢复?

进入 keel 阅读