1. 先搞清楚:Agent 是“干活的人”,不是“聊天窗口”
AI Agent 的工程实现,最大的误区是拿对话机器人的思路来做。我见过太多团队把大模型接进一个聊天窗口,配上几句系统提示词,就对外叫“Agent”。用户问一句,模型答一句,答不上来就编,工具调用也经常半途而废。真正能“干活”的 Agent,至少应该像团队里一个新来的实习生:有脑子、有记性、有手有脚、有工作日志,还得有人盯着。把 Agent 当成“会自己完成任务的小工具”,而不是“更聪明的对话框”,后面所有架构决策都会顺很多。
我最近被问到的项目,几乎都绕不开同一个问题:AI Agent 怎么从 Demo 走向生产环境?有的卡在模型选型,有的卡在并发,有的卡在 Agent 反复调用同一个工具导致费用飙升。聊到最后,我发现大家缺的不是某个框架的 API,而是一套统一的拆解方法。这套方法我总结了两年:七要素加七个决策点。七要素解决“系统里要装哪些零件”,七个决策点解决“零件装好之后怎么跑起来”。把两套东西想清楚,AI Agent 的工程实现就基本不会跑偏。
1.1 为什么先从七要素开始
我刚开始做 Agent 的时候,也喜欢直接看 LangChain、LangGraph 这类框架的文档,跟着示例抄,抄完就上线。结果线上出了问题,根本不知道从哪开始查。后来我意识到,框架给你的是一堆积木,但积木到底属于哪个功能模块,框架文档不会替你回答。七要素是帮你建立“组件地图”的:模型、记忆、工具、规划、感知、执行、护栏,每一块都对应明确的工程职责。
要素模型的好处是,它可以跨框架复用。你今天用 LangGraph 写了图编排,明天团队想迁移到自研状态机,只要七要素没变,你的接口设计和数据模型就还能用。反过来,如果只盯着某个框架的节点、边、状态,换个框架就抓瞎。所以这篇文章先从要素讲起,再讲决策点,最后用一套具体技术组合落地,顺序别搞反。
1.2 七要素到底是哪七样
用一张表把七要素说清楚,后面再逐个展开:
| 要素 | 工程对应物 | 解决什么问题 |
|---|---|---|
| 模型与推理层 | LLM 接口、模型路由、推理参数 | 决定 Agent 的理解与生成能力下限 |
| 记忆系统 | 会话历史、向量库、摘要模块 | 让 Agent 在多轮或长期任务中不“失忆” |
| 工具与 API 层 | 工具注册表、函数调用、外部服务 SDK | 让 Agent 能查询、修改、操作真实系统 |
| 规划与决策链路 | ReAct 循环、任务拆解器、状态机或图编排 | 决定任务执行的顺序、分支和终止条件 |
| 环境感知与输入侧 | RAG 检索、多模态解析、输入清洗 | 让 Agent 知道当前场景下的真实上下文 |
| 行动执行器与反馈闭环 | 工具执行器、异常捕获、结果回传 | 让 Agent 根据执行结果修正下一步动作 |
| 安全与评估护栏 | 权限校验、输出校验、评测集、审计日志 | 防止 Agent 越权、编造结果、污染线上数据 |
这七个要素不是严格分层的,也不一定七个独立服务。小项目里,记忆、规划、执行可能都在同一个 Python 进程里;大项目里,每个要素都可能拆成独立服务。要素的价值在于给你一张“需求清单”,上线之前逐项打勾。
1.3 光有要素还不够,真正难的是七组决定
很多同学看完七要素会说:这些我都知道啊。但知道要素和做出正确决策是两回事。同样是记忆要素,你是把所有历史塞进 Prompt,还是做摘要后存向量库?同样是工具要素,你是让模型任意调用所有工具,还是每次调用前先过权限?同样是规划要素,你是完全让模型自由发挥,还是用 DAG 固定执行路径?
这些“两回事”就是七个决策点。我会在第三章详细讲。决策点没有标准答案,取决于业务容忍度、成本预算、团队维护能力。一个内部提效工具和一个面向用户的生产 Agent,决策方向可以完全相反。这也是为什么我不建议照抄任何人现成的 Agent 架构。
2. 七要素逐个拆:模型、记忆、工具、规划、感知、执行、护栏
七要素拆开讲,每个都能讲出不少工程细节。这一节我不讲理论,只讲我在真实项目里做过的取舍,以及踩过的一些坑。
2.1 模型选型:别只看“智商”榜单
模型与推理层是 Agent 的地基,但“模型越强越好”这个直觉会害了你。生产环境选模型,我一般按四个维度打分:Function Calling 能力、上下文窗口、推理价格、响应速度。
Function Calling 能力排在第一位。Agent 的核心是工具调用,如果模型不会在正确的时候返回结构化工具调用参数,那后面规划、执行全白搭。有些模型聊天很强,但工具调用经常漏参数,你只能靠一堆补丁去修,修到怀疑人生。
上下文窗口也不是越大越好。窗口大,意味着你可以多塞历史,但塞进去的噪声也会变多,模型反而容易被无关信息带偏。而且大窗口的输入价格往往更高,一次调用塞 100K token,成本直接爆炸。我在小项目里的做法是:上下文窗口只做“兜底上限”,日常任务把单轮输入控制在 8K token 以内,靠记忆模块按需补充。
响应速度经常被低估。内部工具类 Agent 通常要求 3 秒内反馈,如果模型单次推理就要 5 秒,用户早就走了。实测下来,同一个任务用不同模型,端到端时间可能差 4 倍。所以选型时我会拉一张表,把延迟、价格、成功率一起看,而不是只盯着榜单分数。
2.2 记忆系统:短期历史和长期知识分离
记忆系统是 Agent 与 Chatbot 最大的分水岭。Chatbot 只需要保证单轮对话通顺,Agent 却要记住用户说过什么、已经执行过哪几步、之前查到的数据是什么。记忆系统至少要分两层。
短期记忆就是当前任务的会话上下文。工程上最常见的实现是维护一个消息列表,按轮次追加,再在调用模型之前做一次“上下文裁剪”。裁剪不是简单截断,而是把历史里的工具返回结果压缩成摘要。比如工具返回了 2000 行 JSON,你只需要保留“共查到 15 条记录,其中状态正常的 10 条”,这个摘要就足够模型继续决策,成本却差几十倍。
长期记忆负责跨会话的持久信息。我一般用两条路:一条是向量库,把用户偏好、历史结论做成向量,每次会话开始时检索 TopK;另一条是对话摘要库,每轮对话结束后用模型生成一段摘要存好,下次直接加载摘要。长期记忆最容易踩的坑是写入了大量临时状态,比如把某次工具返回的原始数据也塞进向量库,结果下次检索出一堆过期数据,Agent 决策直接被带偏。写长期记忆之前,一定要先做字段过滤和去重。
2.3 工具层:Agent 的手和脚,也是风险集中地
工具调用是 Agent 能“干活”的关键,但工具层也是最容易出安全事故的地方。一个 Agent 暴露给模型哪些工具,决定了它的能力边界,也决定了它的破坏力上限。
工具定义要清晰。每个工具都有名称、描述、参数 JSON Schema。描述要写清“什么时候用、什么时候不用”,比如订单查询工具要写明“仅查询已登录用户的订单,未登录请先调用登录校验工具”。我发现很多 Agent 调用错误,不是因为模型不行,而是因为工具描述写得像开发文档,模型根本看不明白。
工具返回结果要做“观察裁剪”。模型不是人,它不会自动忽略工具返回中的无用字段。工具返回 10KB 数据,模型就会把 10KB 都当成上下文处理,既费 token 又容易让决策漂移。我习惯在每个工具内部加一个format_result函数,把返回内容降采样成“业务结论 + 关键指标”,让模型看到的是加工后的信息。
权限最小化原则必须贯穿工具层。不要一次把所有数据库的增删改查都暴露给模型。哪怕内部工具,也建议把“删除”“批量更新”这类高危操作单独做人工审批。后面安全要素部分我还会强调。
2.4 规划链路:从 ReAct 到状态机图
规划是 Agent 最有“智能感”的部分,也是最容易失控的部分。最简单的规划范式是 ReAct:模型先思考(Thought),再决定调用哪个工具(Action),拿到观察结果(Observation),再继续思考,直到任务完成。这种范式实现简单,适合任务路径不确定的场景,缺点是可控性差,模型可能绕圈子。
生产环境我更推荐固定图编排加局部 ReAct。也就是说,整个业务流程用状态机或图来定义,比如“解析输入 -> 查订单 -> 算价格 -> 生成结果”,每个节点内部再允许模型决定如何调用工具。这样既保留了灵活性,又保证了主干流程不会跑偏。LangGraph 这类框架就是把这种图编排工程化,节点、边、条件分支都变成代码结构。
规划链路还要注意三个参数:最大步数、超时时间、终止条件。最大步数不设,Agent 可能在一件小事上循环 20 次,账单比任务本身还贵;超时不设,一个卡住的工具请求会拖死整个请求;终止条件不设,模型可能在一个错误结果上反复打转。我在所有 Agent 项目里都会加一个全局max_steps,默认 8 步,超出就强制结束并报错。
2.5 环境感知与输入侧:先加工,再喂给模型
环境感知听起来抽象,其实就是两个问题:Agent 需要知道哪些外部信息?这些信息怎么变成模型可用的上下文?最常见的环境感知载体是 RAG:把文档、知识库、业务数据检索出来,拼进 Prompt。Agent 和普通 RAG 的区别在于,检索可能是多轮的,Agent 可能需要一边执行任务一边查更多资料。
输入侧加工往往被忽视。用户输入、上游系统消息、工具返回,都是模型要消化的“外部信号”。如果上游给的数据带格式混乱、大量重复或包含敏感字段,直接进 Prompt,模型要么被噪声干扰,要么生成结果时泄露出不该泄露的数据。我建议在输入口做一个标准化的context_assembler,把原始数据清洗、去重、截断,再按固定模板拼接。
多模态输入今年越来越常见,比如用户上传一张截图,Agent 要识别图片里的表格再执行操作。工程上不能直接把图片二进制塞给模型,要先经过视觉模型抽取出结构化文本,再进入 Agent 的文本决策链路。这一步本质上也是环境感知,只是比文本解析多了一层模型调用。
2.6 行动执行器与反馈闭环:失败要能被看见
工具执行本身不难,难的是执行结果如何反馈给模型。很多 Agent 项目失败在“看不见失败”:工具调用抛异常,被最外层 try-except 吞掉,模型拿到空返回,还以为执行成功,继续编后续内容。
行动执行器要标准化错误反馈。我的习惯是定义统一的工具返回结构:status、data、error。每次工具执行完,无论成功失败,都按这个结构返回。模型看到status: failed且error: xxx,下一步才有机会修正,而不是盲目继续。执行器本身还要记录开始时间、结束时间、调用参数、结果摘要,方便后面做可观测性。
反馈闭环还包含人工介入点。不是所有动作都应该让 Agent 自动执行。涉及退款、删除数据、发送对外消息这些动作,我会在工具层之前加一个审批信号。Agent 执行到这一步时,先把结果存在待审批队列,由人工确认后再继续。虽然多了一道流程,但换来的是生产环境的长期安全。
2.7 安全与评估护栏:不是上线前才想的事
安全护栏是七要素里最容易被拖到最后的部分。很多团队先把 Agent 跑通,再回来补安全,结果发现模型已经能通过 Prompt 注入或恶意输入操控工具调用了。安全必须和功能一起设计,至少要包含四层。
第一层是输入校验和脱敏。进入 Agent 的文本先做长度限制、内容过滤,敏感字段比如身份证、密钥,要么打码要么拒绝进入上下文。第二层是工具权限控制。每个模型会话绑定一个身份,身份对应工具白名单和数据范围。第三层是输出校验。模型生成的 JSON 要用 schema 校验,防止格式错误和内容越权。第四层是审计日志。谁在什么时候让 Agent 做了什么操作,全部记录下来。出问题的时候,日志就是唯一能还原现场的东西。
评估体系是长期维护 Agent 质量的杠杆。我会维护一个评估集,里面放几十条典型业务场景,每次改 Prompt、换模型、调参数,都跑一遍离线评估,对比通过率、成本、耗时。这一步看起来费事,但能避免“今天调好,明天又坏”的循环。没有评估集的 Agent,本质上就是个没人验收的黑盒。
3. 七个决策点:写代码前必须拍板的分岔路口
七要素讲完,你会知道系统里要有哪些东西。但真正决定架构长什么样的,是接下来这七个决策点。每个决策点都是一个二选一或者多选一的问题,选错方向,后面返工成本会很高。
3.1 决策点一:任务是单轮还是多轮
第一个要拍板的问题是:你的 Agent 是执行一次性任务,还是和用户多轮对话。单轮任务型 Agent,比如“用户上传一个表格,Agent 自动清洗并输出结果”,可以用无状态设计,请求来了就执行,执行完就返回,状态不保留。多轮对话型 Agent 则需要维护会话状态、历史消息、用户意图的连续性。
我见过的最常见返工,就是一开始没想清楚这个问题。做了一个看起来像单轮的接口,上线后发现用户会在对话里追问“上一轮那个结果再细化一下”,于是不得不回头补会话存储和上下文管理。如果预判业务一定会走向多轮,一开始就把session_id设计进接口,后面会省很多事。
3.2 决策点二:状态到底存在哪里
Agent 的状态是七要素中记忆系统的具象化。工程上要回答:状态存在内存、数据库还是 Redis?如果是多副本部署,内存状态就无法跨实例共享,用户上次请求落在 A 实例,这次请求被负载均衡到 B 实例,上下文就丢了。
我的默认选择是“服务无状态,状态外置”。Agent 进程本身不保存任何会话状态,所有状态都写到 Redis 或数据库中,每次请求根据session_id加载。这样 Agent 服务可以随意水平扩展,扩到几十个副本也不用担心状态漂移。代价是每次请求多一次状态读写,但相比状态迁移的复杂度,这笔开销值得。
3.3 决策点三:同步返回还是异步任务
Agent 执行通常不是一次模型调用就能完成,中间可能要调多个工具、跑多次推理,整体耗时常在 5 秒以上。如果你用同步 HTTP 接口等待最终结果,前端会长时间转圈,网关也可能超时断开。这个决策点要提前想清楚:接口是同步返回,还是异步触发后通过轮询、Webhook 或 SSE 推送结果?
短任务我用同步接口,比如单工具查询,2 秒内返回,体验最直接。长任务我用异步任务,客户端提交后立刻拿到task_id,后端用消息队列跑 Agent,跑完回调或由前端轮询拉取结果。有些交互感要求高的场景,我会用 SSE 流式输出,把 Agent 的每一步动作实时推给用户,让等待过程变得有反馈,这是目前体验最好的方案。
3.4 决策点四:用循环、顺序还是条件分支
Agent 的编排结构直接影响可预测性。最自由的是纯模型驱动,每一步由模型自己决定下一步做什么,但几乎不可控。最固定的是纯流程编排,所有步骤靠代码写死,模型只负责部分节点,可预测性高但灵活性低。现实是大部分业务需要折中。
我判断的标准很简单:如果业务流程相对稳定,就用 LangGraph 这类图编排把主干定死,节点之间用条件边做分支,让模型只在特定节点内做判断。如果业务流程本身高度不确定,比如开放域研究助手,那就用 ReAct 循环加最大步数限制。没有一个架构能兼顾完全灵活和完全可控,你要先知道自己更怕哪一端失控。
3.5 决策点五:token 预算怎么管
AI Agent 的成本和 Token 强相关。很多新手会问“Agent token 是什么意思”,简单说,Token 是模型计费的基本单位,一句话会被切成若干小块,中文通常一个汉字算 1 到 2 个 Token,英文一个单词大概 1.3 个 Token。Agent 一次任务可能调用模型 5 次,每次 4K 输入加 500 输出,成本就是单次对话的 5 倍。
管理 token 预算不是等项目上线后看账单,而是设计阶段就要定预算。我会给每个 Agent 任务设三层上限:单次模型调用的最大输入 Token、单任务最大调用次数、单任务累计 Token 上限。超过上限,Agent 立即结束并把当前状态返回给用户或人工。上下文裁剪、历史摘要、工具结果降采样,都是为了在同样的预算下装更多有效信息。
3.6 决策点六:并发与限流怎么扛
“AI Agent 怎么扛并发”是今年我收到最多的追问。这个问题之所以难,是因为 Agent 是多重慢请求的叠加:一个 Agent 任务内部可能要调 5 次 LLM,而 LLM 本身延迟高、上游有速率限制,你不能像普通 Web 服务那样简单加线程就能扛住。
扛并发的核心是三件事:限制并发、削峰排队、快速失败。在应用层,我会用信号量或连接池限制最大同时运行的 Agent 任务数,超过上限的请求进入队列或直接返回 429。在 LLM 调用层,我会做单独限流,防止一个 Agent 任务瞬间打爆上游配额。在上游层面,要设置超时和熔断,某个模型供应商连续报错时,自动切换到备用模型或降级返回。还有一个常见手法是把部分步骤改为流式输出,让用户感知前置返回的中间结果,而不是死等最终结果。
并发估算有个简单公式:所需并发数 ≈ 每秒请求数 × 单请求平均处理时间。比如 QPS 是 5,单请求平均 5 秒,那至少需要 25 个并发执行槽。如果单实例限制 10 个并发,就需要至少 3 个实例。这个数不是拍脑袋,是可以提前算出来的。
3.7 决策点七:可观测性和安全边界怎么设
最后一个决策点最容易拖累上线进度:Agent 出了错,你能不能快速知道错在哪一步?普通接口的错误栈一目了然,Agent 的错误却可能出在模型思考、工具调用、参数解析、上下文组装任何一个环节。没有可观测性,排查一个 Agent 问题可能要查十几个日志文件。
我要求所有 Agent 任务必须输出结构化轨迹,包含每一步的节点名、模型输入输出摘要、工具名、工具参数、工具结果摘要、耗时、Token 消耗。这个轨迹不仅用于排查,还能用来做评估集和回归测试。安全边界也一样,要从一开始就决定哪些操作必须人工审批,哪些数据不允许进入模型上下文。这个决策点没有定好,后面每次上线都像走钢丝。
4. 一次真实的落地拆解:FastAPI + LangGraph 搭一个最小 Agent
七要素和七个决策点讲完,需要一个具体案例串起来。我最近做了一个“订单价格测算 Agent”的 Demo,用 FastAPI 提供 HTTP 接口,用 LangGraph 做编排,内部串联解析订单号、查订单、算折扣几个步骤。技术组合不稀奇,但每一步都对应前面的决策点。
4.1 最小骨架:先把流程图画成代码
这个 Agent 的业务很简单:用户输入一段自然语言,比如“帮我查一下订单 20240815001 的应付金额”,Agent 先解析出订单号,再查订单金额,最后根据金额计算折扣。完整代码里还有 LLM 调用,但为了讲清楚骨架,我用一个简化版本展示核心编排:
from fastapi import FastAPI from pydantic import BaseModel from langgraph.graph import StateGraph, END from typing import TypedDict, List class AgentState(TypedDict): query: str order_no: str | None price: float | None result: str steps: List[str] def extract_order_no(query: str) -> str: # 简化实现:从文本里用正则抽取订单号 # 真实项目里这里可以由 LLM 完成 return "20240815001" async def get_order_price(order_no: str) -> dict: # 简化实现:调用订单服务 API # 真实项目里这里用 httpx 请求内部服务 return {"price": 1200.0} async def parse_order(state: AgentState): state["order_no"] = extract_order_no(state["query"]) state["steps"].append(f"parse_order -> {state['order_no']}") return {"order_no": state["order_no"]} async def query_order(state: AgentState): data = await get_order_price(state["order_no"]) state["price"] = data["price"] state["steps"].append(f"query_order -> price={data['price']}") return {"price": state["price"]} async def calc_price(state: AgentState): discount = 0.9 if state["price"] > 1000 else 1.0 state["result"] = f"订单 {state['order_no']} 应付 {state['price'] * discount:.2f} 元" state["steps"].append(f"calc_price -> {state['result']}") return {"result": state["result"]} graph = StateGraph(AgentState) graph.add_node("parse_order", parse_order) graph.add_node("query_order", query_order) graph.add_node("calc_price", calc_price) graph.add_edge("parse_order", "query_order") graph.add_edge("query_order", "calc_price") graph.add_edge("calc_price", END) agent_app = graph.compile() app = FastAPI() class AgentRequest(BaseModel): query: str @app.post("/agent/run") async def run_agent(request: AgentRequest): initial_state = {"query": request.query, "steps": []} final_state = await agent_app.ainvoke(initial_state) return { "result": final_state.get("result"), "steps": final_state.get("steps"), }这个骨架里,LangGraph 负责固定流程,每个节点是纯 Python 函数,状态统一放在AgentState里。运行时,请求进来先解析,再查订单,再算价格,最后返回。真实项目中,extract_order_no和get_order_price内部会调用 LLM 或其他服务,但整体编排结构不变。
4.2 几个关键工程参数:超时、并发上限、token 成本
骨架跑通之后,真正花时间的是调参数。我给这个 Demo 配置了如下参数:单次模型调用超时 10 秒,整个 Agent 任务超时 30 秒,最大执行步数 5 步。这些参数不是随手填的,而是根据 LLM 平均延迟和工具接口 P95 延迟算出来的。
再算并发。假设这个 Agent 平均处理时间是 5 秒,线上期望 QPS 是 3,那么并发槽位至少需要 15。我部署两个实例,每个实例用信号量限制 8 个并发任务,总共 16 个槽位,可以覆盖峰值。同时 LLM 调用层单独限制每秒最多 5 次请求,防止单个用户连续刷接口时把上游配额耗尽。成本预算方面,单任务 Token 上限设为 20K,超过直接截断并返回错误,让前端提示用户重新提交。
4.3 部署和观测:发布不是终点
这个 Demo 我部署成 Docker 容器,用 Gunicorn 多 worker 跑 FastAPI 服务。但因为 Agent 是异步任务,worker 数量不能像普通 Web 服务那样开很多,否则内存和 LLM 连接池都会被打爆。我的做法是每个容器 2 个 worker,靠多副本横向扩展。
日志方面,每个请求都会输出一条结构化 JSON 日志,包含任务 ID、耗时、每一步的工具调用、Token 消耗。我还把步骤轨迹额外写到本地文件,方便事后重放。上线第一周,我就靠这个轨迹日志发现了一个问题:模型在解析订单号时,经常把“订单”两个字一起解析进去,导致查询接口报错。没有轨迹日志,这种问题根本没法定位。
5. 常见问题与排查技巧实录
把七要素和七个决策点都过一遍之后,最后分享几个我真实踩过的坑。这些问题几乎每个 Agent 项目都会遇到,排查思路比具体修复更有价值。
5.1 Agent 反复调用同一个工具,像一个死循环
现象是日志里模型不断调用同一个工具,参数几乎一样,结果也一样,但模型就是不知道结束。通常有两个原因:工具返回的结果不够清晰,模型没得到“任务已达成”的信号;或者没有设置最大步数,模型在探索中迷失。
先看工具返回里是否有明确的完成标志。如果工具返回是一堆数据,没有总结性结论,我会增加一个summary字段,比如“查询成功,共 3 条记录,符合条件 1 条”。同时把最大步数从 10 改成 5,强制截断。还有一招:对同一个工具设置短时间内调用次数上限,比如同一个工具连续调用 3 次且参数相同,直接结束任务并返回“需要人工介入”。
5.2 token 费用突然飙升,账单吓人
最常见的原因是历史消息无限累积。多轮对话中,每轮都把完整历史塞进 Prompt,随着轮数增加,单次调用输入越来越大。另一个原因是工具返回过大,模型把它全部当成上下文。还有可能是规划链路失控,小任务循环了 10 步,每步都调大模型。
排查时先看轨迹日志,统计每一步的输入 token 和输出 token。哪里最大就优化哪里。我在项目中做了两层保护:第一层是历史压缩,超过 5 轮后把最早的历史改成摘要;第二层是工具结果截断,超过 2000 字符的部分用“内容过长,已截断”替代。这两招能砍掉 60% 以上的 token 消耗。
5.3 并发一高就大面积超时
很多团队把 Agent 接口当成普通接口,用线程池硬扛,结果 LLM 调用把线程池占满,新请求全部排队,最终超时。排查时先看系统指标:CPU 不高,但请求耗时飙升,大概率是线程池或连接池耗尽。
我的处理是三步走:先把同步调用改成异步任务,长任务直接返回task_id,不让 HTTP 连接干等;再给并发加信号量,超过上限返回 429,而不是让请求在队列里无限堆积;最后给所有 LLM 和工具调用加超时,宁可快速失败,也不拖住后面的请求。另外可以把中间步骤流式输出,用户至少能看到“正在查询订单”这类进度,比一直转圈体感好很多。
5.4 工具明明调用失败,模型却把失败编成成功结果
这个问题最隐蔽,也最危险。比如查询订单服务返回error: order not found,模型却在回复中写“该订单金额为 1200 元”。原因是工具返回的错误格式不标准,或者异常被捕获后返回了空数据,模型误以为查询成功但没查到,于是“补全”了一个结果。
解决方法是执行器必须统一返回{status, data, error},并且当status=failed时,data必须为null,不允许既有错误又有数据。同时要在 Prompt 里明确告诉模型:当工具返回失败时,必须向用户说明失败原因,禁止自行推断或填补数据。再配合评估集,专门用几个“工具失败”的用例做回归,防止以后改 Prompt 时又改坏。
5.5 多轮对话中 Agent “失忆”,忘记自己刚才说过的话
用户问了一句“上一轮那个结果再帮我算一遍”,Agent 完全不知道“上一轮”指什么。原因通常是没有维护短期记忆,或者短期记忆只存了用户消息,没有存工具执行结果和中间状态。比如上一轮查询到的订单号没有被写入状态,下一轮自然接不上。
我的做法是把每轮的状态快照存进会话存储,包括解析出的实体、工具结果、中间结论。下一轮加载时,除了历史消息,还要带上状态快照,Prompt 里明确说“这是当前任务上下文”。如果是长时间跨会话,则用向量库做长期记忆检索。短期记忆管“上一句”,长期记忆管“上周的用户偏好”,两者不能混用。
| 问题 | 典型原因 | 快速排查方向 |
|---|---|---|
| 工具循环调用 | 工具返回缺少完成信号 / 未设最大步数 | 查最后几步观察结果是否重复 |
| token 成本飙升 | 历史无限累积 / 工具返回过大 | 按步骤拆分 token 消耗统计 |
| 并发超时 | 线程池耗尽 / 上游限流 | 查活跃连接数和线程池队列 |
| 失败结果被编造 | 错误返回不标准 | 查工具返回结构是否统一 |
| 多轮失忆 | 状态未保存 / 短期长期记忆混用 | 查会话状态是否有快照 |
6. 最后说点我的个人体会
这套“七要素 + 七个决策点”的分析框架,我用了快两年。第一版 Agent 项目只关注模型和提示词,上线后问题不断;后来把记忆、工具、护栏逐项补上,才慢慢稳定下来。现在每接到一个新的 Agent 项目,我都会先画一张要素清单,再在评审时逐个过七个决策点,不在代码里临时拍板。
如果你是从零开始,我的建议是先做一个最小闭环,哪怕只是固定流程加一个工具,也要把七要素的骨架搭出来。不要一上来就追求“模型完全自主规划”,那样你连失败都不知道该看哪里。另外,多留意社区里围绕 LangGraph、Spring AI、扣子这类平台的最佳实践,它们的共同点都是在帮你解决并发和状态管理,而不是花哨的“智能感”。真正可靠的 Agent 工程,永远是那些看起来无聊、但每一步都可观测、可控制、可回滚的设计。
最后分享一个小技巧:每次 Agent 上线前,我习惯跑一组“故障注入”测试,故意让工具返回超时、让用户输入恶意指令、让模型触达 token 上限,看护栏能不能兜住。这一轮测试暴露的问题,往往比功能测试多一倍。AI Agent 工程实现的难点从来不是“让模型聪明”,而是“让系统可靠”。只要抓住七要素和七个决策点,你的 Agent 项目至少不会在同一个地方反复翻车。