探索LLM推理全阶段JSON格式输出限制方法

确保LLM结构化输出JSON至关重要,本文探索了推理前(提示工程)、推理中(动态限制解码)和推理后(JSON后处理)三个阶段的优化策略,可显著提高JSON输出准确性。

原文标题:探索LLM推理全阶段的JSON格式输出限制方法

原文作者:阿里云开发者

冷月清谈:

**摘要**

本文探索了如何确保大型语言模型(LLMs)输出结构化的JSON格式,这对于自动化数据处理和提高系统互操作性至关重要。介绍了**推理前**、**推理中**和**推理后**三个阶段的优化策略,包括提示工程、动态限制解码和JSON后处理。

**推理前(提示工程):**
- 在提示语中加入特殊指令,如“The JSON object: json”。
- 提供JSON示例作为提示语的一部分。

**推理中(动态限制解码):**
- 动态限制解码:限制词汇表中的词语概率,只允许符合JSON规范的词语输出。
- 根据正则表达式定义期望的JSON输出格式。

**推理后(JSON后处理):**
- 使用JSON Repair库修复不规范的JSON结构。
- 改变LLM推理的随机种子,减少出错概率。

**方法总结:**
| 阶段 | 方法 | 优缺点 | 是否需要本地部署模型 | 是否支持任意正则式定义输出格式 |
|---|---|---|---|---|
| 推理前 | 提示工程 | 简单,无需模型架构调整,能大幅提高JSON输出概率 | 否 | 否 |
| 推理中 | 动态限制解码 | 100%严格输出JSON格式,节省输出token | 是 | 是 |
| 推理后 | JSON后处理 | 修复不规范的JSON结构,减少出错概率 | 否 | 否 |

通过将这些方法结合使用,可以显著提高LLM输出JSON格式的准确性,满足不同应用场景的需求。

**彩蛋:**某些模型已支持直接输出结构化JSON,无需特殊处理,如qwen-max-0919和qwen2.5系列模型。

怜星夜思:

1、LLM的JSON输出限制的未来发展方向是什么?
2、动态限制解码方法是否可以扩展到其他任务?
3、在实践中,如何权衡推理前、推理中和推理后方法的优缺点?

原文内容

阿里妹导读


文章详细讨论了如何确保大型语言模型(LLMs)输出结构化的JSON格式,这对于提高数据处理的自动化程度和系统的互操作性至关重要。

一、引言


1.1 JSON结构化输出的意义

对于基于大型语言模型(LLMs)的应用而言,确保输出能直接以结构化的JSON格式呈现,对于提升数据处理的自动化程度、增强系统的互操作性具有重要意义。例如,客户需要对LLM的输出进行信息提取时,若输出是一个JSON格式则会大大方便工程链路上的后处理;例如,LLM在调用工具(或其它智能体)时,需要按照工具要求传入正确的参数,若能保证LLM的输出是结构化的JSON,则能保证传参正确,从而正确调用工具。

然而,在实践中,即使我们在提示词中反复告诉模型要输出JSON结构,LLM还是偶尔出错。虽然“偶尔”出错的概率很低,但对于工程链路的设计来说,是致命且麻烦的。


1.2 LLM为何不能严格输出JSON

LLM在推理时,基于已经输出的句子,从词汇表中预测下一个词。预测时,为词汇表中的每个词分配一个概率,通过采样得到预测输出,如图1所示。例如,模型在输出"My name is"后,仅有0.62的概率输出自己的名字"Tang",即使我们在提示词中告诉了模型自己的名字是"Tang",模型也有0.38的概率输出别的名字。

图1. LLM推理的预测概率示意

因此,这个依概率采样的推理过程决定了LLM不可能100%按要求输出JSON格式。错误的JSON输出导致了我们在工程链路上无法作后续的解析,因此,能100%严格限制JSON格式输出的方法非常重要。


1.3 友商方案

  • OpenAI JSON Mode

推出于2023年12月份,基于提示词优化,用户仍需要在提示词中给出JSON示例,不能保证严格100%输出JSON。

  • Kimi JSON Mode

近期推出。类似OpenAI的 JSON Mode,用户仍需要在提示词中给出JSON示例,不能保证严格100%输出JSON。

  • OpenAI Structured Outputs

