Tool Call Role Design: Claude Code vs Codex
背景
这次讨论的问题是:为什么 Claude Code / Anthropic 的工具结果看起来会放在 role: user,而 Codex / OpenAI Responses API 更像是把工具调用和工具结果作为独立事件处理。
严格区分一下:
- Claude 不是把 tool call 本身定义成
user。Claude 的 tool call 是 assistant 侧的tool_use。 - 被放到
role: user的是工具执行后的tool_result。 - 这个
user不等于真人用户,更像 client / environment 侧把外部观察结果递回模型。
核心判断
Claude 的设计可以理解成“对话输入流”:
assistant:模型自己说的话、自己的tool_useuser:外部世界给模型的新输入,包括真人消息、环境上下文、工具结果tool_result通过 content block type 和tool_use_id与前面的工具调用对应
这种设计的好处是 schema 简单、对话 loop 直接、content block 统一。代价是 transcript 可读性差:大量 role: user 里其实不是人说的话,而是 shell output、file read、MCP response 这类工具观察。
Codex / OpenAI Responses 的设计更像“agent 事件流”:
- 用户消息仍是
role: user - 模型请求工具是
function_call - 工具返回是
function_call_output - 两者用
call_id对应
这种设计的好处是审计、回放、状态机和权限分析更清楚。工具输出不混进 user role,角色体系主要表达指令权威,工具输出则是 observation。代价是协议更复杂,适配别的模型或框架时需要处理 typed items。
一句话:
Claude 把工具结果放进对话输入流;Codex 把工具结果放进 agent 事件流。
各自适合什么
Claude 这种做法适合:
- 快速实现一个
assistant tool_use -> client execute -> user tool_result -> assistant continue的 loop - 使用两角色 message schema,避免扩展太多 role
- 把文本、图片、工具调用、工具结果都放进统一 content block
Codex 这种做法适合:
- 复杂本地 agent runtime
- 多工具、多轮执行、approval、sandbox、sub-agent 等事件审计
- 把工具输出当作可追踪的 observation,而不是混在 human/user message 里
- 后续做 Trace Log、回放、压缩、权限边界分析
可发展的文章角度
可以写成一篇关于 agent runtime 表示层的文章,暂定角度:
- 标题方向:
对话流还是事件流:agent 工具结果到底应该放在哪里 - 主线问题:tool result 是用户输入、环境观察,还是 agent runtime event?
- 核心冲突:简单 API schema 与长期可审计 runtime 之间的取舍
- 可延展到已有文章里的
Trace Log:一个好的 Trace Log 不只记录最终输出,还要能区分人类指令、系统约束、模型决策、工具观察和环境副作用
后续问题
- 如果工具输出里有 prompt injection,它在
role: user下会不会更容易被误当成高权威指令? - 一个自研 agent harness 是否应该显式定义
observation/tool_result层,而不是复用user? - Trace Log 里应该怎样同时保留可读性和模型可继续消费的结构?