读完北大 Linian Wang 那篇 Kimi K2 排障文,我把大模型 API 想明白了:Tool Calling 根本不存在

前几个月我读到一篇写 Kimi K2 适配 vLLM 踩坑过程的文章,作者是北大的 Linian Wang。这篇文章之所以值得单独拎出来说,不在于它讲了 Kimi/vLLM 的具体 bug------那些 bug 你可能一辈子也不会遇到。

它真正的价值是:把大模型 API 的底层逻辑说透了。

读完之后我总结出一个反直觉的结论:大模型里根本没有"Tool Calling"这个东西 。它是个话术。真实存在的只有一件事------Prompt 补全

理解了这一点,你以后遇到任何 tool_calls 玄学问题,排障路径都会变得清晰。这篇写的就是我对那篇文章的整理和延伸使用心得。


大模型 API 的三段式:Render → Completion → Parse

Linian Wang 那篇文章里的核心心智模型很简单:

不管你调的是 /v1/chat/completions 还是 tool calling,服务端在你看不见的地方做了三件事:

scss 复制代码
你的请求 (messages + tools)
    ↓
[A] Render 展开:把结构化数据渲染成一段 prompt 文本
    ↓
[B] Completion 补全:模型看到 prompt,续写下一段 token
    ↓
[C] Parse 解析:把续写的 token 解析回 assistant.content / tool_calls
    ↓
你拿到的响应 (JSON)

由此可以推出两个等式:

  • Chat Completions = Completions + 自动 A + 自动 C
  • Tool Calling = Chat + C 阶段多一步 schema 校验

Chat 和 Tool Calling 都不是模型的新能力,是服务端在 A 和 C 上做的自动化封装。

Linian 那篇文章里定位的一堆 bug,无一例外都发生在 A 或 C,不在模型本身。


排障方法:直接看 raw prompt

原文里最实用的一条排障建议:跳过 /v1/chat/completions,用 /v1/completions 手动跑

我自己后来在做 Agent 项目时试过这个方法,确实好用。具体做法:

Step 1 :在外部手动跑一次 apply_chat_template,看看你的 messages + tools 到底被渲染成了什么。

ini 复制代码
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("YOUR_MODEL_PATH")

messages = [
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": "查一下深圳今天天气"},
]

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Get weather info",
        "parameters": {"type": "object", "properties": {"city": {"type": "string"}}}
    }
}]

prompt = tokenizer.apply_chat_template(
    messages,
    tools=tools,
    tokenize=False,
    add_generation_prompt=True
)
print(prompt)

Step 2 :把这段 raw prompt 直接丢给 /v1/completions------这是最底层接口,没有 A 和 C 的加工。

arduino 复制代码
curl http://localhost:8000/v1/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "YOUR_MODEL",
    "prompt": "<粘贴上一步打印的 prompt>",
    "max_tokens": 512,
    "temperature": 0
  }'

Step 3 :对比模型的 raw 输出 vs /v1/chat/completions 拿到的解析结果:

  • Raw 输出正常,chat 接口异常 → C 阶段的 parser 有 bug
  • Raw 输出就异常 → A 阶段 render 出的 prompt 就已经错了

一眼定位是渲染出问题还是解析出问题,不用去调温度、换模型、猜 prompt。


Linian 定位到的 3 类典型问题

原文里作者一步步定位出来 3 类问题,都发生在 A/C 环节,非常有代表性:

类型一:Prompt 末尾少了 assistant 起始标记

症状:模型不知道"轮到自己发言了",继续续写用户对话历史,或者输出闲聊,或者干脆输出半截 JSON 就停了。

根因apply_chat_templateadd_generation_prompt 参数没传,或者手写 prompt 时忘了把 <|im_start|>assistant\n 这类回合起始标记加上。

验证:raw prompt 打出来看末尾,如果不是以 assistant 回合标记结尾,就是这个坑。

类型二:空 content 被错误渲染污染上下文

症状:多轮对话中,模型输出质量突然劣化,tool_calls 结构混乱。

根因 :历史消息里某条 content 是空字符串或 None,渲染链路把它错误地展开成 [{"type": "text", "text": ""}]------这段"空文本"污染了后续所有补全。

验证 :raw prompt 里搜 text: "" 或者奇怪的空 role 块,一眼看出来。

类型三:Parser 太严格丢掉正确输出

症状:模型明明输出了正确的 tool_calls JSON,但 chat 接口返回空 tool_calls 数组。