推出于2024年8月份,根据用户给出的JSON示例,严格保证100%输出JSON格式。

https://openai.com/index/introducing-structured-outputs-in-the-api/


图2. OpenAI输出JSON格式的方法,橙、黄、绿分别代表提示词优化、微调、动态限制解码法的JSON输出准确率

(动态限制解码法准确率为100%)

二、前中后三阶段的优化策略 

Motivation: 在一个基于通义千问的AI教评项目场景中,JSON格式输出对客户十分重要。因此,我们在该项目实践中由浅入深,从LLM推理的前、中、后三个阶段探索了限制输出JSON格式的方法。其中,“推理前”和“推理后”这两个阶段的方法用在了项目实践中,大大提高了AI教评任务中JSON格式的输出概率。为了进一步研究如何100%输出JSON格式,我们借他山之石,研究了OpenAI的Structured Outputs方法,在“推理中”这一阶段探索并验证了基于动态限制解码的100%输出JSON格式方法。

在分析相关工作基础上,我们将深入讨论每阶段的方法、优劣及其实现方式,以期帮助读者掌握提升JSON输出概率的办法,并应用在实践中。


2.1 推理“前”:Prompt Engineering

(以下提示词来自大量项目实践验证)

在提示词中加入这句话“The JSON object:json”可提高JSON输出概率。(别问,问就是大量实践总结的经验~)

在提示词中给出"##输出格式规范",并给出JSON示例```json ... ```

## 输出格式规范:
```json
[{
"name":"<评价维度>",
"mentions":"< 提及次数 >",
"references":[{
"time":"<发言时间>",
"text":"<原文内容>"}]}]
```

The JSON object:json

【实践】

在利用Qwen-long作AI教评的一个项目中,我们需要从教师的课堂录音文本中提取结构化的教学维度信息。采用本节中的prompt加上2.3中的JSON后处理方法后,输出样本基本是符合预期的结构化JSON。JSON正确概率从50%左右上升到了95%。可见仅靠prompt和后处理,已经能以很高的概率使得大模型按照JSON格式输出。然而,在一些需要严谨输出JSON格式的场景,100%严格输出JSON格式的方法仍值得研究。

【优势】

实施简便,无需模型架构调整,可以大幅提高输出JSON的概率。

【不足】

高度依赖于人工设计的prompt,灵活性受限。不能100%输出JSON


2.2 推理“中”:基于动态限制解码实现100%输出JSON

【原理】

LLM依据已输出的词,从词汇表中预测下一个词,可以在词汇表中将不符合JSON规范的词概率置零,从而防止输出不符合JSON规范。(原理偏复杂,可跳过本节直接看结论)。假设我们想让LLM的输出为一个城市的如下信息:

city_info_schema=[{
"name":"城市名",
"country":"城市所属国家",
"latitude":"城市纬度",
"population":"城市人口(千万)",
"top 3 landmarks":["知名景点1","知名景点2","知名景点3"]
}]
如上代码块所示,在内存中定义JSON输出的模式city_info_schema。LLM每轮逐个单词输出"response",对于JSON的"key"值,如"name",我们直接从内存拼接到输出字符串"response_str"中;对于JSON的"value",则让LLM通过推理产生。当用户提出问题“请填写杭州的城市信息”后,动态限制解码流程如下:


图3. 动态限制解码法示意图。其中只有绿色词是LLM的推理产生。


上图展示了动态限制解码的工作流程,每一轮推理过程我们给定了JSON的“键”,仅让模型推理“值”。可以进一步用正则式(Python re库)限制我们想要的输出格式:

city_regex = (
r"""\{\n"""
+ r"""  "name": "[\w\d\s]{1,16}",\n"""
+ r"""  "country": "[\w\d\s]{1,16}",\n"""
+ r"""  "latitude": [-+]?[0-9]*\.?[0-9]{0,2},\n"""
+ r"""  "population": [-+]?[0-9]{1,9},\n"""
+ r"""  "top 3 landmarks": \["[\w\d\s]{1,16}", "[\w\d\s]{1,16}", "[\w\d\s]{1,16}"\]\n"""
+ r"""\}"""
)
在推理过程中,根据正则式限制输出格式的流程如下:

