ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI Agent实战:从ReAct到LangGraph的主流架构演进与并发优化

AI Agent实战:从ReAct到LangGraph的主流架构演进与并发优化 如果只看2025年的AI圈热门词可能至少有一半项目都自称“AI Agent”。从自动整理邮件的助手到能接管一整条数据处理管线的智能体框架名字都叫Agent底子却完全不一样。我做了十多年系统开发近两年几乎把所有业余时间都砸在LLM应用落地、尤其是Agent这一块。从最早拿LangChain写带记忆的聊天机器人到后来用LangGraph编排多智能体状态流再用FastAPI把整套东西包成服务扛真实请求中间踩过的坑比写过的demo还多。如果你最近也在搜索“ai agent怎么扛并发”、“ai agent主流架构”、“基于rut语言ai agent”、“基于fastapi langchain langgraph的ai agent服务”这类关键词说明你跟我一样已经不满足于做一个能聊天的壳子而是想让Agent真正下场干活。这篇文章不打算做概念科普我会从演进路线讲起把主流架构、工具选型、代码实现、并发与稳定性问题以及几张实际处理过的排错记录全部摊开讲明白。你只需要有一点Python基础懂一点HTTP服务剩下的我用大白话解释。1. AI Agent演进路线从规则机器人到图执行智能体1.1 萌芽期规则脚本和预训练时代的“假智能”提到Agent的演进不能跳过2022年之前的阶段。早年做对话机器人本质就是一棵决策树。用户输入进来系统先分词再匹配关键词命中就返回预设话术没命中就走兜底。整个过程没有任何“理解”只有规则匹配。做RPA机器人流程自动化的团队更直接录制一套鼠标键盘操作流程定时或者按条件触发模拟人点按钮、填表格。这套方案的维护成本高到离谱因为规则库是无限膨胀的你加一个实体就得补一组分支逻辑。实际业务里稍微来一个没见过的句子机器人马上就哑火。当时我们内部有个段子规则系统不是写出来的是补丁摞出来的。这个时期也有“Agent”这个词比如 IBM 早期的会话代理、微软的小冰但更多是厂商包装。底层技术栈是搜索引擎式的检索匹配或者早期的深度学习分类模型本质上是对意图做槽位填充Slot Filling。这一代系统解决了“能不能自动回复”的问题但没有任何推理能力更谈不上动态规划。现在回头看它是整个演进过程的地基大部分工程规范、会话管理思路、错误处理范式都是从那个时代沉淀下来的。1.2 爆发期LLM让模型第一次拥有了推理与行动闭环2022年底ChatGPT出现之后整个思路彻底变了。LLM在理解上下文、生成计划、抽取意图这三件事上的能力几乎把过去规则系统里最难啃的环节直接抹平。2023年起研究者陆续把“Prompt循环调用工具”做成标准范式最具代表性的就是ReAct论文提出的模式。它的思路很朴素让模型在每轮循环里先“想”一步想清楚接下来该做什么再“做”一步调用一个外部工具然后观察工具返回的结果再继续进入到下一轮“想”。这个模式替代了写死规则的旧路线只要提示词里把工具定义描述清楚模型能自己决定是查数据库、调天气API还是去拉文档。我把这个阶段形容成“从编码到编排”的转变。以前写功能是程序员把每个分支写死在代码里现在是靠模型在推理过程中动态决定调用路径。demo阶段的惊艳源于此生产环境的隐患也源于此ReAct循环天然不可控。模型可能在一个工具上反复调用十几次上下文越来越长前面已经拿到过的结论后面又忘记更麻烦的是没有全局规划遇到分步骤的任务容易陷入局部最优。2023年下半年我接过一个项目让模型帮忙查订单并生成退货运费单模型经常在“查订单详情”和“查运费规则”两个工具之间来回横跳最后token耗尽也没出结果。这就是ReAct的典型毛病。1.3 工程化期LangGraph和图状态编排成为主流ReAct的失控促使大家重新思考一个问题Agent的能力边界应该由模型决定还是应该由流程决定2024年前后LangGraph这类专门做Agent状态编排的框架火起来核心思路是把Agent从“单循环”升级为“图执行”。图中每个节点是一次动作每条条件边是一道决策切换整个执行过程的状态被显式地管理起来可以中断、恢复、持久化。这是我眼中Agent演进最关键的分水岭不再是三万行if-else也不是盲目信任模型而是把人类可理解的流程硬约束和模型自主决策的灵活性结合起来。这种演进的意义打个比方就是ReAct像一个没有项目经理的团队谁有空谁上遇到复杂需求容易乱图执行则像是给项目画了张带里程碑的甘特图关键节点必须过步骤可以动态调整但核心链路不能跳。LangGraph之后出现了大量基于图编排的多智能体平台比如各厂推出的Agent工作流编辑器。普通用户拖拽节点就能搭Agent内部实现依然是状态图那套逻辑。如果现在你还在单纯用“for循环工具调用”写Agent建议尽早升级到图编排思路哪怕不引入LangGraph也应该按这个理念设计自己的状态机。阶段智能来源规划能力典型代表生产可用性规则时代人工规则无决策树聊天机器人、RPA低维护成本极高LLM原生Agent模型推理有限局部最优ReAct、BabyAGI中需要强约束图编排Agent流程模型全局可见LangGraph、多智能体框架高可控可恢复2. 主流架构选型ReAct、Plan-and-Execute与多智能体到底区别在哪2.1 ReAct简单直接但容易在上下文里绕圈ReAct的架构极其简洁一个LLM一组工具一个循环。模型根据用户问题决定要不要调用工具、调用哪个工具拿到结果后继续推理直至认为可以回答用户。因为它把思考过程以自然语言写在上下文里调试非常直观你打开聊天记录就能看到模型一步步想了什么。实测下来ReAct在单一目标、工具数量少于5个的场景里表现还不错比如“查一下明天的天气并生成穿衣建议”。但如果你把工具数量扩到十几个task复杂度一上来ReAct就露怯了。典型问题是工具选择的“犹豫”模型不知道该用哪个工具会反复尝试相近的工具更烦的是它已经拿到关键结果但自己不知道还在继续查。解决手段有两个一是给每个工具写极其精确的描述告诉模型“什么情况下不要用这个工具”二是设定循环上限比如最多执行6步超过就强制输出当前能给出的结论或者直接转人工。2.2 Plan-and-Execute先出一份计划再按计划执行Plan-and-Execute的思路有别于ReAct模型先一次性生成完整任务计划再按顺序执行每一项执行过程中根据结果动态修订后续计划。这样做的好处是避免了ReAct的“局部最优陷阱”模型对整体目标有了全局认知适合目标明确、步骤清晰的任务比如“采集最近一周的行业新闻分类后生成摘要报告”。我在实际项目里用这个模式做数据聚合类Agent效果比ReAct稳定很多。代价也很明显对模型规划能力要求高初始计划如果就是错的后面执行得再稳也是白干。所以用Plan-and-Execute时我一般会在生成计划后强制做一个“自检节点”让模型拿着计划重新读一遍原始需求检查有没有漏项、有没有不合理的步骤顺序。这个自检在工程上就是图里的一个节点成本不高但对最终正确率提升非常明显。2.3 图状态与多智能体协作把流程变成一等公民用LangGraph的StateGraph建模把每个工具封装成节点把节点之间的调度关系画成边这是我现在最推崇的生产架构。关键区别在ReAct是靠模型自由探索的工具循环图则是把“绝对不能跳过的环节”变成强边。比如鉴权、落库、人工审批这些步骤模型就算再聪明也不允许绕过去。这解决了Agent落地时最头疼的合规问题不是所有决策都该交给模型。多智能体协作则是在图之上演变出的下一层复杂度。几种常见模式我列个表帮你快速选型模式核心思路适用场景常见风险Supervisor模式一个主Agent负责任务拆分和分发子Agent各自执行多领域工具、团队级工作流主Agent成为瓶颈token涨幅大层次结构不同层级Agent决策不同粒度事项有审批链的企业流程层级间状态传递容易错乱图协作各Agent是图中节点条件边动态路由分支较多的复杂任务编排逻辑臃肿排障困难辩论/评审多个Agent各自给答案另一个Agent评分文案生成、代码审查重复调用模型成本翻好几倍选择建议很直接如果任务流程稳定、分支固定用图就足够不要为了“多智能体”这个词强行拆多个Agent如果任务里确实出现天然的角色差异比如写稿、审稿、配图是三类不同专家再考虑Supervisor模式。多智能体最大的敌人是状态同步和上下文爆炸拆之前先掂量一下你的token预算。3. 技术栈分工LangChain、LangGraph、FastAPI和Rust各自该承担什么3.1 LangChain与LangGraph不是二选一而是上下层关系我见过不少团队纠结“选LangChain还是LangGraph”这其实是个伪命题。LangGraph本来就是LangChain生态的一部分LangChain负责提供模型封装、Prompt模板、输出解析这些通用能力LangGraph负责状态管理和执行编排。正确姿势是把LangChain当作工具库按需取用而不是整个框架端进来。我现在写Agent只用LangChain的ChatPromptTemplate、输出解析器还有模型封装核心编排全在LangGraph里。如果你被LangChain的抽象层弄得头大可以直接不依赖LangChain用OpenAI SDK和LangGraph核心包也能跑得很稳。3.2 FastAPI凭什么成为Agent服务层的事实标准Agent要对外提供服务本质就是一个API网关需要处理并发请求、鉴权、限流、长连接。FastAPI在这件事上有天然优势原生async支持可以让多个Agent会话在同一个进程内并发执行遇到LLM调用这种网络IO操作会自动让出事件循环。实测中我用FastAPI包LangGraph跑过几十路并发请求只要LLM供应商不限速FastAPI本身几乎没有压力。它内置的Pydantic可以做请求参数校验避免脏数据进入Agent流程。生产部署时配合gunicornuvicorn多worker再挂一层Nginx做负载均衡结构就很完整了。3.3 Rust在Agent生态里的真实定位不是来抢Python饭碗的热词里有“基于rust语言ai agent”我得先说句实话绝大多数业务场景不需要用Rust重写Agent框架也不需要用Rust重新实现一遍推理循环。Rust的优势在高并发、低延迟、内存安全更适合作Agent链路上的专用加速组件。比如你想自己实现一个并发托管层或者对服务启动速度、包体积有变态要求Rust才有发挥空间。我见过一个项目把消费队列里的Agent跑批任务全部用Rust重写单机吞吐翻了接近三倍但代价是开发周期拉长了至少一倍生态里缺很多东西要自己补。个人项目或者中小团队Python是更理性的选择Rust可以留到真正的性能瓶颈出现时再抽出来单独做服务。4. 生产三大关并发、状态持久化和可观测性4.1 Agent怎么扛并发三层策略缺一不可热词里“ai agent怎么扛并发”出现频率极高说明很多人已经踩到生产门槛了。第一层是FastAPI的async能力把每个Agent的执行栈切成协程任务LLM调用和工具HTTP请求遇到IO就自动让出这是最基础的并发来源。第二层是请求队列解耦不要傻等用户同步发起Agent请求、同步等结果而是把请求扔进Redis Streams或RabbitMQ由后台Worker异步消费处理完再回写状态。第三层是资源复用模型调用连接池复用、工具返回结果缓存、对LLM供应商做token限流。这三层缺一层并发一上来就会现原形。4.2 状态持久化Agent会话不丢记忆的保命手段Agent生产事故的高发区就是会话状态。很多demo应用把所有上下文一股脑塞给模型看起来简单一旦量上来就完蛋token爆炸、上下文超长、回复开始驴唇不对马嘴。正确做法是用LangGraph的Checkpointer机制把图的中间状态持久化到Redis或Postgres节点重启、服务挂掉都能从最近一个检查点恢复。我踩过的坑是项目早期用默认的内存Checkpointer路由一重启用户上下文全没多轮问答直接答非所问。后来换成Postgres持久化问题立刻消失。4.3 可观测性和兜底逻辑Agent不是黑盒Agent必须可观测每步推理都要记录哪条边触发了、哪个工具被调用、耗时多少、token消耗多少、返回结果摘要。我发现排障时大多数问题不是模型幻觉而是工具超时没被正确反馈到上下文里导致Agent拿着残缺信息硬编答案。建议每轮循环都输出结构化日志连暂停、恢复都要打点。兜底逻辑同样重要设定最大步数超过就停止循环给稳定回复工具连续三次失败就不再重试模型输出解析失败时给用户友好的提示而不是抛异常挂掉。5. 实操用FastAPI LangChain LangGraph搭一个能对外服务的智能体5.1 场景设定与项目结构这里我用一个真实做过的场景演示企业内部的智能客服助手用户问“我要去上海出差帮我查一下明天的高铁车次如果有优惠券也是好”。Agent需要调用车次查询工具和优惠券匹配工具通过图状态流转完成多步决策最后返回车次、时间和优惠后的价格。项目结构大致如下agent_service/ ├── agent/ │ ├── graph.py # LangGraph状态图定义 │ ├── tools.py # 车次查询、优惠券匹配工具 │ ├── state.py # Agent状态模型 ├── api/ │ ├── main.py # FastAPI入口 │ └── schemas.py # 请求/响应模型 └── config.py # 模型、Redis等配置5.2 定义工具让模型能用的函数必须把错误也变成正常返回先看工具层。一个能被LangGraph调用的工具关键在于函数注释和返回结构要足够清晰让模型知道什么情况唤起什么、结果怎么解读。我习惯把错误也变成可解析的返回信息而不是抛异常打到模型外面from langchain_core.tools import tool tool def query_train_schedule(city_from: str, city_to: str, date: str) - str: 根据出发城市、到达城市和日期查询高铁车次返回车次列表和余票信息。 try: result railway_api.query(city_from, city_to, date) if not result.get(data): return 未查到该日期的高铁车次请提示用户换一个日期。 return format_schedule(result[data]) except Exception as e: return f车次查询接口异常{str(e)}。请告知用户稍后重试。注意一点工具返回的是字符串不是结构化dict。这是为了让模型在推理时可以直接读取不需要额外的解析步骤减少一层出错概率。等模型判断执行完毕再由输出解析器把最终答案提取出来。5.3 构建LangGraph状态图把流程画成代码状态定义就用LangGraph自带的TypedDict记录车次查询结果、优惠券匹配结果和最终答复。图里我设计了三个核心节点查车次、查优惠、生成答复条件边确保必须先查车次成功且用户要求优惠才进入优惠节点否则直接跳到生成答复from typing import TypedDict from langgraph.graph import StateGraph, END class AgentState(TypedDict): query_text: str train_info: str coupon_info: str final_answer: str def node_query_train(state: AgentState): # 这里从state解析出城市和日期调用工具 return {train_info: query_train_schedule(...)} def node_query_coupon(state: AgentState): return {coupon_info: query_coupon(state[train_info])} def condition_need_coupon(state: AgentState): # 用户明确提到优惠/价格则进入优惠节点否则直接答复 return coupon if 优惠 in state[query_text] else answer graph StateGraph(AgentState) graph.add_node(train, node_query_train) graph.add_node(coupon, node_query_coupon) graph.add_node(answer, node_generate_answer) graph.set_entry_point(train) graph.add_conditional_edges(train, condition_need_coupon, {coupon: coupon, answer: answer}) graph.add_edge(coupon, answer) graph.add_edge(answer, END)LangGraph会自动把每个节点的返回合并回全局状态这就是“图执行”的核心便利节点只管自己的输出状态由框架管理不怕节点之间互相踩踏。等你跑通这个流程再把Redis Checkpointer一挂生产可用性立刻上一个台阶。5.4 FastAPI接口普通响应与流式响应两种模式接口层最大的问题是Agent执行耗时长HTTP请求很容易超时。我建议保留两类接口短任务用同步返回长任务或需要用户实时看到过程的场景用流式返回。下面是一个简化版演示普通响应接口from fastapi import FastAPI from pydantic import BaseModel app FastAPI() class ChatRequest(BaseModel): session_id: str query: str class ChatResponse(BaseModel): session_id: str answer: str graph_app graph.compile(checkpointerredis_checkpointer) app.post(/chat, response_modelChatResponse) async def chat(req: ChatRequest): config {configurable: {thread_id: req.session_id}} # 注意LangGraph默认是阻塞执行放在线程池避免卡住事件循环 import anyio result await anyio.to_thread.run_sync( lambda: graph_app.invoke( {query_text: req.query, ...}, configconfig ) ) return ChatResponse(session_idreq.session_id, answerresult[final_answer])为什么这里用anyio.to_thread.run_sync绕一下因为LangGraph的invoke是同步阻塞调用直接扔在async函数里会卡住FastAPI的事件循环导致并发能力骤降。把它丢给线程池执行事件循环就能继续接收其他请求这是并发性能的关键细节之一。等模型逐步支持流式调用你还可以用astream接口把每一步输出推给前端体验会好很多。5.5 部署与压测别只跑通就以为完事部署层面我建议用gunicorn -k uvicorn.workers.UvicornWorker -w 4匹配FastAPI的异步特性每个Worker等于一个是独立进程四路并发就是一个不小的并发池。再加一层限流中间件比如用slowapi把每个session的请求压到每分钟20次防止单用户打爆LLM配额。上线前用Locust压一遍重点关注P95延迟你会很直观地看到LLM调用耗时占了整个链路的90%以上所以真正要做性能优化的不是HTTP层而是模型调用层加缓存、合并请求、必要时上更快的模型。6. 高频故障排查循环、超时、丢状态、工具异常6.1 模型在工具调用里死循环现象同一个工具被触发十几遍最后超时报错。原因多半是工具返回结果没有正确进入模型的下轮推理或者工具描述写得含糊让模型误以为没拿到结果。解法一是给tool的描述加上“什么情况下不要用”的提示二是在LangGraph里给循环边增加步数计数器超过预设值强制跳转到END节点。我一般把最大工具调用次数设为6次用户问复杂问题时的成功率没有因为限步而下降反而因为及时止损稳了不少。6.2 并发一上来就大量504现象30路并发时请求开始排队前端的请求超时率飙升。先看日志里是不是LLM供应商返回429限速错误这几乎占了70%的原因。解法分三层先在入口给相同参数的请求加结果缓存复用之前已经算过的答案再在工具调用层做并发控制用一个信号量把同时打往同一接口的工具调用限制在5以内最后把非关键链路上耗时长的请求改成异步队列用后台任务消费结果。这样做完通常不用加机器就能扛住原来的并发量。6.3 多轮会话丢状态回答牛头不对马嘴现象Agent第一轮查到车次第二轮问价格时它完全“忘”了刚才的信息。原因基本锁定在Checkpointer没有持久化或者服务重启后状态归零。解法把checkpointer从内存版换成Redis或Postgres版同时在每个请求的config里都带上thread_id这是LangGraph找回会话状态的钥匙。老项目有一次状态没恢复用户问“那上午那趟有票吗”Agent回答“哪趟”当场被投诉后来我在日志里加了会话级摘要每次持久化时把前几轮结论压缩存储问题才根治。6.4 工具返回超时或格式异常现象第三方接口返回503或者字段缺失导致后续解析报KeyError。重要认知工具不可靠是常态模型能不能从错误中恢复是Agent质量的试金石。我的固定范式是工具函数里用try/except捕获所有异常将错误信息整理成文本返回给模型模型看到“服务暂不可用请告知用户”后就不会硬编一个假结果会给出诚实回复。同时工具返回的文本尽量模板化尽量不返回自由格式JSON让模型的解析负担减到最低。故障现象首要排查点推荐处理工具反复调用模型是否看到上一步结果加步数上限结果显式回灌P95延迟高LLM调用耗时占比加缓存、并发信号量、异步化多轮对话失忆Checkpointer是否持久化换Redis/Postgresthread_id固定工具解析报错返回格式自由度try/except兜底返回模板化文本7. 最后聊聊我的真实体会从规则脚本到多智能体图执行AI Agent不是一夜封神它是一步步把不确定性往确定性框架里装。早期做对话机器人怕的是规则写不全现在做Agent怕的反而是模型太“自由”。我自己经过这么多项目后的建议只有一个先把架构模式搞清楚再挑一个小场景从ReAct跑起跑通后交给LangGraph重构流程最后才考虑并发和持久化。这样踩的坑最少每个阶段的经验都能沉淀下来。目前我把LangGraph的Checkpointer换成了Redis把大模型调用环节单独拆出来做并发缓存生产稳定性明显上了一个台阶下一步准备尝试在Rust侧重写一个专用工具调用代理专门解决超高频调用的性能瓶颈。Agent这条路还很长但每个阶段做完后的回报都是实打实的。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进