消息结构层:JSON 为什么是事实标准、变体差异、何时该用 Protobuf
在 LLM 协议这两层(消息结构层 / 传输层)里,消息结构层几乎被 JSON 一统天下—— 但 JSON 不是一种格式,是一族格式(标准 JSON / JSONL / JSON5 / JSON Schema / Stringified JSON), 边界都摸不清就敢自称"懂 JSON"是危险的。本页也回答"什么时候该离开 JSON"——答案是 Protobuf, 但LLM 公开 API 永远不用 Protobuf,原因藏在内文里。
概览
| 项目 | 说明 |
|---|---|
| 本卡定位 | 消息结构层专章 · 与 fundamentals 平级,回答"为什么 JSON"与"何时该走" |
| 前置知识 | 已完成 llm-protocol-fundamentals.html(协议是什么) |
| 读完去哪儿 | llm-chat-protocol-guide.html 7 讲讲义 / llm-api-schema-reference.html Schema 参考 |
第一篇 · JSON 现状(为什么 + 变体)
为什么 JSON 是事实标准:4 个相互加强的原因
在 LLM(Large Language Model,大语言模型)协议这个语境里,JSON 几乎是唯一的选择。先交代名字:JSON = JavaScript Object Notation(JavaScript 对象表示法),一种以"键值对 + 数组"组织数据的人类可读文本格式。但"为什么是它"——其实有 4 个互相加强的原因,少一个,JSON 都坐不稳这个位置。这不是技术最优解,是生态最优解。
- 历史偶然性 → 生态必然性:Douglas Crockford 2001 年从 JS 子集提炼出 JSON;JS 在浏览器时代的统治地位让 JSON 顺水推舟进入所有 Web API。每种语言都至少有 1 个 JSON 库——这比任何"标准设计"都更强大的护城河
- 技术恰到好处:自描述(key 名就是文档)、人类可读(debug 友好)、Schema 灵活(不要也行,要也可以用 JSON Schema 加)、树形结构天然契合 messages / tools / tool_calls 这种嵌套数据
- LLM 训练友好:模型训练数据里大量 JSON 例子,模型"看得懂" JSON 格式,function calling / json_mode / structured outputs 都基于这个事实。换成 Protobuf,模型生成的就是二进制,没法直接 parse 进下一轮
- 可调试性:curl 直接发、jq 直接读、print(json.dumps(...)) 直接看——整个工具链对人透明。Protobuf 要靠 protoc / grpcurl / 反射才能看
JSON 不是一种格式:5 个变体你在 LLM 工作里都会撞到
严格说,"JSON" 这个词覆盖了至少 5 种不完全兼容的格式。在 LLM 工作里你会反复撞到其中 4 种——知道它们的边界,能少踩很多坑。
| 变体 | 关键差异 | LLM 场景里的位置 | 与标准 JSON |
|---|---|---|---|
| 标准 JSON RFC 8259 |
严格双引号、无注释、无尾逗号 | 99% 的 API 消息体 | 自身即标准 |
| JSONL / NDJSON | 每行一个完整 JSON 对象(不跨行) | OpenAI 微调数据 / 流式日志 / SFT 训练集 | 是(每行独立) |
| JSON5 | 注释、尾逗号、单引号、十六进制 | 配置文件 / LLM 输出宽松解析 | 不严格兼容 |
| JSON Schema | 描述 JSON 结构的元语言 | OpenAPI / Anthropic tool 定义 / 入参校验 | N/A(元语言) |
| Stringified JSON (LLM 特有) |
JSON 字段的值是字符串,要二次 parse | OpenAI tool_calls.arguments / Anthropic tool_use.input | 需要 parse |
第二篇 · 何时该离开 JSON
Protobuf 何时该用:4 个场景,但 LLM 公开 API 永远不用
先给两个名字:Protobuf = Protocol Buffers(Google 设计的二进制序列化格式,靠 .proto 文件定义结构);API = Application Programming Interface(应用程序接口,程序之间约定的调用方式)。Protobuf 在 LLM 生态里是个"内部英雄"——对外的 API 全部是 JSON,内部的推理服务大量是 Protobuf。这种"内外不一致"不是 bug,是设计。
- 高 QPS / 低延迟:QPS(Queries Per Second,每秒查询数)> 10k 或 P99(99 分位延迟)< 10ms。JSON 解析在 CPU 热点上吃不消,Protobuf 的二进制 + 变长编码快 3-10 倍
- 带宽敏感:移动端、嵌入式、跨数据中心传输。Protobuf 体积约为 JSON 的 1/3
- 强 Schema 演进:多服务依赖同一份契约,需要 .proto 的 field number 保证前后兼容
- 多语言内部 RPC:gRPC + Protobuf 在 C++/Java/Go/Python/Rust 之间无缝对接,序列化代码自动生成
| 维度 | JSON | Protobuf |
|---|---|---|
| 可读性 | 高(人可直接看) | 低(需 protoc --decode) |
| 体积 | 大(文本) | 小(二进制,约 1/3) |
| 解析速度 | 慢(字符串解析) | 快(直接读二进制,3-10×) |
| Schema 强度 | 弱(可选 JSON Schema) | 强(.proto 强制) |
| 跨语言支持 | 几乎所有语言 | 主流语言(C++/Java/Go/Python/Rust) |
| 流式友好 | 一般(要 SSE 包装) | 天然友好(变长编码) |
| LLM 训练友好 | 高 | 低(训练数据里几乎没有) |
| 调试友好 | 高(curl + jq) | 低(要 protoc + 反射) |
- 训练数据:模型训练语料里几乎全是 JSON,换 Protobuf 模型就"看不懂"工具描述,function calling 失效
- 调试门槛:用户拿不到 .proto 定义就调不通,curl 直接挂——LLM 的用户群体远不止后端
- 生态分散:每个 LLM 厂商 JSON 格式虽不同但"长得像",换 Protobuf 各家就完全不通,跨厂商迁移成本爆炸
- 推理服务内部:NVIDIA Triton、TensorRT-LLM、vLLM 之间的通信大量是 gRPC + Protobuf
- 大厂内部 multi-agent 框架:agent ↔ agent 的高速 RPC 走 Protobuf,对外暴露时再转 JSON
- 微服务之间的内部 RPC:即使最终给前端的是 JSON,中间层往往是 Protobuf
实战决策树:你的场景该用哪种?
把上面所有内容压成一张图。从"你的服务是什么"开始,按节点回答就能落到唯一答案。
公开 API?"} LLM -- "是" --> J1["用 JSON
(生态 + 训练数据 + 调试)"] LLM -- "否" --> Internal{"是 LLM
内部推理?"} Internal -- "是" --> P1["用 Protobuf
(gRPC + .proto)"] Internal -- "否" --> Perf{"QPS > 10k
或 P99 < 10ms?"} Perf -- "是" --> P2["用 Protobuf"] Perf -- "否" --> Schema{"强 Schema
跨版本演进?"} Schema -- "是" --> P3["用 Protobuf"] Schema -- "否" --> Multi{"多语言团队
(>3 种语言)?"} Multi -- "是" --> P4["用 Protobuf"] Multi -- "否" --> J2["用 JSON
(简单 + 通用 + 生态)"]