Sublarge

// 杂记 · 2026-10-29

自然语言 vs JSON:为什么模型更爱“说话”,而不是“填表”?

自然语言(或模型友好格式)是模型最擅长的表达方式,强制严格 JSON 会带来不必要的不稳定。DeepSeek 用 DSML、NLT 论文的实验,都在验证这一点。但 MCP 解决的是另一层问题——工具的标准化发现与复用,它和"模型怎么表达工具调用"并不矛盾。模型怎么说话→ 尽量贴合模型优势(自然语言 / DSML 等)。工具怎么被连接→ 需要协议层(MCP 或其他方案)来做标准化。不要把"模型侧的格式选择"和"协议侧的生态标准"混为一谈。两者解决的是不同问题,也都可以根据实际场景灵活选择。

有人问过这样一个问题:

“为啥非要自创协议而不是沿用 MCP 呢?自然语言是各个模型最通用的语言,语言理解力达标,就能用对格式,从而规避 MCP 时期让模型直接输出 JSON 的不稳定。况且现在模型训练 Function Calling 的方法也证实了自然语言对 JSON 是更优越的。比如 DS,内部的工具表示是 DSML 而不是 JSON。”

这段话里其实藏着四个关键问题,值得逐一拆开:

  1. 自然语言 和 严格 JSON,哪个更适合模型来表达工具调用?
  2. MCP 到底是干什么的?它和 Function Calling 是什么关系?
  3. DeepSeek 为什么用 DSML 而不是 JSON?
  4. 我们到底该不该“自创协议”?

本文会把这些问题讲清楚,并配上具体示例。


一、先分清两层:模型侧 vs 协议侧

很多人把 MCP 和 Function Calling 混为一谈,这是理解偏差的起点。

层级 名称 解决什么问题 典型形式
模型侧 Function Calling / Tool Use 模型如何表达“我要调用哪个工具、参数是什么” JSON Schema + 模型输出结构化参数
协议侧 MCP(Model Context Protocol) 工具如何被发现、如何被不同客户端复用、鉴权怎么做 JSON-RPC 2.0

一个简单的类比:

  • Function Calling 是“模型说我想点一杯咖啡(大杯、少糖)”。
  • MCP 是“咖啡店的标准菜单系统,任何 App 都能连上去点单,而不用每家 App 都单独对接一次”。

MCP 并没有取代模型的 Function Calling,它是在 Function Calling 之上加了一层标准化协议。模型仍然需要决定调用什么,只是工具的定义和执行被抽到了独立的 Server 里。


二、为什么模型直接吐 JSON 会不稳定?

模型是在海量自然语言上预训练的,对自然语言的理解和生成能力远强于严格的结构化格式。

常见失败模式

当要求模型输出严格 JSON 时,经常出现:

  • 多写或少写大括号、引号
  • 字段名幻觉(location 写成 city)
  • 类型错误(数字写成字符串)
  • 在 JSON 前后夹杂自然语言
  • 长参数时格式崩溃

这些错误在早期 Function Calling 中非常常见,尤其是较小模型或复杂参数场景。

研究证据:Natural Language Tools(NLT)

2025 年有一篇重要论文 Natural Language Tools: A Natural Language Approach to Tool Calling In Large Language Agents(arXiv:2510.14453),提出用自然语言替代严格 JSON 做工具选择。

核心做法是把工具选择解耦:

  • 让模型对每个工具回答 YES / NO,而不是生成完整 JSON。
  • 工具描述用自然语言。

实验结果(跨 10 个模型、6400 次试验):

  • 准确率从 69.1% → 87.5%(+18.4 个百分点)
  • 方差下降约 70%
  • Token 消耗降低约 31%

后续复现研究(2026)在 14 个模型、8560 次试验中也得到了类似结论:NLT 平均提升 14.9 个百分点,关键错误减少 93%。

结论:对很多模型来说,自然语言描述 + 简单决策 比强制生成复杂 JSON 更稳定、更准确。


三、DeepSeek 的选择:DSML

DeepSeek 没有选择让模型直接生成标准 JSON,而是设计了 DSML(DeepSeek Markup Language)。

DSML 示例

模型内部生成的工具调用大致长这样:

<|DSML|tool_calls>
<|DSML|invoke name="read_file">
<|DSML|parameter name="path" string="true">src/app.ts</|DSML|parameter>
</|DSML|invoke>
</|DSML|tool_calls>
xml5 行

或者一个更完整的搜索示例:

<|DSML|calls>
<|DSML|invoke name="search">
<|DSML|parameter name="queries" string="true">小柴胡冲剂 布洛芬 相互作用 一起吃</|DSML|parameter>
</|DSML|invoke>
</|DSML|calls>
xml5 行

