K1.1.2 Task 1.1

API 是无状态的:每次都要发完整历史

Claude API 在请求之间不记任何东西。每次 API 调用都是全新的开始。如果你不发完整的对话历史——包括之前所有的 tool result——模型不知道自己做过什么、发现过什么、当初为什么调用那些工具。

正确的格式

模型请求工具调用后,你执行工具并把结果追加到对话历史:

  • 角色"user"(不是 “assistant”——tool result 来自 client 端)
  • 内容类型"tool_result",带匹配的 tool_use_id
  • 然后:在下一次 API 请求中发送完整的 message 数组(所有之前的消息 + 这个新的 tool_result)

如果模型在一条响应中发起了多个工具调用(比如 get_customerget_order),把所有结果放在一条 user 消息里,包含多个 tool_result content block,每个带各自匹配的 tool_use_id。不是多条 user 消息——一条消息,多个 content block。

“模型忘了所有事”的 bug

典型症状:模型无视 tool result,重复发起它已经调用过的完全相同的工具调用。这种现象是一致的,不是间歇性的。

根因:tool_result 根本没有被追加到 messages 数组。API 是无状态的——从模型视角看,它请求了工具但从未收到输出。所以它再请求一次。又一次。无限循环。

这是 agentic 循环中最常见的实现 bug。任何”模型在工具调用后丢失上下文”问题的第一个排查步骤:确认完整的对话历史是否随每次请求一起发送。

工具错误也是 tool result

工具执行失败时(非零退出码、API 超时、权限不足),把错误作为 tool_result 送回去,带上错误详情(MCP 场景下带 is_error 标记)。继续循环。模型需要看到失败——它可以换参数重试、换个工具、或者报告问题。

在工具失败时终止循环,剥夺了模型适应的能力。悄悄吞掉错误,让模型等一个永远不会来的结果。

管理不断增长的历史

一个 30+ 次工具调用的会话,每次返回约 2,000 token,历史会膨胀到巨大。某个点上它会超出上下文窗口。三种策略:

追加前精简:从冗长的工具结果中只提取相关字段。一个 3,000 token 的客户记录可能只有约 200 token 的相关数据(姓名、订单号、状态)。这减少了后续所有请求的重传成本。某生产系统用这种方式把 tool-result token 成本降低了 65%。

渐进式摘要:较旧的 tool result 被压缩成精简摘要,近期的保留完整细节。模型以更低的 token 成本保持对早期发现的感知。

持久发现块:维护一个”关键发现”或”分析结果”区块,累积关键结论。即使旧的 tool result 被摘要化,关键结论仍然存在。这防止模型在上下文被压缩后和自己之前的结论矛盾。

反模式:直接丢掉旧的 tool result。模型丧失对早期发现的感知,可能重新请求相同的工具,并有和自己之前结论矛盾的风险。


一句话总结: Tool result 放在 role: "user" 里并匹配 tool_use_id,每次请求必须发完整历史(无状态 API),错误也是结果——冗长的输出在追加前先精简。