Lazy loaded image
Agent丨在多轮工具调用中,如何判断下一步是继续调用工具还是直接结束流程?
Words 6366Read Time 16 min
2026-5-10
type
Post
status
Published
date
May 10, 2026
slug
agent35
summary
tags
Agent
category
icon
password
核心结论:
多轮工具调用的结束条件不能只依赖模型返回的 finish_reasonstop_reason 或“模型说我完成了”。生产级 Agent 应该把它设计成一个显式状态机:
模型是否提出工具调用 → 工具结果是否足够 → 任务目标是否满足 → 是否触发安全/人工/预算边界 → 决定继续、结束、追问、降级或中断。

1. 为什么“是否继续调用工具”是 Agent 的核心问题?

在单轮 LLM 应用里,模型生成一次回答就结束。但在 Agent、Agentic RAG、AI Coding 场景中,模型经常需要循环执行:
观察用户任务
判断是否需要工具
调用搜索、文件、数据库、终端、浏览器、MCP、子 Agent
读取工具结果
再判断是否继续
最后输出结果
OpenAI 的 Function Calling 文档把工具调用描述成一个多步流程:先给模型工具定义,模型返回工具调用,应用侧执行工具,再把工具结果传回模型,模型可能给最终回答,也可能继续产生更多工具调用。也就是说,“工具调用后一定要再进一次模型判断”,而不是工具跑完就直接结束。(OpenAI开发者)
Anthropic Claude 的工具使用机制也是类似的:对于客户端工具,Claude 会返回 stop_reason: "tool_use" 和一个或多个 tool_use block,由应用执行后再把 tool_result 发回模型;服务端工具则由 Anthropic 基础设施执行。(Claude Platform)
OpenAI Agents SDK 对 Agent Loop 的描述更直接:如果 LLM 返回 final_output,循环结束;如果发生 handoff,则切换 Agent 继续;如果产生 tool calls,则执行工具、追加结果、重新进入循环;超过 max_turns 则抛出异常。它还明确说明,final output 的判断规则是:产生目标类型文本输出,并且没有工具调用。(OpenAI)

2. 先看成熟框架怎么做

2.1 LangGraph:最小可用结束条件是“最后一条 AIMessage 是否有 tool_calls”

LangGraph 的 tools_condition 是非常典型的 ReAct 路由逻辑。它检查当前状态里的最后一条 AI 消息:如果存在 tool_calls,路由到工具节点;否则路由到 __end__。LangGraph 源码文档把它定义为 ReAct-style agents 的标准条件路由:有工具调用就执行工具,没有就结束工作流。(GitHub)
简化后的逻辑可以写成:
这套逻辑的优点是简单、稳定、容易可视化:
但它只是最小闭环。生产系统不能只靠这一条规则,因为真实系统还有工具失败、空结果、循环调用、权限拒绝、上下文不足、子 Agent 失败、用户确认等情况。

2.2 OpenAI Agents SDK:继续、结束、handoff、max_turns 是一组状态

OpenAI Agents SDK 的 Runner 明确把循环拆成几个分支:final output 结束;handoff 切换当前 Agent 后继续;否则执行工具调用并重新进入循环;超过 max_turns 或 guardrail 触发则异常。(OpenAI)
这说明生产级 Agent 至少需要四类出口:
正常结束:任务已经完成,返回 final output。
继续执行:模型产生工具调用,工具执行后继续。
转交执行:交给另一个 Agent 或子任务上下文。
异常停止:超过步数、触发安全规则、工具不可用或用户拒绝。

2.3 Vercel AI SDK / OpenCode:需要显式 step limit 和 step 观测

OpenCode 使用 Vercel AI SDK 生态中的工具调用能力。AI SDK 文档里,generateText / streamText 可以通过 stopWhen: stepCountIs(n) 限制工具调用后的连续 LLM 步数,并能通过 stepsonStepFinish 读取每一步的文本、工具调用和工具结果。(AI SDK)
这类设计对 Agent 很重要:
不是只问“这一步有没有 tool call”,还要记录“这是第几步、调用了什么、结果是什么、是否重复、是否越来越接近目标”。
OpenCode 官方文档也把工具权限、内置工具、自定义工具和 MCP 作为 Agent 可行动作的核心能力;其权限文档还专门提供了 doom_loop 安全项:当相同工具调用以相同输入重复 3 次时触发,默认需要询问。(OpenCode)

