AI Study Online
AI教程

如何确保大语言模型稳定输出JSON:开发者实用指南

5 min read

要让大语言模型稳定输出JSON,很多开发者容易掉进一个陷阱:完全依赖提示工程,比如简单地在提示词里写"输出JSON"或"不要添加额外文字"。但在生产环境中,这种方式并不可靠。要构建一个健壮的解决方案,我们需要一个多层策略——结合提示工程、模型原生能力、底层约束和回退机制。下面用实战步骤和代码示例逐一讲解每一层。

1. 带少样本学习的提示工程

第一层是使用精心设计的提示词来引导LLM,特别是利用少样本学习。不要使用模糊的指令,而是提供输入和预期JSON输出的具体示例,让模型照猫画虎。

例如,如果你想从聊天消息中提取用户的姓名和年龄,可以这样构建提示词:

任务:从以下聊天消息中提取姓名和年龄,以JSON格式输出。

输入示例:小明今年十八岁
输出示例:{"name": "小明", "age": 18}

当前输入:你好,我是张三,今年25岁
预期输出:

通过向模型展示清晰的输入输出对,它就会学习模仿所需的JSON结构,大大减少输出无关文本或格式出错的概率。

2. 利用原生LLM能力

现代LLM大多内置了强制执行结构化输出的功能。两种最实用的方法是JSON模式函数调用

JSON模式

许多LLM API(比如OpenAI的API)提供了 response_format 参数。你只需要把它设为 { "type": "json_object" },模型就会优先考虑输出JSON格式。

下面是使用Python和OpenAI API的完整示例:

import openai

response = openai.ChatCompletion.create(
  model="gpt-4o",
  messages=[
    {"role": "user", "content": "从'李四今年30岁'中提取姓名和年龄,返回JSON格式。"}
  ],
  response_format={"type": "json_object"}
)

print(response.choices[0].message.content)

函数调用

函数调用则更进一步:你可以为JSON输出定义一个明确的模式(Schema),LLM生成的JSON会严格遵循这个模式,不会跑偏。

下面是一个提取用户信息的函数模式定义:

functions = [
  {
    "name": "extract_user_info",
    "parameters": {
      "type": "object",
      "properties": {
        "name": {"type": "string"},
        "age": {"type": "integer"}
      },
      "required": ["name", "age"]
    }
  }
]

response = openai.ChatCompletion.create(
  model="gpt-4o",
  messages=[
    {"role": "user", "content": "我叫王五,今年40岁"}
  ],
  functions=functions,
  function_call={"name": "extract_user_info"}
)

# 解析函数调用响应
function_response = response.choices[0].message.function_call.arguments
print(function_response)

3. 本地模型的底层约束

如果你是在本地运行LLM,还可以在Token生成这一底层做文章。通过监控模型逐个输出的Token,实时拦截那些会破坏JSON语法的Token。

用Python的 transformers 库,你可以实现一个Token级别的过滤器:

from transformers import AutoTokenizer, AutoModelForCausalLM
import re

tokenizer = AutoTokenizer.from_pretrained("your-local-model")
model = AutoModelForCausalLM.from_pretrained("your-local-model")

def is_valid_json_token(token):
    # 检查该Token是否为有效JSON语法的一部分
    json_patterns = [r'^\{.*', r'^\}.*', r'^".*"', r'^[0-9].*', r'^,.*', r'^:.*', r'^\[.*', r'^\].*']
    for pattern in json_patterns:
        if re.match(pattern, token):
            return True
    return False

# 使用Token过滤生成文本
input_ids = tokenizer("从'赵六今年28岁'中提取姓名和年龄,输出JSON格式:", return_tensors="pt")
output = model.generate(
    input_ids,
    max_length=100,
    pad_token_id=tokenizer.eos_token_id,
    bad_words_ids=[[tokenizer.encode(token)[0]] for token in tokenizer.vocab if not is_valid_json_token(tokenizer.decode([token]))]
)

print(tokenizer.decode(output[0], skip_special_tokens=True))

这段代码的核心思路是:只允许那些能构成有效JSON的Token通过,其余的全部拦截,从源头上保证输出格式正确。

4. 错误处理的回退机制

即使前面三层都用上了,错误还是有可能出现。这时候就需要一个兜底的回退机制:先验证输出是不是合法JSON,如果不是,就让LLM自己修正。

import json

def validate_and_retry(response_text, model, tokenizer):
    try:
        json.loads(response_text)
        return response_text
    except json.JSONDecodeError as e:
        # 将错误返回给LLM进行修正
        error_message = f"无效JSON:{str(e)}。请修复JSON并重试。"
        correction_response = openai.ChatCompletion.create(
            model="gpt-4o",
            messages=[
                {"role": "user", "content": error_message + "
原始输出:" + response_text}
            ],
            response_format={"type": "json_object"}
        )
        return correction_response.choices[0].message.content

这个函数先用 json.loads 尝试解析。如果抛异常了,就把错误信息塞回给LLM,让它基于原来的输出重新生成一个合法的JSON。大多数情况下,LLM看到自己的错误后都能正确修正。

结论

通过结合这四层——带少样本示例的提示工程、JSON模式和函数调用等原生LLM特性、本地模型的底层Token约束以及错误处理的回退机制——你可以在任何环境下确保LLM稳定输出JSON。这种多层方法不仅对生产环境非常实用,也能体现出扎实的工程思维,在技术面试和实际开发中都是加分项。

对于使用云端LLM API的开发者来说,第三层(本地模型的底层约束)用不了,因为你无法控制服务端Token的生成过程。这也意味着前面三层更加关键,必须做得足够扎实才能保证可靠性。

分享这篇文章

相关文章

AI教程入门

ChatGPT基础:界面、设置和你的前10个提示词

ChatGPT新手?这里有完整的初学者指南——账户设置、界面导览、需要配置的设置,以及10个入门提示词让你从第一天就获得真正价值。

5分钟阅读
ChatGPT入门基础
AI教程入门

如何写出真正有效的提示词:5点框架

模糊的提示词只能得到平庸的回答。掌握5点提示框架——角色、上下文、任务、格式、约束——从任何AI工具中获得显著更好的结果。

5分钟阅读
提示词提示工程框架