执行循环与调用关联
本节目标
- 建立供应商无关的工具调用状态机;
- 正确关联每个 call 与 result;
- 区分完成、需工具、需审批、拒绝、截断和故障;
- 用预算、重复检测和进度条件阻止无限循环。
最小五步循环
OpenAI 当前官方文档把客户端 function calling 概括为五步:
- 请求模型并提供工具;
- 接收模型的 tool call;
- 应用侧执行代码;
- 携带对应 tool output 再请求模型;
- 接收最终回答或更多 tool calls。
Anthropic 客户端工具同样需要应用驱动循环,只是消息块和 stop reason 不同。Gemini 也明确模型不会替应用执行函数。
用内部状态机隔离供应商差异
不要在业务代码到处判断 function_call 或 tool_use。Adapter 把它们归一为有限状态:
MODEL_REQUESTED
├─ FINAL_TEXT ─────────────→ COMPLETED
├─ TOOL_PROPOSALS ─────────→ VALIDATING
│ ├─ approval needed ──→ WAITING_APPROVAL
│ ├─ executable ───────→ EXECUTING → RESULTS_READY
│ └─ invalid/denied ───→ RESULTS_READY(error envelope)
├─ REFUSAL ────────────────→ REFUSED
├─ TRUNCATED ──────────────→ INCOMPLETE
└─ API_FAILURE ────────────→ FAILED/RETRYRESULTS_READY 再送回模型,可能回到 TOOL_PROPOSALS。只有明确完成原因才是成功;“没有 tool call”可能是拒绝、max tokens 或服务错误。
三类 ID
| ID | 范围 | 作用 |
|---|---|---|
| Provider response/item ID | 供应商响应 | 排障与继续 API 状态 |
| call ID | 一次模型提出的调用 | 将 tool result 对回正确 call |
| operation ID | 整个业务任务 | 跨模型回合、服务和审批追踪 |
| idempotency key | 业务意图 | 防止重试产生重复副作用 |
| approval ID | 人类确认记录 | 审计谁批准了哪项动作 |
Call ID 不能代替幂等键:模型重试时可能产生新 call ID,却仍是同一次退款意图。
关联 ID 仍不等于结果绑定
只把 call_id 复制到结果上,无法阻止合法形状的 A 结果被换到 B。项目在受保护审计中同时绑定:
- provider + API family + response ID + call ID;
- operation ID、idempotency key 与 adapter revision;
- 当前 tenant/subject 的引用;
- 输入、输出、效果、handler、producer 与 policy revision;
- 完整
request_sha256、result_sha256与call_binding_sha256。
回传前用当前受信任上下文重算,而不是只检查字段存在或摘要长度。多调用集合按 (response_id, call_id) 建表,拒绝缺失、重复和未知结果;数组顺序可以改变,但关联身份不能交换。详见结果、错误与不可信数据。
Provider Adapter 的职责
Adapter 应:
- 解析零、一个或多个 call;
- 保留 call ID、name 与原始完成原因;
- 严格解析 arguments;
- 把内部 result 变成供应商要求的回传形状;
- 处理流式参数的完整性与 done 事件;
- 携带供应商要求延续的对话/推理项;
- 把 API 错误映射到内部有限状态;
- 记录 provider、API、SDK、model 与 adapter revision。
本库离线 adapter 当前覆盖 OpenAI Responses function_call_output、Anthropic Messages tool_result 与 Gemini Interactions function_result。它们先验证内部双投影,只把 model_result 放入供应商 payload;protected_audit 留在模型外。Anthropic 的 result blocks 需位于普通文本之前,Gemini Interactions 当前使用 result 字段而不是把三家统一伪造成同一外部消息结构。
Adapter 不应:
- 决定用户是否有权退款;
- 自动修正金额/路径并执行;
- 直接调用任意模型给出的函数名;
- 把供应商错误文本作为业务状态。
Registry 与 Dispatcher
registry = { # host 维护固定工具注册表,而不是按模型输出动态导入函数
"get_order": ToolSpec( # 为唯一允许的读取工具建立显式合同
schema_version="get-order-v1", # 参数/结果解析必须匹配这一版本
risk="read", # 只读风险分类,仍可受 tenant 与数据策略限制
timeout_ms=500, # 为外部调用设置有限超时,避免 loop 无限等待
handler=get_order, # 绑定经过审阅的本地 handler 函数对象
) # 结束 get_order 工具规格
} # 结束 allowlist 注册表Dispatcher 从 registry 获取 schema、风险、超时、授权策略和 handler。未知名称拒绝;禁止:
# 危险示意:不要这样做 # 模型文本绝不能决定要从全局命名空间取哪个可执行对象
handler = globals()[model_name] # 不安全:任意全局名称都可能被模型选择,绕过 allowlist 与风险分类
handler(**model_arguments) # 不安全:未做 schema、授权、预算、审批或目标范围校验就产生副作用显式 registry 使能力暴露面可审计、可测试、可按用户/任务裁剪。
每轮算法
deadline / budgets 初始化
while 未终止:
调模型
解析完成原因与 calls
若 final → 校验并结束
若 refusal/truncated/error → 对应状态结束或受控恢复
对 calls:
去重 call ID
schema + auth + policy
若需要审批 → 持久化 pause state
否则执行并产生严格 result
关联所有结果并回传
更新进度、成本与重复轨迹需要审批时不要占用进程等待。保存 operation、conversation continuation、call、参数摘要、策略版本与 expiry;用户批准后恢复并重新校验。
循环保护
至少设置:
- 最大模型回合;
- 最大工具调用数;
- 每工具与总 deadline;
- token/费用预算;
- 最大并行度;
- 重复
tool + canonical arguments检测; - 无进度计数;
- 结果大小上限;
- 人工升级/安全停止路径。
若模型连续调用相同工具与参数却没有新信息,返回结构化 NO_PROGRESS 或转人工,而不是不断把相同结果喂回去。
流式调用
流式 API 可能分片发送工具参数。只能在供应商明确的完成事件后解析并执行:
- 片段按 item/output index 关联;
- 限制累计字节;
- JSON 完整前不运行 handler;
- 流中断视为 incomplete,不“补一个括号”后执行;
- 最终解析仍走 schema、授权和审批。
进度 UI 可以展示“模型正在准备调用 X”,但不能显示未完成参数为已执行。
动手实践
画出“查订单 → 创建退款草稿 → 等待批准 → 提交退款 → 最终说明”的状态机,标出:
- call ID、operation ID、approval ID、idempotency key;
- API max tokens;
- 未知工具;
- 审批过期;
- 提交超时但下游可能已成功;
- 模型再次提出完全相同调用。
为每个分支写终止/恢复状态,不能只写“报错”。
常见错误
stop_reason != tool_use就当成功;- 多个 call 共用一个无标识结果字符串;
- 只保存 call ID,不保存业务 operation;
- 流式 arguments 未完成就执行;
- 审批等待只存在进程内存;
- 无限循环没有 deadline/进度条件;
- Provider message 形状直接渗入 handler。
自测
- 为什么没有 tool call 不一定是完成?
- Call ID 与 idempotency key 各解决什么?
- Adapter 与 dispatcher 的边界在哪里?
- 审批暂停后恢复需要保存哪些状态?
- 流式参数为什么必须等 done 后再执行?
下一步:多调用、并行与依赖。
参考资料
- OpenAI API:Function calling—The tool calling flow
- Anthropic:How tool use works—The agentic loop
- Anthropic:Handle tool calls
来源获取日期:2026-07-19。Provider 消息字段与 continuation 规则属于动态适配内容。