图4. 动态限制解码法的”推理-限制-采样-拼接”流程


如第一个键"key"对应的"name",我们用正则式限制其必须输出16个字以内的英文,则"杭"的概率由于不符合正则式要求,预测概率置零,模型一定会按照我们的要求输出。

由于动态限制解码技术需要我们有冻结模型解码过程、改变词汇表采样概率、改变模型输入的权限,目前在线的API接口

不支持编写动态限制解码算法。但是可以在本地部署模型以实现动态限制解码。

【实践】

在PAI平台的免费体验DSW(NVIDIA A10)上本地部署Qwen2-7B-Instruct实现动态限制解码。基于开源的sglang库,可快速部署动态限制解码算法。

pip install --upgrade pip
pip install "sglang[all]"
# Install FlashInfer CUDA kernels
wget "https://modelscope.oss-cn-beijing.aliyuncs.com/resource/flashinfer-0.1.2%2Bcu121torch2.3-cp310-cp310-linux_x86_64.whl"
pip install flashinfer-0.1.2+cu121torch2.3-cp310-cp310-linux_x86_64.whl
modelscope download --model=qwen/Qwen2-7B-Instruct --local_dir ./Qwen2-7B-Instruct
python3 -m sglang.launch_server --model-path Qwen2-7B-Instruct --port 30000

图5. sglang框架下的千问模型本地部署成功示意图

显示上图即部署成功。

###导入库
import json
import time
from sglang import set_default_backend, RuntimeEndpoint
import sglang as sgl
from sglang.test.test_utils import (
add_common_sglang_args_and_parse,
select_sglang_backend,
)
from sglang.utils import dump_state_text, read_jsonl
##定义“限制模型输出的正则式”
city_regex = (
r"""\{\n"""
+ r"""  "name": "[\w\d\s]{1,16}",\n"""
+ r"""  "country": "[\w\d\s]{1,16}",\n"""
+ r"""  "latitude": [-+]?[0-9]*\.?[0-9]{0,2},\n"""
+ r"""  "population": [-+]?[0-9]{1,9},\n"""
+ r"""  "top 3 landmarks": \["[\w\d\s]{1,16}", "[\w\d\s]{1,16}", "[\w\d\s]{1,16}"\]\n"""
+ r"""\}"""
)
## 将正则式应用在输出范式中
@sgl.function
def chat_example(s,question):
s += sgl.system("You are a helpful assistant.")
# Same as: s += s.system("You are a helpful assistant.")

with s.user():
s += question

s += sgl.assistant_begin()
s += "Answer: " + sgl.gen(“json_output”, max_tokens=256, regex=city_regex)
s += sgl.assistant_end()

设置Qwen2的本地通信端口,上图设置为port30000

