聊 AI Agent,很多人第一反应是“一个能自己调用工具的聊天机器人”。这个类比没毛病,但一到工程落地,你会被一连串问题锤懵:模型选多大合适?工具调用失败了怎么补救?上下文一长 Token 就爆?用户一多并发就乱?这些都不是背几个概念能解决的。所以我今天换个拆法,不按框架讲原理,而是先把一个 Agent 系统拆成七个必要组成部分,再从这七个要素里拎出七个真正需要拍板的工程决策点,最后落到一套基于 FastAPI + LangGraph 的最小实现上。
这篇文章适合两类人看:一类是准备把 Agent 从 Demo 推向生产的工程师,另一类是正在做技术选型、但被 LangChain、Spring AI、Rust 这些生态词绕晕的技术负责人。我尽量用做项目的方式把问题讲透,你可以当它是自己踩坑两年后的复盘,而不是一层层往上叠概念的教程。
1. 先搭骨架:Agent 七要素到底指什么
1.1 用“人”来类比,七件事缺一不可
一个 Agent 能独立完成某个任务,本质上和一个人处理工作没什么区别。你想清楚一个人要做成一件事需要什么,Agent 的骨架也就出来了。
- 目标:这次任务到底要让 Agent 完成什么,边界在哪里。
- 模型:负责推理和决策的“大脑”,也就是 LLM。
- 上下文与提示:模型看到的背景信息、规则、约束,统称 Prompt。
- 工具:Agent 能访问的外部能力,比如查天气、发消息、调数据库。
- 记忆:暂存用户说过什么、Agent 做过什么,以及需要长期记住的知识。
- 规划:把大目标拆成小步骤,决定先做什么后做什么。
- 执行与反馈:真正去调用工具,把结果送回模型,形成一个闭环。
我见过不少团队说自己在做 Agent,结果拆开一看只有模型、提示词和工具三项。这种充其量是“带工具调用的对话机器人”,因为当任务稍微复杂一点,没有记忆、没有规划、没有反馈闭环,系统就会走一步忘一步,遇到错误也无法自我修正。
以做菜来打比方:目标是“四十分钟做出三菜一汤”,模型是厨子,提示词是厨房规矩,工具是锅碗瓢盆和灶台,记忆是“盐刚才放过了”的临时记录,规划是先煮汤还是先炒菜,执行和反馈则是试吃后调整火候。缺了任何一环,这顿饭大概率没法顺利做完。这就是七要素的工程意义,它不是概念清单,是运行闭环。
1.2 为什么不是六个也不是八个
你可以说“安全”算不算第八要素,或者“数据”算不算。但在工程实现上,我更愿意把安全当成一个横切约束,而不是独立组件;数据则分散在记忆、工具、上下文里。七要素的边界不是一个绝对真理,而是一个方便拆解和排障的心智模型。
更重要的是,少一个要素,系统会立刻退化到某种已知形态:
- 没有目标和规划,Agent 变成“你问一句它答一句”的 Chatbot;
- 没有工具,它只是个会写作文、但无法改变世界的大模型;
- 没有记忆,每次对话都像失忆,用户必须重复上下文;
- 没有反馈闭环,工具报错它就停在原地,甚至把错误当成正确答案。
这样看,七要素更像是一张“功能完整性检查单”。你拿到任何 Agent 项目,逐项去问“这一项谁负责”,问不出来,工程上迟早要出事。
1.3 七要素不是 LangGraph 概念,是团队责任分工
很多新手看 LangChain/LangGraph 里一堆概念,会误以为学会了框架就等于学会了 Agent。其实框架只是把七个要素用代码包装了一下,真正的难点在于每个要素背后都要有人做决策。
比如“目标”不是简单写一句 system prompt,而是要定义输入 schema、校验方式、任务边界;“记忆”不是 Redis 里存 chat history,而是要考虑多久清空、要不要做摘要、向量库怎么更新。所以我习惯把七要素当成一份责任清单,开需求会时逐个讨论:
- 目标:谁来解析用户请求?解析不了怎么告知用户?
- 模型:用哪个模型?准确率和成本怎么平衡?
- 上下文:Prompt 有多长?哪些信息永远不能被覆盖?
- 工具:工具注册表怎么维护?权限边界在哪?
- 记忆:哪些记忆进短期窗口,哪些进长期存储?
- 规划:单步决策还是多步拆解?终止条件是什么?
- 反馈:工具失败后,是重试、反思,还是找人工?
把这份清单过一遍,你就会发现,后续所有决策点,其实都是在这七个框架上长出来的。
2. 七要素对应七个决策点:工程实现真正的分岔路
2.1 决策点一:模型选型,不是追最强,而是追“够用且可控”
七要素里最容易被卡住的是“模型”。很多人一上来直接上最强的大模型,结果成本爆炸、延迟感人;也有团队为了省钱用小的开源模型,结果工具调用格式每天都出错。我的建议是:先想清楚你的任务里,模型到底承担多少推理负担。
我通常按四个维度打分:工具调用准确性、上下文长度、推理速度、综合成本。如果任务只是“从邮件里抽取结构化信息然后写进数据库”,那么中等模型配合强 schema 约束就够;如果 Agent 需要长时间自主规划、频繁调用工具、中途修正策略,那就得让更强模型上场。
工程上还要考虑“API 稳定性”和“私有化要求”。能公网调用 OpenAPI 自然省事,但有些项目必须部署开源模型到私服。这时候别只看推理分数,还要看它是否稳定支持 Function Calling,因为弱模型的工具调用会让后面所有环节都难受。
最近总有人问我“用 Rust 写 Agent 是不是性能更强”。我的回复是:Rust 生态确实有 Agent 框架,也适合追求极致吞吐的组件,但大模型的推理接口才是瓶颈,语言带来的性能提升在大多数业务场景根本感知不到。除非你的团队全是 Rust 工程师,否则没必要为了技术热度换赛道。Python 生态在 LangGraph、LangChain、FastAPI 上的成熟度,短期内仍是做 Agent 工程最省路的选择。
2.2 决策点二:目标怎么“写”给模型,系统提示词不只是角色扮演
第二个决策点是“目标”这个要素的落地方式。你以为写个 system prompt 就够了,结果模型经常跑偏。原因很简单:你没有定义清楚边界。
我在实际项目里会把 system prompt 拆成四块:
- 角色与能力范围:你是谁,能做什么,不能做什么;
- 任务拆解规则:拿到用户请求后,先做什么后做什么;
- 工具使用边界:哪些场景必须调用工具,哪些场景禁止调用;
- 输出格式约束:给模型的回答套上固定框架。
举个例子,如果有人想用 Agent 管理小红书自动发消息,我不会让它“自动发”,我会在目标定义里写清楚:“Agent 只能生成草稿并提交给用户人工确认,未经二次确认不得执行发布动作。” 这不是限制,是在保护系统,因为一旦模型失控,对外发出错误消息的后果远比丢掉一点自动化效率严重。
同样的逻辑适用于任何高风险场景。有人问“个人用 Agent 做期货交易可以吗”,我的回答是代码层面当然可以接行情和交易接口,但真正难的是把“目标”转化成严格的约束:什么条件下允许下单、最大仓位是多少、亏损多少必须停手、是否必须人工确认。这些决策如果全交给模型,那就是在赌博。你也可以用人工审批节点来做硬约束,后面我会讲到 LangGraph 的 interrupt 机制。
2.3 决策点三:工具调用,Function Calling 和 MCP 怎么选
七要素里的“工具”,落到工程里第一个问题是:模型怎么知道要调用哪个工具、参数怎么填。现在主流方案有三种:
原生 Function Calling:模型厂商在 API 层支持结构化的工具调用,模型会输出一个 JSON 格式的调用声明。这是最稳的方案,因为它是模型对齐后直接生成的,而不是让模型把 JSON 写进对话文本里。
自绘 JSON 解析:有些模型不支持原生 Function Calling,你得规定它输出“我想调用工具 xxx,参数是 yyy”,然后再从文本里解析。这种方式很脆,模型一旦换个措辞,解析就崩。
MCP(Model Context Protocol):它不是替代 Function Calling,而是把工具暴露方式标准化。以往每接一个外部系统就要写一套自定义工具接入,MCP 提供统一的工具发现和调用协议。如果你的业务里外部工具很多、更新很频繁,我会优先考虑 MCP;如果工具就两三个,直接用 Function Calling 够省心。
真正让工具调用稳定,核心不在选协议,而在工具描述和参数设计。我给过不少人一个建议:每个工具的描述要写清楚“什么时候用、什么时候不用”,参数能少就少,枚举值尽量给死。比如一个“发送消息”工具,不要给一个自由文本的 target 字段,而是给枚举类型,可选值为“微信、短信、邮件”,模型就不容易填错。
还要注意工具数量。一次性把 50 个工具堆给大模型,它的选择准确率会明显下降。工程上可以分组:先给 Agent 一个“工具选择器”,让它决定调用哪一组工具,再进入具体的组。这其实就是在用“规划”要素来降低“工具”要素的复杂度。
2.4 决策点四:记忆和 Token 预算,先弄懂 Token 是什么
“AI Agent token 是什么意思”这个问题,几乎每个新手都会问。Token 可以理解为模型处理文本的最小单位,一个中文词语可能对应一到两个 token,英文则是若干字母凑一个 token。大模型 API 按 Token 计费,模型也有上下文窗口,也就是一次能处理的最大 Token 数。
记忆设计本质上就是 Token 预算管理。你不可能把用户所有历史都塞进窗口,所以要把记忆分成三类:
| 记忆类型 | 存储位置 | 典型实现 | 解决什么问题 |
|---|---|---|---|
| 短期上下文 | 模型窗口内 | 最近的 N 轮消息 | 保持当前对话连贯 |
| 长期记忆 | 外部存储 | Redis / 数据库 | 记住用户偏好和事实 |
| 语义记忆 | 向量库 | 每次检索 Top K 相关记忆 | 从海量历史里捞关键信息 |
工程决策上,我最关注的是“窗口资源分配”。如果模型窗口是 128K,你不能把大半都用来塞历史。一般我会留出足够空间给系统提示词、工具描述和工具返回结果,因为这些是当前决策真正依赖的信息。历史对话可以滚动窗口只留最近几轮,更早的内容做摘要或者交给向量库,等到需要时再检索回来。
长期记忆还要考虑写入时机。不要每次对话都把所有事实写入长期记忆,那样会产生大量脏数据。我常用的策略是:先让模型判断“这句话是否值得记”,值得才写入。这就相当于给记忆加了一个前置过滤器,虽然多消耗了一次模型调用,但长期收益明显。
2.5 决策点五:规划策略,ReAct、Plan-and-Execute 还是图状态机
“规划”是 Agent 和大模型聊天最不一样的要素。聊天模型一次回复就结束,Agent 则要决定下一步干什么。工程上最常见的三种策略:
ReAct:模型在“思考 -> 行动 -> 观察”之间循环,每一步先想一下,然后调用工具,拿到结果再继续想。它胜在灵活,适合探索性任务;败在费 Token,且容易绕圈。
Plan-and-Execute:模型先把整个任务拆成若干步骤,再一步步执行。它省去重复思考的开销,适合步骤相对固定的任务,比如“先查库存 -> 再算价格 -> 最后下单”。缺点是一旦中途实际情况偏离计划,它可能执迷不悟。
图状态机:用 LangGraph 这类框架把流程画成有向图,节点是 Agent 或工具,边是条件跳转。它的优势是工程可控,能精确表达“什么时候必须人工确认”“什么时候重试”“什么时候终止”,特别适合生产系统。
怎么选?我的经验是:先把手头任务的执行路径、异常分支想清楚。如果 80% 的情况路径是固定的,就用图状态机把主干和异常分支显式定义出来;剩下 20% 的开放情况,让图中的某个 Agent 节点用 ReAct 模式处理。换句话说,图状态机管宏观流程,ReAct 管微观决策。
像扣子这类低代码平台,本质就是把图状态机可视化、配置化,适合快速验证流程。但它真正限制人的不是流程画布,而是异常处理、权限控制、观测体系这些工程细节。所以我的习惯是:先用扣子搭个 Demo 给业务看,真正上线再回到代码里用 LangGraph 重写一遍。
2.6 决策点六:失败恢复,重试、反思还是人工兜底
工程实现和论文 Demo 最大的区别,在于对失败的态度。Agent 的每一步都可能失败:工具超时、返回格式不对、模型拒绝回答、上下文被垃圾信息污染。你必须在设计阶段就想好兜底策略。
我的兜底顺序通常是:
- 有限次重试:同一动作最多重试两次,每次带上错误信息。
- 让 Agent 反思:把前一次失败的过程和结果作为 observation 塞回去,要求模型解释失败原因并修正方案。
- 切换路径:如果反思两次仍失败,就不再让 Agent 自动绕了,切换到备用流程或人工处理。
- 必有的人工审批节点:凡是涉及对外发布、资金操作、删除数据的动作,必须拦截住,交给人工点确认才有下一步。
使用 LangGraph 时,这个“人工兜底”非常值得用它的 interrupt 机制来做。Agent 执行到某个节点,发现需要人工确认,就把执行暂停,挂起状态,等人点“通过”或“拒绝”再继续。这比让 Agent 自己决策“要不要发”安全得多。
我见过太多项目翻车,不是因为模型不够聪明,而是没有设置最大迭代次数。模型会在一件事失败后反复用不同方式尝试,看似在反思,实则是在烧 Token 和调用次数。后来我在所有 Agent 流程里都加了一个硬性迭代上限,比如“最多调用工具 15 次”,达到上限直接终止并告诉用户“任务过于复杂,请联系人工”。
2.7 决策点七:并发、可观测性与上线节奏
最后这个决策点,也是最近“AI Agent 怎么扛并发”这个问题问得越来越多的原因。很多人以为 Agent 就是一个 API 接口,用 FastAPI 包一层,然后加几个线程就能扛住用户量。但实际上 Agent 天然是多步、有状态、长耗时的工作负载,它和普通 HTTP 请求处理模型完全不一样。
你说接口收到一个请求,模型要跑几轮、工具要调几次、外部系统要响应多久,完全不可控。如果每个请求占用一个 Worker 进程,把进程卡住等模型返回,那 QPS 稍微一高,整个服务就瘫了。
我的建议是:把 Agent 跑成异步任务队列,而不是同步请求处理。FastAPI 只负责接收请求,把任务 ID 和数据丢进队列(比如 Redis + Arq),然后立刻返回“任务已受理”。后面的 Agent 流程由独立 Worker 异步执行,执行过程中的状态全部写到 Redis 或数据库里。前端或者调用方轮询任务状态接口,拿到结果。
并发场景下还有一个隐蔽的坑:状态串了。如果你把整个 Agent 的对话状态存在一个全局变量或者内存对象里,两个用户同时触发任务,后一个请求就会覆盖前一个请求的数据。解决方法是所有状态都放到按任务 ID 隔离的存储里,绝对不要依赖进程内单例。
可观测性则是另一个容易忽视的问题。普通接口可以看 HTTP 状态码,Agent 流程怎么监控?我之前踩过的坑是:不知道问题出在模型调用上还是工具调用上。后来我把每一步的输入输出、Token 消耗、耗时都通过结构化日志打印出来,再接入 LangSmith 或 OpenTelemetry 做追踪。只有这样,当用户说“Agent 没做对”,你才能定位是模型犯傻、工具报错,还是流程跳错边。
上线节奏上,我强烈建议不要一上来就开放所有工具。可以先灰度一部分低风险工具,比如“查天气”“搜索文档”,让线上流量和观测数据帮你暴露问题;等流程稳定了,再逐步开放“发消息”“写文件”这类有副作用的能力。
3. 实操一套最小实现:FastAPI + LangGraph 的 Agent 骨架
3.1 技术组合为什么选 FastAPI 和 LangGraph
前面的七个决策点讲得再多,落不了地也是空的。这里我给出自己常用的最小可运行方案:FastAPI + LangGraph + Redis。
FastAPI 负责对外 API,简单直接,原生支持异步,很适合做任务接收层。LangGraph 负责把 Agent 的规划、工具调用、人工确认、失败恢复编排成一张图,而不是隐藏在无限循环里。Redis 负责状态暂存和队列。这套组合不是我拍脑袋选的:它有状态隔离、有断点恢复、有可观测接口,后期扩展成分布式 Worker 也方便。
如果你所在的企业技术栈是 Java,也可以关注 Spring AI 为此类 Agent 工程提供的集成能力,本质上同样是构建任务编排和工具调用;选择什么语言取决于团队,但设计思路是通用的。我不推荐一上来就用 Rust 重写,除非你已经确认 Python 是性能瓶颈。
3.2 定义状态、节点和条件路由
在 LangGraph 里,第一步是定义状态。我通常用一个 dict 结构:
from typing import TypedDict, List, Optional class AgentState(TypedDict): task_id: str user_input: str messages: List[dict] # 模型对话历史 tool_outputs: List[dict] # 工具执行结果 current_step: str # 当前节点名称 need_human: bool # 是否需要人工审批 finished: bool这个状态就是整个 Agent 的“工作台”。所有节点读它、改它,LangGraph 会负责把状态传给下一个节点。
然后定义节点。最简版本至少要有两个节点:一个 Agent 节点负责调用大模型做决策,一个 Tool 节点负责执行工具:
from langgraph.graph import StateGraph, END def agent_node(state: AgentState) -> AgentState: # 根据当前状态构造 messages,调用大模型获取下一步动作 response = call_llm(state["messages"], available_tools) # 把模型回复追加到 messages state["messages"].append({"role": "assistant", "content": response.content}) state["need_human"] = response.need_human_confirmation # 自定义逻辑 return state def tool_node(state: AgentState) -> AgentState: # 遍历模型要求调用的工具,逐个执行 for call in state["tool_calls"]: result = execute_tool(call["name"], call["args"]) state["tool_outputs"].append({"name": call["name"], "result": result}) return state这里我没有写 call_llm 和 execute_tool 的具体实现,因为它们跟你的业务强相关。真正关键的是条件路由,也就是决定流程下一步走向:
def router(state: AgentState): if state["need_human"]: return "human_approval" # 停到人工审批节点 if state["tool_calls"]: return "tool" # 有工具要调用,走 Tool 节点 return END # 没有更多动作,结束 graph = StateGraph(AgentState) graph.add_node("agent", agent_node) graph.add_node("tool", tool_node) graph.add_node("human_approval", human_approval_node) graph.add_edge("agent", "tool") graph.add_edge("tool", "agent") graph.add_conditional_edges("agent", router, {"tool": "tool", "human_approval": "human_approval", END: END}) graph.set_entry_point("agent") app = graph.compile()这个图就是一个最简 Agent 闭环:Agent 每次决策,如果要工具就去执行工具,拿到结果再回到 Agent 继续决策;如果遇到人工审批或完成,就退出循环。你可以在“agent”和“tool”之间循环,也可以按业务需要插入更多中间节点。
3.3 让工具调用稳定的小技巧
实现工具节点时,我一般会强迫自己遵守几个规则,否则线上迟早出事。
规则一:永远校验模型回传的参数。模型再强也会偶尔输出缺失字段或错误类型。要用 Pydantic Schema 或者其他 JSON Schema 校验器把你的工具入参卡死。校验不通过,不是直接执行,而是当成“工具调用失败”反馈给模型,让模型重新生成。
规则二:给每个工具套超时。外部接口可能 10 秒才返回,Agent 系统等不起。我用一个统一的函数执行字典,每个工具注册时声明自己的超时时间。超时后返回“工具调用超时”,让模型决定重试还是换方案。
规则三:工具错误要进入反馈,不是中断。很多人写代码时工具一把 try/except 直接吞掉异常,Agent 看到的就是“工具执行成功”,这非常危险。正确的做法是把异常信息封装成一个 observation 返回给模型,比如“调用数据库失败:连接超时”,这样模型才有可能在下一步修正决策。
def execute_tool(name: str, args: dict): tool = TOOL_REGISTRY.get(name) if not tool: return {"ok": False, "error": f"Tool {name} not found"} try: result = tool.fn(**args) return {"ok": True, "result": result} except Exception as exc: return {"ok": False, "error": str(exc)}这条代码虽然朴实,但它保证了反馈闭环不中断。你后续所有的自我纠错、重试机制,都依赖这个“失败结果正确返回”的通道。
3.4 并发控制:把 Agent 跑成有状态工作流
最小实现能跑通之后,你迟早要面临并发。我现在的标准做法是 FastAPI 只做任务 API,真正执行 Agent 的 Worker 另起进程,或者部署成独立服务。
# FastAPI 入口示例(简化) from fastapi import FastAPI, BackgroundTasks import uuid app = FastAPI() @app.post("/agent/tasks") async def create_task(payload: dict, background_tasks: BackgroundTasks): task_id = str(uuid.uuid4()) # 把 task_id 和 payload 丢进 Redis 队列,或者用 BackgroundTasks 直接调度 background_tasks.add_task(run_agent_async, task_id, payload) return {"task_id": task_id, "status": "accepted"} @app.get("/agent/tasks/{task_id}") async def get_task(task_id: str): state = redis_client.get(f"agent:{task_id}") return {"task_id": task_id, "state": state}轻量场景下 FastAPI 自带的 BackgroundTasks 就够用了;一旦任务量上来,我会换成 Celery 或 Arq,这样 Worker 可以横向扩展。这里要特别强调:Agent 的执行过程是异步长任务,所以 API 层不要同步等待 Agent 跑完再返回。客户端通过 task_id 轮询,或者后面用 WebSocket / SSE 推送,都是比“同步等待”更稳的设计。
状态隔离我前面提过,实际操作时我会把 LangGraph 的 StateGraph compile 之后,配合一个 checkpointer 参数,让每一步状态自动持久化到 Redis。这样即使 Worker 中途宕机,任务也能从最近的快照恢复,不至于整个流程推倒重来。
4. 常见问题与排查实录
4.1 Token 超限:窗口不够还是记忆策略不对
很多团队的 Agent 一跑久就报“maximum context length exceeded”。第一反应是换更大窗口的模型,但更常见的病根是记忆策略太粗暴:把用户一整天的会话全部塞进 messages,甚至把一堆工具返回结果原样保留在上下文里。
我的排查顺序:先打印 Token 构成,看哪部分占大头。如果工具结果占了大头,就在工具节点里做结果裁剪,只保留关键字段,长文本交给摘要节点压缩。如果是历史对话占大头,就改成滚动窗口加摘要记忆。如果系统提示词已经很长了,再把不常变的工具描述移到侧存储,需要时动态加载。最后才考虑升级模型窗口。
4.2 工具调用返回非法 JSON:模型还能抢救一下
用不支持 Function Calling 的小模型时,最容易出现模型把 JSON 写在 Markdown 代码块里、或者字段名被翻译成中文之类的“灵异事件”。遇到这种情况,先不要急着换模型,试试以下操作:
- 收紧 system prompt,明确给出 JSON 示例,并强调“不要输出任何解释,只输出 JSON”;
- 在解析失败时,把“你刚才的输出无法解析,原因是……”作为反馈发回模型,让它自行修正;
- 改用厂商提供的结构化输出能力,比如在请求参数里指定 response_format;
- 实在不行,再加一层小的校验模型,专门负责把模型输出转换成合法 JSON。
这个过程中最忌讳的是:解析失败就直接抛异常结束任务,不试任何补救。因为模型具有很强的自我纠错能力,只要把错误信息告诉它,大多数情况下它能自己修回来。
4.3 任务卡死:Agent 反复调用工具停不下来
我之前排查过一个案例,Agent 为了“预订会议室”,反复查了五次日历,每次都说“没有合适时间”,但既不结束,也不换方案。典型问题是没有设置终止条件。
解决办法有三层:
- 设置最大工具调用次数,达到上限强制终止;
- 在系统提示词里写明“如果连续两次相同工具返回相同失败结果,必须停止并告知用户”,主动触发终止;
- 在条件路由里加入“步骤数”判断,第 N 步仍未完成就走人工兜底节点。
这里要养成一个习惯:所有 Agent 循环都必须有显式的退出条件,而且这个退出条件不依赖模型自觉,而是由代码硬性控制。
4.4 并发下状态串了:同一个用户的请求相互覆盖
如果上线后你发现用户 A 的操作结果跑到了用户 B 的任务里,十有八九是状态存到了进程级变量。排查重点:把所有状态都移到 Redis 或数据库,并且以 task_id 为 Key;LangGraph 的 checkpointer 也一定要按 task_id 隔离;如果同一个用户同时发起多个任务,还要再区分是“同一会话的并发”还是“不同会话的并发”。
更稳妥的方法是在创建任务时生成全局唯一的 task_id,而不是用 user_id 当 Key。不管是日志、状态、队列、回调,全部带着 task_id,排查问题时才能顺着链路走。
4.5 效果评测:单点跑通不等于能上线
最后聊一个大家不爱提但必须做的事:评测。我见过太多 Agent 项目,开发时说“效果不错”,一上线用户乱输入就崩。原因是测试只覆盖了三五条 happy path。
我的做法是建一个评测集,至少 50 个问题,覆盖:标准任务、模糊指令、非法输入、工具失败、边界情况。每轮改动后,跑一遍评测集,记录几个核心指标:
| 指标 | 含义 | 观测方式 |
|---|---|---|
| 任务完成率 | 多少请求按预期完成 | 终结为 END 且无人工介入 |
| 工具调用准确率 | 模型选择的工具与参数是否合理 | 对比正确答案分析日志 |
| 平均 Token 消耗 | 每次任务烧多少 Token | LangSmith / 日志统计 |
| 平均时延 | 任务从受理到完成耗时 | 队列 + 状态时间戳 |
别追求一次性做到 95% 完成率,你先要保证每次改动后指标可对比,否则后面模型一升级、Prompt 一修改,你都不知道效果是变好了还是变差了。
5. 落地一点体会
这套七要素加七个决策点的拆法,我自己在实际项目中反复用。最开始我只是拿它当排查问题的干任务,后来发现它还能帮团队对焦:谁是负责人、哪些点还没想清楚、哪些点不需要过多纠结。尤其是工具调用和人工确认这两个地方,是最容易花冤枉钱、走回头路的。
最后分享一个小技巧:把 Agent 的 Prompt、工具描述和评测集全部纳入代码仓库,像管代码一样管它们。每次大模型升级或者 Prompt 调优,都顺手跑一遍评测集,防止某些“看起来 OK 的改动”带来工具调用格式的隐性变化。工程上的 Agent 好不好用,很多时候不靠灵光一现,靠的就是这套笨功夫。