有人问过这样一个问题:
“为啥非要自创协议而不是沿用 MCP 呢?自然语言是各个模型最通用的语言,语言理解力达标,就能用对格式,从而规避 MCP 时期让模型直接输出 JSON 的不稳定。况且现在模型训练 Function Calling 的方法也证实了自然语言对 JSON 是更优越的。比如 DS,内部的工具表示是 DSML 而不是 JSON。”
这段话里其实藏着四个关键问题,值得逐一拆开:
- 自然语言和严格 JSON,哪个更适合模型来表达工具调用?
- MCP到底是干什么的?它和 Function Calling 是什么关系?
- DeepSeek 为什么用DSML而不是 JSON?
- 我们到底该不该"自创协议"?
本文会把这些问题讲清楚,并配上具体示例。
一、先分清两层:模型侧 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|invokename="read_file"><|DSML|parametername="path"string="true">src/app.ts</|DSML|parameter></|DSML|invoke></|DSML|tool_calls>或者一个更完整的搜索示例:
<|DSML|calls><|DSML|invokename="search"><|DSML|parametername="queries"string="true">小柴胡冲剂 布洛芬 相互作用 一起吃</|DSML|parameter></|DSML|invoke></|DSML|calls>服务端再转换成标准格式
推理框架 / API Server 会把 DSML 解析成 OpenAI 兼容的tool_calls:
{"tool_calls":[{"type":"function","function":{"name":"read_file","arguments":"{\"path\":\"src/app.ts\"}"}}]}为什么这样设计?
- 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"]}}}模型输出(理想情况):
{"tool_calls":[{"id":"call_abc123","type":"function","function":{"name":"get_weather","arguments":"{\"location\": \"Hangzhou, Zhejiang\"}"}}]}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"]}}模型返回的是tool_usecontent block,而不是tool_calls数组。
五、MCP 协议示例(协议层)
MCP 使用JSON-RPC 2.0。下面是典型的工具发现与调用流程。
1. 列出工具(tools/list)
请求:
{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}响应:
{"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"get_weather","description":"Get current weather information for a location","inputSchema":{"type":"object","properties":{"location":{"type":"string"}},"required":["location"]}}]}}2. 调用工具(tools/call)
请求:
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_weather","arguments":{"location":"Hangzhou"}}}响应:
{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"Current weather in Hangzhou: 24°C, Cloudy"}],"isError":false}}注意:MCP 客户端拿到工具定义后,仍然是通过模型的 Function Calling 能力把这些工具交给模型,再把模型的决策转成tools/call请求发给 Server。
六、四种思路对比
| 方案 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| 严格 JSON Function Calling | 结构清晰、易解析、生态成熟 | 模型容易格式错误,Token 开销大 | 工具少、参数简单、模型强(有严格模式) |
| DSML / 模型友好标记 | 生成更稳定,模型友好 | 需要专用解析器 | DeepSeek 等自研模型 |
| Natural Language Tools | 准确率高、方差小、Token 少 | 参数复杂时表达能力受限 | 工具选择为主、参数简单的场景 |
| MCP | 工具一次编写、多客户端复用 | 有协议开销,Token 可能更多 | 需要跨应用、跨团队复用工具时 |
七、实践建议
模型侧优先考虑"模型友好"
如果是自研模型或开源模型,可以像 DeepSeek 一样设计更易生成的格式(DSML、XML、甚至自然语言 + 解析),而不是强行让模型当 JSON 生成器。
协议侧按需选择 MCP
- 只给自己的产品用 → 直接 Function Calling 就够了。
- 要给多个客户端(Claude Desktop、Cursor、自己的 Agent)共用工具 → MCP 很有价值。
- 工具数量非常多时,注意 Token 成本,有人开始转向 CLI-first 方案。
两者可以共存
MCP Server 对外暴露标准协议,内部执行时仍然可以用任何模型友好的方式让模型决策。
生产中建议加一层校验与修复
即使模型很强,也建议对工具调用结果做 schema 校验、重试或 fallback。
八、总结
自然语言(或模型友好格式)是模型最擅长的表达方式,强制严格 JSON 会带来不必要的不稳定。
DeepSeek 用 DSML、NLT 论文的实验,都在验证这一点。
但 MCP 解决的是另一层问题——工具的标准化发现与复用,它和"模型怎么表达工具调用"并不矛盾。
正确的理解是:
- 模型怎么说话→ 尽量贴合模型优势(自然语言 / DSML 等)。
- 工具怎么被连接→ 需要协议层(MCP 或其他方案)来做标准化。
不要把"模型侧的格式选择"和"协议侧的生态标准"混为一谈。两者解决的是不同问题,也都可以根据实际场景灵活选择。