AI Agent框架化实战:从ReAct到Plan-and-Solve的范式抽象与工程落地
发布时间:2026/10/7 6:32:02 锦皓数字建站

1. 从手搓Prompt到搭积木Agent范式为什么必须框架化如果你最近半年在折腾AI Agent大概率经历过这样一个过程一开始用几十行代码调个大模型API加个while循环塞几个工具函数感觉Agent不过如此等到业务稍微复杂一点——要支持多轮工具调用、要处理失败重试、要记录中间状态、要在不同任务间切换策略——代码就开始失控了。一个agent.py文件从200行膨胀到2000行里面全是if-else判断这一步该用哪个工具上一步失败了要不要重来用户这句话到底该走哪条分支。这就是我最初做Agent开发时踩过的坑。问题的根源不在于模型不够强而在于我们把思考逻辑和执行逻辑揉成了一团。Agent范式的框架化实现本质上就是把这团乱麻拆成可复用、可组合、可替换的标准件——就像前端从jQuery时代的手动DOM操作进化到React的组件化声明式编程一样。所谓范式指的是Agent在解决问题时遵循的一套固定思维模式。目前业界最主流的三种范式是ReActReasoning Acting推理与行动交替、Plan-and-Solve先规划再执行、Reflection反思与自我修正。而框架化实现就是把这三种范式的共性抽象出来形成一套统一的执行引擎让开发者只需要关注我的Agent要做什么而不是我的Agent该怎么循环。这篇文章适合三类人一是刚接触Agent开发、被各种框架名词绕晕的新手二是手写过Agent循环、但代码越写越乱的中级开发者三是想理解主流Agent框架底层设计思想、为技术选型做准备的架构师。我会从范式原理讲到框架设计再落到可运行的代码骨架把框架化这件事拆开揉碎讲清楚。提示本文讨论的框架化是设计思想层面的抽象不绑定任何特定商业框架。理解了这套思想你看LangChain、AutoGPT、Coze这类工具时会一眼看穿它们的本质。2. 三种核心范式的底层逻辑ReAct、Plan-and-Solve、Reflection到底在解决什么在动手搭框架之前必须先把这三种范式的为什么搞清楚。很多人能背出ReAct的定义但说不清它和Plan-and-Solve的本质区别结果就是选型时凭感觉用错了范式还怪模型不行。2.1 ReAct为什么边想边做比想好再做更适合开放任务ReAct的核心思想是推理Reasoning和行动Acting交替进行。每一步Agent先输出一段思考Thought然后决定调用哪个工具Action拿到工具返回结果Observation后再进入下一轮思考。这个循环一直持续到Agent认为任务完成输出最终答案。为什么这个范式有效因为它模拟了人类解决未知问题时的真实过程。你让一个人去查某公司去年营收增长率他不会一上来就规划出完整的十步方案而是先搜公司财报看到数据后再决定是算同比还是环比发现缺数据再补搜。ReAct的价值就在于用环境的实时反馈来修正推理路径避免在信息不足时做出错误的长程规划。ReAct的典型执行循环用伪代码表示就是while not done: thought llm.reason(context, history) action llm.decide_action(thought, available_tools) if action.is_final_answer: return action.content observation execute_tool(action) history.append(thought, action, observation)这里有个容易被忽略的细节history的管理策略直接决定Agent能跑多久。如果把所有历史都塞进context几轮之后token就爆了如果粗暴截断Agent又会失忆。成熟框架通常会做分层记忆——近期完整保留远期做摘要压缩。这个后面讲框架设计时会展开。2.2 Plan-and-Solve什么时候先谋后动反而更高效Plan-and-Solve走的是另一条路先让模型生成一个完整的任务计划Plan然后逐步执行Solve。它把规划和执行拆成两个独立阶段规划阶段不调用任何工具纯粹基于任务描述生成步骤列表执行阶段则按部就班地完成每一步。这个范式适合什么场景步骤相对确定、依赖关系清晰的任务。比如分析这份销售数据并生成报告你可以先规划出读取数据→清洗→计算指标→生成图表→撰写结论五步然后逐步执行。这种情况下ReAct的边想边做反而低效因为每一步该做什么其实在开始时就能确定没必要每步都重新推理。但Plan-and-Solve有个致命弱点计划一旦生成就很难动态调整。如果执行到第三步发现数据格式和预期不符原计划可能整个作废。所以实践中常见的做法是Plan-and-Solve 局部ReAct——大框架用计划驱动每个步骤内部用ReAct处理不确定性。2.3 Reflection让Agent学会回头看的自我修正机制Reflection反思不是独立的执行范式而是一种增强机制。它的核心是让Agent在产出结果后主动审视自己的输出发现问题并修正。典型实现是生成→批判→修订的三段式先让模型生成一个答案再让另一个角色或同一模型换prompt扮演批评者找出问题最后根据批评意见修订。为什么Reflection重要因为大模型有个通病——第一次输出往往不是最优的但它自己不知道哪里不好。通过引入反思环节相当于给Agent加了一个内部质检员。实测下来在代码生成、长文写作这类任务上加一轮Reflection能让质量明显提升。但Reflection不是免费的。每反思一轮就多一次模型调用成本和延迟都翻倍。所以关键问题是反思的触发条件——是每步都反思还是只在关键节点反思是固定反思一轮还是根据置信度动态决定这些策略选择正是框架化要解决的核心问题。范式核心机制适用场景主要代价ReAct推理与行动交替开放探索、工具密集循环次数不可控Plan-and-Solve先规划后执行步骤确定、流程清晰计划僵化难调整Reflection生成后自我批判修订高质量产出要求调用成本翻倍3. 框架化的核心抽象把Agent拆成哪几个标准件理解了范式接下来是框架设计的关键——抽象。一个好的Agent框架应该让开发者用声明式的方式描述我要什么而不是命令式地写怎么循环。我参考了主流框架的设计结合自己的实践把Agent拆成五个核心抽象层。3.1 执行引擎Executor统一驱动所有范式的心脏执行引擎是整个框架的心脏它负责驱动思考-行动-观察的循环。关键设计是把循环逻辑和范式逻辑解耦——引擎只负责跑循环具体每轮做什么由范式策略决定。这样设计的好处是切换范式只需要换一个策略对象引擎代码完全不用动。比如ReAct策略的next_step方法是推理→选工具→执行而Plan-and-Solve策略的next_step是如果还没计划就先规划否则执行下一步。class Executor: def __init__(self, strategy, tools, memory, max_steps20): self.strategy strategy self.tools tools self.memory memory self.max_steps max_steps def run(self, task): state {task: task, history: [], step: 0} while state[step] self.max_steps: action self.strategy.next_step(state, self.tools) if action.type final: return action.content observation self._execute(action) state[history].append((action, observation)) state[step] 1 return self.strategy.fallback(state)注意max_steps这个参数——它是防止Agent陷入死循环的最后一道防线。我见过太多Agent因为工具反复返回错误、模型反复重试同一个动作而烧光token。硬性步数上限配合连续N次相同动作则强制终止的检测能省下不少冤枉钱。3.2 工具注册表Tool Registry让Agent知道有什么能用工具是Agent的手脚。框架化实现里工具不应该硬编码在逻辑里而应该通过注册表统一管理。每个工具需要声明名称、描述、参数schema、执行函数。描述字段特别关键——模型是靠描述来决定用哪个工具的。描述写得含糊模型就会选错工具。我的经验是工具描述要包含什么时候用和什么时候不用比如查询天气当用户询问某地当前或未来天气时使用不用于历史气候数据查询。registry ToolRegistry() registry.register( namesearch_web, description搜索实时网络信息。适用于查询最新新闻、实时数据不适用于查询历史知识。, parameters{query: {type: string, required: True}}, funcsearch_web_impl )3.3 记忆管理Memory短期、长期、工作记忆的分层设计记忆是Agent框架里最容易被低估的部分。简单实现就是一个list但真正好用的记忆系统需要分层短期记忆当前任务的完整对话历史保留全部细节工作记忆从短期记忆中提炼的关键信息如已确认的事实、已达成的结论长期记忆跨任务持久化的知识通常用向量库存储为什么要分层因为context窗口是有限资源。把几百轮对话全塞进去不仅贵还会让模型注意力涣散。我的做法是短期记忆保留最近K轮完整内容更早的做摘要压缩进工作记忆只有明确需要跨会话复用的才写入长期记忆。3.4 状态机State Machine让Agent的执行过程可观测、可干预Agent执行过程中有很多状态思考中、等待工具返回、等待人工确认、已完成、已失败。把这些状态显式建模成状态机好处是整个执行过程变得可观测、可干预。比如在等待人工确认状态下框架可以暂停执行把当前决策推给人工审核确认后再继续。这在涉及敏感操作如发送邮件、修改数据库的Agent里是刚需。没有状态机你根本不知道Agent现在卡在哪一步。3.5 输出解析器Output Parser把模型的自由发挥约束成结构化数据大模型的输出是自然语言但框架需要的是结构化的动作指令。输出解析器的职责就是把模型的自由文本解析成{action, params}这样的结构。这里有个实战技巧优先用模型的function calling能力而不是靠正则解析文本。早期很多框架靠解析Action: xxx这样的文本格式模型稍微不听话就解析失败。现在主流模型都支持结构化输出直接约束成JSON schema稳定性高一个数量级。如果模型不支持退而求其次用输出格式示例重试的策略。4. 从零搭一个可运行的Agent框架骨架理论讲完上代码。这一节我会给出一个精简但完整的框架骨架能跑通ReAct范式并且预留了Plan-and-Solve和Reflection的扩展点。代码用Python写但设计思想是语言无关的。4.1 定义核心数据结构Action、Observation、State先把数据契约定下来。这三个结构贯穿整个框架定义清楚了后面就顺了。from dataclasses import dataclass, field from typing import Any, Optional dataclass class Action: type: str # tool | final | reflect tool_name: Optional[str] None params: dict field(default_factorydict) content: Optional[str] None # final答案或反思内容 dataclass class Observation: success: bool data: Any error: Optional[str] None dataclass class State: task: str history: list field(default_factorylist) step: int 0 scratchpad: str # 模型的推理草稿scratchpad这个字段值得单独说。它是ReAct范式的灵魂——模型把推理过程写在这里下一步决策时再读回来。很多实现把它和history混在一起导致结构混乱。单独拎出来既方便调试你能清楚看到模型每步在想什么也方便做压缩推理过程可以比工具结果更激进地截断。4.2 ReAct策略的具体实现推理、选工具、解析结果策略类负责每一步该做什么。ReAct策略的核心是一个prompt模板加一个解析逻辑。class ReActStrategy: def __init__(self, llm, max_reflect0): self.llm llm self.max_reflect max_reflect def next_step(self, state, tools): prompt self._build_prompt(state, tools) raw self.llm.generate(prompt) action self._parse(raw) return action def _build_prompt(self, state, tools): tool_desc \n.join( f- {t.name}: {t.description} for t in tools.list_all() ) return f你是一个能思考并行动的智能体。 可用工具 {tool_desc} 任务{state.task} 历史 {self._format_history(state.history)} 请以JSON格式输出你的下一步动作 {{thought: 你的推理, action: 工具名或final, params: {{...}}}} _parse方法要做健壮性处理——模型可能返回带markdown代码块的JSON可能字段缺失可能工具名拼错。我的做法是先尝试直接解析失败则剥离代码块标记再解析再失败则触发一次格式修正的模型调用。不要指望模型100%听话解析器的容错能力直接决定框架的稳定性。4.3 工具调用的错误处理失败重试与降级策略工具调用失败是常态不是异常。网络超时、参数错误、返回空结果都得有应对。def _execute(self, action, max_retry2): tool self.tools.get(action.tool_name) if not tool: return Observation(False, None, f工具 {action.tool_name} 不存在) for attempt in range(max_retry 1): try: result tool.func(**action.params) return Observation(True, result) except Exception as e: if attempt max_retry: return Observation(False, None, str(e)) time.sleep(2 ** attempt) # 指数退避指数退避这个细节很实用。工具失败往往是瞬时的限流、网络抖动立刻重试大概率还是失败等2秒、4秒再试成功率高很多。但要注意区分可重试错误和不可重试错误——参数错误重试一百次也没用直接返回失败让模型换个思路更明智。4.4 把Reflection挂载到执行循环反思时机的选择Reflection的挂载点很讲究。我实践下来有两种模式模式一终局反思。Agent产出最终答案后触发一次反思检查答案是否完整、准确不满意则带着批评意见重新执行。这种模式成本可控适合大多数场景。模式二关键节点反思。在每完成一个子任务后反思及时纠偏。适合长流程任务但成本高。def run_with_reflection(self, task): result self.executor.run(task) if self.max_reflect 0: critique self._reflect(task, result) if critique.has_issue: result self.executor.run( f{task}\n\n上次结果的问题{critique.issue}\n请修正。 ) return result关键经验反思的prompt要和生成的prompt有明确区分。如果还用同样的prompt让模型再看看它大概率会说没问题。要明确要求它扮演严格的审稿人列出具体问题点甚至给出如果找不到问题就说明理由的约束逼它认真检查。5. 框架化之后并发、安全与可观测性怎么补框架骨架跑通只是第一步。真正上生产还有三座大山并发、安全、可观测性。这三块如果不在框架层解决每个业务都自己造轮子框架化就白做了。5.1 Agent扛并发的三个瓶颈点与应对AI Agent怎么扛并发是最近被问得最多的问题之一。我的经验是Agent的并发瓶颈和普通Web服务完全不同主要卡在三个地方瓶颈一模型API的速率限制。这是最硬的约束。解决方案是请求队列令牌桶限流在框架层统一管理模型调用而不是让每个Agent实例各自为战。同时做好优先级调度——交互式请求优先于后台批处理。瓶颈二工具调用的外部依赖。搜索、数据库、第三方API这些调用的延迟和稳定性都不受你控制。框架层应该提供工具级熔断和超时某个工具连续失败就暂时摘除避免拖垮整个Agent。瓶颈三长任务的资源占用。一个Agent跑十分钟期间一直占着内存和连接。解决方案是状态外置——把Agent的State序列化存到Redis或数据库执行线程可以释放需要时再恢复。这就是所谓的可中断、可恢复执行。瓶颈表现框架层应对模型限流429错误频发统一队列令牌桶优先级工具不稳单点拖垮全局熔断超时降级长任务占用内存连接耗尽状态外置可恢复执行5.2 Agent安全工具权限、输入校验与最小必要原则Agent安全是个容易被忽视但极其重要的话题。一个能调用工具的Agent本质上是一个有副作用的程序它可能删数据、发邮件、调支付接口。安全设计必须贯穿框架。核心原则是最小必要权限。每个工具在注册时就声明它需要什么权限框架在执行前校验当前Agent是否有该权限。比如读取数据库和写入数据库应该是两个独立工具默认只授予读权限。另一个关键是输入校验。模型生成的工具参数不能直接信任——它可能生成SQL注入、路径穿越这类恶意输入。框架层要对参数做类型校验和危险字符过滤。我见过一个案例Agent被诱导生成了删除整表的SQL幸好框架层有写操作二次确认才没出事。注意涉及资金、数据删除、对外发送这类高风险操作务必在框架层强制人工确认环节不要指望模型自己判断风险。5.3 可观测性让Agent的每一步都看得见Agent最让人头疼的就是黑盒感——它跑完了你不知道它为什么这么决策。可观测性建设包括三个层次日志层每一步的thought、action、observation全量记录带时间戳和trace_id。这是排查问题的基础。指标层步数分布、工具调用成功率、平均延迟、token消耗。这些指标能帮你发现哪个工具最常失败哪类任务最容易超步数。追踪层把一次Agent执行做成一个完整的trace每个span对应一步。这样你能直观看到时间花在哪、哪步重试了。OpenTelemetry这类标准可以直接用。我的经验是可观测性要在框架第一版就做不要等出问题再补。因为Agent的行为太不确定了没有观测数据你连它是不是变差了都判断不了。6. 踩坑实录框架化过程中最容易翻车的几个地方讲完设计说点掏心窝的踩坑经验。这些是文档里不会写、但实际开发中一定会遇到的。6.1 过度抽象为什么第一版框架往往设计过度我第一版框架设计了七层抽象结果写业务时发现为了加一个简单工具要改五个文件。这就是典型的过度抽象。教训是框架的抽象层数应该由实际需求驱动而不是由看起来优雅驱动。我的建议是第一版只做三个抽象——Executor、Tool、Memory其他等真正遇到重复代码时再抽。YAGNI原则You Arent Gonna Need It在框架设计里尤其适用。6.2 Prompt与代码的边界哪些逻辑该交给模型哪些必须硬编码这是框架设计里最纠结的问题。我的判断标准是涉及确定性规则的硬编码涉及语义理解的交给模型。比如最多调用10次工具是确定性规则硬编码在引擎里这个任务该用搜索还是计算是语义判断交给模型。把确定性规则交给模型它可能不遵守把语义判断硬编码框架就失去了灵活性。这条边界划清楚了框架会稳定很多。6.3 状态污染多轮对话中Agent记串了的根因多轮对话里Agent经常记串——把上一轮的信息带到这一轮或者把不同用户的信息混在一起。根因是状态没有做好隔离。解决方案是给每个会话一个独立的State实例长期记忆按用户ID分区。框架层要强制这个隔离而不是靠业务代码自觉。我见过因为状态没隔离导致A用户看到B用户数据的严重事故这个坑一定要在框架层堵死。6.4 测试Agent有多难如何给不确定的行为写测试给Agent写测试是公认的难题因为它的输出不确定。我的做法是分层测试单元测试测工具函数、解析器、状态机这些是确定性的契约测试测给定输入Agent是否调用了正确的工具不校验具体输出回归测试维护一批典型任务每次改动后跑一遍人工评估结果是否退化关键是不要试图断言Agent的最终输出文本那会让你陷入无尽的维护地狱。测行为模式不测具体内容。7. 范式组合与选型什么时候该用哪个怎么混搭最后聊聊选型。很多人问ReAct和Plan-and-Solve到底选哪个我的答案是大多数真实任务需要组合使用。7.1 任务复杂度与范式选择的对应关系我的经验对应关系是这样的简单问答、单工具调用不需要复杂范式直接function calling中等复杂度、探索性任务ReAct边做边调整流程明确、步骤较多Plan-and-Solve先规划提效高质量产出要求在以上基础上叠加Reflection判断标准是任务的不确定性。不确定性高不知道要用几次工具、不知道会遇到什么情况用ReAct不确定性低步骤基本可预知用Plan-and-Solve。7.2 一个混合范式的实战案例先规划、再ReAct、后反思举个我实际做过的例子——调研某技术方案并输出对比报告。这个任务我用了三段式第一阶段Plan让模型先规划出确定对比维度→逐个调研→整理数据→撰写报告的大框架。第二阶段ReAct在逐个调研这个子任务里用ReAct因为每个技术点需要查什么资料是不确定的得边查边调整。第三阶段Reflection报告初稿出来后让模型扮演挑剔的技术评审找问题再修订。这套组合拳下来效果比单一范式好很多。框架化的价值在这里体现得淋漓尽致——因为范式被抽象成了可替换的策略组合它们就像搭积木一样自然。7.3 框架选型的现实考量自研还是用现成的最后一个现实问题到底该自研框架还是用现成的我的建议是学习阶段自己手写一遍理解原理这个投入绝对值得快速验证用成熟框架别重复造轮子生产环境根据团队情况通常是在成熟框架上做二次封装而不是完全自研自研框架最大的坑是维护成本。模型能力在快速迭代今天的最优实践明天可能就过时了。除非你的业务有非常特殊的需求否则站在成熟框架的肩膀上把精力花在业务逻辑上是更明智的选择。我在实际项目里的体会是框架化最大的收益不是代码复用而是思维方式的统一。当团队所有人都用同一套抽象语言讨论这个Agent该用什么策略记忆该怎么管沟通成本会大幅下降。这种一致性带来的效率提升比省下的那点代码量值钱得多。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。