「手写中文版 claude code」教学系列:codeAgent 是我从零手写的 claude code 复刻------不套壳不翻译,从界面到 Agent 主循环一行行实现,注释全是中文大白话,适合当 Agent 开发教材从头读到尾。 上一课治好了工具的"钱袋子"(按套餐发说明书省钱);这一课钻进工具真正跑起来之后的环节------模型一次点好几道菜,结果怎么端回去才不被 API 拒。 仓库:github.com/Harvil1/cod...
翻车现场
模型一口气发起三个工具调用(读文件 A、读文件 B、跑个命令),我执行完按 OpenAI 的习惯逐条回喂:
scss
assistant (tool_calls: [A, B, C])
tool (result A)
tool (result B)
tool (result C)
切到 Anthropic 格式的端点,直接 400。原因:Anthropic 协议规定所有 tool_result 必须装在同一条 user 消息里,而且不允许两条 user 消息相邻。OpenAI 的"一菜一盘"在它家是非法摆盘。
修复:翻译层统一合并
agent/llm_client.py 里的转换函数,docstring 就是踩坑记录:
python
def _convert_messages_to_anthropic(self, messages: list) -> list:
"""把 OpenAI 格式的整段对话历史翻译成 Anthropic 格式。
最关键的一步:把连续多条工具结果合并成一条 user 消息。
因为 Anthropic 协议规定所有工具结果必须装在同一个 user 消息里,
不允许两条 user 消息挨着;而 OpenAI 那边每个工具结果各自成条。
(另外 system 消息在这里被抽走------它要走顶层参数,见上层拼装。)
参数:
messages:OpenAI 格式的消息列表
返回:Anthropic 格式的消息列表。
"""
conversation = []
i = 0
while i < len(messages):
m = messages[i]
role = m.get("role")
if role == "system":
i += 1
continue # system 走顶层参数,不进消息列表
...
核心手法是双指针扫描:遇到连续的 tool 消息块,一口气收进同一个 user 消息再往下走;顺带把 system 抽走(OpenAI 的 system 是消息列表的一员,Anthropic 的 system 是顶层参数------又一个差异点)。
并行回喂还有三个坑,一并说了
1. id 配对不能错。 三个结果必须各回各的 tool_call_id,张冠李戴模型会拿着 A 的结果回答 C 的问题------不报错但全错,比 400 更难查。
2. 顺序无所谓,配对才要命。 协议不要求结果按发起顺序回,但 id 必须一一对应。
3. 换端点不换代码。 这层翻译存在的意义:主循环只说 OpenAI 方言,Client 子类负责翻译------加新服务商只写一个翻译器,主循环零改动。
小结
- 并行 tool_calls 的结果回喂,Anthropic 要求合并成单条 user、禁连续 user
- 转换层双指针扫描:合并 tool 块 + 抽离 system,一次遍历搞定
- id 严格配对;协议差异全部闷在 Client 层,主循环保持方言纯净
下一篇:模型点了菜厨房没做------发送前的孤儿 tool_call 对账兜底(附 _fix_tool_call_pairs 源码)。
仓库在这,注释全中文,欢迎 Star ⭐:github.com/Harvil1/cod...
标签 :Agent Function Calling 大模型API Python