简介:面向AI智能体设计与开发人员,这份源码包围绕“Agent运行模式对比”主题,对ReAct(推理-行动)与Plan-and-Execute(规划-执行)两种主流范式展开拆解。资源提供可运行的HTML演示页面与配套源码,直观呈现“边推理边行动”的即时调整逻辑,以及“先规划后执行”的全局视野与清晰步骤,并结合动态变化任务、目标明确任务等典型场景,给出模式选型建议和两者结合使用的实践思路。压缩包内共3个文件,包括HTML演示页、inscode工程配置文件和.gitignore,整体仅10KB,结构精简。当前已有53人学习,适合正在学习智能体设计或准备在项目中落地Agent决策流程的开发者,可作为快速理解两种运行模式差异并动手运行的轻量参考。 Agent 开发这几年被聊得特别多,但真正落地的时候,我发现很多人卡在同一个问题上:同样的模型底座,换一种运行模式,效果能差出一大截。有的模式能让 Agent 老老实实按步骤干活,有的模式让模型反复调工具、绕圈子,最后把上下文撑爆。这篇内容我就围绕“Agent 运行模式对比”这个话题,从源码的角度拆一遍主流模式的底层逻辑、适用场景和我在实际项目中踩过的坑。核心关键词很简单:Agent、运行模式、源码。适合正在做 Agent 开发、或者准备从 Demo 走向生产环境的同学参考。
1. 为什么运行模式是 Agent 项目的分水岭
1.1 3个维度看清运行模式本质区别
判断一个 Agent 项目能不能稳定落地,运行模式的选择比模型选型更关键。模型决定智商上限,运行模式决定它能不能把智商用出来。我从三个维度来拆解它们的区别:推理控制流、工具调用密度、状态管理复杂度。
推理控制流指的是 Agent 在每轮里是“先想再做”还是“边想边做”。ReAct 这种模式把推理和行动绑定在一个循环里,模型每输出一句思考,就跟一个工具调用绑定在一起。Plan-and-Execute 则把规划从执行里拆出来,先让模型一次性列出完整计划,再逐步执行。这种控制流的差异直接决定了系统的稳定性和延迟。
工具调用密度描述的是单位任务里模型与外部工具的交互次数。工具调用密度越高,出错概率越大,上下文消耗也越快。比如 ReAct 模式处理一个“查天气并定闹钟”的任务,可能只需要调两次工具,但处理“分析竞品并生成报告”这类任务,可能需要八个以上的工具调用,中间还可能反复检索和汇总。
状态管理复杂度是我在源码阅读里最关注的维度。ReAct 需要把每一轮思考和工具结果都留在上下文里,上下文越长,状态越混乱。Plan-and-Execute 相对好一些,因为规划和执行分开,执行阶段的中间状态可以被裁剪掉。多智能体协作模式就更麻烦了,每个子 Agent 都有自己的记忆,状态管理几乎是一个分布式系统的复杂度。
这三个维度是选型的基本盘。你在心里画一个三角,然后把你自己的业务需求往里丢,基本就能排除掉一半的选项。
1.2 一张表把主流模式对应到适用场景
我把目前主流的 Agent 运行模式整理成一张选型表,方便你按图索骥。
| 运行模式 | 核心特征 | 典型场景 | 落地难度 |
|---|---|---|---|
| ReAct | 推理与行动交替循环 | 客服问答、个人助手、搜索摘要 | 低 |
| Plan-and-Execute | 先规划后执行,计划可调整 | 数据分析、报告生成、多步骤任务 | 中 |
| Reflection | 生成后自我批判再修正 | 代码生成、写作润色、方案优化 | 中 |
| 多智能体协作 | 多个角色分工协作 | 复杂系统模拟、软件开发、研究助理 | 高 |
这张表不是绝对的,比如 ReAct 经过改造后也能做很复杂的任务,但你会被迫处理上下文爆炸的问题,还得自己设计记忆清理机制,成本很高。我见过有的项目非要用 ReAct 硬扛复杂任务,结果工具调用次数飙到二十几次,模型开始复读之前的错误结论,最后只能回滚到 Plan-and-Execute。
选型没有绝对的对错,只有匹配度的问题。理解每个模式的底层设计意图,比记住什么“ReAct 最强”这种结论有用得多。
2. 源码视角:主流的4种 Agent 运行模式怎么跑起来的
2.1 ReAct:边想边做的循环
ReAct 是我最早接触的模式,也是最容易理解的。它的核心逻辑就是一个 while 循环:模型思考,然后调用工具,把工具结果返回给模型继续思考,直到模型判断任务完成。
用伪代码表达大概是这样:
while not task_finished: thought = llm.generate(prompt + tool_results_history) if thought.has_tool_call: result = execute_tool(thought.tool_call) tool_results_history.append(result) else: task_finished = True这段代码看着简单,但里面有三个细节决定了它的稳定性。观察条件 task_finished 的判断时机得放在 thought 生成之后,模型必须明确输出“完成任务”的标记才能跳出循环,否则它会被工具结果带偏,一直调工具停不下来。第二个细节是 tool_results_history 的长度控制,我建议用滑动窗口保留最近 N 轮,而不是无限追加,不然上下文迟早会爆。第三个细节是工具调用语句的结构,必须严格用 JSON 或固定格式输出,方便后续解析。
还有一个我没在代码里体现但实际很重要的点:给模型提供“终止选项”。很多初版 Agent 没有设计终止信号,模型会陷入死循环。我通常会在 system prompt 里加一行“如果你判断任务已经完成,请输出 FINAL_ANSWER”,然后在解析逻辑里优先检查这个标记。
ReAct 的优点是实现成本极低,逻辑直观,适合快速验证想法。缺点是每一步都需要模型参与,延迟高且 token 消耗大。你可以把它理解成一个很勤快但没什么记忆规划的实习生,你给它一个任务,它干一步来问一步,每一步都要你确认。
2.2 Plan-and-Execute:先计划再执行
Plan-and-Execute 把“思考”和“行动”拆成两个独立的阶段。规划器先拆解出任务清单,执行器逐项完成。我在生产环境里最常用的是这种模式,因为它的可预测性远高于 ReAct。
架构上通常分成两层,第一层规划,第二层执行。大致的结构我拆成伪代码:
planner_prompt = "你是一个任务规划器,请基于用户目标拆解出可执行步骤清单,每一步必须匹配一个可用工具。" plan = llm.generate(planner_prompt + user_goal) steps = parse_plan(plan) for step in steps: result = execute_step(step) if result.need_replan: new_plan = llm.generate(planner_prompt + remaining_goal + result) steps = parse_plan(new_plan)关键点在于 replan 逻辑。如果中间某一步执行失败,且失败的原因改变了任务的后续依赖关系,就必须停下来重新规划,而不是继续执行旧计划。我踩过的坑是过早把整个计划拆得太细,结果计划里的步骤 3 依赖步骤 2 的结论,步骤 2 执行失败了,步骤 3 还在硬跑。
我给执行器加了依赖检查机制,每一步在执行前检查它依赖的上一步是否成功。失败的话就直接进入 replan 流程。这个改动看起来小,但能把失败率降低三到四成。
这个模式的优点在于上下文消耗可控,因为规划阶段的输出可以压缩后重新注入;执行阶段每步的输入输出都比较干净,不会把所有历史都堆积在窗口里。缺点是规划器的质量决定整体上限,如果第一步计划就有问题,后面执行全部白费。它更适合流程相对固定、步骤之间有清晰边界的任务。
2.3 Reflection:生成后自我审视
Reflection 模式的本质是给 Agent 加一层“自我批判”的过程。它不会只生成一次就交付结果,而是生成后让模型以裁判视角重新审视自己的输出,发现问题再修改。它本身不是独立于 ReAct 的替代者,更像一个叠加在生成模块上的增强策略。
从源码看,核心结构是生成器和评判器的相互调用:
first_output = generator.generate(prompt) for round in range(max_rounds): feedback = critic.review((first_output, prompt)) if feedback.is_acceptable: break first_output = generator.revise(first_output, feedback)这里有个容易被忽略的问题:critic.review 和 generator.revise 用的是同一个模型。同一个模型的自评可信度并不高,它容易认可自己生成的内容。我在实际项目中会让评判器强制使用更严格的评分标准,或者在 prompt 里注入“你是一个严格的评审专家,不允许轻易给出通过结论”这类约束,实测下来比让模型自由发挥自评有效。
Reflection 模式适合用在生成后质量要求高的场景,比如代码补全、论文摘要、营销文案精修。它的代价是推理次数翻倍,延迟和成本都上来了。我不建议对实时性要求高的场景滥用,比如聊天机器人,用户等不了你做三轮自我批判再回复。
2.4 多智能体协作:多个角色分工
多智能体协作模式是这些模式里最复杂的一种。它并不仅仅是一个控制循环,而是一整套角色分配与会话协议。不同 Agent 承担不同职责,比如一个负责检索资料,一个负责数据分析,还有一个负责汇总输出,它们之间通过消息队列或函数调用进行通信。
我在源码里比较关注的是调度器的设计。调度器负责决定哪个 Agent 在什么时机被激活,以及如何把消息路由给正确的接收者。
class Scheduler: def __init__(self): self.agents = {} self.message_queue = deque() def register(self, agent_id, agent_instance): self.agents[agent_id] = agent_instance def route(self, message): target = message.receiver_id self.agents[target].handle(message)架构图上看起来很美好,落地时你会发现最坑的是上下文隔离问题。每个子 Agent 如果共享一份全量上下文,很快内存和 token 就会爆炸。如果不共享,子 Agent 之间互相传话时又容易丢失信息。我最终采用的方案是隔离+摘要:每个子 Agent 只保留自己的任务上下文,在需要把信息传给下游时,用一小段摘要替代完整上下文。
多智能体适合解决需要多视角或多种专业能力组合的任务,但它的维护成本和失败排查难度是几何级上升的。我建议团队没有足够工程储备前,不要轻易上多智能体架构。先把单 Agent 做好,再考虑拆角色。
3. 核心参数与上下文管理:源码里真正影响效果的细节
3.1 max_iterations:循环终止的第一道闸
所有循环式 Agent 都必须设置 max_iterations,这是防止失控的底线。没有这个参数,模型在 ReAct 模式里可能无限调工具,最后把 token 烧光。我在不同项目里的设置经验是:简单问答任务给 5 次,普通工具调用任务给 8 次,复杂数据分析任务给 12 次,超过 15 次基本说明任务设计有问题或规划器能力不够。
实现的时候要区分两种超限:一种是总循环次数超限,一种是连续失败的轮次超限。后者比前者更有参考价值。比如一个 Agent 连续三次在同一工具上失败,这时候再重试没有意义,应该直接中断并返回到可恢复的状态。
if consecutive_failures >= 3: return partial_result, "consecutive_failure_limit_reached"这个阈值要叠加到回调里,而不是在循环尾部判断。我之前因为只判断总循环次数,结果某个工具连续失败了十几次,每次都换一种参数又试,白白浪费了时间和 token。后来给连续失败单独加计数器,问题立刻缓解。
3.2 工具调用的超时与错误处理
工具调用是 Agent 和外部世界交互的窗口,也是最容易出故障的地方。网络抖动、接口限流、响应格式变化,都会导致调用失败。源码层面需要给每个工具设置独立的超时时间,而不是用全局默认值。比如查询数据库的工具 5 秒超时,调用大模型接口的工具 30 秒超时,检索网页的工具 10 秒超时。
超时后的处理策略要分级。第一次失败,可以原参数重试一次;第二次失败,换简化参数再试;第三次失败,就必须把错误信息返回给模型,让模型自己判断是否需要换一种方案。我发现很多团队的 Agent 在工具失败后让模型“硬答”或“强行继续”,这是不对的。应该把错误归类并传给模型:超时属于可重试错误,限流属于等待错误,参数错误属于永久错误,永久错误不应该重试。这个区分逻辑在框架里用 RetryPolicy 实现最稳。
错误处理的终极目标不是“永不失败”,而是“失败后可恢复”。我推荐在代码里把工具结果统一包装成包含 status、data、error_code 的结构,这样模型看到的结构化错误信息更清晰,判断也更准确。
3.3 上下文窗口与记忆裁剪:最容易忽略的坑
上下文管理是 Agent 工程里最容易被低估的问题。模型能力再强,上下文塞满了乱七八糟的历史信息后,输出质量会直线下降。我在源码里看到过很多项目直接用列表保存所有消息,不管长度直接发送到模型接口,这就是典型的坑。
合理的做法是三层裁剪策略。第一层,系统指令固定压缩,包含角色设定和全局规则,永不裁剪。第二层,工具调用历史滑动窗口,只保留最近 5 到 8 轮的工具输入输出,超出部分用摘要替代。第三层,用户原始诉求永远保留,不能因为裁剪把任务目标给弄丢了。
摘要的实现方式不要过度设计。我用的方法是当轮次超过阈值时,调用模型对前面的历史生成一个 200 字以内的摘要,替换掉旧轮次。这个操作本身就是一次模型调用,所以不要每轮都做,只在达到裁剪阈值时触发。裁剪后还要检查一下摘要里是否保留了关键约束,比如用户特别强调“不要用某类数据源”,这种信息一旦被摘要吞掉,后续整个任务都会跑偏。
我在生产环境里还习惯把上下文压缩策略独立成一个模块,和 Agent 主循环解耦。这样方便单独测试压缩效果,也能在出问题时快速定位是压缩导致的信息丢失,还是生成模块本身的问题。
4. 实操对比:同一任务在不同模式下的表现
4.1 任务:整理市场调研报告
为了验证模式差异,我设计了一个相对典型的多步骤任务:搜集三款竞品的基本信息,整理成对比表,再基于对比结论输出建议。这个任务包含检索、结构化整理和生成结论三个子问题,适合用来对比 ReAct 和 Plan-and-Execute 的差别。
我用同一个模型底座,分别用 ReAct 和 Plan-and-Execute 各跑一轮,记录工具调用次数、耗时、上下文消耗量、最终报告可用性。这个过程里我没有做额外调优,统一使用默认参数来保留对照的公平性。
结果显示 ReAct 模式先是搜索竞品 A,再搜索竞品 B,搜完 C 后发现 A 的信息不够详细,又回头补充,中间还因为网页内容格式问题多调了一次解析工具。最终工具调用 9 次,完整耗时比 Plan-and-Execute 长了不少。Plan-and-Execute 先一次性列出“搜索竞品 A、搜索竞品 B、搜索竞品 C、整理对比表、输出建议”这五步,按顺序执行,中途没有返工,工具调用只有 5 次。
这个对比不是说 Plan-and-Execute 一定更好。任务里竞品信息相对独立,没有复杂的依赖关系,所以 ReAct 的“动态调整”优势没能发挥。如果在过程中发现有竞品 C 其实和竞品 A 是同一家公司,需要临时改变分析维度,ReAct 的灵活优势就会体现出来。
4.2 三个模式的产出质量对比
除了 ReAct 和 Plan-and-Execute,我还用 Reflection 模式进行了测试。基于同一个输出结果追加一轮自我批判修正,看生成报告的质量是否有提升。
粗看 Reflection 修正后的报告确实更有条理,但细看内容后我发现,它更像是在原有内容上做措辞优化,并没有增加新的洞察。因为评判器本身的推理深度有限,它很难发现自己生成内容背后的逻辑错误。只有当任务本身有客观标准可以检验时,比如代码能跑,报告里面的数据与来源一致,Reflection 才能发挥真正价值。
我还会特别留意所有模式的输出一致性。同一套输入连续跑三次,ReAct 因为它的自由度过高,三次结果差异较大;Plan-and-Execute 的差异主要体现在结果呈现的语言风格上,核心数据点基本一致。对于生产环境,稳定一致往往比单次惊艳更重要。
4.3 我踩过的坑和排查技巧
实验过程中我踩了几个比较经典的坑,整理出来供你参考。
第一个是工具结果太长导致上下文截断。某个搜索工具的返回结果包含了大段正文,直接放进上下文导致窗口被大量占用,后续轮次的信息被挤掉。我的解决方法是自定义一个 截断器,工具返回值超过一定字符数就只保留摘要和关键字段,原始内容单独存储,按需检索。
第二个坑是模型在 Plan-and-Execute 模式下过度规划。规划器把“整理对比表”拆成了“生成表格框架”、“填入竞品 A 数据”、“填入竞品 B 数据”、“填入竞品 C 数据”四步。这种细粒度没有提升执行质量,还增加了失败点。后来我直接在规划 prompt 里约束步骤数量上限,并强调“按职能合并步骤,不要为单一动作建模”。
第三个坑是连续失败检测和重试策略打架。如果不提前声明策略,模型会自己在错误后臆想一个解决办法继续执行,而不是把错误上报。我在工具执行的外层设置了异常处理,任何工具抛出的错误都会被捕获并映射成结构化信息返回,不经过模型“自由发挥”。
排查时先看日志,重点看循环里的控制信号,比如 max_iterations 超限和连续失败计数,再去看工具调用记录,分析是哪一步结果异常导致模型误判。如果模型开始复读相同的失败输出,基本可以确定是没有触发强制终止机制。
5. 选型建议和实操经验
说了这么多,最后还是落到选型建议上。Agent 运行模式不是越复杂越好,而是越匹配业务越好。根据我个人经验,可以从任务复杂度、上下文敏感度、成本容忍度三个维度做一个简单打分。任务复杂且步骤依赖强,优先 Plan-and-Execute;任务要求高质量输出且可客观校验,叠加 Reflection;任务需要快速迭代探索多种可能性,选 ReAct;任务涉及多角色多维度协同,才考虑多智能体。
我在实际项目里经常做组合,比如 Plan-and-Execute 为主干,在单步执行里嵌入 ReAct 的灵活性,在最终交付前叠一个 Reflection 校验。这种“主模式 + 子策略”的组合往往比单一模式更能平衡稳定性和效果。源码上也不难实现,只需要把执行步骤抽象成接口,每种模式实现同一个接口就行。
最后分享一个细节:无论选哪种模式,都给我的 Agent 加一个中间态持久化机制。也就是每完成一个步骤,就把状态保存到外部存储,而不是只存在内存里。这样一旦后续步骤失败,还能从最近的成功状态恢复运行,不用整个任务重新跑一遍。这个机制帮我避免了很多让人崩溃的“从头再来”问题。
Agent 模式选型其实没有标准答案,但理解了源码背后的控制流设计,你就会发现每个模式都有自己的脾气和适用边界。希望这篇对比能让你在搭 Agent 的时候少走一些弯路。
本文还有配套的精品资源,点击获取