2.4 Claude Code:工具调用判断之外,还需要权限与 Hook

Claude Code 的公开文档显示,它不是只让模型自由调用工具,而是引入权限系统:只读类操作通常不需要审批,Bash、文件修改等能力需要审批;规则按 deny、ask、allow 顺序执行,且权限由 Claude Code 宿主程序强制执行,不由模型自己决定。(Claude Code)
Claude Code Hooks 也体现了一个生产级原则:某些动作不应该依赖 LLM 自觉执行,而应由宿主在生命周期事件中确定性触发,例如格式化、命令校验、通知、项目规则检查等。(Claude Code)
这对“是否继续调用工具”的启发是:
LLM 可以提出下一步,但最终路由应该由宿主 Agent Runtime 决定。

2.5 OpenClaw / OpenCode:长任务需要会话生命周期,而不只是一次循环

OpenClaw 的仓库说明它是运行在用户设备上的个人 AI 助手,并支持跨渠道交互;其工具文档把能力面分为 tools、skills、plugins,并说明可见工具会以结构化函数定义提供给模型,且会受到 profile、allow/deny policy、provider restriction、sandbox、channel permissions、plugin availability 等条件过滤。(GitHub)
围绕 OpenClaw 的 openclaw-code-agent 插件进一步体现了长任务 Agent 的复杂度:它把 Claude Code 和 Codex 作为可管理的后台编码会话,增加 plan approval、session lifecycle、worktree isolation、merge/PR follow-through、explicit goal loops、verifier-driven repair loops 等机制。(GitHub)
这类系统里,“结束”并不只是“模型不再调用工具”,还可能是:
计划等待审批。
子任务已完成但等待合并。
工作树需要人工决策。
后台 session 暂停、恢复、fork、interrupt。
verifier 判定未达标,需要 repair loop。
成本、时长、风险达到上限,需要停止或升级给用户。

3. 生产级判断不能只看 finish_reason

很多 Agent Bug 都出在“结束条件过于单一”。
OpenCode 有 issue 记录:某些 OpenAI-compatible provider 在响应中已经包含工具调用时,仍返回 finish_reason: "stop",导致 Agent 在工具执行后提前停止,未把工具结果再次交给模型处理。该 issue 的预期行为是:只要有工具调用并执行了工具结果,就应该继续处理,而不是只相信 provider 的 finish_reason。(GitHub)
另一个 OpenCode issue 则是反方向问题:provider 返回 finishReason: "unknown",但没有 tool calls,循环却把 unknown 当成非终止状态,导致重复请求、无限循环。(GitHub)
还有 issue 记录了 opencode run 在完成 read/write 工具调用后没有退出,进程挂起;这说明 CLI 型 Agent 必须有清晰的“所有工具调用完成 + 最终响应已产生 + 进程退出”终止协议。(GitHub)
所以,推荐规则是:
第一优先级:看结构化 tool_calls 是否存在,而不是只看 finish_reason
第二优先级:看工具结果是否已经被模型消费,而不是工具执行完就结束。
第三优先级:看任务目标是否满足,而不是模型是否停止生成。
第四优先级:看安全、预算、重复、人工审批边界。
第五优先级:如果 provider 信号冲突,进入保守恢复逻辑。

4. 一个可落地的“继续 or 结束”状态机

这个状态机把“继续调用工具还是结束”拆成两个问题:
模型层问题:模型有没有提出下一步行动?
系统层问题:这个行动是否必要、允许、有效、没有陷入循环?

5. 五层判定模型

第一层:结构化动作层

先解析模型输出里是否有以下结构:
tool_calls:继续执行工具。
handoff / task:转给子 Agent 或专家 Agent。
final_output:进入最终结果校验。
非法 JSON / 不完整 tool block:进入恢复逻辑。
纯文本但任务明显未完成:不要直接结束,进入自检或补检索。
Anthropic 文档提醒,如果 Claude 因 max_tokens 截断,且最后包含不完整 tool use block,需要用更高 max_tokens 重试,而不是把半截工具调用当成正常结束。(Claude Platform)

第二层:工具结果层

