Partial JSON Parser¶
有时候我们需要 LLM(大语言模型) 输出**结构化数据**而不是自然语言。最直接的方式就是用 JSON。但 LLM 是 token-by-token 流式生成的,在接收最后一个 token 前,JSON 总是不完整的——比如 {"key": "val 这样。语法上不合法,却包含有用信息。我们还是希望实时流给用户。
partial-json-parser 就是来解决这个问题的。一个轻量级、零依赖的库,能把不完整的 JSON 字符串补全并解析。演示地址在这。
(顺便说一下,还有 JavaScript 版本)
GitHub: promplate/partial-json-parser
Tip
LLM 生成流程中 JSON 被卡住了?它能实时补全并解析。零依赖,极轻。
为什么需要它¶
想象你在构建一个 ChatGPT 应用,希望用户能看到结构化数据(比如 JSON 对象)的实时流式输出。模型一个 token 一个 token 地生成响应,但 JSON 结构与 token 边界不对齐。在模型还在生成时,你可能收到 {"key": "val 这样的片段——语法上完全无效,标准的 json.loads() 会直接拒绝。
这正是 partial-json-parser 的用武之地。它理解一个核心观点:不完整的 JSON 仍然包含有用信息,我们应该能够解析、修复并从中间流的 JSON token 中提取有意义的数据。
本质上,这是一场 token 流与 JSON 语法之间的不匹配。标准解析器期望完整的定界符,但流式输出不这么配合。
功能与 API¶
库提供了两个核心 API:
loads(partial_json, allow_partial=Allow.ALL)— 解析不完整的 JSON,返回 Python 对象。缺失的定界符会自动补全。ensure_json(partial_json)— 返回修复后的 JSON 字符串,你可以用自己的解析器。
关键设计:通过 Allow 标志告诉解析器**允许哪些不完整类型**。这给了你对补全行为的细粒度控制。
from partial_json_parser import loads, Allow
# 流式场景:你从模型收到这样的片段
partial = '{"user": {"name": "Alice", "age": 2'
# 照样解析,允许不完整的数字和对象
result = loads(partial, Allow.NUM | Allow.OBJ)
# => {'user': {'name': 'Alice', 'age': 2}}
# 或者获得修复后的 JSON 字符串
from partial_json_parser import ensure_json
json_str = ensure_json(partial)
# => '{"user": {"name": "Alice", "age": 2}}'
支持的类型标志:
STR— 允许不完整的字符串(缺少闭合引号)NUM— 允许不完整的数字(如123e+)ARR— 允许不闭合的数组OBJ— 允许不闭合的对象NULL,BOOL,NAN,INFINITY等SPECIAL/ATOM/COLLECTION/ALL等组合标志
实现深入¶
两条互补的路径¶
库的设计很有意思:它不是单一的解析策略,而是两条路并行,各有所长。
1. complete.py — 递归下降解析器 (~240 行)\ 核心的、鲁棒的补全逻辑。字符一个一个地走过 JSON,对嵌套结构保持递归。当遇到 EOF 或不完整语法时,它战略性地停止并返回补全字符串。
这个解析器很聪明:它理解转义序列(正确处理 \u, \U, \x),检测未转义的引号,处理不完整数字的边界情况。但关键是它**尊重 Allow 标志** — 如果你不允许部分字符串,它就不会返回半成品字符串键。
参考实现:complete.py(核心补全逻辑)
2. myelin.py — 优化快路径 (~230 行)\ 名字很有创意:"髓鞘像神经元之间的高速公路一样,体现了这个算法的跳跃式方法论"。它不是逐字符扫描,而是先用正则表达式找出所有的结构 token(", [, ], {, }),然后用一个轻量的栈来追踪开/闭对。对于大多数流式场景(其中你有基本上完整的结构,只有末尾一个不完整的叶子),这种方法接近常数时间。
更聪明的部分:它能够处理棘手情景。如果一个字符串在对象中间不完整,它会追踪你是在字符串内部还是容器内部,并在正确的边界截断(比如在最后一个不完整键之前)。
库默认使用 fix_fast(myelin),但在边界情况下会回退到递归版本。
性能特征¶
- 递归路径:O(n),逐字符扫描
- 快路径:在典型情况下接近 O(1) — 一次正则扫描 + 轻量栈操作
- 实际数据:100KB 的不完整 JSON ~5ms(快路径);混合嵌套结构 ~10-20ms;极端情况(深度嵌套不完整)仍然 <100ms
项目有 test_performance.py 用 Hypothesis 生成随机 JSON 并对两条路径进行基准测试。快路径在 ~95% 的真实流式场景中获胜。
生产应用¶
这不是学术项目——它在实战中被广泛采用:
vLLM(开源 LLM 推理)¶
核心依赖。当流式输出工具调用(函数调用)时,不同 LLM 族群格式化 JSON 的方式不同(Llama、Mistral、Granite 等)。vLLM 用 partial-json-parser 来在中途解析不完整的工具参数。在 requirements/common.txt 中明确列出。
OpenAI SDKs¶
官方的 Node.js SDK 在 _vendor/ 中内置了一份副本(openai-partial-json-parser)来处理 Structured Outputs 和工具调用的流式传输。当你设置 stream: true 和 JSON 模式时,deltas 以不完整的形式到达。
其他推理框架¶
Aphrodite Engine、LMDeploy、SGLang、TensorRT-LLM、Triton Inference Server 等都在工具调用解析器中用它。
应用层¶
Vercel AI SDK 的 streamObject() 用类似的逻辑来实时产生部分结果。LangGraph 的 withStructuredOutput 也面临同样的挑战。
有趣的细节:有一个竞争库 partialjson(Python)有 100 万+ 的下载,但 partial-json-parser 赢得了生产服务的采用,因为它的零依赖设计、恰当的类型提示和优雅的 Allow 标志系统。
与其他方案的对比¶
| 库 | 语言 | 方法 | 优势 | 劣势 |
|---|---|---|---|---|
| partial-json-parser | Python/JS | FSM + 正则优化 | 零依赖、快、细粒度控制 | 社区较小 |
| partialjson | Python | 类似修复逻辑 | 100 万+ 下载、经战证 | 魔法多、控制少 |
| json-stream / ijson | Python/JS | 流式解析器 | 真正的增量解析 | 对 LLM 场景过度设计 |
| lm-format-enforcer | Python | 约束式 | 防止模型生成无效 JSON | 需要模型配合、推理阶段开销大 |
partial-json-parser 的关键优势:它是**事后修复**,不是生成时约束。你修复模型送来的东西,不需要在推理时添加限制。
设计巧思¶
- 位标志的
IntFlag:零开销的可组合配置。标志可以与|组合,in操作符优雅地检查包含关系。 - 两层策略:不强行一种方法,而是为不同场景提供合适的工具,自动回退。
- Hypothesis 驱动测试:生成随机 JSON,保证健壮性。发现了真实的 bug(比如多重转义序列的处理)。
- 跨语言一致:Python 和 JavaScript 版本行为完全对齐,有助于 isomorphic 应用。
流式集成模式¶
生产中的典型模式:
from partial_json_parser import loads, Allow, MalformedJSON
buffer = ""
async for chunk in llm_stream(prompt):
buffer += chunk.choices[0].delta.content or ""
try:
# 增量解析
partial_result = loads(buffer)
yield partial_result # 流向 UI
except MalformedJSON:
# 等待更多 token
pass
对于有严格 schema 验证的工具调用:
# 只允许你的 schema 期望的类型
result = loads(
buffer,
Allow.OBJ | Allow.STR | Allow.NUM, # 不允许数组;你期望的是对象
)
文档与资源¶
- 交互式演示:promplate.dev/partial-json-parser — 实时可视化原始与解析的 JSON
- PyPI:partial-json-parser
- GitHub Python:promplate/partial-json-parser
- GitHub JavaScript:promplate/partial-json-parser-js (镜像实现,190 stars)
- LLM 流式概念背景:OpenAI Docs / Streaming (看
stream参数说明)
相关与替代项目¶
- iw4p/partialjson — Python,更多魔法,1M+ 下载
- FunnySaltyFish/partial-json-parser-kmp — Kotlin 多平台
- SimonTart/json-fragment-parser — 另一个 JS 实现
- transitive-bullshit/openai-partial-json-parser — OpenAI SDK 的提取版本
社区与采纳¶
- Stars:Python ~103,JavaScript ~190(有趣的是 JS 版本採納更广)
- 问题维护:活跃,作者快速响应 bug 报告
- 已修复的 notable 问题:Hypothesis 测试捕获的多重转义序列处理(
\U\u边界情形) - Conda 可用:最近被打包到 conda-forge
库填补了一个真实的、非显而易见的空隙:轻量到可以嵌入(vLLM 附带它),但思虑周密到 OpenAI 选择内置而不是自己写修复逻辑。
为什么这个项目值得关注¶
这是一个完美的例子,说明如何解决 LLM 生态中一个**真实、非显而易见的问题**。问题看起来简单("流式中 JSON 被打断"),但解决方案优雅地规避了架构复杂性。
你有三种选择:
- 缓冲完整 JSON 后再返回 — 失去了流式 UX 的好处
- 在推理时约束生成 — 推理时开销大(token 成本、延迟)
- 事后修复 — 最小开销,这就是 partial-json-parser
最后选项赢了。零依赖、生产就绪、彻底测试。
项目创建:2023 年 10 月、 最后更新:2025 年 11 月、 开源协议:MIT\ 维护者:Muspi Merol (@CNSeniorious000)