ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

0% 的工具调用成功率:模型太笨,还是考场根本没开门?

0% 的工具调用成功率:模型太笨,还是考场根本没开门?

🌊 专注AI 大模型与前沿科技深度解析,习惯从工程师视角拆解技术热点,让我们一起在技术浪潮中保持清醒与好奇 🚀


0% 的工具调用成功率:模型太笨,还是考场根本没开门?

前阵子我在做一个小项目:用 Ollama 在本地跑几个开源模型,评测它们的工具调用(tool calling)能力。规则很简单——给模型一段 JSON Schema 描述的工具列表,看它能不能输出一个可解析的调用,比如{"name": "get_weather", "arguments": {"city": "北京"}}。跑完第一轮,结果让我愣住:Gemma-3 和 Phi-3 的成功率刺眼的 0%,而 Llama-3.2 却表现正常。

我差点就在报告里写下"Phi-3 不具备工具调用能力"。幸好我多翻了五分钟原始日志:请求根本没送到模型那里——服务层在推理开始前就把它拒了。这就像你怀疑考生交了白卷,调监控一看:人家连考场门都没进去。

30 秒结论

  • 核心判断:本地工具调用评测跑出来的数字,相当一部分度量的是"服务栈行为"而不是"模型能力"。不控制服务层,结论可能整个错掉。
  • 0% 不等于模型不行:可能是请求在推理前就被模板标志拒掉了,模型连表现的机会都没有。
  • 换协议、换推理栈、换统计口径,结果都会变——仅聚合方式不同,差距最多能到约 55 个百分点。
  • 适合谁:在本地(Ollama / llama.cpp / vLLM / SGLang)做 Agent 评测、模型选型、或想给作品集加一个硬核评测项目的同学。
  • 不适合谁:只调云端官方 API、不做系统性对比评测的人——不过面试被问"你的评测可信吗"时,这套思路依然是加分项。

关键证据

  1. 门卫式拒绝:Ollama 的tools=请求被每个模型的静态模板标志门控。Phi-3、Gemma-3 在推理前直接被拒;有的模型被接受但以纯文本返回调用;有的返回原生tool_calls字段。同一个"模型会不会调工具"的问题,三种答案全部来自模板配置,与模型权重无关。
  2. 失败被误分类:如果 harness 不把"服务层拒绝"和"重试耗尽"记录为结构化元数据,下游统计会把它们统统算作"模型没调用",于是天真地报告 0% 保真度。失败原因丢了,结论就脏了。
  3. 协议干预并不对称:给请求额外加一份文本版工具列表(同时保留原生通道),能救回大部分"被接受"模型的分数;但把它做成统一文本协议,反而拉低了 Llama-3.2——它本来就有原生工具调用支持,多此一举成了干扰。
  4. 跨栈行为分裂 + 统计口径陷阱:同一请求发给 Ollama、llama.cpp、vLLM、SGLang,处理各不相同;约束解码(constrained decoding)消除了全部解析失败,却可能让模型生成停不下来;"按轮次池化"与"按实例"两种聚合估计最多相差约 55 个百分点。

展开说明

想深挖的话,先建立一个心智模型:工具调用其实有两扇门。第一扇,模型要生成格式正确的内容;第二扇,harness 要能成功解析。而本地服务栈在两扇门之前还加了一位"门卫"——聊天模板系统。Ollama 用每个模型自带的静态模板标志决定它"配不配"接收 tools 请求,拒绝发生在 token 生成之前,所以你在输出里看不到任何异常,只能看到一个沉默的零分。

这就意味着:评测 harness 必须把失败原因当成一等公民记录下来。一个最小可用的分类长这样:

fromenumimportEnumclassFailureKind(Enum):NO_CALL="model_no_call"# 模型真的没调PARSE_ERROR="parse_error"# 调了,但输出解析失败STACK_REJECT="stack_reject"# 服务层推理前拒绝RETRY_EXHAUSTED="retry_exhausted"# 重试次数耗尽# 每次请求落盘时同时写入 failure_kind 与原始响应

再说两个容易踩的坑。约束解码(比如用 GBNF 语法或 XGrammar 强制输出符合 Schema)确实能把解析失败清零,但语法被锁死后模型可能丧失自然的停止时机,陷入不终止生成——解析率 100%、完成率却掉了。另一个是聚合口径:把所有轮次混在一个池子里算比例,长对话实例会主导结果;先按实例算再平均,则是另一种权重。两种算法本身都没错,错的是不写明用了哪一种。

这套能力完全可以写进作品集:一个"区分模型失败与基建失败的评测 harness",比千篇一律的聊天 Demo 亮眼得多。面试里被追问"你怎么证明分数反映的是模型能力"时,把上面这套分类讲出来,就是答案。

落地建议:今天就能做的 3 件事

  1. 给 harness 加失败分类:把"模型没调 / 解析失败 / 服务层拒绝 / 重试耗尽"拆成四个独立计数器,落盘保留原始响应。一两个小时就能改完。
  2. 跑一次跨栈探针:同一个 prompt、同一份 Schema,至少发给两个本地栈(比如 Ollama 和 llama.cpp),对比返回结构是原生tool_calls还是纯文本。这个脚本本身就是很好的作品集项目。
  3. 报告双口径数字:同时给出按轮次池化和按实例的聚合结果,并注明协议细节(是否带文本工具列表、是否开约束解码)。

风险与反例

  • 只用云端官方 API 的场景:如果你调用的是当前主流云端大模型(如 GPT-5.5、Qwen3.6 Max 这一代)的官方托管接口,服务层由厂商维护、行为相对稳定,这类"门卫拒绝"少见——但版本灰度切换同样可能悄悄改变返回格式,日志分类依然值得做。
  • 同栈同模型的相对比较:如果你只比较同一栈上不同 prompt 策略的相对差异,混杂因素被控制住了,结论可能仍然成立;但你的绝对数字换个环境大概率无法复现。
  • 别因噎废食:在严格 Schema 输出(如结构化信息抽取)场景,约束解码仍是首选,只需要配合最大长度与停止条件兜底。
  • 文本工具列表不是银弹:它对"模板接受但解析弱"的模型是解药,对原生支持好的模型是毒药——干预手段本身也需要被评测。

测量的第一层原则:在怪罪被测对象之前,先校准你的尺子。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进