根因:解析器对 JSON 格式过于严格,一个多余的换行、一个 trailing comma、或者输出前多了一句自然语言,就当异常丢掉。

验证:/v1/completions 拿到的 raw 输出里能看到 JSON,但 /v1/chat/completions 拿不到------parser 的问题。修法是换个宽松点的 parser,别指望模型 100% 输出格式完美。


我自己延伸出来一个骚操作:Tool Calling 当 JSON 限定器

理解了本质之后,我在自己项目里发现了一个 Linian 文章没直接讲、但可以推导出来的用法------你可以不真的调用工具,只用 tool calling 来强约束输出格式

场景:我做一个内容分析 Agent,需要模型稳定输出结构化 JSON 给下游系统。直接让它输出 JSON 经常格式漂移------多个逗号、多层嵌套错、或者输出前多一段"好的,让我为您分析..."的废话。

方案:把要求变成一个 fake tool 定义,强制模型"调用"这个 tool。服务端会用 tool calling 的 schema 校验机制帮你把住格式关。

ini 复制代码
tools = [{
    "type": "function",
    "function": {
        "name": "output_result",
        "description": "输出最终分析结果",
        "parameters": {
            "type": "object",
            "properties": {
                "sentiment": {"type": "string", "enum": ["positive", "negative", "neutral"]},
                "confidence": {"type": "number"},
                "reason": {"type": "string"}
            },
            "required": ["sentiment", "confidence", "reason"]
        }
    }
}]

# 强制模型调这个 fake tool
resp = client.chat.completions.create(
    model="...",
    messages=[...],
    tools=tools,
    tool_choice={"type": "function", "function": {"name": "output_result"}}
)

# tool_calls[0].function.arguments 就是保证格式的 JSON
result = json.loads(resp.choices[0].message.tool_calls[0].function.arguments)

比让模型输出"请返回 JSON,格式如下..."稳定多了。我的项目里这么改之后,格式错误率从大概 15% 掉到接近 0。


更硬核:把约束前置到解码阶段

原文最后推荐了两个"constrained decoding"方向,如果你觉得 "parser 兜底"还是不够放心,可以看看:

  • XGrammar :把 JSON schema / 语法编译成 token 级约束,解码时不可能走出非法分支。速度和 vanilla decoding 差不多。
  • LLGuidance:用 guidance 库驱动生成,把结构化正确性前置到解码过程本身,而不是生成完再靠 parser 猜。

用这两个方案,输出 100% 符合 schema------因为非法 token 根本没机会被采样出来。我还没在生产环境用过,但作为下一步优化方向存下了。


一点感想

大模型 API 的复杂度大部分是服务端封装的复杂度,不是模型本身的复杂度。

Linian 那篇文章的价值不在具体的 vLLM 修复代码,而在它让我意识到:我之前遇到的所有 tool_calls 玄学问题,本质都可以还原到"最终 prompt 长什么样"这一个问题上

下次遇到 tool_calls 报错,别去调温度、换模型、加 prompt。先把 raw prompt 打出来看一眼。你会节省很多天的时间。


参考

原文:北大 Linian Wang 关于 Kimi K2 适配 vLLM 的 tool calling 排障过程(原文我没找到公开链接,是从朋友圈技术分享看到的)。本文是我读完的整理 + 自己项目实践中的补充延伸。

延伸阅读:XGrammar / LLGuidance(constrained decoding 方向)

相关推荐
yzin1 小时前
零侵入接口 Mock:mockoon-gen skill
ai编程·vibecoding
怕浪猫2 小时前
AI Agent 应用开发实战封面大集合
aigc·openai·ai编程
南方程序猴2 小时前
Codex CLI + VS Code 组合教程:从 Node.js 安装到接入国内中转站
gpt·ai·chatgpt·node.js·ai编程
谭光志11 小时前
Vibe Coding 时代,程序员该何去何从
前端·后端·ai编程
wangruofeng12 小时前
2026 年 7 月 GitHub 趋势观察:Skills 生态爆发,744B MoE 跑进消费级机器
github·aigc·ai编程
Maynor99614 小时前
AI Coding 零基础实战教程|第七部分:Codex Desktop 安装和使用教程
人工智能·ai编程·codex·claude code·ai coding
Hilaku16 小时前
前端大裁员背后的恐慌:我们还剩什么底牌?
前端·javascript·程序员
奋飛17 小时前
AI应用工程:从模型能力到企业生产力,AI 应用为什么正在进入工程化时代?
agent·ai编程