差的不是技术栈,是一个约束:输出不确定。
传统后端写单测,断言 expect(calc(2,2)).toBe(4),绿了就过了。Agent 的输出是概率生成的,同一个问题问两遍答案可能不一样——它没有对错,只有好坏。
这一个约束往下推,会推出后面所有事:
把这层想通,六步就不是背出来的清单,而是从一个约束推导出来的必然结果。
反过来说,凡是跳过这层、直接讲"先调 API 再写 Prompt"的,都是在描述 demo,不是开发流程。
flowchart TB
A["① 场景判断<br/>这个场景该不该上 Agent"] --> B["② 能力拆解<br/>意图 / 工具 / 检索 / 记忆 / 规划"]
B --> C["③ 评估先行<br/>先定尺子:数据集 + 指标"]
C --> D["④ 原型快跑<br/>打通主干,用评估集验收"]
D -->|路线不成立| A
D -->|路线成立| E["⑤ 评估迭代<br/>改 Prompt → 跑集 → 看分数"]
E -->|分数降了,回滚| E
E -->|达标| F["⑥ 工程化上线<br/>可观测 / 安全 / 成本 / 灰度"]
F -->|线上 bad case 回流| C
这一步要做的事:在写任何代码之前,用三段话写清两件事——这个 Agent 负责什么,不负责什么。写不出来,说明场景还没想清楚。
判断该不该上,三个条件同时成立才做:
| 条件 | 反例(不满足就不该上) |
|---|---|
| 需要理解语义 | 输入是枚举值、ID、固定格式,用 if 就够 |
| 需要多步决策 | 一次查询就出结果,那是 API 调用,不是 Agent |
| 步骤事前不确定 | 流程固定成 A→B→C,写成代码更快更稳 |
一个可量化的做法:翻最近 100 条真实用户请求,人工看规则能覆盖多少。覆盖超过 80%,就别上 Agent——剩下 20% 用人工兜底,比做一个 Agent 便宜得多。这个数字不是拍脑袋,是真的数一遍,半小时的事。
为什么容易被跳过:写代码比想清楚爽。而且技术选型错了,往往要两三个月后才疼,那时候已经投了很多。
常见错误:把"这件事能用大模型做"当成"这件事该用 Agent 做"。这两句话差别很大——前者是技术可能性,后者是工程决策。
这一步的产出物:一句边界描述,比如"这个 Agent 只处理售后退款咨询,不处理物流查询,不处理投诉"。这句话后面会变成评估集的边界——评估集只测该测的东西。
五项能力,每项都要落到具体参数上,不能只写名字。
模型选不选得对工具,几乎完全取决于那段描述文字。所以要定三件事:
第一,粒度。 一个工具干一件事,还是干一串事?
get_order / get_logistics / refund_order —— 灵活,但模型要多轮才能完成一件事,费 tokenhandle_refund —— 一步到位,但内部逻辑你不可控,出错难定位经验:读操作拆细,写操作拆粗。查询类工具拆细,模型自己组合;有副作用的操作合成一个,减少误操作面。
第二,描述。 必须写清三件事,缺一件就会出问题:
name: get_logistics
description: 查询指定订单的物流状态。
- 什么时候用:用户问"到哪了""什么时候送到""为什么还没发"
- 什么时候不用:用户问退款进度(用 get_refund_status)
- 返回:当前状态、承运商、最近一条轨迹、预计送达时间
第二行"什么时候不用"最常被漏掉,而它直接决定模型会不会拿它去干不相关的活。工具之间描述不互斥,是选错工具的头号原因。
第三,返回格式。 返回给模型的应该是"人话 + 关键字段",不是整坨原始 JSON。
// 不好:模型要自己从 30 个字段里猜哪个有用,还占 context
{ code: 0, data: { status: 3, cp: 'SF', ts: 1698000000, nodes: [...] } }
// 好:直接把结论写给模型
"订单 A123 已揽收,顺丰,10-22 14:30 从杭州发出,预计 10-25 送达。"
为什么这条重要:模型看到的工具结果,就是它的"眼睛"。你给它一坨它读不懂的 JSON,它只能瞎猜或者再调一次工具。
| 能力 | 要定的参数 | 什么时候真的需要 |
|---|---|---|
| 意图识别 | 有几个意图、要不要前置分流、置信度阈值 | 意图超过 10 个,或有"绝对不能进 Agent"的类型 |
| 检索 | 切分粒度、召回条数、要不要 rerank、要不要引用来源 | 答案依赖你私有的文档,不是模型常识 |
| 记忆 | 短期(裁剪/摘要)、长期(落库的用户偏好) | 有跨会话的个性化需求。别一上来就做,先确认真的需要 |
| 规划 | 是否需要显式拆步骤、子任务怎么串 | 任务超过 5 步、步骤间有依赖。否则单循环就够 |
常见错误:一上来就五项全上。五项全上的结果是每项都做得很浅,主路径反而跑不通。先做工具,其余按需加。
这一步的产出物:一张工具表(名字、参数、描述、返回样例)+ 一句是否需要检索/记忆/规划的结论。
这一步是整个流程里唯一反直觉的地方:传统开发是先写代码再写测试,Agent 要反过来。
为什么必须提前? 因为你没法断言。没有尺子,你改 Prompt 就是在碰运气——而且你感觉不到自己在碰运气。每次改完跑一两个例子,看起来都还行,于是你以为自己在优化,其实在随机游走。
数据集从哪来,按可信度排序:
规模:起步 20~50 条就够。 分层配比大致是这样:
| 层级 | 占比 | 作用 |
|---|---|---|
| 简单 | 约 60% | 主路径,必须 100% 过。过不了说明基础没打好 |
| 中等 | 约 30% | 换个说法、多个条件、参数要推断 |
| 困难 | 约 10% | 信息不全、需要追问、有歧义 |
| 长尾对抗 | 单独一组 | 恶意输入、提示注入、超出范围的请求——这组不求高分,求"不出事" |
指标至少三个,每个都要定义判分方式:
| 指标 | 定义 | 怎么判分 |
|---|---|---|
| 任务成功率 | 最终结果对不对 | 精确匹配 / 包含关键字段 / LLM 打分 / 人工抽查 |
| 工具调用准确率 | 工具选对了吗,参数对吗 | 对比期望的工具序列,逐项比对参数 |
| 端到端延迟 | 用户等了多久 | 直接测,看 p95 不看均值 |
| 单任务成本 | 一个任务烧多少 token、几轮 | 从 usage 里累加 |
判分方式的选择很关键:有唯一答案的(查订单号、算数)用精确匹配;开放式答案只能用 LLM 打分,但要接受裁判本身有偏差——所以关键用例要人工抽检一遍,校准裁判。
三个最容易犯的错:
这一步的产出物:一个 evalset.jsonl(每条包含:输入、期望结果、期望工具调用)+ 一个能一键跑完输出分数的脚本。脚本大概五十行,不用框架。
这一步的目标不是"做好",是"验证这条路值不值得走"。
怎么砍范围:
通过标准要提前定。 比如"核心简单层用例成功率 ≥ 70%",到了继续,不到就分析原因。
什么时候该掉头,不是看分数低,是看失败是否集中在同一个地方:
| 失败现象 | 通常说明 |
|---|---|
| 模型根本不选这个工具 | 工具描述写得不对(这是可修的) |
| 选了但参数总是错 | Schema 太复杂,或者参数需要模型推断而它没有信息来源(这是设计问题) |
| 工具调对了,但回答跑偏 | System prompt 缺约束,或工具返回的格式模型读不懂 |
| 同样的问题每次都不同答案 | 要么是模型能力不够,要么是需求本身有歧义(这是需求问题,改代码没用) |
最后两种属于"改 Prompt 改不掉"的问题。在这两种情况下继续优化代码,是最常见的浪费。
这个阶段最该克制的冲动:想加抽象、想加第二个工具、想接真实数据。都放到 ⑤ 之后。
这一步是 Agent 开发和传统开发最大的区别——迭代节奏由评估驱动,不由感觉驱动。
基本循环:改一处 → 全量跑 → 看总分 + 看 diff。
关键在"看 diff",不只是看总分。
假设你改了一版 Prompt,总分从 68% 涨到 74%,看起来是进步。但 diff 显示:新增对了 4 条,原本对的 2 条变错了。
这意味着你在拆东墙补西墙。每一次回归都要能解释,解释不了就别合入——因为它会在你看不见的地方积累。
bad case 要先归因,再动手。 归成五类,分类统计才知道该改哪:
| 归因 | 典型表现 | 改哪里 |
|---|---|---|
| 检索没召回 | 工具调对了,但拿不到需要的信息 | 切分粒度、召回条数、加 rerank |
| 工具选错 | 该调 A 调了 B | 工具描述、减少工具数量 |
| 参数错误 | 工具对,参数编了 | Schema 简化、让模型先追问 |
| 约束不足 | 答非所问、超范围回答 | System prompt |
| 模型能力不足 | 同一条无论怎么改都错 | 换模型,或者把任务拆小 |
回流机制:线上每一个 bad case → 去重 → 标注期望结果 → 加进评估集 → 下次必须过。评估集越滚越厚,这个 Agent 才越来越稳。
版本要留痕:Prompt 的每一版、对应分数、改动点,记在一张表里。不记的话,两周后你会遇到"这个 71 分是哪一版、为什么这么改"这种问题,然后重跑一遍浪费一天。
两个常见错误:
前面五步保证"做得对",这一步保证"能扛住"。
每条请求至少留四样东西,否则出事只能靠猜:
有了这四样,你能按一个 ID 回放整条链路,直接看出是检索错了、工具选错了还是模型编了。没有它们,你面对的是一个黑盒——改什么都是在猜。
middlewares.mjs 里那个 approval)maxSteps 上限(防止死循环烧钱)灰度放量 → 按比例观察错误率、延迟 p95、单任务成本 → AB 对比 → 监控告警 → 用户反馈闭环。
顺序建议:先做可观测,再做成本上限,再做安全审批,最后才做灰度。理由很实际——没有可观测,后面三样做错了你也发现不了。
这一步比记流程有用。对完之后,缺口一目了然。
| 步骤 | 我手上已有的 | 还缺什么 |
|---|---|---|
| ① 场景判断 | — | 没认真做过,属于"想到就写" |
| ② 能力拆解 | core.mjs 的工具表,两个工具带 description |
返回格式没设计,全是给人看的字符串;没有"什么时候不用" |
| ③ 评估先行 | 空白 | 一条用例都没有,这是最大的洞 |
| ④ 原型快跑 | 01 / core / 02 三层 demo 已跑通 |
通了,但没有验收标准 |
| ⑤ 评估迭代 | 靠肉眼看输出判断好坏 | 没有分数,改完不知道是变好还是变坏 |
| ⑥ 工程化 | middlewares.mjs 里有 logger / approval / pii / summarize |
没有 trace 落库,没有单任务成本上限 |
两个洞最扎眼:③ 和 ⑤。而这两个恰好是整条流程里唯一"跳过就失效"的环节——没有它们,前四步的判断全部失去依据,第六步也只是在加固一个你不知道好坏的盒子。
这也解释了之前那个困惑:为什么明明显得很简单的东西,做出来总觉得不放心。原因不是代码写得不好,是没有尺子。
第一,工具描述比工具数量重要得多。 十个工具配十段准确的描述,胜过一个工具配一段含糊的。"什么时候不该用"这一句尤其重要,它负责把工具之间的边界切开。
第二,评估集起步不必大,但必须有盲测集。 二十条覆盖主路径的用例就足以挡住绝大部分回归。真正的风险不是样本少,是一条都没有——因为在小样本上,"手感"几乎总是错的。而如果评估集被你反复用来调 Prompt,它就不再是尺子,变成了答案本。
第三,个人项目做工程化,只留两样:结构化日志 + 单任务成本上限。 限流、灰度、AB 属于有流量之后的事。一个人做产品,先把"出事能查"和"跑飞了能停"做到,就够活下来了。
Agent 开发是工程闭环,不是调个 API 再写个 Prompt。多出来的那个环节叫评估,因为它的输出没有对错,只有好坏。
六步:场景判断、能力拆解、评估先行、原型快跑、评估迭代、工程化上线。
其中评估先行和可观测是地基。没有这两样,Agent 就是黑盒——你改什么都是在猜。