最近半年,我身边每隔几天就有人甩过来一个大厂AI Agent白皮书的链接。白皮书看了不少,框架也追过几个,但真正把AI Agent推到生产环境之后,我最大的感受是:行业正在从“能不能跑”切换为“怎么跑得稳、怎么扛得住、怎么看得清”。概念阶段大家都在讲“Agent会用工具、会自己规划”,工程阶段你面对的全是实际问题——模型乱调工具参数怎么办、多用户会话串了状态怎么办、单个请求超时重试直接把下游打爆怎么办、一次Case Study明明能过但换个说法就崩怎么办。
这篇不是来复述概念的,我想直接把我做工程实现的经验拆开来讲。我自己的方法论是两条线:一条线是“七要素”,用来给Agent画解剖图,保证你理解一个Agent系统时不会漏掉关键部位;另一条线是“七个决策点”,用来在落地时做判断题,保证你从框架选型到上线治理都有一个可执行的思考顺序。这篇文章适合两类人:一类是刚看完LangChain文档、正准备做第一个Agent的开发者,另一类是Agent跑通Demo但不确定怎么上生产的同学。我会把这两条线串起来讲,尽量不给空话。
1. 七要素:给 Agent 建一张完整的解剖图
1.1 大脑、规划、记忆:Agent 的“思维三件套”
很多人以为Agent就是“LLM加调用工具”,这个理解不是错,但用在工程上太粗了。我习惯把Agent拆成七个要素:模型、规划、记忆、工具、行动、反馈、护栏。你先别急着记名词,我在做系统设计的时候是把它们当成七个必须回答的问题来用的。
**模型(大脑)**是Agent的裁决器。它负责读懂用户意图、决定下一步调用什么、判断当前结果是否满足目标。它不一定需要是参数最大的模型,但它的推理能力直接决定了Agent的上限。我见过很多团队一上来就上最贵的模型,结果成本爆掉,后来换成中等规模的模型配合好的工具描述,效果反而稳定。模型选型这件事,后面决策点里细讲。
**规划(Plan)**是指把一个大目标拆解成若干子任务的能力。有的Agent是显式规划,把计划写出来再逐步执行;有的是隐性规划,靠模型在每一轮对话里“想一步走一步”。工程上我更推荐隐性规划为主、显式规划为辅,因为显式规划一旦写成固定流程,Agent就会显得“笨”,稍微偏离预设场景就转不动。
**记忆(Memory)**分为工作记忆和长期记忆。工作记忆是当前会话的上下文窗口,长期记忆是存在向量库或者数据库里的历史事实。工程里最常见的坑是:会话一长,上下文塞满,模型开始答非所问。这个问题好多人最后归因于“模型不行”,其实根因是记忆设计没有做摘要和淘汰策略。
1.2 工具、行动、反馈、护栏:Agent 的“四肢与刹车”
**工具(Tools)**是Agent能力的边界。一个Agent能做什么,不取决于模型多聪明,而取决于你给它接了多少个质量过硬的工具。工具描述写得好不好,比模型大小更影响成功率。我后来把工具描述当成一等公民来写,每条描述都包含功能边界、参数含义、返回值格式、失败场景,模型调用工具的准确率能提高一大截。
**行动(Action)**是真正去执行工具调用的过程。它可能是HTTP请求、数据库查询、文件写入,也可能是调用另一个Agent。很多Demo里行动就是一个函数,但生产环境里行动要包含超时、重试、幂等、审计,否则一次工具调用就能拖垮整个链路。
**反馈(Feedback)**是Agent观察行动结果并调整策略的回路。工具返回了什么、报了什么错、用户是否满意,这些信息必须回流到模型下一轮决策里。没有反馈回路的Agent,本质上就是一个带工具的单轮问答,不是Agent。
**护栏(Guardrails)**是我这两年特别强调的要素。它负责限定Agent能做什么、不能做什么,包括权限控制、内容过滤、成本上限、操作审批。你可以把护栏理解成汽车的刹车系统——平时用不上,但真到失控边缘的时候,它是唯一能救你的东西。
这七个要素可以落成一张我常用自查表:
| 要素 | 负责回答的问题 | 工程对应物 |
|---|---|---|
| 模型 | 谁来裁决下一步做什么 | LLM/推理模型 |
| 规划 | 怎么拆解目标 | ReAct循环、子任务编排 |
| 记忆 | 哪些信息需要留存 | 上下文窗口、向量库、KV缓存 |
| 工具 | 能对外部世界做什么 | Function Calling、MCP、API网关 |
| 行动 | 怎么做一次真实调用 | Worker、HTTP Client、任务队列 |
| 反馈 | 怎么知道做没做对 | 返回值解析、异常捕获、用户确认 |
| 护栏 | 哪些事不能做 | 权限系统、内容审核、熔断器 |
2. 从 Demo 到生产:Agent 工程化的真实差距
2.1 Demo 跑通容易,稳定复现难
我说句实话,现在用LangGraph或者Coze搭一个能聊天、能查天气、能算数的Agent Demo,半小时就能搞定。真正难的是让它稳定复现。我做过一个内部知识库问答Agent,单轮测试通过率能做到90%以上,但是一旦加进多轮对话、历史记忆、工具调用失败重试这些条件,通过率直接掉到60%以下。
问题出在哪?Demo里所有路径都是预设好的,模型正常发挥就能走通;生产环境里每一步都可能偏离:用户输入不规范、工具返回超时、模型返回了不在工具列表里的调用名、数据库连接池满了。任何一个环节出错,Agent不会像普通接口那样直接报错,而是会“自作聪明”地编一个结果继续往下跑。这是Agent工程和传统后端工程最不一样的地方:传统后端是确定性系统,Agent是概率性系统。所以工程手段的核心不是消灭概率,而是把概率失败关进笼子里。
2.2 主流架构模式:别一上来就跑多 Agent
现在主流架构大概有三类:单Agent循环、多Agent协作、人机协同。单Agent循环是最常见的,一个模型加若干工具,循环执行“思考-行动-观察”。多Agent协作是多个角色分工,比如一个研究员Agent、一个写作Agent、一个审核Agent,它们之间传递消息。人机协同是在关键节点插入人工审批,比如转账、发邮件、删除数据这类敏感操作必须人来确认。
我的建议是:第一版不要做多Agent。多Agent表面上是分工,实际上是问题放大器——每个Agent都有自己的上下文和状态,它们之间的消息格式、上下文共享、决策冲突处理都是额外复杂度。我见过一个团队硬上三个Agent,结果他们花了70%的时间在调试Agent之间互相“误解”的问题上。优先把单Agent做到高成功率,再考虑拆角色。
2.3 为什么 FastAPI + LangChain + LangGraph 这个组合经常出现
很多人问我“主流技术栈到底是什么”,其实从开源社区和招聘需求来看,FastAPI + LangChain + LangGraph 是目前最务实的一套组合。FastAPI负责提供HTTP入口和并发承载,LangChain负责模型封装、工具封装、Prompt管理,LangGraph负责把Agent的循环逻辑画成一张可执行的图。这个组合最舒服的地方在于每一层都有明确边界:FastAPI管流量,LangGraph管流程,LangChain管模型和工具适配。你换模型、换工具、加节点,都只动其中一层。我后面给的最小骨架就是基于这套组合。
3. 七个决策点:构建 Agent 时最绕不开的选择题
3.1 决策点一:骨架选型——流程编排还是自由发挥
第一个决策点是Agent的“骨架”:你到底是让Agent自由规划,还是规定好流程图让Agent按部就班走?固定流程适合那些步骤明确、顺序固定的场景,比如“读邮件-提取附件-转写-归档”。自由规划适合开放式任务,比如“帮我分析一份竞品报告”。工程上我建议混合式:大框架固定,小步骤放权。举例来说,你可以规定Agent必须经过“理解需求—检索资料—生成结果”三大阶段,但每个阶段内部怎么走,让模型自己决定。
这个决策点用LangGraph表达最直观。StateGraph的每一个节点就是一条规定路径,但节点内部的Prompt可以写得非常开放。固定流程保证下限,自由发挥拉高上限,两者结合比单用任何一种都稳。
3.2 决策点二:模型选型——推理、上下文、成本怎么平衡
模型选型是七个决策点里最容易被“名气”带偏的。我建议用三个维度打分:推理能力、上下文窗口、单次调用成本。推理能力决定它能多好地驾驭工具,上下文窗口决定它能承载多少历史和工具返回,成本决定你敢不敢放开用。实际项目中不同节点可以配不同模型:入口路由用便宜的小模型,需要复杂推理的节点用最强模型,工具结果解析可以用中等模型。别追求一个模型打通所有节点,既不经济也不灵活。
我还建议做一个“模型回归集”,把你们业务最典型的20条Prompt固化下来,每次换模型或升级模型版本,都在回归集上跑一遍,对比工具调用参数准确率和最终答案正确率。没有这个回归集,你换任何模型都是在赌。
3.3 决策点三:记忆设计——哪些该记、哪些该忘
记忆设计是Agent工程里最容易被低估的一环。我的经验是“分层记忆”:会话上下文只保留最近几轮;历史事实进向量库,按相关性召回;用户画像级的信息单独存结构化字段。每一层都有自己的生命周期和淘汰策略。会话上下文做了窗口截断之后,一定要把早期对话的关键信息“提炼”成摘要塞回上下文,否则Agent会“失忆”,用户说“我刚刚不是说了吗”的时候就是打脸时刻。
另一个关键点是记忆的隔离。多用户场景下,记忆必须按用户ID分桶,向量库检索也要带租户过滤条件,否则A用户的历史记录被B用户的Agent检索到,那不只是体验事故,是数据安全事故。
3.4 决策点四:工具协议——Function Calling 和 MCP 怎么选
工具协议这个决策点越来越重要。一种是模型厂商的Function Calling原生机制,简单直接,Fine-tuning过的模型调用准确率高;另一种是MCP(Model Context Protocol),它把工具定义从代码里抽出来变成一个标准化服务,工具可以被多个Agent动态发现和调用。
我的判断是:单体项目用Function Calling,平台化项目考虑MCP。如果你只有十几个工具,写死在代码里完全够用;如果你要做工具市场、多Agent共享工具、工具由不同团队维护,那MCP的动态发现机制能省掉大量联调成本。但MCP也有代价:多了一层网络传输,延迟和故障点都增加了,选它之前先想清楚你的团队有没有能力把这一层运维好。
3.5 决策点五:状态设计——图的状态怎么流转和持久化
Agent不是无状态接口,它的每一次工具调用都可能改变状态。LangGraph里的State就是用来传递这种上下文的数据结构。我在设计State时有一条原则:只放必要字段,别把整个上下文塞进去。因为State每次流转都可能被序列化、持久化,塞太多不必要的数据会拖慢执行,还会让调试时整个人淹没在信息里。
状态持久化是另一个关键选择。跨会话续跑、人工审批后恢复现场、异常断点续跑,都需要把State存下来。LangGraph提供Checkpointer机制,生产环境建议用PostgreSQL或者Redis实现,按会话ID做持久化。这样用户关掉页面再回来,Agent还能接着上次的进度继续跑。
3.6 决策点六:并发与韧性——Agent 怎么扛流量
“AI Agent怎么扛并发”是最近被问烂的问题。我直接给结论:Agent的并发瓶颈不在模型,而在下游资源和状态隔离。模型API是外部依赖,你能做的只有限流和超时控制;真正会打爆你的,是你自己的工具服务、数据库连接池、以及共享状态。
工程上我分三层做韧性。第一层是入口层,FastAPI用异步接口承载并发,每个请求独立跑一组Agent状态,互不干扰;第二层是执行层,给每个工具调用设置超时和重试,重试必须带退避和幂等;第三层是保护层,给Agent整体执行时间设上限,比如30秒内必须产出结果,否则中断返回兜底文案。这三层都做齐了,Agent才谈得上“扛并发”。
3.7 决策点七:权限、审计与熔断——怎么防止失控
最后这个决策点最不性感,但线上事故往往都出在这。Agent的工具调用权限一定要做最小化授权,比如查天气的工具不需要访问数据库,写邮件的工具不需要读通讯录。每个Agent要有一个身份标识,所有工具调用以该身份做鉴权,这样出了问题能追踪到是哪个Agent干的。
审计日志必须记录下来“模型看到了什么、做了什么决策、调用了哪个工具、传了什么参数、拿到了什么结果”。这一条我在踩过坑之后才真正重视起来——没有审计,Agent乱发了一封邮件后你连复现都是靠猜。熔断机制则是最后一根保险丝:当Agent在短时间内连续报错或者触发安全规则次数过多,直接熔断该Agent的调用,转人工处理。
七个决策点汇总成一张检查清单,我每次评审Agent方案时基本就是对着它打的:
| 决策点 | 推荐默认值 | 需要升级的情况 |
|---|---|---|
| 骨架 | 固定大框架 + 自由小步骤 | 开放式研究型任务 |
| 模型 | 按节点配不同模型 | 核心推理节点用最强模型 |
| 记忆 | 三层分层 + 按用户隔离 | 强个性化场景加强画像存储 |
| 工具协议 | Function Calling | 多团队共享工具时上MCP |
| 状态 | 轻量State + 外部持久化 | 长任务续跑需Checkpointer |
| 并发韧性 | 异步入口 + 超时重试熔断 | 大流量期引入队列削峰 |
| 安全治理 | 最小权限 + 全程审计 | 金融、医疗等强合规场景加审批节点 |
4. 一个能跑的最小骨架:FastAPI + LangGraph 实现
4.1 先把这个例子里面的要素和决策点定下来
我下面给的这个骨架,不是为了炫技,是为了让你看到七要素和七个决策点是怎么落进代码的。例子做的是一个“股票行情查询Agent”:用户输入股票代码,Agent需要调用行情工具,返回价格信息。工具很简单,但链路是完整的模型-工具-反馈-循环。
这里的七要素分别对应:模型用OpenAI兼容接口的ChatOpenAI;规划能力走LangGraph的循环节点;记忆暂用会话消息;工具是get_stock_price;行动通过ToolNode执行;反馈通过工具返回结果回流给模型继续决策;护栏通过FastAPI层的鉴权占位来体现。七个决策点我只演示骨架选型(图编排)、工具协议(bind_tools)、状态设计(AgentState)、并发韧性(异步接口)这四点,其余按默认值处理。
4.2 核心代码:StateGraph 的节点、工具和条件路由
from typing import TypedDict, Annotated from langgraph.graph import StateGraph, START, END from langgraph.graph.message import add_messages from langgraph.prebuilt import ToolNode, tools_condition from langchain_openai import ChatOpenAI from langchain_core.tools import tool class AgentState(TypedDict): messages: Annotated[list, add_messages] @tool def get_stock_price(code: str) -> str: """查询股票代码对应的最新行情价格。 参数: code: 股票代码,A股为6位数字,美股为字母代码。 返回: 包含最新价格和更新时间的字符串。 """ # 这里替换成真实的行情API即可 return f"{code} 的最新价格为 52.30 元,更新于 2025-01-15 10:30。" tools = [get_stock_price] llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) llm_with_tools = llm.bind_tools(tools) def agent_node(state: AgentState) -> dict: response = llm_with_tools.invoke(state["messages"]) return {"messages": [response]} graph = StateGraph(AgentState) graph.add_node("agent", agent_node) graph.add_node("tools", ToolNode(tools)) graph.add_edge(START, "agent") graph.add_conditional_edge( "agent", tools_condition, {"tools": "tools", "__end__": END} ) graph.add_edge("tools", "agent") agent_app = graph.compile()这段代码里最核心的是tools_condition这个条件路由:模型返回了工具调用就去tools节点,没返回就说明任务结束,直接到END。这就是一个最简的ReAct循环——思考、调用、观察结果、再思考,直到模型判定任务完成。add_messages注解的作用是让每一轮的新消息追加到状态里,而不是覆盖旧消息,这是多轮对话的基础。
4.3 用 FastAPI 暴露异步接口并支持流式输出
from fastapi import FastAPI, HTTPException from fastapi.responses import StreamingResponse from langchain_core.messages import HumanMessage fastapi_app = FastAPI() def verify_auth(token: str): # 生产环境这里做JWT或API Key校验 if token != "your-secret": raise HTTPException(status_code=401, detail="invalid token") @fastapi_app.post("/agent") async def run_agent(prompt: str, token: str): verify_auth(token) messages = [HumanMessage(content=prompt)] async def event_stream(): async for event in agent_app.astream_events( {"messages": messages}, version="v2" ): if event["event"] == "on_chat_model_stream": chunk = event["data"].get("chunk") if chunk and chunk.content: yield f"data: {chunk.content}\n\n" yield "data: [DONE]\n\n" return StreamingResponse(event_stream(), media_type="text/event-stream")流式输出对Agent体验非常重要,尤其是工具调用多、耗时长的时候,只有实时流token用户才觉得系统“活着”。你可能会注意到这里我用的是异步流式事件循环,FastAPI的天然异步能力在这里直接承接了并发请求——每个请求进来,都是一组独立的StateGraph运行实例,互不干扰。这就是上个决策点里说的“入口层并发隔离”。
如果你要把这个骨架放到生产,还需要补四样东西:数据库Checkpointer做会话持久化、向量库做长期记忆、工具层加超时重试、审计日志Middleware。骨架本身不需要大改,这四样都是沿着图结构做增量扩展。
5. 真实战场:五个典型坑与排查路径
5.1 Token 消耗失控,白花几百块才看懂
第一个坑发生在我做的第一个Agent上。当时只测功能,没看成本,结果一周烧了几百美元的Token费用。查下来发现两个原因:一是工具返回了一大段JSON,模型每次循环都把完整JSON重新读一遍;二是在模型已经拿到答案之后,因为Prompt里没有明确的“终止条件”,它又自己多跑了一轮“总结”。
排查链路也不复杂:先看Trace里的Token统计,定位到具体节点——工具返回的JSON字段大到每次循环都占用上千Token;再用提示词加固,在工具返回前做裁剪;最后在系统Prompt里写明“如果用户问题已得到完整回答,不要再调用工具,直接回复”,同时给Agent总步数设置上限,比如最多5轮工具调用,超过就强制结束。现在我做任何Agent,第一件事就是给Token消耗和步数上限做配额。
5.2 工具参数幻觉,模型会把参数编出来
第二个坑特别典型。有一次Agent调用查询工具,传了一个根本不存在的产品ID,工具返回空结果,Agent居然没识别出异常,反而一本正经地回答“该产品暂无库存”。你仔细看它的回答逻辑,其实它是在用工具返回的空结果强行编一个合理答案。
根因有两层:一层是工具返回空结果时没有明确语义,模型不知道“空”到底是“没查到”还是“不该查”;另一层是Prompt里没有约束模型“工具结果无法回答问题时必须如实说明”。我当时的修复是:把所有工具的空结果统一规范成{"status": "NOT_FOUND", "message": "未查询到该产品信息,请确认ID是否正确"},并且在工具描述里写明“结果为空时必须告知用户未查到,禁止编造”。修复之后,这类幻觉基本消失。
5.3 状态污染,多用户串了数据
状态污染是我见过最严重的数据事故。当时为了省事,把Agent状态放进了全局变量,结果两个用户同时在用的时候,A用户查询的记录被B用户的Agent看到了。这属于非常低级但非常隐蔽的错误——单用户测试永远发现不了,压测一上就出事。
根治方法是状态隔离。LangGraph的State默认是单次run实例的局部变量,只要你不用全局变量存状态,天然隔离;但如果用了Checkpointer,就必须在调用时显式传入config={"configurable": {"thread_id": "user-123"}},用thread_id做隔离键。我后来要求所有Agent接口都要通过请求头里的用户ID生成thread_id,不允许客户端自己传任意值,防止越权访问别人的会话。
5.4 重试风暴,一次工具超时引发的连锁反应
第四个坑是关于重试的。我给工具调用加了重试机制,设置了3次重试、间隔1秒。逻辑上没问题,但有一个工具本身就有Bug,每次调用都5秒超时。于是一个Agent请求会变成3次调用、外加下游每次都等5秒,整个链路耗时超过30秒,用户投诉接口卡死。更糟的是并发200个请求全部进入重试,下游服务直接被拖到雪崩。
修复路径是三层:工具级重试改为最多1次且只针对网络超时,不针对业务错误;Agent整体执行时长上限设为20秒,到期就返回“当前查询超时,请稍后再试”;再加一个并发信号量,限制Agent实例同时处理的任务数,超出直接排队。记住,重试是把双刃剑,滥用重试比不用重试更危险。
5.5 上下文膨胀,会话越聊越慢
最后一个坑是上下文膨胀。我们有个Agent是服务C端用户的,会话一长,模型请求的输入Token从2千涨到2万,响应时间从2秒涨到8秒,而且用户的早期意图对当前问题已经没有帮助了。
解决思路是:对话超过6轮后,启动摘要线程,把前6轮压缩成一则200字的摘要放进上下文,替代原始消息;同时对工具返回结果按需截断,只保留模型决策所需的字段。这相当于给Agent做了“记忆整理”,短期记忆只留最重要的,长期记忆靠向量库。经过这个改造,响应时间稳定回来了,而且用户满意度没有下降,因为Agent反而更专注于当前问题。
| 坑位 | 现象 | 根因 | 对策 |
|---|---|---|---|
| Token失控 | 单次任务成本高 | 工具返回冗余、缺少终止约束 | 输出裁剪、配额限制、步数上限 |
| 参数幻觉 | 编造查询参数给出假答案 | 工具空结果语义不明、缺少约束 | 规范空结果格式、Prompt声明禁编造 |
| 状态污染 | 用户A看到用户B数据 | 全局状态、thread_id未隔离 | 按用户生成thread_id、禁止客户端传任意值 |
| 重试风暴 | 下游被打爆 | 重试策略过激 | 超时熔断、整体时长上限、并发信号量 |
| 上下文膨胀 | 响应越来越慢 | 上下文塞满历史 | 摘要压缩、工具返回截断 |
6. 上线后怎么做:Agent 的评测与可观测
6.1 评测集:三条线并行
Agent上线之前我强烈建议先建评测集。我们的评测集分三条线:单步工具调用准确率、整体任务成功率、安全合规率。单步准确率是指模型是否选对了工具、传对了参数;整体成功率是指一个多轮任务最终是否交付了正确结果;安全合规率是专门测那些恶意输入、越权操作、敏感信息请求能不能被护栏拦住。
评测集里的每条Case都带着“输入上下文+期望行为”,而不是只带“期望回答”。因为Agent行为的正确性不只体现在回答内容,还体现在它的工具调用路径。跑评测的时候记录每条路径,对比新旧版本的差异,你就能直观看到某个Prompt改动到底影响了什么行为。
6.2 可观测性:Trace 必须从第一行日志开始
Agent的可观测性和传统后端完全不是一个难度等级。传统接口你只需要记录参数、状态码、耗时;Agent你要记录的是决策链路:每一轮模型输入了什么、模型为什么选这个工具、工具返回了什么、模型又是如何根据反馈做下一步判断。没有这条链路,Agent出问题你连复现都难。
我的落地做法是:请求全链路带Trace ID,从FastAPI入口一路透传到LangGraph配置里;每个节点执行前后各打一条结构化日志;工具调用记录请求参数和截断后的返回结果;整个执行链路用标准追踪协议推送到可观测平台。LangChain生态有LangSmith可以做追踪,如果你的预算不允许,用自研的中间件把关键节点日志串起来也有效,但有现成工具就不要重复造轮子,稳定性永远是第一位的。
6.3 成本与迭代:每次改动都要做回归
Agent工程最容易被忽视的事情是“回归”。你改了一个Prompt,可能让某个Case变得更好,但悄悄让另外十个Case变得更差,这种“按下葫芦浮起瓢”的规律在Agent项目里是常态。所以每次改动不论多小,都要跑一遍评测集。我们团队的规矩是:Prompt改动和代码改动一样走CR流程,模型版本升级必须附带评测对比报告,不通过不允许上生产。
成本方面,除了Token,还要盯缓存。相同或相近问题的模型结果可以做语义缓存,把常用Query的答案缓存起来,能省掉一大笔重复调用的开销。不过缓存要带时效性,比如行情查询缓存30秒是合理的,但新闻摘要缓存30秒就不合适了。成本治理是持续的过程,每个月我都会回去看一次链路Token分布,找出最容易被削减的节点。
这套东西做下来,Agent项目就不再是“玄学调Prompt”,而是有评测、有追踪、有回归的工程系统。我刚入坑的时候也迷信过“模型够强就能解决一切”,被线上事故教育过几次以后才明白,Agent工程的核心不是模型,而是把模型能力稳定地关进流程、记忆、工具、护栏这些工程结构里。如果你正打算开始一个Agent项目,我最后想给你一条具体建议:先花两天时间把上面提到的评测集写出来,再开始写Agent代码。评测集是你在无数个决策点之间徘徊时唯一不会骗你的坐标。