Agent 循环控制:如何优雅地判断“继续调用”还是“结束流程”?

大家好,我是你们的老朋友,一名深耕后端与 AI 工程化的技术博主。

在构建 LLM Agent(智能体)应用时,我们最常遇到的一个核心痛点就是:Agent 什么时候该停?

很多开发者在初学 ReAct(Reasoning + Acting)模式时,都会陷入一个误区:认为只要把工具交给 LLM,它就能完美地自主决定何时停止。但在生产环境中,你可能会遇到这样的尴尬场景:

  • Agent 陷入了死循环,反复调用同一个搜索工具。
  • Agent 明明已经拿到了答案,却还在执着地查询无关数据,导致 Token 费用飙升。
  • Agent 因为网络超时返回空结果,直接崩溃或胡言乱语。

这背后的本质问题,就是 Agent Loop Control(代理循环控制)。今天,我们就来深入浅出地拆解这个问题,看看在企业级开发中,我们是如何让 Agent “知进退”的。


一、核心本质:思考 vs 完成

简单来说,Agent 的每一轮交互都在做一个二选一的决策:

  1. 继续思考(Continue):当前信息不足,需要调用工具获取更多数据。
  2. 任务完成(Finish):信息已足够,可以生成最终回答给用户。

经典的 ReAct 范式 流程如下:

Thought (思考) → Action (行动/调用工具) → Observation (观察结果) 
      ↓
Thought (基于新结果再次思考) → ...
      ↓
Final Answer (最终回答)

举个栗子 🌰

假设用户问:“查询患者最近血糖趋势”

  • 第一轮
    • Thought: 我需要先查检验数据。
    • Action: query_lis(patient_id=1001)
    • Observation: {"glucose": [6.1, 7.8, 8.5]}
  • 第二轮
    • Thought: 数据已获取,数值呈上升趋势,我可以总结回答了。
    • Final Answer: “患者最近三次血糖分别为 6.1, 7.8, 8.5 mmol/L,呈升高趋势。”

在这个理想过程中,LLM 通过输出特定的结构(如 Final Answer 标记)来告诉框架:“我结束了”。


二、为什么不能只信 LLM?

在 Demo 阶段,LLM 的自主判断通常工作良好。但在企业级生产环境中,完全依赖 LLM 的“自觉”是危险的。

LLM 本质是一个概率模型,它可能会出现:

  • 幻觉循环:一直认为自己没找到答案,无限调用工具。
  • 格式错误:输出的 JSON 解析失败,导致流程卡死。
  • 资源浪费:为了追求“完美”,进行无意义的冗余查询。

因此,生产系统必须引入“外部控制层”,不能完全把方向盘交给 LLM。


三、企业级常见的四种控制策略

为了让 Agent 既聪明又稳定,我们通常采用以下四种手段组合拳:

1. 最大循环次数(硬止损)

这是最简单也最有效的防死循环机制。

MAX_ITERATIONS = 5

for i in range(MAX_ITERATIONS):
    response = agent.step()
    if response.is_final:
        break
else:
    # 超过最大次数,强制终止
    raise Exception("Agent exceeded maximum iterations")

建议:根据任务复杂度设置阈值,简单问答设为 3-5 次,复杂分析设为 10-15 次。

2. 置信度与信息充分性判断

有时候,LLM 可能不知道自已“知道了”。我们可以引入一个评估环节,判断当前收集的信息是否足以回答问题。

  • 逻辑:如果 Context 中已经包含了 检验结果 + 患者历史,则无需再调用 知识库
  • 实现:可以在 Prompt 中明确指示:“如果你认为当前信息已足够回答用户问题,请直接输出 Final Answer,不要额外调用工具。”

3. 工具结果质量判断(Fallback 机制)

工具调用不是每次都能成功的。如果工具返回了异常,继续循环没有意义。

// 情况 A:空结果
{ "result": [] }

// 情况 B:超时
{ "timeout": true }

处理策略
检测到上述情况时,立即中断循环,进入 Fallback 流程,例如回复用户:“暂时无法获取实时数据,请稍后重试”,而不是让 Agent 去瞎猜。

4. 状态机控制(LangGraph 的核心价值)

这是目前最推荐的企业级架构方式。使用如 LangGraph 这样的框架,将流程显式地建模为状态机。

流程图示

结果有效且需更多数据

结果有效且任务完成

结果无效/超时

Start

Tool Node
执行工具

Judge Node
判断下一步

End Node
生成回答

Fallback Node
错误处理

在 LangGraph 中,我们可以通过一个简单的函数来控制路由:

def should_continue(state: State) -> str:
    """
    判断是继续调用工具,还是结束流程
    """
    messages = state["messages"]
    last_message = messages[-1]
    
    # 1. 检查是否有工具调用请求
    if hasattr(last_message, "tool_calls") and last_message.tool_calls:
        return "tools"
    
    # 2. 检查是否达到最大步数(外部保护)
    if state["step_count"] > MAX_STEPS:
        return "end"
        
    # 3. 默认结束
    return "end"

这种方式的优点是:流程可视、可控、可调试。你可以清楚地看到 Agent 在哪个节点做出了什么决定。


四、进阶方案:Planner + Executor 模式

对于非常复杂的任务(比如:“分析过去半年某科室的医保违规风险并生成报告”),简单的 ReAct 循环容易迷失方向。

这时,我们推荐采用 Planner(规划者) + Executor(执行者) 架构:

  1. Planner:先生成完整的执行计划(Plan)。
    • Step 1: 查询 LIS 获取血糖数据。
    • Step 2: 查询 HIS 获取用药记录。
    • Step 3: 结合指南进行风险分析。
    • Step 4: 生成报告。
  2. Executor:严格按照计划一步步执行。

优势

  • 确定性高:不会无限发散。
  • 可解释性强:用户可以随时看到当前执行到了计划的哪一步。
  • 易于并行:如果 Step 1 和 Step 2 无依赖,可以并行调用工具。

五、总结与最佳实践

在多轮工具调用的场景中,我的建议是构建三层防御机制

  1. 第一层:LLM 自主判断
    • 利用 ReAct 或 Function Calling 的原生能力,让 LLM 根据 Observation 决定下一步。
  2. 第二层:状态机编排(Orchestration)
    • 使用 LangGraph 等框架,显式定义 Tool NodeJudge Node,掌握流程控制权。
  3. 第三层:系统级兜底(Guardrails)
    • 设置 Max Iterations
    • 设置 Timeout
    • 对空结果、错误结果做明确的 Fallback 处理。

最后的一句话建议
对于简单任务,ReAct + 最大循环次数足矣;对于复杂、高可靠要求的生产任务,请毫不犹豫地选择 Planner-ExecutorState Graph(状态图) 架构。稳定,才是 AI 应用落地的基石。


参考资料

希望这篇文章能帮你理清 Agent 循环控制的思路。如果你在实战中遇到过奇怪的循环 Bug,欢迎在评论区分享你的踩坑经历!👇

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