双轨评估:MCP 智能体的模型得分与产品得分
简而言之:双轨评测用同一批用例,分别跑「只给模型」和「走完整产品」两条路,分数各算各的。2026 年 8 月,同一个 Qwen3-4B 智能体在模型轨拿到 42/70(没过上线门槛),在产品轨拿到 46/70(过了)。全部提升都来自一条守卫规则——stall rule(既没调用工具、也没向用户提问的一轮,直接打回重来)。而我引以为傲的参数修复,实测收益为零。
我在做一个跑在本机的小智能体:Qwen3-4B(4-bit 量化,MLX 推理)驱动 Lovie Formation MCP 服务器(195 个工具),业务是美国公司注册。动手训练之前,我先写了一套评测。然后我花了两天时间优化一个数字——而这个数字从设计上就根本看不到产品。真是够讽刺的。
评测设置
套件:70 个完整流程用例 × 3 个随机种子,按维度打分后取多数票,采样参数与生产环境一致。其中一个 14 例的「核心」子集是上线门槛,要求 12/14。
模型轨(golden)
直接调用 engine.generate,只对模型输出的第一次工具调用打分。
基线:42/70,核心 11/14。没过门槛。
盲区
模型轨根本不跑编排器(orchestrator)。所以这些东西它一概看不见:步骤映射、支付占位符的打回处理、参数类型强制转换,还有我正在加的两条新守卫。
作为「模型本身」的度量,它是干净的——它本来就是为这个写的。问题是我一直把它当产品指标来读。
同一批用例上的产品轨(system-golden)
跑真实的对话轮循环,对同一批用例打分,但打分的对象是服务器实际收到的调用——在所有修复、打回和守卫都执行完之后。
它连的是一个 DryRunMCP:schema 和服务器指令从真实端点来(匿名的 tools/list 就够了),但 callTool 只返回一个假的成功,并把请求记录下来。之所以要这样:这些用例会让一个注册流程走完名称、地址、股东等环节,而这些都是真实服务器上的活数据,评测轨道绝不能创建真实记录。
| 轨道 | 总分 | 核心 |
|---|---|---|
| golden(模型) | 42/70 | 11/14 |
| system-golden(产品) | 46/70 | 12/14 |
模型没过门槛,产品过了。
两条轨回答的是不同的问题,所以这两个数字不是「之前」和「之后」的关系。
我给每项保护机制都加了环境变量开关(LOVIE_NO_STALL_BOUNCE、LOVIE_NO_ARG_REPAIR),然后重跑产品车道,结果如下:
| Stall bounce | Arg repair | Total | Core |
|---|---|---|---|
| on | on | 46/70 | 12/14 |
| off | on | 42/70 | 11/14 |
| on | off | 46/70 | 12/14 |
| off | off | 42/70 | 11/14 |
可见,全部收益都来自 stall bounce(拦截空转轮次)。
这条规则的逻辑是:如果助手的一轮回复既没有调用工具,也没有向用户提问,就把它回弹一次。在更早的 78 个用例中,28 个基线失败里有 27 个是“什么都没调用”的轮次,其中 14 个既没调用也没提问——例如先说一句“我会把它注册成 C 公司,就这么办”,然后就没有下文。
在这个产品里,一轮回复既不做事也不提问,永远不可能正确。所以这是个确定性触发器,不需要去猜模型的意图。
设计约束,每条都有测试固定:
- 它从不指定该调用哪个工具。 强制“标准下一步调用”会让模型在本该列出选项的场景里凭空编造参数值。
- 它避开合法的收尾。 提问、拒绝并反问(而不是编造)、工具执行后的总结、普通闲聊、被拒绝的调用,都不触发。
- 每轮最多触发一次。 二次空转就直接结束这一轮,不会陷入循环。
拒绝类别的成绩保持在 8/8。回弹从未把模型逼到编造地址。
ArgumentRepair:实测收益为零
ArgumentRepair 在调用发出前,按工具自身的 inputSchema 重塑参数:扁平值 ↔ 包裹值、嵌套对象与数组,以及在和 schema 的枚举列表匹配无歧义时做枚举规范化(例如 "LLC" → ENTITY_TYPE_LLC)。
不变式:它只重塑模型发来的内容,绝不增加或删除字段,遇到歧义枚举就原样放过。
实测结果:零收益。没有任何类别发生变化。
早前我把一次冒烟测试当成了它有效的证据,因为执行调用里出现了 ENTITY_TYPE_LLC。其实那个值是模型自己输出的。我看到正确结果,就把功劳记到了自己代码头上,没检查它到底有没有起作用。
它保留下来是出于维护原因,这点要说清楚:它用一条从工具契约推导出的规则,替换掉一份硬编码的四字段列表——那份列表是在多次线上事故后一点点加长的。
流程上改了什么
两条通道,绝不混用:模型通道回答的是「训练有没有改变模型?」,产品通道回答的是「产品能不能用?」。上线闸门(ship gate)只看产品通道。每加一道新校验,都配一个开关,这样它的效果是测出来的,不是猜出来的。如果你在评估一个大语言模型(LLM)产品(法律类也好,其他也罢),先问清楚:这个分数来自模型通道还是产品通道,又是哪个组件挣来的。
常见问题
AI 智能体里的「僵持规则」(stall rule)是什么?
当某一轮助手既没有调用工具、也没有向用户提问时,这条规则会把这轮回复打回一次。在这个案例里,它单靠这一条就把产品通道从 42/70 提到 46/70。
为什么要把模型通道和产品通道分开?
只测模型的评估看不到编排层的那些校验逻辑,所以同一个模型可能过不了模型闸门、却能过产品闸门。两边的数字回答的是不同问题,不能直接比较。
怎么把收益归因到某一道具体校验上?
每道校验都配一个开关(一个环境变量 flag),然后把同一批随机种子在关闭该开关的情况下重跑一遍。
Daniel Aydin 是 Lovie(lovie.co)LegalTech 产品线的总经理。他在土耳其接受律师训练,并非美国执业律师,在旧金山 SF Vibe House 主持 Legal AI Vibe Lab。Lovie Formation(lovie.co/formation)是一项 AI 原生的美国公司注册服务,提供 MCP 服务器,让 Claude、ChatGPT 这类 AI 智能体也能帮你开公司;每一份递交到州政府的文件,都会有人工复核。
原文首发于 Substack。
摘要:双轨评测的做法是:同一批测试用例,一轨只跑模型本身(模型轨),另一轨跑完整产品(产品轨),两边的得分分开记录。2026 年 8 月,同一个基于 Qwen3-4B 的 agent 在模型轨拿到 42/70,没能通过上线门槛;在产品轨拿到 46/70,通过了。全部提升都来自一条保护规则——卡壳规则(stall rule):某一轮既不调用工具、也不向用户提问,就直接打回。我原本引以为傲的参数修复(argument repair),实测收益为零。
我正在做一个跑在设备端的小型 agent,模型用的是 Qwen3-4B。