set_default_backend(RuntimeEndpoint(“http://localhost:30000”))

捕捉用户输入

state = chat_example.run(
question=input(“请输入城市名:”),

temperature=0.1,

stream=True
)

打印必然的JSON输出结果

for out in state.text_iter():
print(out, end=“”, flush=True)

运行效果:试输入“杭州”和“纽约”两个城市。输出严格按照了正则式的限制。

图6. 基于动态限制解码的JSON格式输出结果。

【优势】
  • 100%严格输出JSON格式,甚至是任意正则式可以定义的格式。

  • 在输出的JSON中,节省了输出"key"值的token:因为"key"值是内存中定义好的,不需要由LLM推理而得。因此,相对于prompt的方式让模型输出全JSON的方式,节省了输出的token数量。(这也是为什么OpenAI的JSON 模式每token价格有30%的折扣的原因)

【不足】
  • 必须本地部署LLM。


2.3 推理“后”:JSON数据后处理

在模型返回response后,也可以利用后处理的技术,校正JSON结构以提高JSON输出的概率。

  • JSON Repair库

Python 的json_repair库,可以解决一部分模型输出JSON格式不规范的问题。

from json_repair import loads #pip install json_repair
import json

if name == ‘main’:

bad_string= ‘’’
[
{
“foo”: “Foo bar baz”,
“tag”: “foo-bar-baz”
},
{
“中文”: “foo bar foobar foo bar baz.”,
“标签”: “foo-bar-foobar”
}
]
‘’’

parsed_json = loads(bad_string)
json_str = json.dumps(parsed_json,ensure_ascii=False)
print(json_str)

经实践验证,json_repair可以解决输出的JSON中缺少"},],"的问题。


  • 随机种子控制:可改变LLM推理的seed, 在不同的seed下输出以减少出错概率。

三、总结与展望

以上介绍的三种类型的方法,可以同时使用,但需要注意不同的场景限制:

【前、中、后三阶段方法总结】

【彩蛋】

qwen-max-0919、qwen-max-latest、qwen-plus、qwen-plus-0919、qwen-plus-latest、qwen-turbo-0919、qwen-turbo-latest以及qwen2.5系列模型已支持结构化输出JSON。(设置response_format = { "type": "json_object" } )

正如技术在不断进步,不要排除出现我们无法预料的创新方法的可能性。未来可能是令人兴奋的,因为研究人员和从业人员共同努力,将LLM的JSON输出限制推向新的高度。

未来可能出现新的突破性技术或算法,彻底改变LLM的JSON输出限制方式。例如:
- **元学习:**将元学习技术应用于LLM训练,使其能够根据给定的JSON模式快速调整输出格式。
- **知识图增强:**将知识图融入训练数据中,使LLM能够更好地理解和输出结构化的信息。
- **协作式机器学习:**让LLM与人类专家协作,共同开发和完善JSON输出限制策略。

动态限制解码方法的扩展潜力非常广阔。其核心理念是通过限制LLM的输出空间,引导模型生成符合特定要求的结果。只要任务涉及结构化或受约束的输出,动态限制解码方法都可以发挥作用。

随着LLM技术的不断发展,预计未来JSON输出限制将更加灵活和高效。探索的方向包括:
- **基于提示的设计:**开发更巧妙的提示设计方法,使LLM能够更准确地理解和遵循JSON输出要求。
- **LLM架构改进:**研究新的LLM架构,例如基于 transformers-XL 或 GPT-4,它们可能天然更适合输出结构化的数据。
- **后处理技术的进步:**改进现有JSON后处理技术,使其更鲁棒且通用,能够处理更广泛的LLM输出格式。

权衡推理前、推理中和推理后方法的优缺点对于在实践中有效实现JSON输出限制至关重要:

推理前(提示工程):
- 优点:简单实施,不需要修改模型或后处理步骤。
- 缺点:高度依赖于提示设计的质量,可能无法保证100%的JSON输出准确性。

推理中(动态限制解码):
- 优点:100%保证JSON输出准确性,无需提示工程。
- 缺点:需要本地部署模型,目前在线API接口不支持。

推理后(JSON后处理):
- 优点:简单实施,无需修改模型或本地部署。
- 缺点:无法保证100%的JSON输出准确性,只能修复部分格式错误。

因此,方法的选择取决于具体应用的优先事项和限制。例如,如果准确性至关重要且可以部署本地模型,则动态限制解码是最佳选择。如果简单性和在线部署更重要,则推理前或推理后方法可能是更好的选择。

权衡这些方法时,还需要考虑所需的灵活性水平。提示工程方法最灵活,因为它允许对提示进行调整以适应不同的输出要求。推理中和推理后方法通常更受限,因为它们依赖于预定义的模式或修复规则。因此,如果需要高度可定制的输出格式,则提示工程方法可能是更好的选择。

权衡推理前、推理中和推理后方法的另一个关键因素是性能和效率。动态限制解码通常比推理前和推理后方法慢,因为需要对词汇表和采样过程进行修改。推理前方法最有效率,而推理后方法介于两者之间。因此,对于延迟敏感的应用,推理前或推理后方法可能更合适。

动态限制解码方法具有很强的通用性,可以扩展到广泛的任务中,例如:
- **代码生成:**限制模型输出的词语,确保生成的代码符合语法和风格规范。
- **图像字幕:**限制模型输出的词语,使字幕与图像内容高度相关且结构合理。
- **问答:**限制模型输出的词语,确保答案简洁、完整并符合问题的主题。