从一次 API 调用到完整 Agent Loop:上下文到底如何流动
摘要
Agent Loop 常被简化成"模型返回工具调用就执行,否则结束"的 while True。这个 Demo 能解释主干,却省略了生产最关键的部分:供应商消息协议并不相同,工具调用与结果必须正确关联,最终文本不一定代表任务完成,错误需要分类恢复,循环还要受轮次、时间、token 和费用约束。本文从消息角色差异出发,建立供应商无关的内部事件模型,再给出包含校验、观测、恢复与安全停止的 Loop 状态机。
关键词
上下文工程、消息角色、工具调用、Agent Loop、调用轨迹、终止条件、max iterations、错误恢复
CSDN 分类建议:人工智能 / 大模型应用
推荐标签:AI Agent、LLM、智能体、大模型、Agent Engineering
本文知识地图
我们先拆"消息是什么",再看一次工具调用怎样往返,最后把这个往返放进状态机。两张图分别回答:谁产生什么消息,以及循环在什么时候继续、恢复、完成或安全停止。

图 1:Agent 工具调用消息时序。来源:作者依据 OpenAI 与 Anthropic 官方工具调用文档整理。
读图重点是 call_42:模型输出的调用请求、执行器结果和下一轮上下文必须保持关联。由图可得的工程结论是,工具结果不能作为一段无来源普通文本塞回模型;否则并行调用、重试和审计都可能串线。
先纠正"消息有四种角色"
源稿以 OpenAI Chat Completions 风格说明 system、user、assistant、tool 四种角色。这对理解特定接口有用,却不是跨供应商标准。
Anthropic Messages API 的官方参考明确说明:系统提示通过顶层 system 参数提供,常规输入消息可用 user 与 assistant,并不存在把系统提示作为输入消息 role: "system" 的通用做法。事实;Anthropic Messages API 工具调用则出现在内容块里:模型返回 tool_use,应用执行客户端工具,再用 tool_result 回传。事实;Handle Tool Calls
OpenAI Chat Completions、Responses API 和 Agents SDK 又有各自的消息或 Item 结构。工程结论不是发明一个"万能 role",而是在应用内部规范化少量语义事件,再由适配器转换:
text
Instruction 开发者/系统约束
UserInput 用户或外部事件
ModelOutput 文本、结构化输出或调用请求
ToolCall call_id + tool + args
ToolResult call_id + result/error
StateUpdate 任务进度、预算、环境状态
这样业务状态机不依赖某家字段名。适配器负责遵守供应商的顺序、内容块、签名或调用 ID 规则;日志层保存规范化事件与原始响应引用。
上下文不是"消息列表"这么简单
不论状态存在哪里,模型在某个决策点实际可见的内容仍是关键。Anthropic 把上下文描述为采样时包含的 token 集合,并强调它有限且需要策展。事实;Effective Context Engineering 生产系统还要保存模型看不见但 Harness 必须知道的控制状态,例如租户、授权票据、幂等键、剩余预算和审计元数据。这些不应全塞进 prompt。
因此建议分成三层:
- 模型上下文:模型决策需要的最小信息;
- 运行状态:循环、预算、权限、取消信号、幂等和恢复点;
- 审计记录:原始请求响应、策略判定、版本和环境证据。
一次工具调用怎样完整往返
以客户端工具为例,通用时序是:
- Harness 组装当前模型上下文和工具定义;
- 模型返回文本、工具调用,或两者按协议允许的组合;
- Harness 解析调用,验证 Schema、业务语义、权限和预算;
- 执行器在受控环境运行工具,记录耗时与副作用;
- 将结果或错误与原调用 ID 关联,追加到下一轮上下文;
- 模型依据新观测给出最终输出或继续调用。
Anthropic 官方工具文档区分客户端与服务端工具:客户端工具由应用执行,服务端工具由 Anthropic 基础设施执行。事实;Tool Use Overview 所以"模型不执行工具"只对客户端工具这一层成立,文章和代码应写清执行位置。
OpenAI Function Calling 的 Structured Outputs 在 strict: true 时可保证参数匹配所给 JSON Schema。事实;OpenAI Function Calling 但 Harness 仍必须验证:订单属于当前用户吗?金额在余额内吗?此动作需要确认吗?工具参数结构正确不等于动作安全。
并行调用的关联规则
模型可能一次请求多个无依赖工具。执行器可并行运行,但结果返回顺序可能不同。不要按数组位置猜对应关系,应以调用 ID 关联。上下文适配器还要遵守供应商对调用与结果相邻、顺序和内容块的要求。
工具输出不要无限回灌
网页、日志或数据库结果可能非常大,也可能包含提示注入。工具层先做长度限制、结构化提取、来源标记和敏感信息处理;原始结果保存到外部制品存储,给模型的是任务所需摘要与可追溯引用。摘要是有损的,涉及证据核验时允许模型按需读取原文片段。
核心循环:从 while 变成状态机

