跳转至

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),但在边界情况下会回退到递归版本。

参考实现:myelin.py(fast-path 优化)

性能特征

  • 递归路径: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,  # 不允许数组;你期望的是对象
)

文档与资源

相关与替代项目

社区与采纳

  • Stars:Python ~103,JavaScript ~190(有趣的是 JS 版本採納更广)
  • 问题维护:活跃,作者快速响应 bug 报告
  • 已修复的 notable 问题:Hypothesis 测试捕获的多重转义序列处理(\U\u 边界情形)
  • Conda 可用:最近被打包到 conda-forge

库填补了一个真实的、非显而易见的空隙:轻量到可以嵌入(vLLM 附带它),但思虑周密到 OpenAI 选择内置而不是自己写修复逻辑。

为什么这个项目值得关注

这是一个完美的例子,说明如何解决 LLM 生态中一个**真实、非显而易见的问题**。问题看起来简单("流式中 JSON 被打断"),但解决方案优雅地规避了架构复杂性。

你有三种选择:

  1. 缓冲完整 JSON 后再返回 — 失去了流式 UX 的好处
  2. 在推理时约束生成 — 推理时开销大(token 成本、延迟)
  3. 事后修复 — 最小开销,这就是 partial-json-parser

最后选项赢了。零依赖、生产就绪、彻底测试。


项目创建:2023 年 10 月、 最后更新:2025 年 11 月、 开源协议:MIT\ 维护者:Muspi Merol (@CNSeniorious000)