工具执行完后,不应该马上结束。应该检查:
工具是否成功。
工具结果是否为空。
工具结果是否可解析。
工具结果是否和请求匹配。
是否所有并行工具调用都返回了结果。
是否每个 tool call id 都有对应 tool result。
OpenAI Function Calling 文档强调,模型响应可以包含零个、一个或多个工具调用,应用需要执行它们并把结果按调用 ID 返回给模型。(OpenAI开发者)
LangGraph 的 ToolNode 文档也强调工具节点要处理并行工具调用、错误处理、状态注入、持久存储注入和命令式状态更新;这说明工具结果不是简单字符串,而是会影响后续状态流转的数据。(GitHub)

第三层:任务完成度层

这一层不要问“模型停了吗”,而要问“用户目标完成了吗”。
例如:
用户问事实:是否已经得到足够证据?
用户让改代码:是否已经修改、测试、总结变更?
用户让查资料:是否覆盖核心来源、是否引用可靠?
用户让执行命令:是否命令成功、是否结果符合预期?
用户让做计划:是否输出了可执行计划,还是还缺输入?
用户让 RAG 问答:检索片段是否足以支撑答案?
Agentic RAG 里,这一层尤其重要。Self-RAG 的思路是让模型按需检索,并通过 reflection tokens 判断检索是否必要、检索片段是否有用、生成结果是否被支持。(arXiv)
CRAG 则引入轻量级 retrieval evaluator,对检索结果质量打分,再根据置信度触发不同动作,例如继续检索、Web search、过滤重组文档或直接生成。(arXiv)

第四层:循环风险层

继续调用工具可能带来收益,也可能进入死循环。生产系统需要显式检测:
同一个工具 + 同一参数重复出现。
工具结果没有新增信息。
连续 N 次自我修复没有改善。
反复搜索相同 query。
反复读同一个文件但没有写入或总结。
子 Agent 返回后父 Agent 没有消费结果。
模型只是在“再确认一下”,但没有新信息需求。
OpenCode 权限系统里专门有 doom_loop 保护项,用于相同工具调用和相同输入重复 3 次的情况。(OpenCode)

第五层:边界控制层

即使模型还想继续,也要检查:
最大步数 max_steps / max_turns
最大工具调用次数。
最大成本。
最大运行时长。
最大 token。
高风险工具是否需要人工批准。
是否触发权限拒绝。
是否触发安全策略。
是否需要用户补充信息。
OpenAI Agents SDK 明确支持 max_turns,超过后抛出 MaxTurnsExceeded;Runner 文档还说明 guardrail tripwire 会触发异常。(OpenAI)
Claude Code 和 OpenCode 也都把工具权限作为宿主层控制,而不是让模型自己决定是否安全。Claude Code 通过 allow/ask/deny 权限规则管理工具;OpenCode 通过配置字段控制工具 allow、deny 或 ask。(Claude Code)

6. 推荐的判定优先级

可以把每一步 Agent 循环后的决策写成下面这张表:
优先级
条件
决策
P0
输出不完整、tool call JSON 解析失败、工具调用 ID 缺失
恢复或重试,不直接结束
P1
有有效 tool_calls
执行工具,然后继续让模型消费结果
P2
有 handoff / subagent task
转交后等待结果,父 Agent 继续消费结果
P3
无工具调用,但 final answer 通过完成度检查
正常结束
P4
无工具调用,但证据不足、任务未完成
继续检索、继续工具、或追问用户
P5
工具失败但可恢复
返回错误观察给模型,允许有限次数修复
P6
重复调用、预算耗尽、权限拒绝、安全风险
停止、降级回答或人工确认
P7
Provider 信号与结构冲突
以结构化 tool_calls 和本地状态为准
关键点是:
finish_reason == "stop" 只能作为辅助信号,不能作为最高优先级终止条件。OpenCode 的相关 issue 已经说明,某些兼容 provider 可能在包含工具调用时仍返回 stop,导致循环提前退出。(GitHub)

7. 源码级实现:一个生产可用的 Loop Controller

下面是一个 TypeScript 风格的简化实现,重点是“路由决策”,不是某个具体 SDK 的封装。
这段代码体现了一个关键原则:
先看本地可验证结构,再看模型声明;先看安全和预算,再看模型意图;先看任务完成度,再决定是否最终返回。