图 2:Agent Loop 状态机。来源:作者依据 OpenAI Agents SDK 生命周期与生产错误处理模式整理。
读图重点是两个出口:完成与安全停止。由图可得的工程结论是,"模型没有返回工具调用"最多表示一个最终输出候选,不能自动证明外部任务完成;相反,预算耗尽时即使任务未完成,也必须退出并报告可恢复状态。
OpenAI Agents SDK 的官方运行文档说明,Runner 会在模型输出最终结果时结束,在产生工具调用时执行并追加结果后重跑;超过 max_turns 会抛出 MaxTurnsExceeded。事实;Running Agents 这验证了轮次上限是当前生产 SDK 的一等概念,但具体默认值与异常类型属于 SDK,实现自己的 Loop 时不能照搬字段名。
供应商无关的伪代码可以写成:
python
state = load_or_create_run()
while True:
enforce_deadline_budget_and_max_turns(state)
response = model(generate_context(state), available_tools(state))
events = adapter.normalize(response)
if events.final_candidate:
verdict = verify_completion(events.final_candidate, state)
if verdict.accepted:
return finalize(verdict, state)
state.observe(verdict.as_error())
for call in events.tool_calls:
verdict = policy.validate(call, state)
result = executor.run(call) if verdict.allowed else verdict.as_error()
state.observe(link(call.id, result))
伪代码省略了并发、流式、取消和持久化,却保留五个硬点:每轮检查预算;供应商响应先规范化;最终输出独立验证;调用先过策略;成功与错误都成为观测。
终止条件应该是组合条件
只用 if not tool_calls: break 会产生两类错误:模型过早给出文本,或用文本询问澄清却被当作完成。生产 Loop 常组合以下条件:
- 完成信号 :模型返回指定结构、调用
submit_result,或工作流到达终态; - 环境验证:文件存在且测试通过、交易状态已提交、引用可访问;
- 用户交互:需要澄清或审批时进入暂停,而非完成;
- 安全停止 :达到
max_turns、deadline、token、金额或调用预算; - 不可恢复错误:权限永久拒绝、资源不存在且无替代路径、策略禁止。
完成信号是候选,环境验证才决定能否交付。安全停止要返回当前状态、已发生副作用、未完成事项和恢复标识,方便之后续跑。
max iterations 不是随手填一个数字
轮次上限过低会截断正常任务,过高会放大循环成本和风险。工程经验是从任务族统计分布出发:记录成功任务所需轮数、长尾失败和每轮成本,为不同工具风险设置差异化预算。例如只读检索可以给更多轮,高风险写操作不仅轮次更低,还应限制每类工具次数。
除了总轮次,还要检测"无进展循环":同一工具与同一参数重复、错误签名重复、状态哈希不变、计划反复切换。检测到后可注入明确观测、切换策略、请求用户信息或停止。不要让模型自己数历史调用次数;Harness 应用代码维护计数。
错误恢复:先分类,再决定动作
| 错误类别 | 例子 | 合理动作 |
|---|---|---|
| 瞬时基础设施 | 429、短暂超时、连接重置 | 指数退避、抖动、遵守 Retry-After |
| 参数/格式 | Schema 失败、枚举值错误 | 把精确错误送回模型,限制纠参次数 |
| 业务拒绝 | 余额不足、状态不允许 | 不盲目重试,换方案或询问用户 |
| 权限/策略 | 越权、缺审批、高风险禁止 | 停止执行,走授权或人工路径 |
| 部分成功 | 邮件已发但数据库更新失败 | 查幂等状态,补偿或人工处置 |
| 模型协议 | 调用 ID 丢失、内容块非法 | 适配器拒绝,保存原始响应并回退 |
恢复必须知道副作用是否已经发生。超时不等于失败:支付请求可能服务端已成功,只是客户端没收到响应。执行器应使用幂等键并先查询状态,避免重复执行。
上下文增长与恢复点
长 Loop 不应只存在进程内存。每轮完成后持久化规范化事件、预算、工具副作用和可恢复游标。恢复时重建供应商上下文,但不要把所有审计字段喂给模型。对非常长的轨迹,使用结构化状态、压缩摘要与按需制品引用;第四、五篇会继续讨论缓存、压缩和隔离。
常见误区与修正
误区一:所有厂商都有四种相同角色。 修正:按官方规范实现适配器;Anthropic Messages 的系统提示在顶层,工具用内容块表达。
误区二:每次 API 调用都绝对无状态。 修正:客户端全量历史是一种方式;当前接口也可续接服务端 response、conversation 或 session 状态,权衡可编辑性与治理。
误区三:无工具调用就是完成。 修正:它只是最终输出候选;结合结构、环境证据和业务终态验证。
误区四:工具错误直接变普通文本。 修正:保留调用 ID、错误类型、是否可重试和副作用状态。
误区五:max_iterations=10 能解决循环。 修正:还要有时间、费用、工具次数和无进展检测,并按任务族校准。
生产检查清单
- 每家模型接口有独立适配器和契约测试,不假设角色相同。
- 内部事件区分指令、用户输入、模型输出、调用、结果和状态更新。
- 并行工具调用以
call_id关联,不依赖返回顺序。 - 工具输出限制长度、标记来源、脱敏并保留原始制品引用。
- 最终输出经过结构、业务和环境证据验证。
- Loop 同时限制轮次、时间、token、费用和高风险工具次数。
- 检测重复参数、重复错误和状态无进展。
- 瞬时、业务、权限、部分成功和协议错误采用不同恢复策略。
- 工具有超时、取消、幂等键与副作用查询接口。
- 每轮持久化恢复点;停止时报告已完成、未完成和已发生副作用。
本文小结
Agent Loop 的本质不是一个 while,而是一套消息协议适配、运行状态、策略校验、工具执行、环境观测和终止判定。供应商 API 的角色与内容块并不通用,应用应规范化语义事件并保留原始关联。最终文本只是完成候选;环境证据、硬预算和错误分类共同决定循环是交付、继续、暂停、恢复还是安全停止。下一篇将深入循环的推理成本:Chat Template、KV Cache、跨请求 Prompt Cache 与前缀稳定性。
延伸阅读与参考资料
官方 API 与 SDK 文档
- Create a Message,Anthropic Messages API。(V1-R015)
- Tool use overview,Anthropic。(V1-R003)
- Handle tool calls,Anthropic。(V1-R017)
- Running agents,OpenAI Agents SDK。(V1-R018)
- Function Calling in the OpenAI API,OpenAI。(V1-R004)
官方工程文章
- Effective context engineering for AI agents,Anthropic。(V1-R005)