服务端再转换成标准格式

推理框架 / API Server 会把 DSML 解析成 OpenAI 兼容的 tool_calls:

{
  "tool_calls": [{
    "type": "function",
    "function": {
      "name": "read_file",
      "arguments": "{\"path\":\"src/app.ts\"}"
    }
  }]
}
json9 行

为什么这样设计?

  • XML 风格的标记对模型来说更容易稳定生成(标签有明确起止)。
  • 参数值直接写在标签之间,避免复杂的 JSON 转义。
  • 服务端负责解析和校验,模型只需“说清楚意图”。

这正是“自然语言(或模型友好格式)优先”的实践体现。


四、标准 Function Calling 示例(对比)

OpenAI / 兼容接口风格

工具定义:

{
  "type": "function",
  "function": {
    "name": "get_weather",
    "description": "Get weather of a location",
    "parameters": {
      "type": "object",
      "properties": {
        "location": {
          "type": "string",
          "description": "The city and state, e.g. San Francisco, CA"
        }
      },
      "required": ["location"]
    }
  }
}
json17 行

模型输出(理想情况):

{
  "tool_calls": [{
    "id": "call_abc123",
    "type": "function",
    "function": {
      "name": "get_weather",
      "arguments": "{\"location\": \"Hangzhou, Zhejiang\"}"
    }
  }]
}
json10 行

Claude(Anthropic)风格

工具定义使用 input_schema:

{
  "name": "get_weather",
  "description": "Get the current weather for a given city.",
  "input_schema": {
    "type": "object",
    "properties": {
      "city": {"type": "string"},
      "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
    },
    "required": ["city"]
  }
}
json12 行

模型返回的是 tool_use content block,而不是 tool_calls 数组。


五、MCP 协议示例(协议层)

MCP 使用 JSON-RPC 2.0。下面是典型的工具发现与调用流程。

1. 列出工具(tools/list)

请求:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {}
}
json6 行

响应:

2. 调用工具(tools/call)

请求:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": {
      "location": "Hangzhou"
    }
  }
}
json11 行

响应:

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Current weather in Hangzhou: 24°C, Cloudy"
      }
    ],
    "isError": false
  }
}
json13 行

注意:MCP 客户端拿到工具定义后,仍然是通过模型的 Function Calling 能力把这些工具交给模型,再把模型的决策转成 tools/call 请求发给 Server。


六、四种思路对比

方案 优点 缺点 适合场景
严格 JSON Function Calling 结构清晰、易解析、生态成熟 模型容易格式错误,Token 开销大 工具少、参数简单、模型强(有严格模式)
DSML / 模型友好标记 生成更稳定,模型友好 需要专用解析器 DeepSeek 等自研模型
Natural Language Tools 准确率高、方差小、Token 少 参数复杂时表达能力受限 工具选择为主、参数简单的场景
MCP 工具一次编写、多客户端复用 有协议开销,Token 可能更多 需要跨应用、跨团队复用工具时

七、实践建议

  1. 模型侧优先考虑“模型友好”

    如果是自研模型或开源模型,可以像 DeepSeek 一样设计更易生成的格式(DSML、XML、甚至自然语言 + 解析),而不是强行让模型当 JSON 生成器。

  2. 协议侧按需选择 MCP

    • 只给自己的产品用 → 直接 Function Calling 就够了。
    • 要给多个客户端(Claude Desktop、Cursor、自己的 Agent)共用工具 → MCP 很有价值。
    • 工具数量非常多时,注意 Token 成本,有人开始转向 CLI-first 方案。
  3. 两者可以共存

    MCP Server 对外暴露标准协议,内部执行时仍然可以用任何模型友好的方式让模型决策。

  4. 生产中建议加一层校验与修复

    即使模型很强,也建议对工具调用结果做 schema 校验、重试或 fallback。


八、总结

自然语言(或模型友好格式)是模型最擅长的表达方式,强制严格 JSON 会带来不必要的不稳定。

DeepSeek 用 DSML、NLT 论文的实验,都在验证这一点。

但 MCP 解决的是另一层问题——工具的标准化发现与复用,它和“模型怎么表达工具调用”并不矛盾。

正确的理解是:

  • 模型怎么说话 → 尽量贴合模型优势(自然语言 / DSML 等)。
  • 工具怎么被连接 → 需要协议层(MCP 或其他方案)来做标准化。

不要把“模型侧的格式选择”和“协议侧的生态标准”混为一谈。两者解决的是不同问题,也都可以根据实际场景灵活选择。

原文 https://blog.csdn.net/2301_79518550/article/details/169368587