Agent 工具调用循环怎么实现?从完整响应到安全续跑
梳理模型响应组装、调用校验、执行与结果配对,并按各家接口协议安全保存和续跑工具调用循环。
所属问题主题Agent 架构与协作↗先带着一个真实任务阅读,再完成页面中的工作台。能让另一位同事复核你的产物,才算完成本课。
一个可运行的 Agent 不是“模型说下一步做什么,程序就照做”。真正的循环由应用控制:发出一轮模型请求,识别完整响应,验证其中的工具请求,执行获准的动作,把每个结果按正确协议放回会话,再决定是否请求下一轮。任一环节错配,都可能让模型依据错误观察继续,或把未完成的动作误当成失败。
本文讨论自建或审查工具调用循环时需要实现的状态往返。它不要求记录模型的隐藏推理;复核重点是可见的工具请求、执行端判断、外部效果和结果。若尚未定义单项工具的参数、权限和重复行为,先写工具调用契约;每次调用之后是否继续,则由运行循环护栏判断。
先确定谁拥有循环控制权
即使采用 ReAct、函数调用或某个 Agent SDK,应用仍需知道本轮从哪里开始、哪种响应状态意味着“等待工具”、哪个人可以批准写操作,以及任务停止后状态存在哪里。框架可以替你管理消息协议或调度,但不能替组织决定权限,也不会天然保证远端写入的结果可查询。
先把运行建模成有限状态,而不是无条件的 while True:
| 当前事件 | 控制器的下一步 |
|---|---|
| 模型返回已完成的最终回答,且无工具请求 | 检查回答与任务验收条件,保存终态 |
| 返回一个或多个已完成的工具请求 | 保存原始 assistant 请求,逐个规范化并校验 |
| 响应被拒绝、截断或请求失败 | 保存失败类型;按任务规则重试、降级或交接,不执行残缺参数 |
| 调用需要人工批准 | 保存待审批动作与绑定参数,暂停自动执行 |
| 工具明确成功或拒绝 | 保存结果及其调用编号,按接口协议回填 |
| 外部写入结果未知 | 查询权威系统;仍未知则暂停,不能直接重放 |
关键是把“模型请求”和“工具执行”分成两个信任边界。模型只能提出动作;运行器负责检查用户身份、对象范围、参数、业务前置条件与当前审批。对工具的定义不要把授权藏在描述文字里。
用适配层隔开不同提供商的会话格式
各接口对一轮工具往返的表示并不相同。OpenAI Responses API 使用 function_call 和关联的 call_id,后续交回 function_call_output;Chat Completions 使用 assistant 消息中的 tool_calls,结果消息通过 tool_call_id 关联。Anthropic Messages API 使用 tool_use 内容块及对应的 tool_result,并用各自的停止状态表示交接。使用 SDK 时,这些字段或状态也可能由 SDK 封装。
适配层可以把它们映射为内部事件,例如 assistant_tool_request、tool_result、assistant_final、paused_for_approval,但持久化时仍需保留提供商、接口版本、原响应标识和必要的原始调用结构。发送回模型时,再由对应适配器恢复它要求的角色顺序和关联字段。不要用一套自造的通用 JSON 去代替接口要求的消息序列。
一个安全的往返顺序是:
- 保存本轮请求标识及模型响应;确认响应已按该提供商的规则完成,且工具参数不再是流式片段。
- 逐个解析工具名和参数;先检查工具是否在白名单,再校验 Schema、业务条件、当前身份、资源范围和审批。
- 执行获准的工具,并为每个调用编号保存成功、拒绝、普通失败或结果未知;需要审批的动作在此暂停。
- 保存 assistant 的原始工具请求及与其匹配的结果,再按提供商协议组成下一轮会话。
- 只有当当前结果允许继续、任务预算尚未触顶、状态没有待人工处理时,才再次调用模型。
若一轮请求含多项调用,不能只回填成功的那几项。为每个调用编号都生成一份明确结果;被拒绝的请求可以返回结构化错误,但不要执行。并行处理仅适用于彼此独立的读取,或系统已经定义好并验证过的操作;同一记录上的写入和有先后依赖的动作应按确定顺序执行。完成顺序可以不同,结果仍必须按调用编号准确配对。
流式输出要先组装,后执行
流式响应可能把工具名、参数 JSON 或调用编号拆成多个片段。第一个片段不是完整授权请求:要按响应事件中的索引或调用编号收集片段,等待明确的完成事件,确认最终参数能解析且必填字段齐全,再进入校验。如果连接结束时参数仍不完整,应保存为 incomplete 并停止这一轮。
也要分清拒绝、最终回答、工具交接、模型超时和传输中断。不同接口字段不同,不存在适用于所有提供商的单一 finish_reason 判断。检查对应 API 的完成状态和停止原因;若 SDK 提供标准化结果,仍要针对所用版本写测试。截断响应中出现看似合法的片段,也不能触发副作用。
以调用编号关联结果,不依赖数组位置
每项模型工具请求都有自己的关联标识。运行器可以并发执行两项安全读取,但天气结果和交通结果不能因为完成顺序不同而放错位置。应用应以调用编号为键保存请求摘要、工具版本、授权判断、执行结果和完成时间;恢复任务时,以编号及权威目标状态判断哪些动作已完成。
模型请求、执行结果与对话上下文要作为一组持久化状态。崩溃可能发生在“工具已经执行,但结果尚未写回”之间。对于读取,可按契约安全重做;对于写入,应先查询目标系统,或使用下游实际支持的幂等机制。只有把调用状态标为已完成之后才发送下一轮,避免恢复时再次触发同一外部效果。长期任务还要把审批和用量累计保存,参见长任务续跑状态契约。
实现时可以先按下面的伪代码划分责任;其中 adapter 承担提供商协议,executor 承担服务端授权和执行:
save(user_event)
for turn in remaining_turn_budget:
response = adapter.request(history, allowed_tools)
event = adapter.read_complete_event(response)
save(response, event)
if event is final:
return validate_and_present(event)
if event is incomplete_or_refused:
return pause_or_handoff(event)
calls = validate_batch(event.calls, identity, permissions, approval)
if calls.need_human_approval:
return save_and_pause(calls)
results = executor.run_independent_calls(calls)
save_results_by_call_id(results)
history = adapter.append_results(event, results)
return save_and_handoff("turn budget reached")
这里的“批量”不表示数据库事务。若一组请求有些可执行、有些待审批,要明确记录是否允许部分完成;对可能产生副作用的一批请求,通常应先停下来重新规划或由人批准,而不是悄悄执行前半部分。
案例一:并行查询行程状态
差旅助手要回答某趟火车是否晚点,以及目的地天气。模型请求 get_train_status 与 get_weather 两个只读工具。运行器先验证列车编号和日期在用户授权范围内,再并行查询;天气服务先返回也不改变两个调用的身份。回填时分别把天气结果写给天气调用编号、列车结果写给列车调用编号。若天气请求超时,仍可把另一个已核验的结果回填,并明确报告天气暂不可得,而不是把它填进相邻的工具结果。
该案例要测试反序完成、一个工具失败、列车编号格式错误及响应截断。验收证据是最终回答引用的每项状态都与正确的工具调用编号和原始响应一致。
案例二:预约工作坊遇到审批与超时
活动助手收到为参与者预约名额的请求。它先调用只读工具确认名额与报名资格,再提出 reserve_seat 写入请求。运行器验证当前登录身份、活动编号、参与者授权和审批是否仍然匹配;如果还需负责人批准,就将具体活动与参与者绑定在待审批记录中,不把它先送回模型请求别的写入。
获批后,预约服务接受写入但响应超时。控制器将状态记为 unknown,通过操作编号查询预约系统。若查到唯一预约,回填已确认结果;若查询仍无结论,暂停并通知负责人核对。测试应覆盖“预约已成功但响应丢失”,保证恢复后的下一轮不会重复占位。
把协议往返变成回归用例
至少覆盖:没有工具的最终回答、单项调用、多个调用乱序返回、未知工具、非法参数、流式响应截断、工具错误、审批暂停、服务端拒绝、写入后连接中断、进程恢复以及模型拒绝。对每个用例记录期望状态、实际执行次数、调用结果关联方式和是否触发外部效果。接口升级、SDK 更新、消息持久化结构变化或工具行为变化时,重跑这些用例。
轨迹只保留排错必需的信息:运行编号、响应编号、工具名与版本、参数摘要、策略决定、调用编号、外部状态和结果编码。避免把凭据、完整个人资料或隐藏推理写入通用日志。出错后,再用逐事件轨迹复核定位首次出现的请求、执行或回填偏差。
常见问题
用 Agent SDK 是否还需要关心这些状态?
需要知道 SDK 为你管理了什么、哪些执行会持久化,以及重试、暂停、审批和错误如何表达。可以让 SDK 处理消息往返,但仍要测试业务权限和外部效果,并为升级后的协议行为保留验收用例。
模型的思考过程要不要存到轨迹?
无需记录隐藏推理。保存外部可见的工具请求、执行端校验、工具观测、审批和终止原因,通常足以排查协议是否正确。根据政策决定是否保存用户原文或工具结果,并做脱敏与访问控制。
不适用边界
伪代码只说明循环边界,不可直接用于生产。提供商的消息字段、拒绝状态、流事件及工具结果格式会变化,应按当前 SDK/API 文档实现适配器。循环本身也不能替代工具授权、下游幂等保证、沙箱隔离、人工审批和独立的事故恢复机制。
核验来源
这篇内容参考了哪些一手资料?
来源用于核对定义、风险边界或操作事实;判断框架和工作台由 AI Vista 独立编写。
- OpenAI API documentation — Function calling核验于 2026-10-02
- Anthropic API documentation — Tool use overview核验于 2026-10-02
- OpenAI Agents SDK — Running agents核验于 2026-10-02
- pi ReAct Agent implementation notes核验于 2026-10-02
AI 工具可能协助研究、提纲和语言编辑;事实、案例和最终措辞由编辑复核。发现错误请通过联系页提交更正。
可带走的工具
Agent 循环执行契约
明确一轮模型输出如何变成获授权的工具动作,并配对写回下一轮观察。
此工作台会在当前浏览器保存草稿。只写合成工具名、脱敏状态和受控证据位置;不要粘贴真实用户消息、调用参数、凭据或工具原始返回。
- 01写任务摘要用脱敏摘要写明要交付的结果。
- 02补齐判断字段写清输入类别、风险、证据位置和接手方式。
- 03交给同事复核对方能复述结论,工具才算完成。
本课已读
完成工作台,再标记已读。
已读状态会更新课程目录与学习进度。
- 01字段齐全
- 02案例走过
- 03可以复核
文章实践笔记
读到这里,留下一条可复核的判断。
下方虚构示例展示写法;你的笔记只保存在当前浏览器。记录哪一步有效、哪个边界不成立,或一个值得测试的问题。
这篇文章还没有示例或本机笔记。你可以记录第一条具体观察。