我今年重构了一个客服 Agent,从第一版写提示词到跑通 Demo 只花了一个下午,但从“能跑通”到“敢把真实流量切进去”,前后折腾了快三周。这个落差让我想明白一件事:AI Agent 的工程实现,真正的难点从来不是把几个模块凑齐,而是想清楚每个模块之间的约束关系。这篇文章我会用“七要素 + 七个决策点”这个框架,把一台 Agent 从里到外拆一遍——七要素告诉你它由哪七部分组成,七个决策点告诉你动手实现时每一处该拍板选什么、为什么这么选。不管你在用 LangChain、LangGraph、CrewAI,还是打算从零手写,这套框架都适用;只要你正在做 Agent 项目,或者准备做,都能从中找到可以直接抄的选型逻辑和踩坑经验。
1. 先拆解:一台 Agent 的七要素,少哪块都跑不稳
先说整体的感觉。Agent 本质上是把“理解、决策、执行、记忆、复盘”这五个能力,用程序的方式串起来。你可以把它想象成一家外卖店:店长负责接单和安排任务,服务手册规定了话术和红线,后厨的锅碗瓢盆是工具,订单记录是记忆,出餐流程是规划,备料间的面积决定了同一时间能接多少单,而应急预案决定了翻车之后能不能重建。任何一个环节缺失,这条“能独立干活”的链路就不成立。这七块,就是我说的七要素。
1.1 大模型:它是 Agent 的“脑”,但选脑子和选聊天模型不是一回事
大模型承担了 Agent 里面所有的理解、生成和推理工作,这是Agent 最核心的“引擎”。但工程视角下,“聊天效果好”和“适合做 Agent 引擎”是两码事。你需要重点看三个工程维度:
- 上下文长度:能不能把客户提供的长订单截图、长文档塞进去,还留有余量。
- 工具调用稳定性:模型能不能从用户说的话里准确抽取出工具参数,并且在工具返回错误后正确修正。
- 价格和延迟:这个直接决定你的商业模式成不成立,日请求量一大,Token 成本会非常敏感。
我第一版客服 Agent 用的是一款对话体验很好的通用模型,接入后发现问题基本都出在工具调用上:让它调订单查询接口,它经常把订单号传成座位号,或者干脆漏传参数。这不是模型笨,而是这类模型的训练目标更偏向“像人一样聊天”,并没有针对函数调用做足够多的对齐。换成一款 Agent 增强型模型之后,同样一个工具调用场景,稳定性马上就上来了。
提示:不要看榜单选模型。建一个二十到三十条的工具调用回归测试集,每次换模型先把测试集跑一遍。这比任何评测文章都靠谱。
1.2 系统提示词:把角色和红线写进每一轮对话
系统提示词是每轮对话都会生效的“总纲”。它是软约束,告诉模型你是什么角色、要解决什么问题、哪些事不能做、回答风格是什么。很多人写提示词只写一句“你是一个智能客服”,这远远不够。
我常用的系统提示词是分层结构:角色定义、任务边界、工具使用策略、输出格式、安全红线、兜底话术。举一个实际片段:
你是一个电商客服助手,只能使用提供的工具回答问题。 - 订单相关问题:必须调用 query_order_status,绝不猜测订单状态。 - 知识库问题:必须基于检索结果回答,检索不到时直接说明不知道。 - 禁止编造工单号、赔付金额等任何事实性信息。 - 输出格式:最终回答必须为纯文本,禁止 Markdown 表格。 - 如果用户表达不满,先道歉并引导转人工,不要争论。但你要记住,提示词是“软约束”,代码里的校验才是“硬约束”。比如很多提示词写“必须按 JSON 格式输出”,模型还是偶尔会飘出解释性文字。正确做法是在后端强制解析 JSON,解析失败就走修复逻辑,而不是指望提示词兜底。另外,提示词要版本化管理,改版了能回滚。我把提示词放到配置中心,每次改动都有记录,出了问题一条命令切回旧版本。
1.3 工具调用:Agent 对外部世界唯一能出手的地方
没有工具的 Agent 只是个“聊天机器人”,有了工具,它才能查数据库、查订单、发消息、操作业务系统。工具由四部分组成:名称、描述、参数 Schema、执行函数。前面两个是给模型看的,后面两个是给你自己用的。
这里面最容易被低估的是“描述”。模型是靠描述来判断什么时候该调用这个工具、该传什么参数的。描述写得模糊,模型就会在错误时机调用;描述写得太长太啰嗦,又会让模型混淆。我的经验是,一个工具的描述尽量控制在三到五行,把“什么时候用”“必须传什么参数”“返回值长什么样”说清楚即可。
工具数量也要克制。每加一个工具,模型选错工具的概率就高一点,系统维护成本也多一笔。客服 Agent 我一开始挂了十几个工具,效果反而不如后来精简到六个。那些低频操作,我把它收进子 Agent 或者干脆去掉。像“让小红书自动发消息”这类自动化需求,本质就是包一个内容审核 + 发布接口的工具,但这类工具一定要放在审批流程后面,不能让它自己随手发。
工具返回给模型的内容也要做裁剪:千万不要把数据库原始表或者几万字文档直接塞回去,模型根本处理不过来。我会在工具内部先做摘要提炼,只把结论性的字段返回,让模型的上下文保住“有余量”。
1.4 记忆:短期是上下文,长期是资产
记忆分两层。短期记忆就是当前会话里的对话上下文,模型处理完这一轮,前面的内容如果不手动留存,就彻底丢了。长期记忆是跨会话保存的用户偏好、历史结论、关键事实,比如“这位用户上个月投诉过一次配送慢”“他习惯用微信支付”。
工程上,短期记忆最自然的载体就是上下文窗口,但它有个问题:会话一长,前面内容太多,你塞不回去。所以我会在会话进行到一定长度后,先把旧对话压缩成一段摘要,再放回上下文里。这就是“摘要式记忆”。
长期记忆的实现要看场景。我的客服 Agent 用 Redis 存最近三十天的会话摘要和关键事实,用键值结构按 session_id 组织;知识问答里的产品资料,才用向量库做语义检索。很多人一听“ Agent 记忆”就上向量数据库,这其实是杀鸡用牛刀——如果只是“按用户 ID 拉最近十条摘要”,一张 Redis 或者 SQLite 表就够了,又便宜又快。
1.5 规划:从“下一步干嘛”到“一整条执行路径”
规划能力决定 Agent 是“问一句答一句”,还是能自主完成多步任务。比如用户说“我要退掉这个订单,换个新的”,Agent 需要先查订单、再查退款规则、然后判断是否可退、最后发起退款流程,这是四步以上的任务,需要明确的规划机制。
规划有三档:固定链(Chain)、图编排(Graph)、自由规划(Plan-and-ReAct)。固定链适合流程永远不会变的场景;图编排适合有分支和条件判断的场景;自由规划适合需求开放的场景,比如“分析这份报表,找出异常”。但自由规划最危险,因为它意味着模型可以自己决定下一步干什么。
工程上必须给自由规划设置硬性上限:最大步数、最大工具调用次数、超时时间。我见过一个 Agent 因为没有步数上限,在“找资料”阶段反复调用搜索工具十几轮,花掉了数百元 Token 才被人工发现。设置了 max_turns=5 之后,它最多只能跑五步,超了就走兜底话术。
还有一个原则:能用代码写死的规划路径,就别让模型去“想”。比如“用户说查订单 -> 先调订单接口,再判断结果”,这个流程是确定的,直接画成图;只有“用户这句话到底是不是投诉”这种语义判断,才需要模型决策。
1.6 上下文窗口:所有要素都要在 Token 预算内运行
很多人问“AI Agent token 是什么意思”,这里统一说清楚。Token 是模型处理文本的最小单位,可以理解成文本被切成的“字块”。英文里一个词通常对应 1~1.5 个 Token,中文里一个字大约对应 1~2 个 Token。一万字中文,大概对应一万五到两万 Token。Token 决定了三件事:能塞进模型窗口的内容上限、生成答案的长度上限、还有你要付的钱。
上下文窗口不是模型自己管理的,是你在代码里管理的。我每个 Agent 项目都会做一张 Token 预算表,比如一个上下文窗口为 80k Token 的模型,我会这样分配:
- 系统提示词 + 工具描述:预留 10k。
- 工具结果注入:单次上限 20k,超了就压缩。
- 历史会话摘要:最多 15k。
- 用户当前输入:最多 10k。
- 生成回答的预留空间:10k 左右。
有了预算表,每次请求前先算一遍当前占用,超了就触发截断、摘要或者丢弃策略。这样才不会出现“第二轮对话就超出窗口”的低级事故。
1.7 反馈与自愈:让 Agent 从错误里爬起来而不是原地转圈
工具会失败,模型会跑偏,第三方接口会超时。Agent 要能在生产环境稳定运行,必须有错误反馈闭环:捕获异常 -> 把结构化错误信息回灌给模型 -> 模型修正方案 -> 有限重试 -> 重试失败后熔断兜底。
举个例子,Agent 调用订单查询工具时,如果参数格式错了,接口返回{"code": 400, "message": "order_id is invalid"}。这时候我们不能直接把原始报错丢给模型就完事,而是要把错误包装成模型能理解的结构化语言:
【工具调用失败】 工具:query_order_status 输入:{"order_id": "abc"} 错误:order_id invalid,订单号应为纯数字,长度 12 位。 请修正参数后重试,或向用户说明无法查询。这样模型下一轮就能自我修正,要么补全订单号,要么转人工。没有这个闭环,模型可能会拿着错误日志继续推理,甚至编造一个不存在的订单状态。这是“ Agent 能干活”和“ Agent 能持续干活”之间的分水岭。
2. 再落地:七个决策点,每一处都决定工程成败
七要素拆完了,接下来进入动手阶段。一个 Agent 能不能从 Demo 变成生产系统,取决于你在七个关键决策点上怎么选。注意,这些决策点没有绝对正确的答案,只有“适不适合当前场景”的取舍。
2.1 决策一:框架选什么——自研循环、LangGraph 还是别的
市面上框架太多了:LangChain、LangGraph、CrewAI、AutoGen,还有 Java 团队在关注的 Spring AI,以及追求极致性能的 Rust 实现。我的看法是:先想清楚你的业务复杂度,再选框架。
如果业务只是“接收问题 -> 调一两个工具 -> 生成回答”,我推荐直接用几十行代码写一个循环,手动管理上下文和工具调用。这样最简单、最好调试、也最可控。我见过太多项目为了用 LangChain 而用 LangChain,最后被抽象层绕得晕头转向。
一旦业务出现这些特征,就该认真考虑 LangGraph 这类图编排框架:
- 流程有多个分支,不同意图走不同路径。
- 需要持久化中间状态,进程重启后能恢复。
- 需要“人工审批节点”,比如自动发消息前等待管理员确认。
- 需要断点续跑,而不是每次从头执行。
我当时选 LangGraph,就是因为它把“节点 + 边 + 状态 + 断点”做得足够成熟。它在底层是一张 StateGraph,每个节点是一个函数或模型调用,边决定执行顺序,状态对象在节点间传递。我可以非常清晰地看到每一步在干什么、卡在哪里。至于 Rust 写 Agent,性能确实好,但生态、调试工具和团队迭代速度会拖后腿,如果没有特殊的高吞吐硬需求,不建议;Java 团队如果本身就是 Spring 技术栈,Spring AI 可以平滑接入,但同样要注意“为框架而框架”的陷阱。
2.2 决策二:模型选什么——上下文、价格、工具能力三条底线
模型选型的决策本质是三条底线的平衡:上下文长度、价格、工具调用稳定性。
上下文长度决定你能喂多大的材料。如果知识库检索经常返回上万字,你就不能选上下文太短的模型。价格决定成本模型能不能跑通,尤其你打算做 to B 服务时,单次调用成本直接关系到毛利。工具调用稳定性则是 Agent 场景里最要命的一条,那些“榜单上很强”的模型,实际跑工具调用可能连参数都填不对。
我的选型方法是拿真实的业务场景做回归测试,不拿通用榜单当标准。比如客服场景,我会准备二十条消息,每条都要求模型抽出“订单号 + 查询意图”,然后看准确率。这样测出来的结果,比看任何评测文章都直观。不要只盯着一个模型用,当前模型不行,换一个可能立刻解决问题;但换模型前,一定把原来模型的工具调用和提示词版本全部归档,方便回滚。
2.3 决策三:编排走哪条路——Chain、Graph 还是自由规划
这个决策和框架选型相伴而生。固定 Chain 简单可靠,适合问卷式流程,比如“先收集问题类型,再收集订单号,再查询”,每一步都是固定的;Graph 适合有分支、有人工审批、需要中途暂停的场景;自由规划适合开放探索,让模型自己决定要不要搜索、要不要画图。
我的建议是:不确定的分支交给模型,确定的分支交给代码。这句话值得写进每个 Agent 项目的开发规范里。所谓“模型决策点”,是指模型需要从候选步骤里选一个,这是必要成本;但如果只是“根据返回码判断走 A 还是 B”,这就是确定分支,应该用代码的 if/else 写死。每减少一个模型决策点,就减少一分成本、一分失控风险、一分延迟。所以我在客服 Agent 里保留的模型决策点不超过三个:意图识别、是否补问、最终回答生成。其余全部由代码和图结构控制。
2.4 决策四:记忆放哪——向量库不是唯一答案
做记忆方案前,先回答四个问题:
- 需要跨会话记忆吗?还是只记住当前会话就够。
- 要记住什么粒度?是“用户全名 + 最近一次投诉”,还是“过去三十天所有行为轨迹”。
- 数据敏感吗?能不能进外部向量库。
- 读写频繁吗?需要多低延迟。
根据答案选存储:
| 存储方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| Redis + JSON | 会话摘要、用户画像 | 快、简单、便宜 | 不适合语义检索 |
| SQLite / MySQL | 结构化记录、关系查询 | 通用、好维护 | 不适合模糊语义匹配 |
| 向量数据库 | RAG、语义检索 | 能查“语义相近” | 运维重、成本高、延迟高 |
| 内存 | 单机临时记忆 | 最快 | 重启即丢 |
我的客服 Agent 只需要记得“这个用户上次问了什么、上上次投诉过什么”,Redis 存摘要绰绰有余。只有当你要做“从产品手册中找到语义相近的段落”时,向量库才真正必要。别被“ Agent 必须有向量数据库”这种话绑架。
2.5 决策五:并发怎么扛——先把账算清楚再选方案
“AI Agent 怎么扛并发”这个问题,几乎每次技术分享都会被问。核心是要先算清楚账,再选架构。
假设一次请求从进来到最后返回,平均处理时长为 T 秒,目标并发数是 N,那么系统里同时处于“处理中”状态的请求大约是 N 个。如果 T=8 秒,你想要支撑 20 个并发请求,就需要约 20 个 Worker 并行处理。如果你只有一个同步的 HTTP 服务,每个请求都等着 Agent 跑完再返回,那么第 20 个请求要排队 160 秒,体验直接崩掉。
我采用的方案是:FastAPI 接请求 -> 立即返回task_id-> 把任务写进 Redis 队列 -> 后台 Worker 消费队列并跑 LangGraph -> 前端轮询任务状态。这样 HTTP 层和 Agent 执行层彻底解耦,Web 服务不会被长任务拖死。
异步也很关键。模型 API 调用、工具 HTTP 请求绝大多数是 IO 等待,不占 CPU,所以可以用asyncio.Semaphore控制并发模型请求数,让一个进程同时等十几个模型返回。CPU 密集的解析、向量化工作再丢到进程池。
import asyncio sem = asyncio.Semaphore(20) async def run_agent_with_limit(session_id: str, message: str): async with sem: return await run_agent(session_id, message)2.6 决策六:Token 怎么控——成本不是上线后才操心的事
Token 成本必须从一开始就算。费用公式很简单:
单次调用费用 = 输入Token数 / 1e6 × 输入单价 + 输出Token数 / 1e6 × 输出单价举个例子,假设模型输入单价 20 元/百万 Token,输出单价 60 元/百万 Token。一次客服问答平均输入 2500 Token、输出 600 Token,单次成本是:
2500 / 1e6 × 20 + 600 / 1e6 × 60 = 0.05 + 0.036 = 0.086 元如果每天一万次,就是 860 元/天,一个月约两万五。这还没算工具调用多次导致的额外输入。所以成本控制不是事后看账单,而是要从架构上省钱。我的三个主要手段:
- 缓存:相同系统提示和前几轮对话前缀可复用缓存,命中后输入成本大幅下降。
- 压缩:历史会话超过预算就压缩成摘要,宁可丢失细节,也不能让每次请求都满载长文本。
- 精简单次注入:工具结果只返回摘要和关键字段,不把原始全文塞进上下文。
另外,每个请求都要记录input_token、output_token、工具调用次数,按会话归集。我见过没有 Token 监控的项目,一个失控 Agent 在半夜把当月预算烧掉大半,等早上发现已经晚了。
2.7 决策七:失控怎么熔断——给 Agent 装一个手刹
Agent 越自由,越需要熔断机制。这一步最容易偷懒,但偷懒的代价也最大。我会把所有工具按风险分级:
- 只读操作(查订单、查知识库):可以自动执行。
- 写操作(更新状态、保存记录):需要二次确认。
- 高风险操作(发消息、下单、涉及资金的动作):人工审批不可省。
像期货自动交易这类需求,如果做成 Agent 自动下单,一旦策略出问题,损失是不可逆的。所以我的原则是:凡可能造成不可逆后果的动作,模型只负责生成“拟执行指令”,挂起等待人工确认后才真正放行。在 LangGraph 里,这就是一个“人审节点”:Agent 执行到该节点,状态挂起,等到管理员点击通过后才继续。
同时要配置熔断参数:单任务最大轮次、整体超时、连续失败次数、兜底话术。我在生产环境通常这样设:
max_turns = 5,超过直接终止。- 单次执行整体超时 30 秒。
- 工具调用连续失败 3 次,不再重试,转人工。
- 所有工具入参出参全部写入审计日志,便于复盘。
3. 端到端走一遍:客服 Agent 从七要素到落地
理论说完了,拿我重构的客服 Agent 来做一次完整的走查。这个项目是和某电商业务方合作的,需求有三块:基于产品手册的问答、订单状态查询、投诉问题分类与转交。
3.1 项目需求和整体链路
整体链路长这样:
用户消息 -> FastAPI /chat 接口 -> Redis 任务队列 -> Worker(LangGraph 执行) -> 知识库检索 / 订单API -> 生成回答 -> 写 Redis 任务结果 -> 前端轮询 /task/{task_id}七要素在这个项目里怎么配的:
- 大模型:选了 Agent 增强型模型,上下文 80k Token,重点验证了工具调用稳定性。
- 系统提示词:采用分层结构,角色、边界、工具策略、输出格式、红线、兜底都在里面。
- 工具:精简到四个——知识库检索、订单查询、投诉工单创建、转人工。
- 记忆:Redis 存最近三十天会话摘要;知识库段落走向量检索。
- 规划:LangGraph 固定图,意图分支交给三个模型决策点,其余代码化。
- 上下文管理:Token 预算表严格控制注入量。
- 反馈与自愈:工具错误结构化回灌,连续失败三次转人工。
3.2 核心代码:LangGraph 节点、工具注册与记忆封装
先看 FastAPI 接口。它只负责收请求、写队列、返回 task_id,不等待图执行完,这样 HTTP 层永远轻快:
from fastapi import FastAPI, BackgroundTasks from pydantic import BaseModel app = FastAPI() class ChatRequest(BaseModel): session_id: str message: str @app.post("/chat") async def chat(req: ChatRequest): task_id = task_queue.enqueue( session_id=req.session_id, message=req.message, ) return {"task_id": task_id, "status": "pending"}LangGraph 的图我简化成五个节点和三条条件边。核心是那个路由函数,它根据意图决定下一步走哪个节点:
from typing import TypedDict class AgentState(TypedDict): session_id: str user_message: str intent: str tool_results: dict answer: str def decide_next(state: AgentState) -> str: if state["intent"] == "order_query": return "query_order" if state["intent"] == "knowledge_query": return "retrieve_kb" if state["intent"] == "complaint": return "create_ticket" return "fallback"工具注册是重头戏。每个工具要把参数 Schema 写清楚,这是模型能不能正确调用的关键。订单查询工具的注册逻辑大致是这样:
from pydantic import BaseModel, Field class OrderQueryInput(BaseModel): order_id: str = Field(description="订单号,12位纯数字") def query_order_status(input: OrderQueryInput) -> dict: raw = order_api.fetch(input.order_id) return { "ok": raw["success"], "data": { "order_status": raw["status"], "delivery_time": raw["eta"], "summary": f"订单{input.order_id}当前状态为{raw['status']}", }, "error": raw.get("error"), }记忆封装。短期记忆就是会话摘要的读写,我把它做成一个独立的 MemoryClient,这样 LangGraph 节点里不直接碰 Redis:
class MemoryClient: def get_recent_summary(self, session_id: str) -> str: # 从 Redis 按 session_id 取摘要,没有则返回空 ... def save_turn(self, session_id: str, user_msg: str, assistant_msg: str) -> None: # 把本轮对话追加进摘要,超长时重新压缩 ...3.3 关键配置表与成本估算
上线前的关键参数,我整理成了一张配置表:
| 配置项 | 值 | 说明 |
|---|---|---|
| Worker 数 | 20 | 后台执行 LangGraph 的进程数 |
| Redis 队列最大长度 | 500 | 超过直接返回“当前繁忙” |
| 单请求最大轮次 | 5 | 防止 Agent 失控死循环 |
| 单次整体超时 | 30 秒 | 超过则任务标记失败 |
| 工具连续失败上限 | 3 次 | 超过转人工兜底 |
| 检索结果注入上限 | 4000 Token | 超出先截断再注入 |
| 历史摘要上限 | 15000 Token | 超过后重新压缩 |
| 缓存时间 | 1 小时 | 相同前缀对话复用缓存 |
成本估算以日均 1.5 万次请求为例:
| 指标 | 数值 |
|---|---|
| 平均输入 Token | 2200 |
| 平均输出 Token | 550 |
| 缓存命中率 | 40% |
| 单次成本(均值) | 约 0.062 元 |
| 日成本 | 约 930 元 |
| 月成本 | 约 2.8 万元 |
这个成本量级决定了后续的优化方向:优先提高缓存命中率、精简工具结果、加大上下文压缩力度,而不是盲目换贵的模型。
3.4 上线后的实测数据
上线压测和灰度观察到的数据如下:
- P50 响应时间:3.2 秒。
- P95 响应时间:8.1 秒。
- 任务成功率:99.2%。
- 主要失败原因:第三方订单 API 超时、知识库检索偶发无结果。
- 故障事件:两周内两起,一起是订单 API 抖动导致大面积超时,一起是上下文压缩策略误伤,把关键订单信息压缩丢了。
这两起故障分别推动了两项改进:一是给工具调用加了独立超时和快速失败,避免单个接口拖垮整个任务;二是压缩策略加了“关键字段保护”,订单号、工单号、金额这类字段永远跳过压缩逻辑。
4. 排障记:最常见也最贵的三个 Agent 生产事故
理论说得再多,不如真实排障记录有价值。这里把生产环境最常见的三个事故写出来,每个都给出从现象到根因的完整排查链路。
4.1 事故一:上下文被检索结果灌爆
现象:用户在第 5 轮对话后,响应速度明显变慢,并且出现答非所问。问用户订单号,它反而重复上一轮的结论。
排查链路:
- 先看任务日志,发现每次请求的 Token 从 3k 涨到 20k 以上,并且单调递增。
- 看上下文注入明细,发现知识库检索的结果是“原样注入”的。
- 拆开结果看,一次检索返回了 5 段,每段 2000 字,模型每轮都背着这上万字在跑。
- 再看模型行为,Token 一涨,模型开始抓不住重点,把旁支信息当成主问题。
修复:检索结果先做相关性排序,只取最相关的 3 段,每段再截断到 500 字以内,并且用模型把三段压成一个 150 字摘要再注入。同时设置单次检索注入上限 4000 Token,超出就触发二次裁减。
结果:单请求 Token 下降了 70%,对话到第 20 轮仍然稳定。
4.2 事故二:工具返回不符合预期,Agent 反复重试同一个动作
现象:用户投诉“一个问题问十遍,Agent 一直在复读‘请提供订单号’”。后台日志显示,同一个查询工具被调用了 11 次,每次输入参数都一样。
排查链路:
- 查看工具调用记录,发现工具确实被重复调用。
- 看工具原始返回,发现返回体前面有一大段解释性文本,JSON 解析器把它当作正常 data 拿了进去。
- 模型拿到这段“解释文本”,以为调用成功了,继续要求用户提供更多信息,实际上啥都没查。
- 根因是工具返回结构不规范,解析器没有区分“成功”和“失败”,失败信息被当成了正常数据。
修复:所有工具统一返回{ok, data, error}结构,解析失败时明确标记为“失败”,把失败原因结构化回灌给模型,并提示模型更换策略;同时加重试计数,连续失败三次直接转人工。
结果:同类错误率从 6% 降到 0.5% 以下。这件事让我意识到,工具返回规范是 Agent 稳定性的地基,任何非结构化的返回都是埋雷。
4.3 事故三:压测时同步调用把进程卡死
现象:压测 30 路并发,P95 响应时间从 5 秒涨到 20 秒,超时率 8%,进程 CPU 占用反而不高。
排查链路:
- 看链路各环节的计时,发现卡点不在模型 API,而在数据库查询和工具 HTTP 调用。
- 查代码,发现这几个调用都是同步阻塞写法,FastAPI 的 worker 被长任务占满。
- 查数据库连接池,发现默认连接数是 10,而 30 路并发一进来,连接全被占住,剩余的请求排队等连接。
- CPU 不高但延迟极高的原因就是:大量线程在等 IO,没有让事件循环发挥作用。
修复:关键 IO 全部改成异步;数据库连接池调大到 50;更根本的修复是让 HTTP 请求只负责入队,Agent 执行放到后台 Worker。三层改完,P95 回到 8 秒以内,超时率归零。
4.4 排障方法论:一条可复现的排查链路
三个事故排查下来,我总结出一条通用链路:
用户反馈 -> 任务日志 -> Token 明细 -> 工具调用记录 -> 模型原始输出 -> 环境资源指标先说结论:任务日志做不好,排障就是盲人摸象。我在项目里把每次请求的以下信息全部落地:
- 任务 ID、会话 ID、时间戳。
- 每个节点的入参出参。
- 每次工具调用的参数和返回体。
- 每次模型调用的 Token 消耗。
- 整体链路各环节耗时。
这样排查时,先看是“哪一环”出了问题,再看“为什么这一环会出问题”,而不是直接猜。Agent 系统比传统接口复杂就在于状态是跨节点的,没有完整日志,你连模型当时在想什么都看不到。
5. 收尾:七要素决定上限,七个决策点决定下限
这篇写完,我也把过去三周重构这个客服 Agent 的体会沉淀得差不多了。最后说几句真实的感受。
七要素决定了一台 Agent 的能力上限:模型不行、工具不顺手、提示词没写清、记忆丢三落四,Agent 都只能停留在“玩具”级别。但真正决定它在生产环境能跑多久的,是七个决策点——框架、模型、编排、记忆存储、并发、Token 成本、失控熔断,每一处取舍都是长期影响。
我有一个习惯,每个 Agent 项目都建一张“决策记录表”,把七个要素和七个决策点的最终选择、选择理由、踩过的坑全部写进去。下次迭代或者换模型时,直接查这张表,不用从零开始想。这个习惯省了我大量重复试错的时间,强烈建议你也这么做。
最后分享一个小技巧:做 Agent 不要第一步就想建宇宙飞船。先把最小闭环跑通——模型 + 提示词 + 一个工具 + 简单记忆,跑通之后再加上下文管理,再上并发,再上护栏。每一步都验证、每一步都留日志。这样出来的系统,才是真正能在生产环境里下地干活的 Agent。