这两年AI Agent架构的文章我看了不下几十篇,名字一个比一个唬人:ReAct、Plan-and-Execute、Reflexion、多Agent系统、图式编排、龙虾架构……但真能把“从A到B是怎么一步步走出来”讲明白的,确实不多。尤其对小程序员来说,最痛苦的就是概念堆了一堆,回头自己写代码还是不知道从哪下手。这篇我打算换个思路,不按教科书来,而是按我实际开发里摸爬滚打的经验,把最经典的ReAct到所谓“龙虾架构”的整条演进路线完整捋一遍。你会看到每个架构到底为了解决什么问题而生、代码长什么样、坑在哪里,以及什么场景下应该选哪种。不管你是刚开始接触Agent开发,还是已经在单Agent里泡了几个月想往多Agent方向走,这篇应该都能帮你把散落的知识点串成一条线。1. 从单次对话到自主循环:Agent架构到底在解决什么问题很多初学者会把Agent和大模型混为一谈,觉得“能聊天的就是Agent”。实际上,大模型本身只是一台“概率推理引擎”,你问一句它答一句,它不会主动去执行查询、不会去查数据库、也不会因为上一步操作失败而调整策略。Agent架构解决的核心问题,就是把这些被动的单次能力组合成一个主动的、可循环的、能完成多步骤任务的系统。1.1 大模型本身的三个短板我拿一个最常见的需求来说:让AI帮我把客服工单分类,然后根据分类去查知识库,最后给出处理建议。如果只靠单次提示词,你会发现三个问题。第一,大模型回答一次之后就没有“后续”了。它说“我建议你去查退款政策”,但不会真的去查。第二,大模型没有工具执行能力,它不能调用工单系统API、不能读数据库、不能发邮件。第三,也是最隐蔽的:大模型容易在长任务中“忘掉”之前的中间结果。比如它分析了工单内容,转头生成建议时,可能就把分析结论给丢了。Agent架构就是在外面套一层“循环控制逻辑”:让模型思考下一步做什么、调用什么工具、拿到结果后再继续思考,直到任务结束。这套机制不复杂,但它就是从“能用”到“好用”的分水岭。1.2 Agent循环:观察-思考-行动的闭环用生活化的类比来解释:人类处理问题也不是一次到位的。你收到一个售后工单,你会先读一遍内容(观察),判断是什么问题(思考),然后去系统里查订单(行动),看到查询结果后再决定要不要退款(再次思考)。Agent循环的本质就是把这套流程算法化。在这个循环里,每个Agent的核心组件只有三块:大脑(大模型)、工具集(API/函数)、记忆(上下文或外部存储)。大脑负责决策,工具负责执行,记忆负责把“当前进展”传递给下一轮循环。1.3 为什么架构设计值得单独讨论你可能会说,那不就是写个while循环吗?是,也不是。单Agent的while循环很简单,但一旦任务变复杂,比如需要多个专业角色协作、需要并行处理、需要中途人工审批,单循环就撑不住了。这时候你才会真正理解,为什么社区里会演化出各种编排架构。2. ReAct:最经典的单Agent推理执行范式ReAct这个名字,是Reasoning和Acting的组合,核心思想就一句话:让大模型在每一步里同时产生推理轨迹和行动指令,而不是像传统做法那样“先想完再做”或者“不想直接做”。2.1 ReAct的原理拆解我经常用“开车导航”来类比ReAct。你开车去一个陌生地方,不是启动前就把整条路线背下来然后闭眼开,而是开一段、看路牌、判断、再开一段。ReAct也是这个逻辑:模型生成Thought(当前判断),生成Action(调用哪个工具、传什么参数),然后等待Observation(工具返回结果),把结果拼进下一次推理。这跟让模型一次性输出完整计划的区别在于:如果计划中途出错了,ReAct可以根据Observation实时修正。比如模型本来想查订单表,结果工具返回“没有订单号”,它可以接着想“那先查客户ID”,而不是死板地按原计划走。2.2 ReAct的标准循环:Thought-Action-Observation我直接给你看一个最简伪代码,这个是我自己项目里抽出来的简化版,适合入门理解:def run_react_agent(query, tools, llm, max_steps=8): messages = [{"role": "user", "content": query}] for step in range(max_steps): # 让模型思考并决定下一步动作 response = llm.invoke(messages) print(f"[Step {step}] {response}") action = parse_action(response) if action["type"] == "finish": return action["answer"] if action["type"] == "tool": observation = call_tool(action["tool_name"], action["tool_args"]) messages.append({"role": "assistant", "content": response}) messages.append({"role": "tool", "content": f"Observation: {observation}"}) return "Reached max steps, stopping."这段代码里最关键的是最后两行:把模型的决策和工具返回结果都追加到messages里。这样模型在下一步推理时,才能看到“我之前做了什么、结果是什么”,然后决定接下来是调整策略还是继续执行。2.3 ReAct的实际运行效果我拿模拟项目X里的一个简单场景演示过。输入的是工单文本:“用户说收到货是坏的,要求换货,但订单号没填。”ReAct的输出轨迹大概是这样的:Thought:需要先查询用户信息,但工单里没有订单号,只有手机号,可以用手机号查订单。Action:调用query_order(phone="138xxxx")Observation:查到两个订单,最近一笔签收时间为3天前,符合换货周期。Thought:订单确认了,接下来需要查询退换货政策。Action:call_knowledge_base("换货政策")Observation:支持7天无理由换货。Thought:可以同意换货,生成答复。Action:finish,输出答复内容。你看,整个过程模型没有一次把计划全部想好,而是走一步看一步。这就是ReAct能成为单Agent基座的原因:流程简单,行为可控,适合各种工具调用场景。2.4 ReAct的变体与演进ReAct出来之后,社区又冒出几个方向。一个是Plan-and-Execute,就是先把计划列好,再逐步执行,步骤之间不依赖于推理轨迹,好处是省token,坏处是中途变化就废了。另一个是Reflexion,就是任务失败后,让模型总结失败原因,把经验写进记忆,下一次重试时避开同样的坑。我自己实际使用下来,Reflexion对调试特别有用,但会把系统复杂程度拉高一个量级。2.5 单ReAct的四个天花板说句公道话,ReAct虽好,但对稍微复杂的业务就力不从心了。我总结了四个必须正视的天花板。第一是上下文膨胀。每轮循环都要把之前的Thought、Action、Observation全部塞进Prompt,任务步骤一多,上下文就爆了。第二是角色冲突。你让同一个模型既当客服又当质检员,它的判断标准会很混乱,经常自己放水。第三是串行瓶颈。多个独立子任务只能一步一步来,效率低。第四是失败恢复脆弱。某一步工具调用报错,如果模型没有及时察觉,整个任务就从那里开始跑偏。这四个天花板,就是多Agent架构出现的根本原因。3. 为什么必然走向多Agent协同:编排范式演进单Agent撑不住复杂业务后,社区自然的反应就是:一个Agent干不了,那就拆分角色,让多个Agent各司其职。但“多个Agent”只是第一步,真正难的,是怎么编排它们。3.1 多Agent和单Agent的本质区别很多人以为多Agent就是“在循环里多调用几次模型”,这就理解偏了。多Agent的重点不在模型数量多,而在于把任务拆成可独立验证的模块,每个模块有明确输入输出,再用一套“调度规则”把它们衔接起来。还是拿后厨打比方。单Agent就像一个小饭馆,一个厨师从洗菜到炒菜全包。多Agent就像大饭店后厨,切菜的只管切菜,炒锅的只管炒菜,传菜的只管传菜。每个人不需要知道完整菜单,只需要干好自己的活,然后按流程把菜传下去。图式编排架构,就是饭店里那套“主厨调度系统”:谁在什么时间做哪道菜、谁要等谁的结果、哪个环节出错了怎么重做。3.2 第一阶段:顺序管道编排最早的编排最简单,就是流水线。Agent A做完,输出传给Agent B,B做完传给C。适合业务流程非常固定的场景,比如:意图识别Agent → 信息提取Agent → 回答生成Agent。这个模式好处是简单直观,坏处是没有任何灵活性,中间环节无法跳转,也无法根据中间结果改变后续路径。3.3 第二阶段:路由与计划编排为了弥补“太死板”的问题,社区引入了路由器角色。由一个规划Agent分析任务,决定下一步交给哪个子Agent处理。典型代表就是Plan-and-Execute模式:先有一个Planner生成任务清单,然后Executor逐个执行,每步执行完,Planner再看结果决定是要重新规划还是继续执行。这个模式比顺序管道灵活,但有一个隐患:规划Agent如果判断错了路由方向,整个任务就走歪了。而且每个Agent之间没有共享的“全局状态”,信息传递全靠参数,层级深了之后参数管理会非常痛苦。3.4 第三阶段:图式编排与状态机再往后发展,就是我们现在说的图式编排。思路也很直接:把整个任务流程画成一张有向图,每个节点是一个Agent或一个工具调用,节点之间的边代表流转条件,全局共享一份状态数据。Agent之间不直接通信,而是往上读写状态。这种模式最大的优点有两个:一是可控性强,任意节点可以设置条件边,满足条件才走某条路;二是可恢复性强,某个节点失败后,可以回退到指定状态节点重新执行。图式编排也是“龙虾架构”的底层骨架。4. 龙虾架构:多智能体图式协作的工程化形态“龙虾架构”这个名字,我第一次听到也觉得挺有意思。龙虾的身体结构其实很适合用来理解这类图式编排系统:头部是决策中枢,躯干是核心处理流程,步足是并行执行的工具Agent,触角是外部感知和检索模块。各个部分形状不同、职责不同,靠关节和肌肉连接成一个整体,协调行动。4.1 为什么叫龙虾,不只是讨巧社区里用“龙虾”来称呼这类架构,本质上是在强调两个特征:分节与可伸缩。分节是说每个Agent像龙虾的体节一样,结构独立、边界清晰,可以单独演化替换;可伸缩是说整个系统可以像龙虾蜕壳一样,在运行时动态增减Agent节点,而不会破坏整体联动。当然,这不是一个严格的学术概念,更像一种工程直觉的称呼。在具体落地时,它对应的就是“多Agent图式编排”这个方向。你去看各种主流的Agent编排框架,底层抽象几乎都是同一套:状态图、节点、边、条件路由、共享记忆。4.2 核心设计元素拆解龙虾架构的核心设计元素,我归纳成五个模块。第一个是状态图(StateGraph),整个任务的流转地图,决定了Agent执行的顺序和分支。第二个是节点(Node),可以是Agent节点、工具节点、或者普通函数节点。第三个是边(Edge),分为普通边和条件边,普通边就是“执行完A一定执行B”,条件边就是“根据状态里的某个字段决定下一步走哪个节点”。第四个是全局状态(State),一个所有节点都能读写的数据结构,比如工单号、意图、处理结果都放里面。第五个是共享记忆(Shared Memory),比全局状态更偏长期存储,比如向量数据库、KV存储,用来让Agent记住跨session的信息。这五个元素组合起来,就形成了一个极其灵活的编排系统。我画不了图,但你想象一个流程:入口节点 → 意图识别Agent → 条件分支 → 信息收集Agent或知识库检索工具节点 → 汇聚节点 → 执行Agent → 质检Agent → 出口节点。这就是一个典型的龙虾架构雏形。4.3 与普通多Agent编排的关键差异我在第3章讲过路由编排,那你可能会问:图式编排和它到底有什么区别?区别体现在三个方面。首先,显式状态机。路由编排中流程逻辑散落在各个Agent的Prompt里,不可审计;图式编排把流程显式定义为状态图,逻辑在代码里,不在Prompt里。其次,共享状态。图式编排中每个Agent通过State共享数据,不再需要来回传参数,减少串联风险和上下文冗余。第三,可恢复性。图里可以定义回退边,比如质检不通过,直接从质检节点跳回执行节点,而不是整个流程重新跑。这套能力在多轮协作任务里非常有价值。比如我做一个跨部门业务流程,前一步生成的结果有问题,后一步能明确感知并打回,而不是稀里糊涂继续往下走。4.4 典型场景举例龙虾架构不是炫技,它有特定的适用场景。我实践下来比较顺的有三类。一类是内容生产流水线,拆成选题Agent、资料搜集Agent、草稿Agent、审核Agent,并行搜集资料,最后汇总生成,审核不过打回重写。第二类是复杂告警排查,告警分类Agent、日志检索Agent、根因分析Agent、修复建议Agent,不同节点可以并行查日志和查指标。第三类是数据分析报告自动化,数据获取Agent、数据清洗Agent、图表生成Agent、报告撰写Agent,每一步都有明确输入输出,方便人工抽查。5. 实操落地:从ReAct到龙虾架构的完整示例理论讲再多,不落地等于零。这一章我用一个虚构项目“模拟项目X”完整演示一遍:从单Agent的ReAct代码,一步步改造成一个多Agent图式编排系统。为了让代码尽量通用,我用的是接近主流程图式框架的伪代码,但你把它映射到任何主流框架上都成立。5.1 需求背景模拟项目X是一个智能工单处理系统,输入是用户提交的售后工单文本,需要完成以下操作:判断工单类型:换货、退款、投诉、咨询根据工单类型收集必要信息:比如退款必须有订单号和退款原因查询知识库或调用业务API获取相关数据给出处理建议,并对于退款类生成待审核任务单Agent阶段,我用ReAct跑通了整个流程,但很快遇到问题:模型偶尔会在“判断类型”和“查询知识库”之间来回震荡,上下文越滚越长,而且退款审核和普通咨询不能共用一套流程。于是改造为图式编排。5.2 状态定义:先把数据契约设计好改造的第一步不是写Agent,而是定义全局状态。这个我踩过坑,一定要记住:先把状态结构定清楚,再写节点逻辑,否则后面所有节点都在各说各话。from typing import TypedDict, Optional class TicketState(TypedDict): raw_text: str # 原始工单文本 intent: Optional[str] # 工单类型 order_id: Optional[str] # 订单号 user_phone: Optional[str] # 用户手机号 missing_info: list[str] # 缺失信息清单 knowledge_result: Optional[str] # 知识库查询结果 solution: Optional[str] # 处理建议 approved: bool # 是否通过审批 retry_count: int # 重试计数这个状态对象会在整个图的所有节点间共享。每个节点只负责读取自己需要的字段,并把结果写回状态。这样每个Agent都不需要传递完整对话历史,只用关注状态切片。5.3 节点设计:五个核心Agent我拆了五个节点:意图识别Agent、信息补全Agent、知识检索Agent、方案生成Agent、审批Agent。意图识别Agent负责读取raw_text,输出intent并写入状态。信息补全Agent则检查order_id和user_phone是否存在,如果缺失,就把缺失项写入missing_info,并生成追问话术。知识检索Agent根据intent查询不同的知识库分类,可能调API,也可能查向量库。方案生成Agent组合intent和knowledge_result,生成初步solution。审批Agent只处理“退款”这类高风险动作,构造审批任务。每个Agent用同一个大模型,但Prompt完全不同。这比单Agent里“一个Prompt包打天下”可控得多。5.4 图式编排:定义节点、边和条件路由核心代码逻辑长这样:def build_workflow(): graph = StateGraph(TicketState) graph.add_node("entry", entry_handler) graph.add_node("intent_agent", intent_agent) graph.add_node("info_completion_agent", info_completion_agent) graph.add_node("knowledge_retrieval_agent", knowledge_retrieval_agent) graph.add_node("solution_agent", solution_agent) graph.add_node("approval_agent", approval_agent) graph.add_node("end", end_handler) # 普通边 graph.set_entry_point("entry") graph.add_edge("entry", "intent_agent") graph.add_edge("intent_agent", "info_completion_agent") graph.add_edge("info_completion_agent", "knowledge_retrieval_agent") graph.add_edge("knowledge_retrieval_agent", "solution_agent") # 条件边:信息缺失则回到补全节点 graph.add_conditional_edge( "info_completion_agent", "knowledge_retrieval_agent", # 默认路径 lambda state: "info_completion_agent" if state["missing_info"] else "knowledge_retrieval_agent" ) # 条件边:退款才走审批,其他直接结束 graph.add_conditional_edge( "solution_agent", "end", lambda state: "approval_agent" if state["intent"] == "退款" else "end" ) # 审批通过结束,不通过则打回方案生成 graph.add_conditional_edge( "approval_agent", "end", lambda state: "solution_agent" if not state["approved"] else "end" ) return graph.compile()这套代码的优点是:业务流转逻辑完全不在Prompt里,而在代码里。你想审计某个工单为什么走了审批打回,直接看状态记录就行,不需要猜模型当时在想什么。5.5 从ReAct迁移到龙虾架构的改造清单如果你现在手上有一个跑通的ReAct单Agent代码,想迁移过来,我给一个稳妥的改造顺序。第一步,梳理流程节点。把ReAct循环里“模型的每一步动作”重新归类:哪些是纯分类,哪些需要工具调用,哪些是结果生成,分列出来。第二步,定义状态字段。把ReAct轨迹里的关键中间信息,比如已查询到的订单号、知识库结论,升级为状态字段。第三步,每个动作改造成独立节点。第四步,用条件边替代ReAct循环里的条件判断逻辑。最后一步,加上回退逻辑和人工介入节点。这一步迁移做完,你会发现代码量虽然变多了,但系统变透明了,测试也简单了。原来ReAct只能端到端黑盒测试,现在可以单独调试任意节点。6. 常见问题与避坑实录写到这里,我再把实际开发中踩过的一些坑集中说一遍。这些都是文档里不会写的,但真实项目里几乎必遇。6.1 上下文越滚越长,成本直接失控图式编排虽然比ReAct省了不少上下文,但分布式Agent还有自己的问题。每个节点如果要带上共享State的所有字段,那字段一多,Prompt照样爆炸。我建议大家在状态设计上坚持最小化原则:每个节点读取的输入和写入的输出,都要显式声明,别让节点拿到无关字段。另外一定要给状态加“裁剪机制”。比如工单文本如果超长,先在入口做摘要;比如方案生成节点,只需要intent和knowledge_result,不需要带原始文本。这些都是细活,但实际效果非常明显。6.2 Agent之间踢皮球,流程死循环在早期版本里,我遇到过信息补全Agent和意图识别Agent互相踢皮球:意图识别发现缺订单号,交给信息补全;信息补全追问用户,拿到回复后又去意图识别,结果意图识别又说信息不足,又丢回来。最后整个图跑了几十步,白白烧了一大笔token。解决办法有两个。一个是硬性限制,在图上增加最大步数或重试次数,超过阈值强制转人工。另一个是软性约束,给“信息补全”节点加一个规则:同一个字段最多追问两次,超过就带着missing_info继续往下走,宁可让下游Agent处理缺失值,也不要无限循环。后者的思路更贴合真实业务:数据不完美是常态,系统要学会带残缺状态前进。6.3 图太复杂,调试看不过来多Agent图式编排最大的隐形成本,是排障难度。一个节点出错,可能是上游节点数据没写对,可能是自己的Prompt有问题,也可能是条件边判断错误。初期我把十几个节点全部画在了一张图里,结果出问题根本定位不到。后来我总结出一套调试习惯。第一,每个节点都记录结构化日志:输入了哪些字段、输出改了哪些字段、调用了什么工具。第二,条件边判断结果必须打印出来:从哪个节点来、根据什么条件去了哪个节点。第三,遇到问题先查状态流转,再查节点内部。先看整个图的数据流对不对,再看具体Agent的输出质量。6.4 Agent的幻觉在编排里会被放大单Agent写错了,最多是答案不对。多Agent里如果上游Agent产生幻觉,把错误信息写进状态,下游所有节点都会基于错误数据继续工作,错误会被层层放大。这个问题非常隐蔽,因为下游Agent“看起来”逻辑是正确的,结果却是错的。应对的做法是给关键节点加验证器。比如知识检索节点返回的内容,必须包含知识库条目标识,否则判定检索失败。比如方案生成节点的输出,要通过一个规则检查是否能对应到intent和knowledge_result。不要指望大模型自己纠错,用代码做硬校验才可靠。6.5 什么时候不要用龙虾架构这点对小白特别重要。如果你的业务流程不超过三个环节、工具调用不超过两个、也不需要人工介入,真的不用上多Agent。我自己见过不少项目,明明一个ReAct循环就能搞定,非拆出四五个Agent,结果Prompt维护成本翻了好几倍,效果还更差。我给自己定了一条线:什么时候用图式编排?当且仅当业务流程存在分支跳转、并行处理、人工介入或回退重试这四个特征之一时才考虑。否则,请老老实实写一个完美的ReAct循环。最后分享一个我自己的体会。最初带模拟项目X时,我一股脑把图拆得很细,总想着“一步到位”直接上复杂架构,结果调度成本高得离谱,光调条件边就花了三天。后来学乖了,先把最核心的链路用ReAct跑通,再根据实际失败点把稳定分支拆成节点,最后才慢慢长出整张图。这套“先单Agent,后图式”的路径,我建议新手直接照着走。架构不是越复杂越好,能承载当前业务并留出演进空间,就是最好的架构。
这两年AI Agent架构的文章我看了不下几十篇,名字一个比一个唬人:ReAct、Plan-and-Execute、Reflexion、多Agent系统、图式编排、龙虾架构……但真能把“从A到B是怎么一步步走出来”讲明白的,确实不多。尤其对小程序员来说,最痛苦的就是概念堆了一堆,回头自己写代码还是不知道从哪下手。这篇我打算换个思路,不按教科书来,而是按我实际开发里摸爬滚打的经验,把最经典的ReAct到所谓“龙虾架构”的整条演进路线完整捋一遍。你会看到每个架构到底为了解决什么问题而生、代码长什么样、坑在哪里,以及什么场景下应该选哪种。不管你是刚开始接触Agent开发,还是已经在单Agent里泡了几个月想往多Agent方向走,这篇应该都能帮你把散落的知识点串成一条线。1. 从单次对话到自主循环:Agent架构到底在解决什么问题很多初学者会把Agent和大模型混为一谈,觉得“能聊天的就是Agent”。实际上,大模型本身只是一台“概率推理引擎”,你问一句它答一句,它不会主动去执行查询、不会去查数据库、也不会因为上一步操作失败而调整策略。Agent架构解决的核心问题,就是把这些被动的单次能力组合成一个主动的、可循环的、能完成多步骤任务的系统。1.1 大模型本身的三个短板我拿一个最常见的需求来说:让AI帮我把客服工单分类,然后根据分类去查知识库,最后给出处理建议。如果只靠单次提示词,你会发现三个问题。第一,大模型回答一次之后就没有“后续”了。它说“我建议你去查退款政策”,但不会真的去查。第二,大模型没有工具执行能力,它不能调用工单系统API、不能读数据库、不能发邮件。第三,也是最隐蔽的:大模型容易在长任务中“忘掉”之前的中间结果。比如它分析了工单内容,转头生成建议时,可能就把分析结论给丢了。Agent架构就是在外面套一层“循环控制逻辑”:让模型思考下一步做什么、调用什么工具、拿到结果后再继续思考,直到任务结束。这套机制不复杂,但它就是从“能用”到“好用”的分水岭。1.2 Agent循环:观察-思考-行动的闭环用生活化的类比来解释:人类处理问题也不是一次到位的。你收到一个售后工单,你会先读一遍内容(观察),判断是什么问题(思考),然后去系统里查订单(行动),看到查询结果后再决定要不要退款(再次思考)。Agent循环的本质就是把这套流程算法化。在这个循环里,每个Agent的核心组件只有三块:大脑(大模型)、工具集(API/函数)、记忆(上下文或外部存储)。大脑负责决策,工具负责执行,记忆负责把“当前进展”传递给下一轮循环。1.3 为什么架构设计值得单独讨论你可能会说,那不就是写个while循环吗?是,也不是。单Agent的while循环很简单,但一旦任务变复杂,比如需要多个专业角色协作、需要并行处理、需要中途人工审批,单循环就撑不住了。这时候你才会真正理解,为什么社区里会演化出各种编排架构。2. ReAct:最经典的单Agent推理执行范式ReAct这个名字,是Reasoning和Acting的组合,核心思想就一句话:让大模型在每一步里同时产生推理轨迹和行动指令,而不是像传统做法那样“先想完再做”或者“不想直接做”。2.1 ReAct的原理拆解我经常用“开车导航”来类比ReAct。你开车去一个陌生地方,不是启动前就把整条路线背下来然后闭眼开,而是开一段、看路牌、判断、再开一段。ReAct也是这个逻辑:模型生成Thought(当前判断),生成Action(调用哪个工具、传什么参数),然后等待Observation(工具返回结果),把结果拼进下一次推理。这跟让模型一次性输出完整计划的区别在于:如果计划中途出错了,ReAct可以根据Observation实时修正。比如模型本来想查订单表,结果工具返回“没有订单号”,它可以接着想“那先查客户ID”,而不是死板地按原计划走。2.2 ReAct的标准循环:Thought-Action-Observation我直接给你看一个最简伪代码,这个是我自己项目里抽出来的简化版,适合入门理解:def run_react_agent(query, tools, llm, max_steps=8): messages = [{"role": "user", "content": query}] for step in range(max_steps): # 让模型思考并决定下一步动作 response = llm.invoke(messages) print(f"[Step {step}] {response}") action = parse_action(response) if action["type"] == "finish": return action["answer"] if action["type"] == "tool": observation = call_tool(action["tool_name"], action["tool_args"]) messages.append({"role": "assistant", "content": response}) messages.append({"role": "tool", "content": f"Observation: {observation}"}) return "Reached max steps, stopping."这段代码里最关键的是最后两行:把模型的决策和工具返回结果都追加到messages里。这样模型在下一步推理时,才能看到“我之前做了什么、结果是什么”,然后决定接下来是调整策略还是继续执行。2.3 ReAct的实际运行效果我拿模拟项目X里的一个简单场景演示过。输入的是工单文本:“用户说收到货是坏的,要求换货,但订单号没填。”ReAct的输出轨迹大概是这样的:Thought:需要先查询用户信息,但工单里没有订单号,只有手机号,可以用手机号查订单。Action:调用query_order(phone="138xxxx")Observation:查到两个订单,最近一笔签收时间为3天前,符合换货周期。Thought:订单确认了,接下来需要查询退换货政策。Action:call_knowledge_base("换货政策")Observation:支持7天无理由换货。Thought:可以同意换货,生成答复。Action:finish,输出答复内容。你看,整个过程模型没有一次把计划全部想好,而是走一步看一步。这就是ReAct能成为单Agent基座的原因:流程简单,行为可控,适合各种工具调用场景。2.4 ReAct的变体与演进ReAct出来之后,社区又冒出几个方向。一个是Plan-and-Execute,就是先把计划列好,再逐步执行,步骤之间不依赖于推理轨迹,好处是省token,坏处是中途变化就废了。另一个是Reflexion,就是任务失败后,让模型总结失败原因,把经验写进记忆,下一次重试时避开同样的坑。我自己实际使用下来,Reflexion对调试特别有用,但会把系统复杂程度拉高一个量级。2.5 单ReAct的四个天花板说句公道话,ReAct虽好,但对稍微复杂的业务就力不从心了。我总结了四个必须正视的天花板。第一是上下文膨胀。每轮循环都要把之前的Thought、Action、Observation全部塞进Prompt,任务步骤一多,上下文就爆了。第二是角色冲突。你让同一个模型既当客服又当质检员,它的判断标准会很混乱,经常自己放水。第三是串行瓶颈。多个独立子任务只能一步一步来,效率低。第四是失败恢复脆弱。某一步工具调用报错,如果模型没有及时察觉,整个任务就从那里开始跑偏。这四个天花板,就是多Agent架构出现的根本原因。3. 为什么必然走向多Agent协同:编排范式演进单Agent撑不住复杂业务后,社区自然的反应就是:一个Agent干不了,那就拆分角色,让多个Agent各司其职。但“多个Agent”只是第一步,真正难的,是怎么编排它们。3.1 多Agent和单Agent的本质区别很多人以为多Agent就是“在循环里多调用几次模型”,这就理解偏了。多Agent的重点不在模型数量多,而在于把任务拆成可独立验证的模块,每个模块有明确输入输出,再用一套“调度规则”把它们衔接起来。还是拿后厨打比方。单Agent就像一个小饭馆,一个厨师从洗菜到炒菜全包。多Agent就像大饭店后厨,切菜的只管切菜,炒锅的只管炒菜,传菜的只管传菜。每个人不需要知道完整菜单,只需要干好自己的活,然后按流程把菜传下去。图式编排架构,就是饭店里那套“主厨调度系统”:谁在什么时间做哪道菜、谁要等谁的结果、哪个环节出错了怎么重做。3.2 第一阶段:顺序管道编排最早的编排最简单,就是流水线。Agent A做完,输出传给Agent B,B做完传给C。适合业务流程非常固定的场景,比如:意图识别Agent → 信息提取Agent → 回答生成Agent。这个模式好处是简单直观,坏处是没有任何灵活性,中间环节无法跳转,也无法根据中间结果改变后续路径。3.3 第二阶段:路由与计划编排为了弥补“太死板”的问题,社区引入了路由器角色。由一个规划Agent分析任务,决定下一步交给哪个子Agent处理。典型代表就是Plan-and-Execute模式:先有一个Planner生成任务清单,然后Executor逐个执行,每步执行完,Planner再看结果决定是要重新规划还是继续执行。这个模式比顺序管道灵活,但有一个隐患:规划Agent如果判断错了路由方向,整个任务就走歪了。而且每个Agent之间没有共享的“全局状态”,信息传递全靠参数,层级深了之后参数管理会非常痛苦。3.4 第三阶段:图式编排与状态机再往后发展,就是我们现在说的图式编排。思路也很直接:把整个任务流程画成一张有向图,每个节点是一个Agent或一个工具调用,节点之间的边代表流转条件,全局共享一份状态数据。Agent之间不直接通信,而是往上读写状态。这种模式最大的优点有两个:一是可控性强,任意节点可以设置条件边,满足条件才走某条路;二是可恢复性强,某个节点失败后,可以回退到指定状态节点重新执行。图式编排也是“龙虾架构”的底层骨架。4. 龙虾架构:多智能体图式协作的工程化形态“龙虾架构”这个名字,我第一次听到也觉得挺有意思。龙虾的身体结构其实很适合用来理解这类图式编排系统:头部是决策中枢,躯干是核心处理流程,步足是并行执行的工具Agent,触角是外部感知和检索模块。各个部分形状不同、职责不同,靠关节和肌肉连接成一个整体,协调行动。4.1 为什么叫龙虾,不只是讨巧社区里用“龙虾”来称呼这类架构,本质上是在强调两个特征:分节与可伸缩。分节是说每个Agent像龙虾的体节一样,结构独立、边界清晰,可以单独演化替换;可伸缩是说整个系统可以像龙虾蜕壳一样,在运行时动态增减Agent节点,而不会破坏整体联动。当然,这不是一个严格的学术概念,更像一种工程直觉的称呼。在具体落地时,它对应的就是“多Agent图式编排”这个方向。你去看各种主流的Agent编排框架,底层抽象几乎都是同一套:状态图、节点、边、条件路由、共享记忆。4.2 核心设计元素拆解龙虾架构的核心设计元素,我归纳成五个模块。第一个是状态图(StateGraph),整个任务的流转地图,决定了Agent执行的顺序和分支。第二个是节点(Node),可以是Agent节点、工具节点、或者普通函数节点。第三个是边(Edge),分为普通边和条件边,普通边就是“执行完A一定执行B”,条件边就是“根据状态里的某个字段决定下一步走哪个节点”。第四个是全局状态(State),一个所有节点都能读写的数据结构,比如工单号、意图、处理结果都放里面。第五个是共享记忆(Shared Memory),比全局状态更偏长期存储,比如向量数据库、KV存储,用来让Agent记住跨session的信息。这五个元素组合起来,就形成了一个极其灵活的编排系统。我画不了图,但你想象一个流程:入口节点 → 意图识别Agent → 条件分支 → 信息收集Agent或知识库检索工具节点 → 汇聚节点 → 执行Agent → 质检Agent → 出口节点。这就是一个典型的龙虾架构雏形。4.3 与普通多Agent编排的关键差异我在第3章讲过路由编排,那你可能会问:图式编排和它到底有什么区别?区别体现在三个方面。首先,显式状态机。路由编排中流程逻辑散落在各个Agent的Prompt里,不可审计;图式编排把流程显式定义为状态图,逻辑在代码里,不在Prompt里。其次,共享状态。图式编排中每个Agent通过State共享数据,不再需要来回传参数,减少串联风险和上下文冗余。第三,可恢复性。图里可以定义回退边,比如质检不通过,直接从质检节点跳回执行节点,而不是整个流程重新跑。这套能力在多轮协作任务里非常有价值。比如我做一个跨部门业务流程,前一步生成的结果有问题,后一步能明确感知并打回,而不是稀里糊涂继续往下走。4.4 典型场景举例龙虾架构不是炫技,它有特定的适用场景。我实践下来比较顺的有三类。一类是内容生产流水线,拆成选题Agent、资料搜集Agent、草稿Agent、审核Agent,并行搜集资料,最后汇总生成,审核不过打回重写。第二类是复杂告警排查,告警分类Agent、日志检索Agent、根因分析Agent、修复建议Agent,不同节点可以并行查日志和查指标。第三类是数据分析报告自动化,数据获取Agent、数据清洗Agent、图表生成Agent、报告撰写Agent,每一步都有明确输入输出,方便人工抽查。5. 实操落地:从ReAct到龙虾架构的完整示例理论讲再多,不落地等于零。这一章我用一个虚构项目“模拟项目X”完整演示一遍:从单Agent的ReAct代码,一步步改造成一个多Agent图式编排系统。为了让代码尽量通用,我用的是接近主流程图式框架的伪代码,但你把它映射到任何主流框架上都成立。5.1 需求背景模拟项目X是一个智能工单处理系统,输入是用户提交的售后工单文本,需要完成以下操作:判断工单类型:换货、退款、投诉、咨询根据工单类型收集必要信息:比如退款必须有订单号和退款原因查询知识库或调用业务API获取相关数据给出处理建议,并对于退款类生成待审核任务单Agent阶段,我用ReAct跑通了整个流程,但很快遇到问题:模型偶尔会在“判断类型”和“查询知识库”之间来回震荡,上下文越滚越长,而且退款审核和普通咨询不能共用一套流程。于是改造为图式编排。5.2 状态定义:先把数据契约设计好改造的第一步不是写Agent,而是定义全局状态。这个我踩过坑,一定要记住:先把状态结构定清楚,再写节点逻辑,否则后面所有节点都在各说各话。from typing import TypedDict, Optional class TicketState(TypedDict): raw_text: str # 原始工单文本 intent: Optional[str] # 工单类型 order_id: Optional[str] # 订单号 user_phone: Optional[str] # 用户手机号 missing_info: list[str] # 缺失信息清单 knowledge_result: Optional[str] # 知识库查询结果 solution: Optional[str] # 处理建议 approved: bool # 是否通过审批 retry_count: int # 重试计数这个状态对象会在整个图的所有节点间共享。每个节点只负责读取自己需要的字段,并把结果写回状态。这样每个Agent都不需要传递完整对话历史,只用关注状态切片。5.3 节点设计:五个核心Agent我拆了五个节点:意图识别Agent、信息补全Agent、知识检索Agent、方案生成Agent、审批Agent。意图识别Agent负责读取raw_text,输出intent并写入状态。信息补全Agent则检查order_id和user_phone是否存在,如果缺失,就把缺失项写入missing_info,并生成追问话术。知识检索Agent根据intent查询不同的知识库分类,可能调API,也可能查向量库。方案生成Agent组合intent和knowledge_result,生成初步solution。审批Agent只处理“退款”这类高风险动作,构造审批任务。每个Agent用同一个大模型,但Prompt完全不同。这比单Agent里“一个Prompt包打天下”可控得多。5.4 图式编排:定义节点、边和条件路由核心代码逻辑长这样:def build_workflow(): graph = StateGraph(TicketState) graph.add_node("entry", entry_handler) graph.add_node("intent_agent", intent_agent) graph.add_node("info_completion_agent", info_completion_agent) graph.add_node("knowledge_retrieval_agent", knowledge_retrieval_agent) graph.add_node("solution_agent", solution_agent) graph.add_node("approval_agent", approval_agent) graph.add_node("end", end_handler) # 普通边 graph.set_entry_point("entry") graph.add_edge("entry", "intent_agent") graph.add_edge("intent_agent", "info_completion_agent") graph.add_edge("info_completion_agent", "knowledge_retrieval_agent") graph.add_edge("knowledge_retrieval_agent", "solution_agent") # 条件边:信息缺失则回到补全节点 graph.add_conditional_edge( "info_completion_agent", "knowledge_retrieval_agent", # 默认路径 lambda state: "info_completion_agent" if state["missing_info"] else "knowledge_retrieval_agent" ) # 条件边:退款才走审批,其他直接结束 graph.add_conditional_edge( "solution_agent", "end", lambda state: "approval_agent" if state["intent"] == "退款" else "end" ) # 审批通过结束,不通过则打回方案生成 graph.add_conditional_edge( "approval_agent", "end", lambda state: "solution_agent" if not state["approved"] else "end" ) return graph.compile()这套代码的优点是:业务流转逻辑完全不在Prompt里,而在代码里。你想审计某个工单为什么走了审批打回,直接看状态记录就行,不需要猜模型当时在想什么。5.5 从ReAct迁移到龙虾架构的改造清单如果你现在手上有一个跑通的ReAct单Agent代码,想迁移过来,我给一个稳妥的改造顺序。第一步,梳理流程节点。把ReAct循环里“模型的每一步动作”重新归类:哪些是纯分类,哪些需要工具调用,哪些是结果生成,分列出来。第二步,定义状态字段。把ReAct轨迹里的关键中间信息,比如已查询到的订单号、知识库结论,升级为状态字段。第三步,每个动作改造成独立节点。第四步,用条件边替代ReAct循环里的条件判断逻辑。最后一步,加上回退逻辑和人工介入节点。这一步迁移做完,你会发现代码量虽然变多了,但系统变透明了,测试也简单了。原来ReAct只能端到端黑盒测试,现在可以单独调试任意节点。6. 常见问题与避坑实录写到这里,我再把实际开发中踩过的一些坑集中说一遍。这些都是文档里不会写的,但真实项目里几乎必遇。6.1 上下文越滚越长,成本直接失控图式编排虽然比ReAct省了不少上下文,但分布式Agent还有自己的问题。每个节点如果要带上共享State的所有字段,那字段一多,Prompt照样爆炸。我建议大家在状态设计上坚持最小化原则:每个节点读取的输入和写入的输出,都要显式声明,别让节点拿到无关字段。另外一定要给状态加“裁剪机制”。比如工单文本如果超长,先在入口做摘要;比如方案生成节点,只需要intent和knowledge_result,不需要带原始文本。这些都是细活,但实际效果非常明显。6.2 Agent之间踢皮球,流程死循环在早期版本里,我遇到过信息补全Agent和意图识别Agent互相踢皮球:意图识别发现缺订单号,交给信息补全;信息补全追问用户,拿到回复后又去意图识别,结果意图识别又说信息不足,又丢回来。最后整个图跑了几十步,白白烧了一大笔token。解决办法有两个。一个是硬性限制,在图上增加最大步数或重试次数,超过阈值强制转人工。另一个是软性约束,给“信息补全”节点加一个规则:同一个字段最多追问两次,超过就带着missing_info继续往下走,宁可让下游Agent处理缺失值,也不要无限循环。后者的思路更贴合真实业务:数据不完美是常态,系统要学会带残缺状态前进。6.3 图太复杂,调试看不过来多Agent图式编排最大的隐形成本,是排障难度。一个节点出错,可能是上游节点数据没写对,可能是自己的Prompt有问题,也可能是条件边判断错误。初期我把十几个节点全部画在了一张图里,结果出问题根本定位不到。后来我总结出一套调试习惯。第一,每个节点都记录结构化日志:输入了哪些字段、输出改了哪些字段、调用了什么工具。第二,条件边判断结果必须打印出来:从哪个节点来、根据什么条件去了哪个节点。第三,遇到问题先查状态流转,再查节点内部。先看整个图的数据流对不对,再看具体Agent的输出质量。6.4 Agent的幻觉在编排里会被放大单Agent写错了,最多是答案不对。多Agent里如果上游Agent产生幻觉,把错误信息写进状态,下游所有节点都会基于错误数据继续工作,错误会被层层放大。这个问题非常隐蔽,因为下游Agent“看起来”逻辑是正确的,结果却是错的。应对的做法是给关键节点加验证器。比如知识检索节点返回的内容,必须包含知识库条目标识,否则判定检索失败。比如方案生成节点的输出,要通过一个规则检查是否能对应到intent和knowledge_result。不要指望大模型自己纠错,用代码做硬校验才可靠。6.5 什么时候不要用龙虾架构这点对小白特别重要。如果你的业务流程不超过三个环节、工具调用不超过两个、也不需要人工介入,真的不用上多Agent。我自己见过不少项目,明明一个ReAct循环就能搞定,非拆出四五个Agent,结果Prompt维护成本翻了好几倍,效果还更差。我给自己定了一条线:什么时候用图式编排?当且仅当业务流程存在分支跳转、并行处理、人工介入或回退重试这四个特征之一时才考虑。否则,请老老实实写一个完美的ReAct循环。最后分享一个我自己的体会。最初带模拟项目X时,我一股脑把图拆得很细,总想着“一步到位”直接上复杂架构,结果调度成本高得离谱,光调条件边就花了三天。后来学乖了,先把最核心的链路用ReAct跑通,再根据实际失败点把稳定分支拆成节点,最后才慢慢长出整张图。这套“先单Agent,后图式”的路径,我建议新手直接照着走。架构不是越复杂越好,能承载当前业务并留出演进空间,就是最好的架构。