这段时间我一直在折腾 AI Agent 的工程落地,发现一个很有意思的现象:概念文章满天飞,从"什么是 Agent"到"Agent 会取代谁",人人都在讲,但真到了自己动手搭一个能跑、能扛流量、能上生产的 Agent 时,很多人就卡住了。问题主要出在信息太散:今天看到 ReAct 框架,明天看到 LangGraph,后天又看到"七要素",谁和谁什么关系,从哪里下手,完全没谱。
这篇文章想做的,就是把"概念"和"工程"之间的那段空白填上。我会用一套我在多个项目里落地验证过的拆解方式:先用七要素把 Agent 的内部结构看明白,再用七个决策点把工程实现过程中的关键岔路口挨个过一遍,最后给一个基于 FastAPI + LangGraph 的最小可运行示例。无论你是正准备选型的后端工程师、想用 Agent 做业务产品的技术负责人,还是刚入门的 LLM 应用开发者,这套思路应该都能直接拿去用。
1. 先把概念砸实:Agent 到底是怎么"干活"的
1.1 一个最容易理解的 Agent 模型:会开会的小团队
你可以把 Agent 想象成一个"AI 项目经理 + 若干外包员工 + 一堆工具"的迷你团队。项目经理是大模型,它负责接收任务、拆解任务、决定下一步该找谁干;外包员工就是各种工具,比如搜索、查数据库、发请求、执行代码;团队得有个记事本,这就是记忆;干完活儿之后项目经理要检查结果,发现不对就重新安排,这就是反馈循环。
这个比喻能解释 Agent 和普通 API 调用的本质区别:普通 API 是一次性问答,Agent 是循环推进的"工作流"。大模型在一个循环里反复做"思考—调用工具—观察结果—再思考"这个动作,直到认为任务完成了。每一轮它都可以改变主意,这就让系统有了"自主性"。
1.2 从聊天机器人到智能体,中间差的是一个闭环
Chatbot 的执行流程是线性的:用户输入进到模型,模型生成回复,完事儿。Agent 多了一个关键机制——工具调用与结果回填。模型先根据用户请求规划一下,可能需要查天气接口,于是它生成一个结构化的调用指令:调 get_weather,参数是北京。系统拿到这个指令去真的请求天气 API,把返回的"晴、25度"塞回上下文,模型再基于这个真实数据组织最终回答。
"调用—观察—再决策"这个循环,就是 Agent 的引擎。没有闭环,模型永远只能靠自己的训练数据瞎猜;有了闭环,它才可以"下地干活"。后面讲的七要素,本质上都在服务这个闭环的某一个环节。
2. 解构 Agent 的七要素:一个智能体是由什么拼出来的
我比较常用的拆法,是把 Agent 分成七个部分:大模型、规划、记忆、工具、行动、环境、反馈。每一块都不难懂,但它们在工程落地时分别对应不同的设计和坑,所以有必要逐一展开。
2.1 要素一:大模型——Agent 的决策大脑
模型负责两件最核心的事:理解用户意图,和决定下一步动作。前者决定了它能不能听懂人话,后者决定了它能不能正确选择工具、生成参数、判断"任务是否已完成"。
为什么模型选型决定 Agent 的能力上限?因为 Agent 的每一步决策质量都取决于模型的推理能力。一个逻辑弱的模型,在调用工具时可能生成格式错误的参数,或者在被工具返回结果打脸后不会自我纠正。工程上选模型时,除了看常识问答能力,更要看它的function calling / tool use 能力——也就是能不能稳定地输出符合 JSON Schema 的调用指令。这部分细节放在第三个大章的决策点一说。
2.2 要素二:规划——把大目标拆成小步骤
规划是 Agent 能从"回答一个问题"升级为"解决一个问题"的关键。经典的规划模式有两种:
- ReAct(Reason + Act):让模型边推理边行动,每走一步看看结果再走下一步。灵活性高,但步子太多时 token 消耗大。
- Plan-and-Execute:先让模型生成一个完整计划,再按计划逐步执行。节省中间推理的开销,但计划一旦错误,后续全错,需要额外的动态修正机制。
实践经验是:简单任务用 ReAct 就够了,复杂任务要用 Plan-and-Execute 配合阶段性检查点。规划在工程上对应的是"如何定义执行步骤、如何判定任务完成、如何在计划出错时回退",这些在后面决策点五(执行编排)里会细讲。
2.3 要素三:记忆——Agent 的记事本
一个完全没有记忆的 Agent,每轮对话都是"失忆患者"。AI Agent 的记忆一般分成两层:
- 短期记忆:当前会话里的上下文,包括用户输入、模型思考过程、工具返回结果。它本质上是 token 窗口的一部分,窗口满了就按策略丢弃或摘要压缩。
- 长期记忆:跨会话保留的用户偏好、历史结论、业务数据,通常存在向量数据库(做语义检索)、关系型数据库或键值存储里。
不要小看记忆设计。很多 Agent 在 demo 里表现惊艳,一接入真实业务就"一问三不知",就是因为长期记忆层没做。记忆的存储策略、检索策略、更新策略,分别对应决策点三要解决的问题。
2.4 要素四:工具——Agent 能力的边界维基
工具是 Agent 连接外部世界的桥梁,也是决定它"能干什么"的天花板。一个没有工具的 Agent 只能靠模型内置知识回答,有工具之后它可以:
- 通过搜索接口获取实时信息;
- 通过数据库查询拿到业务数据;
- 通过代码解释器执行计算或数据分析;
- 通过业务 API 完成下单、发消息、改配置等操作。
工程上,工具在模型眼里其实就是一段函数描述:函数叫什么、参数是什么、什么时候用它。模型根据描述决定调用哪个工具。这里的坑集中在描述写不好、参数定义不严谨、返回结果太长等,后面决策点四专门讲。
2.5 要素五:行动——真正对世界产生改变
行动和工具容易混淆。工具是"能力声明",行动是"实际执行"。模型说"我要调用 query_order 这个函数",这不叫行动;系统真的拿着参数去请求订单服务、拿到真实结果,这才叫行动。
在工程实现里,行动层要处理大量脏活:HTTP 请求的重试、超时、鉴权、参数校验、结果截断、错误上报。我见过太多失败案例是模型正确发出了指令,但行动层没有做超时控制,一个慢接口把整个 Agent 循环卡住,最后用户以为服务挂了。行动层,必须把它当生产系统做,而不是当 demo 做。
2.6 要素六:环境——Agent 运行的空间和约束
环境包括两层含义:
一是运行时环境:代码执行沙箱、Python 解释器、网络访问策略、文件系统权限、环境变量。Agent 要执行代码或脚本时,必须把这些限制死,否则一个"帮我把服务器所有文件删掉"的指令就会变成灾难。
二是业务环境:当前用户是谁、属于哪个租户、有哪些权限、上下文有什么业务数据。一个合格的 Agent 系统必须能在每次行动前注入这些约束,防止"越权调用"。工程上,很多团队把这部分抽象成 Context 中间件,在所有节点执行前统一注入。
2.7 要素七:反馈与反思——Agent 的自我纠错机制
反馈是 Agent 和传统脚本最大的不同。脚本跑完一个分支就结束了,Agent 则可以在拿到结果后检查自己的输出是否符合预期,不符合就调整策略再来一次。这就是主流的自我反思(Self-Reflection)机制:模型根据工具返回结果判断"我上一步做得对吗?下一步该怎么改?"。
在工程上,反馈机制对应的是状态机里的条件分支:工具调用成功走成功分支,失败走重试分支,多次失败走"放弃并寻求人类帮助"分支。设计反馈机制时,一个核心原则是每次失败重试都要给模型提供足够的错误信息:返回的状态码、异常堆栈、解释性文本,而不是一个干巴巴的 "error"。
3. 工程落地的七个决策点:每一步都是在做权衡
把七要素讲清楚,只是建立了一个静态模型。真正到写代码时,你会面对七个反复出现的决策,每个决策都会影响性能、成本和维护性。下面是我在自己的项目里反复踩过之后总结的选型经验。
3.1 决策点一:模型选型——闭源 API 还是开源私有化
这是第一个岔路口。我的建议是:
| 场景 | 推荐方向 | 原因 |
|---|---|---|
| 快速验证、非敏感数据 | 闭源 API(GPT、Claude、通义等) | 效果好,function calling 稳定,省心 |
| 敏感业务数据、合规要求高 | 开源私有化(Qwen、DeepSeek 等) | 数据不出内网,可微调,长期成本可控 |
| 兼顾成本与效果的内部工具 | 混合模式 | 简单任务用轻量模型,复杂任务用强模型 |
在 AI Agent 里,模型调用是循环里的高频操作,一个任务可能调用 5~10 次模型。选型时不能只比较单次问答精度,要看多轮决策稳定性:同一个模型能不能在连续多次工具调用后不"跑偏"。我实测下来,闭源头部模型的多轮稳定性明显优于开源中低档模型,但开源模型的迭代速度很快,这个差距正在缩小。
另一个关键参数是Token 成本。一个正常的 Agent 单任务消耗大约 3k~10k token,其中还包括工具描述和历史上下文。假设单任务消耗 5k token,一万次任务就是 50M token,按主流 API 价格算是一笔不小的开销。所以选模型时要把"单任务 token 消耗"和"单 token 价格"一起放进成本模型算,只看单价是会被坑的。
3.2 决策点二:框架选型——LangGraph、AutoGen 还是自研
现在主流框架有 LangGraph、AutoGen、CrewAI,还有一些团队自研。它们各有侧重:
| 框架 | 核心抽象 | 适合场景 | 学习成本 |
|---|---|---|---|
| LangGraph | 图状态机 | 生产级、可控性强、复杂流程编排 | 偏高 |
| AutoGen | 多智能体对话 | 多 Agent 讨论、协作研究 | 中等 |
| CrewAI | 角色化协作 | 固定角色式任务流 | 较低 |
| 自研 | 业务定制 | 核心逻辑简单、团队掌控力强 | 取决于实现 |
我的建议是:生产级业务优先考虑 LangGraph。原因是它的"节点 + 边 + 状态"模型足够直观,每个环节的输入输出都是显式的,方便打日志、加检查点、做人工审核。AutoGen 的多 Agent 对话模式在复杂协作里很强大,但它的内部循环不容易控制,调试成本高。
自研框架不是不行,但前提是你已经把 LangGraph 这类框架吃透,能说出来它哪点不满足你的业务。我见过不少团队一上来就自研 Agent 框架,最后连"对话历史存哪里"都要重新发明一遍。框架不是银弹,但它能帮你少踩大量基础设施的坑。
3.3 决策点三:记忆管理——短期窗口、长期存储怎么搭配
记忆设计是 Agent 工程里最容易被低估的环节。短期记忆要解决的是"上下文超窗"问题。常见策略有:
- 滑动窗口:只保留最近 N 轮消息,简单但会丢早期关键信息;
- 摘要压缩:把早期消息用模型压缩成摘要,信息保留率更高,但多一次摘要调用;
- 混合策略:关键实体和用户偏好进长期记忆,一般消息走滑动窗口。
长期记忆的检索方式也有讲究。向量检索适合查"语义相近的过去经验",比如用户上次说喜欢简洁回答;但向量检索不适合精确匹配,比如"用户上次的订单号是多少",这种要落到结构化存储里。不要把所有记忆都塞进向量库,这是新手最容易犯的错误——语义检索不能替代精确查询。
工程上的一个实用建议:给记忆加一个 layer 层,区分"系统指令级记忆"、"会话级记忆"、"全局用户级记忆",每一层有独立的读写权限。这样可以避免模型在回答时把某个用户的历史数据"串"到另一个用户头上,我在这上面吃过不小的亏。
3.4 决策点四:工具定义——Function Calling 的 schema 决定成败
工具定义的核心是给模型"看"的函数描述,模型不看代码,只看 JSON Schema。一个高质量的工具定义,必须满足三个要求:
第一,工具的 description 要写清楚"什么时候用什么不用"。别写"这是查询订单的接口",要写"当用户询问订单状态、物流进度、发货时间时使用;当用户询问退换货政策时不要使用,应使用查询售后政策工具"。模型能不能选对工具,一半看推理能力,一半看描述质量。
第二,参数定义要严格。参数要有明确类型、是否必填、枚举范围。比如"订单号"参数应该注明格式要求。否则模型会自由发挥,传出缺失或错误参数。
第三,返回结果要"喂得回去"。工具返回的原始数据经常会很长,直接塞回上下文既浪费 token 又干扰模型。工程上要加一层后处理:截断长文本、提取关键字段、格式化错误信息。比如一个接口返回 200 个字段,我们只需要把其中的状态、时间、物流公司映射成一句"订单 123 已发货,预计 2025-03-20 前送达",塞给模型。这个过程叫Tool 结果精简,能显著降低 token 消耗,也能提高模型判断准确率。
3.5 决策点五:执行编排——循环、分支、并行怎么控
Agent 的执行流程本质上是一个状态机。状态机的节点是"模型调用"、"工具执行"、"条件判断"、"人工审核",边是状态转移。在设计状态机时,最容易出问题的三个地方是:
- 循环终止条件。Agent 会一直思考、调用、再思考,如果没有终止条件就是一个死循环。工程上必须设置两个硬限制:最大执行步数(比如 10 步)和最大 token 消耗。超过限制就终止循环,把当前状态交给人工处理或输出一个兜底回复。
- 并行与串行。有些 Agent 流程可以并行,比如同时查三个订单、同时搜索多个关键词。用异步并发能大幅降低延迟,但要注意下游 API 的限流,以及并行结果如何合并。
- 分支回退。当某个步骤失败时,是重试、换一种思路,还是直接放弃?我的经验是设计"失败策略优先级":先同参数重试一次,再让模型换方案重试一次,再失败就放弃并记录失败原因。
可以说,编排能力直接决定了 Agent 是"可控的生产系统"还是"随机的玩具"。用 LangGraph 这类框架,整个过程就是把节点和边写清楚,然后用图执行器去跑,这个心智模型我比较推荐。
3.6 决策点六:可观测性——Trace 是 Agent 的救命稻草
Agent 的调试难度和普通接口完全不在一个量级。普通接口的输入输出是确定的,Agent 的行为却带有随机性,同一个问题可能走不同的工具调用链。要定位问题,必须有完整的调用链追踪:
- 每一步模型的输入和输出是什么;
- 模型选择了哪个工具,参数是什么;
- 工具返回了什么,延迟是多少;
- 状态转移是否符合预期,是哪个节点终止的。
工具层面,LangSmith、Langfuse、MLflow 都有 Agent Trace 能力,选择时关注三点:是否支持你的框架、是否方便接入公司日志系统、能否做会话维度检索。我的是用 Langfuse,它能把 trace 和内部系统打通,团队排障的效率提升非常明显。
还有一个实用建议:给每个 Agent 会话生成一个 trace_id,从 HTTP 入口一路透传到每个节点和工具。没有这个 ID,出了问题你连日志都对不上,排查成本会高到让你怀疑人生。
3.7 决策点七:并发与部署——Agent 怎么扛住生产流量
"AI Agent 怎么扛并发"是最近被问爆的问题,很多团队在 demo 阶段很爽,一上生产就发现。Agent 服务是一个"长时、多步、IO 密集"的系统,每个任务可能持续几秒到几十秒,期间要多次调用模型和外部 API。它的并发瓶颈和普通接口不一样,主要有三个:
一是上游模型 API 的速率限制。闭源模型 API 都有 RPM 和 TPM 限制,大量 Agent 任务并发时会触发限流报错。解决办法是自建限流层:用信号量控制并发数、实现令牌桶做平滑限流、失败时指数退避重试。
二是下游业务 API 的承受能力。Agent 一个任务会多次调用业务接口,如果有 50 个用户同时发起任务,下游可能瞬间收到几百个请求。部署时要在行动层加上熔断和排队机制。
三是** CPU/内存和长连接管理**。Agent 服务如果是纯等待 IO(等模型返回、等工具返回),CPU 占用通常不高,要担心的是数据库连接池、HTTP 连接池和内存中的上下文存储。部署上建议用异步框架(FastAPI 是异步的,配合异步客户端),这样单个进程能支撑大量"挂着等响应"的请求。
并发设计的核心思路是:把"任务调度"和"任务执行"解耦。高流量场景下,不要每个 HTTP 请求同步跑完一个完整 Agent,而是把任务丢进队列(Redis/RabbitMQ),由 worker 消费,前端轮询获取结果。这样你能精确控制并发 worker 的数量,模型 API 限流也好控,比同步网关模式稳得多。
4. 实战:一个能跑起来的 Agent 最小实现
理论讲了一堆,不上代码等于白说。这里我给出一个基于 FastAPI + LangGraph 的最小完整示例,场景是一个"企业内部工单助手",它能根据用户的问题调用查询工具,并在多轮工具调用后给出最终建议。这个场景很典型:有工具调用、有状态流转、有真实业务价值,而且代码量控制在能看懂的范围。
4.1 场景定义和依赖准备
工单助手需要支持两个功能:查询订单状态、根据问题给售后建议。用户可能直接问"我的订单 10086 到哪了",也可能问"我的东西迟迟不到怎么办"。Agent 需要自己决定调哪个工具、要不要多调几个。
依赖安装,直接用 pip:
pip install fastapi uvicorn langgraph langchain-openai pydantic如果你用的是开源模型或公司内网模型,把模型客户端换成对应的 LangChain 集成即可,核心逻辑不变。本示例为了好读,用 OpenAI 兼容接口的ChatOpenAI。
4.2 定义状态、工具和图执行器
先定义 Agent 的状态。状态是一个 TypedDict,LangGraph 会根据它在节点间传递数据。我们需要的状态字段有:消息历史、当前任务是否完成、最终答案。
from typing import TypedDict, Annotated from langgraph.graph import StateGraph, END from langgraph.checkpoint.memory import MemorySaver import operator class AgentState(TypedDict): messages: list task_finished: bool final_answer: str接着定义工具。这里模拟真实的业务 API,返回精简后的结果,方便模型二次使用。
def query_order_status(order_id: str) -> str: """查询订单当前状态,返回精简结果。 当用户询问订单发货、物流、配送状态时使用。 """ # 实际开发中这里替换为真实的订单服务 HTTP 调用 status_map = { "10086": "已发货,预计 2025-03-20 前送达", "10010": "仓库处理中,预计 24 小时内出库", } return status_map.get(order_id, "未查询到该订单,请核对订单号") def get_after_sale_advice(issue: str) -> str: """根据用户描述的售后问题,返回处理建议。 当用户反馈商品破损、少件、延迟发货、需要退货时使用。 """ # 实际开发中这里可以接 FAQ 检索或规则引擎 if "延迟" in issue or "没到" in issue or "物流" in issue: return "建议先核实物流轨迹;若超过承诺送达时间 48 小时,可提交售后工单申请退款或补发。" if "破损" in issue or "坏了" in issue: return "请用户提供破损照片,核实后走补发或退货流程。" return "建议用户补充更多订单信息,再生成对应的售后方案。"然后构造图。LangGraph 的核心是节点和边:节点是一个函数,接收 state 返回 state;边定义节点之间的跳转。
from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage import json llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.2) tools = [query_order_status, get_after_sale_advice] llm_with_tools = llm.bind_tools(tools) def llm_decision(state: AgentState) -> AgentState: """模型决策节点:决定直接回答,还是调用工具。""" response = llm_with_tools.invoke(state["messages"]) state["messages"].append(response) # 简单判断:如果模型没有要求调用工具,说明认为可以给出最终答案了 if not response.tool_calls: state["task_finished"] = True state["final_answer"] = response.content return state def execute_tool(state: AgentState) -> AgentState: """工具执行节点:把模型要求的工具调用真正执行,结果回填到消息。""" last_message = state["messages"][-1] results = [] for call in last_message.tool_calls: fn_name = call["name"] args = call["args"] if fn_name == "query_order_status": results.append(query_order_status(args["order_id"])) elif fn_name == "get_after_sale_advice": results.append(get_after_sale_advice(args["issue"])) # 把工具返回结果作为新的消息追加到上下文 state["messages"].append(HumanMessage(content="工具返回结果:" + ";".join(results))) return state接下来连接这些节点:
builder = StateGraph(AgentState) builder.add_node("decision", llm_decision) builder.add_node("tool", execute_tool) builder.set_entry_point("decision") def should_continue(state: AgentState) -> str: if state["task_finished"]: return "end" return "tool" builder.add_conditional_edges("decision", should_continue, {"tool": "tool", "end": END}) builder.add_edge("tool", "decision") # 工具执行后回到模型继续决策 # 编译。MemorySaver 用来做检查点,支持会话级别记忆 graph = builder.compile(checkpointer=MemorySaver())这里的关键点是add_conditional_edges:模型每决策一次,系统判断是继续还是结束,构成 "decision -> tool -> decision" 的闭环。MemorySaver是 LangGraph 的检查点机制,可以把每一步状态存下来,方便恢复和观测。实际生产环境可以换成 Postgres 等持久化实现。
4.3 用 FastAPI 包一层 HTTP 服务
Agent 图写好后,用 FastAPI 暴露成接口。这里要注意设置最大步数,否则模型反复调用工具会出现失控。LangGraph 可以通过配置recursion_limit限制最大执行步数。
from fastapi import FastAPI from pydantic import BaseModel app = FastAPI(title="Agent Demo") class ChatRequest(BaseModel): session_id: str message: str class ChatResponse(BaseModel): final_answer: str steps: int @app.post("/agent/chat", response_model=ChatResponse) async def chat(req: ChatRequest): config = { "configurable": {"thread_id": req.session_id}, "recursion_limit": 10, # 限制最多 10 步,防止死循环 } result = await graph.ainvoke( {"messages": [HumanMessage(content=req.message)]}, config=config, ) # 记录实际走了多少步,方便观测 usage = result.get("__metadata__", {}) return ChatResponse(final_answer=result["final_answer"], steps=len(result["messages"])) if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)启动服务:
uvicorn main:app --host 0.0.0.0 --port 8000调一下看看效果:
curl -X POST http://localhost:8000/agent/chat \ -H "Content-Type: application/json" \ -d '{"session_id": "s1", "message": "我的订单 10086 到哪了?"}'模型的决策过程大致是:第一轮决定调query_order_status;工具返回"已发货,预计 2025-03-20 前送达";模型拿着这个结果再生成最终回答:"您的订单 10086 已发货,预计在 3 月 20 日前送达。"整个过程两步完成。
再试一个需要两步的:"我的东西延迟了,怎么办?"模型第一轮可能决定同时调用两个工具——查订单状态和查售后建议,或者先查订单再查建议,取决于模型自己的规划。这种"任务多步展开"正是 Agent 的核心价值。
4.4 把这个 Demo 推向更接近生产的状态
演示代码能跑,但离生产还差几步。根据我的经验,至少有四个地方必须加强:
- 工具定义和真实 API 对接。示例里工具是硬编码返回,生产环境要替换成真实 HTTP 调用,并且要加上超时、重试、鉴权。工具返回结果太长时间不要直接拼进消息,先做个摘要。
- 记忆从内存搬到持久化存储。
MemorySaver是内存实现,进程重启就丢了。生产环境用检查点持久化,并增加会话清理策略,防止状态无限膨胀。 - 接口从同步改为异步任务模式。真实业务里 Agent 一个任务可能要跑几十秒,同步 HTTP 会让网关超时。建议把任务 ID 返回给前端,前端轮询结果。
- 人工审核节点。对于"下单、转账、发消息"这类高风险动作,流程里一定要插入人工确认节点,Agent 只生成"待确认指令",由用户点击确认后才真正执行。这个边界,别等到出事再补。
5. 常见问题与排查技巧实录
这个部分是我在实际项目里踩过、也帮别人排过的高频问题,给各位做个速查。
5.1 Agent 陷入死循环,token 烧个不停
表现:模型不断调用工具,工具返回后继续调用,像失控一样,直到配额被耗尽。
原因:最常见的是工具返回了"模型无法理解"或"无法满足预期"的结果,模型又执意要完成用户请求,于是反复尝试。其次是条件分支设计缺失,模型判断"未完成"时永远没有退出条件。
解决:优先设置硬性上限——上面代码里的recursion_limit: 10就是一种方案。更精细的做法是在状态里记录"连续相同 tool_call 次数",如果模型连续多次调用同一个工具且参数一致,直接终止并换一个提示。还要让工具错误信息带上更多可操作内容,比如"该订单号不存在,请确认订单号是否是 10086 格式",帮助模型快速跳出死磕。
5.2 工具调用的参数解析失败
表现:模型输出了一个工具调用指令,但系统解析json.loads直接报错,或者必填参数缺失。
原因:小模型的 function calling 稳定性不够,输出 JSON 里带了多余文字、缺引号、值是空字符串。另一个常见原因是没有按 schema 严格传给模型,工具描述里的参数名和代码里取参的名字不一致。
解决:不要自己手写 parser,用框架内置的bind_tools加底层模型厂商的 function calling 能力,它们做了容错。如果必须自己解析,至少实现"寻找第一个{到最后一个}截取 JSON"的兜底逻辑。更重要的是:这个工具的参数如果经常解析失败,问题大概率在模型选型或工具描述措辞,而不是解析代码。
5.3 Token 消耗失控,一个任务烧掉平时 10 倍的量
表现:同样一个任务,有时候 2k token 完成,有时候 20k token 才完成,成本波动巨大。
原因:Agent 的每一步都会累积消息历史,加上工具返回结果、模型思考过程,整个上下文会越来越长。模型在长上下文里容易"失焦",反复调用重复工具。
解决三个方向:一是控制步数,限制单次任务模型调用次数;二是精简工具返回,前面说的"结果映射成一句话"非常有效;三是上下文裁剪,在每一步工具返回后,用摘要替换最早的历史消息,或者去掉模型思考中间结果,只保留必要信息。我在生产系统里做过一个不做任何优化和做完全优化的对比,单任务 token 差距普遍在 4~8 倍。
5.4 并发一大,接口延迟暴增甚至报错
表现:压测时刚开始响应正常,并发上来后大量请求超时,或者上游模型 API 开始返回 429。
原因:Agent 是长任务,同步处理会把请求线程全部占满;模型 API 有速率限制,并发过高直接触雷。
解决:我实践的靠谱组合是:
- FastAPI 异步路由 +
asyncio.Semaphore控制模型调用的最大并发数; - 自建令牌桶对上游 API 做平滑限流,而不是等 429 了才重试;
- 任务队列化,HTTP 只收请求,worker 异步消费;
- 对相同或相似的请求做结果缓存(比如查询订单状态),能挡掉 30% 以上的重复流量。
5.5 排查工具与调试习惯
最后分享一个我很受用的调试思路:在本地把 Agent 的每一步手动跑一遍。具体做法是写一段脚本,输入和线上问题一样的消息,打开 trace 工具,逐步看模型的 tool_calls 和工具返回。动态的问题要用 trace 看,静态的问题直接单测工具函数。
配合 Langfuse 或 LangSmith,把每一步的 token 消耗、延迟、工具参数都结构化记录下来。遇到模型行为"随机抖动"时,把同一个问题跑 5 遍,对比它们的分叉点,通常很快就能定位是模型推理问题、工具描述问题还是流程编排问题。
6. 写在最后:我对 Agent 工程化的一点真实体会
项目做了不少,说句掏心窝的话:Agent 的难度不在"调用模型",而在"约束模型"。大模型本身就是概率系统,你不给它设边界,它就会以最天马行空的方式帮你干活——听起来是特色,落到生产就是事故。所以工程化的核心,就是给一个概率模型套上确定性的骨架:明确的工具 schema、硬性步数限制、结构化状态流转、完整的 trace 日志。框架选 LangGraph 这类状态机,不是为了赶时髦,而是因为它天然就是做"约束"的。
另一个体会是成本意识的建立要趁早。Agent 单任务的 token 消耗是普通聊天接口的 5~10 倍,一套工具描述、一段历史上下文、一次失败的重复调用,都在烧钱。我在架构设计阶段就养成了每个节点都要记录 token 用量的习惯,上线后按"每完成一个业务任务的成本"做监控,而不是只看接口延迟。只有把成本、准确率、稳定性一起放在桌面上讨论,Agent 项目才可能从 demo 走向真正挣钱的业务。
最后一个技巧:如果你的 Agent 经常在某个环节出问题,先别急着换模型、改提示词,去翻一下那个环节的 trace。很多时候问题不是"模型不够聪明",而是"工具返回的信息不够用",或者"上一步的判断条件写错了"。把数据链路捋清楚,再看模型行为,你会有一种豁然开朗的感觉。希望这篇从七要素到七个决策点的梳理,能让你在动手之前,先想明白每条路到底通向哪里。