Agent 循环
Agent 的本质是一个感知-推理-行动的闭环。与传统程序"输入 → 确定性逻辑 → 输出"的单次执行不同,Agent 在每一步都需要决定下一步做什么:继续调用工具、返回结果给用户、还是请求人工介入。这个循环控制逻辑(Loop)是 Agent 架构的核心——LLM 提供推理能力,工具提供行动能力,而循环决定整个系统的行为模式。
基础循环结构
所有 Agent 循环都可以抽象为一个 while 循环:
while not done:
observation = 获取当前环境状态(用户输入 + 工具返回 + 记忆检索)
action = LLM.decide(observation) # LLM 决定下一步
if action.type == "respond":
return action.content # 直接回答用户,退出循环
elif action.type == "tool_call":
result = execute(action.tool, action.args)
history.append(result) # 将工具结果追加到历史
elif action.type == "ask_human":
result = wait_for_human_input()
history.append(result)这个循环的每一次迭代称为一步(Step),每一步包含一次 LLM 调用。循环的退出条件是 LLM 决定直接回答用户(不再需要调用工具)或达到最大步数限制。最大步数是防止无限循环的安全机制,通常设为 10-25 步,超过后强制返回当前结论或错误提示。
循环的核心开销在 LLM 调用。每一步的延迟等于 LLM 推理时间(通常 1-5 秒)加上工具执行时间(通常 0.1-3 秒)。一个需要 10 步的 Agent 任务,端到端延迟可能达到 10-50 秒。因此减少循环步数是 Agent 优化的关键方向之一。
ReAct 循环
ReAct(Reasoning + Acting)是最基础的 Agent 循环模式,它的核心是在每一步显式生成推理过程(Thought),然后基于推理决定行动(Action)。Thought 的作用是让 LLM "想清楚再动手"——显式的推理链让后续步骤能参考之前的分析,减少重复思考和错误决策。
ReAct 的 Prompt 模板固定了输出格式:
Question: 用户的问题
Thought 1: 我需要分析这个问题...我认为应该先搜索信息
Action 1: Search["关键词"]
Observation 1: 搜索返回的结果...
Thought 2: 根据搜索结果,我还需要计算一下
Action 2: Calculator["表达式"]
Observation 2: 计算结果...
Thought 3: 现在我有足够的信息来回答了
Answer: 最终答案每一步包含 Thought → Action → Observation 三元组。Thought 是 LLM 生成的推理文本,Action 是工具调用指令,Observation 是工具返回的结果。LLM 看到的是 Question + 所有历史 Thought/Action/Observation,在此基础上生成下一步。
ReAct 的实现依赖 LLM 的停止条件(stop token)。Prompt 中约定当 LLM 输出 Answer: 时停止生成,程序检测到停止标记后退出循环并返回答案。如果 LLM 输出 Action:,程序解析工具名和参数,执行后将结果作为 Observation 追加到历史,继续下一轮循环。
ReAct 的局限在于串行执行——每一步只能调用一个工具,且必须等待结果返回后再决定下一步。当多个工具调用相互独立时(如同时查询三个城市的天气),串行执行浪费了等待时间。
并行工具调用
现代 LLM(GPT-4o、Claude 3.5+)支持在单次响应中返回多个 tool_calls,使得并行执行成为可能。循环需要相应调整:
while not done:
response = LLM.decide(history)
if response.has_tool_calls:
results = parallel_execute(response.tool_calls)
history.extend(results)
else:
return response.content关键变化在于 parallel_execute:收集所有 tool_calls,用 asyncio.gather 或线程池并行执行,将所有结果一次性追加到历史,然后进入下一次 LLM 调用。这样原本需要 N 步串行执行的 N 个独立工具调用压缩到了 1 步。
但并行调用引入了依赖关系的问题。如果第二个工具的参数依赖第一个工具的返回值,就不能并行。LLM 需要理解工具之间的依赖关系——实际上模型通常能做到这一点,当参数中包含 {previous_result} 这类引用时,模型会自动将这些调用放在后续步骤中串行执行。
Plan-and-Execute 循环
ReAct 是"边想边做"的模式,适合简单任务,但对于复杂任务容易迷失方向——Agent 可能在步骤 3 就忘记了步骤 1 的规划,或者反复在多个工具之间跳转而无法收敛。Plan-and-Execute 将规划和执行分离:先让 LLM 生成完整的执行计划(Plan),然后逐步执行计划中的每一步。
# 规划阶段
plan = LLM.plan("用户的问题") # 生成步骤列表
# ["1. 搜索公司财报", "2. 提取营收数据", "3. 计算增长率", "4. 生成分析报告"]
# 执行阶段
for step in plan:
result = execute(step)
plan.update(step, result) # 记录执行结果
if need_replan(plan): # 检查是否需要调整计划
plan = LLM.replan(plan) # 基于当前进展重新规划Plan-and-Execute 的优势在于全局视野。规划阶段生成完整的步骤序列,执行阶段按序执行,每一步都知道自己在全局计划中的位置。这避免了 ReAct 的"走一步看一步"导致的效率低下和方向偏差。当执行过程中发现计划不可行(如工具返回错误、前置条件不满足),可以触发重新规划(Re-planning),基于已有的执行结果调整剩余步骤。
代价是额外的 LLM 调用——规划阶段需要一次完整的 LLM 调用生成计划,可能还需要一次额外的调用验证计划的可行性。对于简单任务(1-3 步就能完成),这个开销不值得;对于复杂任务(5 步以上),全局规划带来的效率提升通常远大于开销。
条件路由循环
实际业务中的 Agent 循环很少是纯粹的 ReAct 或 Plan-and-Execute,而是带有条件分支的状态机。LangGraph 的图结构是条件路由的标准实现:每个节点是一个执行步骤,每条边定义了流转条件,边的条件可以是工具返回的结果类型、LLM 的判断、或者外部状态的变化。
用户输入 → 意图分类节点 → [问答] → LLM 直接回答
→ [搜索] → 检索节点 → [有结果] → 生成回答
→ [无结果] → 人工介入
→ [操作] → 工具执行 → 成功/失败处理条件路由的价值在于确定性和可控性。对于企业级 Agent,不希望 LLM 在每次循环中自由决定调用什么工具——业务流程通常是固定的(如"用户咨询 → 查询知识库 → 未找到则转人工"),只是在每个节点内部由 LLM 做语义理解和内容生成。这种"固定骨架、LLM 填充"的模式是当前生产环境 Agent 的主流架构。
循环终止与异常处理
Agent 循环的终止条件设计直接影响用户体验和系统安全。正常终止是 LLM 判断任务完成,输出最终答案。异常终止包括:最大步数超限(防止死循环)、工具连续失败超过阈值(如连续 3 次搜索失败)、LLM 返回格式错误(无法解析 Action)、超时(总执行时间超过限制)。
异常终止时的处理策略很重要。直接返回错误信息对用户体验很差。更好的做法是:将已收集到的部分结果返回给用户,并说明哪些步骤未完成、失败的原因是什么。例如"我已经找到了相关文档,但在生成最终报告时遇到了问题,以下是搜索到的参考材料"。对于需要人工介入的场景,可以将当前循环状态持久化,保存到数据库,稍后由人工审核后恢复执行。
工具执行的幂等性也是循环设计的重要考虑。如果 Agent 在工具执行成功后、将结果追加到历史之前崩溃(网络中断、服务重启),恢复后重新执行同一步会导致重复操作。对于写操作(如发送邮件、创建订单),重复执行可能产生严重后果。解决方案是在执行前记录操作日志(write-ahead),恢复时先检查日志中是否已有该步骤的执行记录。