8. RAG 场景:什么时候继续检索,什么时候回答?

在 Agentic RAG 中,“工具调用”通常是检索、重写查询、rerank、Web search、数据库查询、文档解析等。判断是否继续的关键不是“有没有检索过”,而是“当前证据是否足够支撑最终答案”。
推荐设计一个 RetrievalSufficiencyEvaluator
Self-RAG 和 CRAG 都说明了一个方向:检索不是固定的一次性动作,而应该由模型或检索评估器判断“是否需要检索、检索结果是否可信、是否需要纠正”。Self-RAG 用 reflection token 控制是否检索和批判生成;CRAG 用 retrieval evaluator 对检索质量进行分级并触发不同补救动作。(arXiv)

9. AI Coding 场景:什么时候继续工具调用?

AI Coding Agent 比普通 RAG 更容易陷入循环,因为它可以读文件、改文件、跑测试、修复、再跑测试。
推荐把结束条件设计成 checklist:
是否理解任务范围?
是否定位相关文件?
是否完成代码修改?
是否运行必要测试或静态检查?
是否测试失败并完成修复?
是否存在未处理错误?
是否生成最终摘要?
是否需要用户审批 merge / PR / destructive action?
OpenCode 的 Agent 文档区分 primary agents 和 subagents,并指出 Build agent 可以启用所有工具,而 Plan agent 受限,用于分析代码和审查建议而不做代码修改。(OpenCode)
这给我们的启发是:
“是否继续调用工具”也和 Agent 角色有关。Plan Agent 可以在产生计划后结束;Build Agent 可能要继续 read/edit/test;Review Agent 可能只需要 grep/read,不应该写文件。

10. 常见错误与修复方式

错误一:只看 finish_reason == stop

风险:provider 返回 stop 但实际有工具调用,导致工具结果没有被模型消费。OpenCode 相关 issue 已经记录了这种情况。(GitHub)
修复:

错误二:把 unknown 永远当成继续

风险:没有工具调用,但 finish reason 是 unknown,循环重复请求。OpenCode issue 中就出现了 finishReason: "unknown"hasToolCalls: false 导致无限循环的情况。(GitHub)
修复:

错误三:工具执行完就结束

风险:工具结果没有经过模型综合,用户只看到原始工具输出。OpenAI 的工具调用流程明确要求工具输出要返回给模型,然后模型再给最终响应或继续工具调用。(OpenAI开发者)
修复:

错误四:没有重复调用检测

风险:搜索同一个 query、读取同一个文件、执行同一个失败命令。OpenCode 的 doom_loop 权限项就是针对相同工具和相同输入重复调用的保护。(OpenCode)
修复:

错误五:子 Agent 完成后父 Agent 没有继续

风险:子 Agent 执行完成,但父 Agent session 丢失上下文,后续步骤被跳过。OpenCode 有 issue 描述 task 工具触发子 Agent 后,父 session 结束且后续步骤丢失的问题。(GitHub)
修复方向:
子 Agent 返回结果必须写回父 Agent 的消息流。
父 Agent 必须有明确 continuation token / task id。
子 Agent 失败、取消、超时都必须转成父 Agent 可见的 ToolResult。
父 Agent 不能因为子 Agent 成功返回就默认整个任务结束。
编排器要区分“子任务完成”和“用户目标完成”。

11. 最终推荐架构

生产系统里建议拆成这些组件:
组件
作用
Output Parser
解析模型文本、tool calls、handoff、finish reason
Tool Executor
执行工具,支持并行、超时、重试、标准化结果
Permission Guard
控制危险工具、敏感路径、外部系统写操作
Loop Controller
决定继续、结束、追问、恢复、降级
Completion Verifier
判断用户目标是否满足
Repetition Detector
检测重复工具调用和无进展循环
Budget Manager
控制步数、成本、token、时间
Human-in-the-loop
处理审批、拒绝、补充信息
Trace Logger
记录每一步输入、输出、工具结果、路由原因

12. 一句话总结

多轮工具调用的终止条件,不应该写成“模型 stop 就结束”,而应该写成“状态机路由”:
有工具调用就执行并回灌;无工具调用就做完成度校验;信息不足就继续;风险过高就人工确认;无进展或超预算就降级停止;最终只有在用户目标被验证满足时才结束。
回到首页