1. 引言
在大语言模型(LLM)的应用中,我们经常需要模型以 JSON 格式返回结构化数据,例如信息抽取、函数调用、配置生成等。然而,LLM 的输出往往不是严格符合 JSON 规范的,常见问题包括:
- 键或字符串缺少引号:
{name: "张三"} - 使用单引号代替双引号:
{'name': '张三'} - 多余的逗号:
["a", "b",] - 包含注释:
// 这是注释或/* ... */ - 输出前后夹杂自然语言描述或 Markdown 标记:
以下是JSON:{"key": "value"} - 转义错误、控制字符等
直接使用 Python 内置的 json.loads() 解析这类字符串会抛出异常,导致程序中断。为了提升程序的鲁棒性,我们需要一种能够自动修复常见 JSON 错误的方法。
在 LLM 驱动的应用中,处理非标准 JSON 输出是一个普遍而棘手的挑战。json_repair 库通过智能修复机制,让我们能够轻松地将各种"脏" JSON 转换为可用的 Python 对象。通过本文的介绍和示例,您可以快速掌握以下要点:
- 理解
json_repair的工作原理:预处理 + 容错解析。 - 掌握
repair_json函数的使用,特别是return_object参数带来的便利。 - 学会构建鲁棒的解析函数,保护程序免受 LLM 格式错误的影响。
本文将详细介绍 json_repair 库及其 repair_json 方法,并通过完整的示例展示如何让 LLM 输出解析变得可靠而简单。
2. json_repair 库简介
json_repair 是一个轻量级 Python 库,专门用于修复和解析不规范的 JSON 字符串。它的核心思想是容错解析:采用宽松的解析器,允许许多常见的 JSON 编写错误,并在遇到错误时尝试局部修复。修复后,既可以返回标准的 JSON 字符串,也可以直接返回反序列化后的 Python 对象(字典/列表)。
该库的主要特点:
- 自动去除首尾无关文本:剥离自然语言、Markdown 代码块标记等。
- 修复常见语法错误:补全缺失的引号、处理多余逗号、转换单引号等。
- 保留数据类型 :正确识别
null、true、false以及数字。 - 提供两种返回形式:修复后的 JSON 字符串或 Python 对象。
- 轻量无依赖:纯 Python 实现,易于集成。
3. 工作原理
json_repair 的工作原理可以概括为三个步骤:
-
预处理
首先,它会尝试从输入字符串中提取 JSON 部分 。如果字符串以自然语言开头和结尾,解析器会智能地忽略这些非 JSON 内容,仅保留可能的 JSON 片段(例如,查找第一个
{或[到最后匹配的}或])。 -
容错解析
接下来,它使用一个宽松的词法分析器和解析器逐字符解析。当遇到不符合 JSON 标准的地方时,不会立即报错,而是尝试进行修复:
- 如果遇到单引号,将其转换为双引号。
- 如果键名缺少引号,自动添加双引号。
- 如果出现多余的逗号(如数组最后一个元素后),将其忽略。
- 如果存在 C 风格注释,直接跳过。
- 处理不规范的转义序列等。
-
生成结果
修复完成后,解析器构建出标准的 JSON 结构。根据用户选择,返回修复后的 JSON 字符串或直接将其转换为 Python 对象。
这种修复策略覆盖了绝大多数 LLM 输出中可能出现的格式问题,使得原本无法解析的字符串变得可用。
4. 安装 json_repair
使用 pip 可以轻松安装:
bash
pip install json_repair
安装后即可在 Python 中导入:
python
from json_repair import repair_json
5. repair_json 方法详解
repair_json 是库的核心函数,其完整签名如下:
python
repair_json(json_string: str, return_object: bool = False) -> Union[str, dict, list]
- json_string (str):待修复的原始字符串。
- return_object (bool,默认 False):
- 如果为
False,函数返回修复后的JSON 字符串。 - 如果为
True,函数返回反序列化后的 Python 对象(字典、列表或基本类型)。
- 如果为
当 return_object=True 时,实际上相当于 json.loads(repair_json(json_string)) 的简写,但内部修复和解析是同时进行的,效率更高,且能避免两次解析可能出现的细微差异。
如果修复失败,函数会抛出 json_repair.JsonRepairFailedError 异常,上层可以捕获并处理。
6. 基础代码示例
下面通过一个简单的例子展示 repair_json 的强大之处。
python
from json_repair import repair_json
# 模拟一个常见的"脏" JSON 输出
dirty_json = """
根据请求,生成如下数据:
{
name: '李华', // 键无引号,值使用单引号
age: 28,
tags: ["工程师", "管理者",], // 多余的逗号
address: {
city: "北京",
code: 100000
}
}
注意:仅供测试使用。
"""
# 方案1:仅修复为 JSON 字符串
clean_str = repair_json(dirty_json)
print("修复后的 JSON 字符串:")
print(clean_str)
# 方案2:直接获得 Python 对象
data = repair_json(dirty_json, return_object=True)
print("\n解析后的 Python 对象:")
print(data)
print("姓名:", data['name'])
输出:
修复后的 JSON 字符串:
{
"name": "李华",
"age": 28,
"tags": ["工程师", "管理者"],
"address": {
"city": "北京",
"code": 100000
}
}
解析后的 Python 对象:
{'name': '李华', 'age': 28, 'tags': ['工程师', '管理者'], 'address': {'city': '北京', 'code': 100000}}
姓名: 李华
可以看到,原始字符串中的各种错误(缺少引号的键、单引号、注释、多余逗号、首尾文本)都被完美修复,我们直接获得了可用的 Python 字典。
7. 完整实战:构建鲁棒的 LLM 输出解析函数
在实际项目中,我们通常需要处理来自 LLM 的各种输出,并希望即使修复失败也能有默认行为。下面是一个封装好的函数,它结合了异常处理和默认值返回,可以安全地集成到任何 LLM 调用流程中。
python
import logging
from json_repair import repair_json, JsonRepairFailedError
def safe_parse_llm_output(llm_output: str, default=None):
"""
安全解析 LLM 输出的 JSON 内容,自动修复格式错误。
Args:
llm_output: LLM 返回的原始字符串。
default: 解析失败时返回的默认值(可选)。
Returns:
解析后的 Python 对象(dict/list 等),失败时返回 default。
"""
try:
# 尝试修复并直接返回对象
return repair_json(llm_output, return_object=True)
except JsonRepairFailedError as e:
logging.warning(f"JSON 修复失败,将返回默认值。错误:{e}")
return default
except Exception as e:
logging.error(f"解析过程中发生未知错误:{e}")
return default
# 模拟多种 LLM 输出场景
test_inputs = [
# 标准 JSON
'{"name": "张三", "age": 30}',
# 包含 Markdown 代码块
'```json\n{"name": "李四", "age": 25}\n```',
# 夹杂自然语言 + 注释
"""
您好,这是查询结果:
{
'name': '王五', // 用户名
'age': 32,
'hobbies': ['读书', '编程',] // 末尾逗号
}
""",
# 完全非 JSON 内容
"对不起,我无法理解您的请求。",
# 空字符串
"",
]
for i, raw in enumerate(test_inputs, 1):
result = safe_parse_llm_output(raw, default={})
print(f"Case {i}: {result}")
可能的输出:
Case 1: {'name': '张三', 'age': 30}
Case 2: {'name': '李四', 'age': 25}
Case 3: {'name': '王五', 'age': 32, 'hobbies': ['读书', '编程']}
Case 4: {}
Case 5: {}
通过这个封装,我们的程序在面对各种 LLM 输出时都能稳定运行,不会因格式问题而崩溃。同时,日志记录了失败情况,便于后续调试和优化。
8. 注意事项
尽管 json_repair 非常强大,但在使用时仍需注意以下几点:
- 修复不是万能的 :对于严重损坏或完全无关的字符串,修复可能失败(抛出
JsonRepairFailedError)。建议始终提供默认值或降级方案。 - 性能考虑 :修复过程比标准
json.loads()稍慢,但对于大多数 LLM 应用来说完全可以接受(通常修复一个 JSON 在几毫秒内)。如果对性能有极致要求,可以先尝试json.loads(),失败后再调用修复。 - 适用版本 :确保使用最新版本以获取最好的修复能力。
json_repair持续更新,以覆盖更多边缘情况。 - 安全性 :修复后的 JSON 是通过解析器重新生成的,不会执行任意代码,安全性等同于
json.loads()。