有时候我们希望LLM以格式化输出一些文本,比如json。这在LLM输出多种信息的时候尤其有用。
下面的测试使用RTX4070显卡(12G显存),LLM为Qwen3-1.7B

提示词修改

最简单的办法是在系统/用户提示词内指定要求以json格式进行输出。我们以之前的Qwen3-1.7B为例子:

user_prompt = """
请根据以下内容生成一个用户信息,并以 JSON 格式输出,包含字段:name, age, city。

输出格式示例:
{
  "name": "张三",
  "age": 28,
  "city": "北京"
}
"""

我们的模型是通过vllm.entrypoints.openai.api_server起的,因此改动提示词就行。此外,我们通过"chat_template_kwargs": {"enable_thinking": False}关闭了思考模式,避免输出think的内容:

payload = {
    "model": "qwen3",
    "messages": [
        {"role": "system", "content": "请使用尽可能简短的语言回答我的问题"},
        {"role": "user", "content": user_prompt}
    ],
    "temperature": 0.7,
    "max_tokens": 1024, 
    "chat_template_kwargs": {"enable_thinking": False}
}

response = requests.post(
    f"{api_base}/chat/completions",
    headers=headers,
    data=json.dumps(payload)
)

这样输出的结果如下:

{
  "name": "李四",
  "age": 30,
  "city": "上海"
}

这样的一个问题是,提示词未必能百分之百控制LLM的输出,这在提示词膨胀到几百字、且对输出的准确性要求极高的场景下尤为明显。

参数控制(API)

支持以Openai格式调用的大模型可以通过传递参数来控制,比如这里可以通过参数"response_format":{"type":"json_object"}控制使LLM按json格式进行输出,请求参数变成如下的形式:

payload = {
    "model": "qwen3",
    "messages": [
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": user_prompt}
    ],
    # "temperature": 0.7,
    "max_tokens": 1024, 
    "chat_template_kwargs": {"enable_thinking": False},
    "response_format":{"type":"json_object"}
}

需要注意的是,此更改需要在修改提示词的基础上进行。如果提示词(系统或者用户)没有明确要求LLM以json格式响应,那这里的返回值只会变成空白的json:
空白的响应
我们更改系统和用户的提示词:

system_prompt = """
你是一名百科全书式的学者,请使用如下json格式回答问题

{"name":人物名字, "intro":人物介绍}
"""

user_prompt = """
介绍下成龙
"""

LLM就会以我们想要的json格式化输出了:

生成时间: 1.15秒
模型: qwen3
回复: {"name": "成龙", "intro": "成龙(1954年-),中国香港演员、导演、制片人,被誉为“功夫巨星”。他以精湛的武打技巧和丰富的表演经验闻名,主演过许多经典动作片,如《英雄本色》《警察故事》《杀人回廊》等。成龙不仅在电影界有巨大影响力,还积极参与慈善事业,是香港演艺界的重要人物。"}

# 参数控制(代码)

生产环境中的LLM大多以上述openai server的方式部署,不过如果LLM是某个外部组件的一部分,那也很正常。此时一般采用vllm内置的LLM对大模型进行初始化,使用SamplingParams进行采样。

初始化LLM

from vllm import LLM, SamplingParams

model_path = "/home/bluebonnet27/models/Qwen3-1.7B"  # 替换为你的本地路径或 HF ID

# 初始化 LLM
# 注意:vLLM 要求模型支持 chat template 才能正确处理 messages
llm = LLM(
    model=model_path,
    tensor_parallel_size=1,      # 单卡设为 1
    dtype="auto",                # 自动选择 float16/bfloat16
    max_model_len=8192,
    gpu_memory_utilization=0.9,
    enforce_eager=False,         # 推理时可设为 False 提升性能
    # enable_prefix_caching=True # 可选:提升重复 prompt 性能
)

初始化提示词:

prompt = llm.llm_engine.tokenizer.tokenizer.apply_chat_template(
    conversation=messages,
    tokenize=False,  # 返回字符串
    add_generation_prompt=True  # 添加生成起始标记
)

初始化采样器:

sampling_params = SamplingParams(
    max_tokens=1024,
    temperature=0.7,
    top_p=0.9,
    stop=["<|im_end|>", "<|endoftext|>"],  # Qwen 的结束标记
)

然后进行推理:

outputs = llm.generate(prompt, sampling_params, use_tqdm=False)
generated_text = outputs[0].outputs[0].text.strip()

我们需要做的就是自定义一个json的范式,然后输入给采样器:

from vllm.sampling_params import GuidedDecodingParams

guided_json={                           # 强制输出合法 JSON
    "type": "object",
    "properties": {
        "name": {"type": "string"},
        "intro": {"type": "string"}
    },
    "required": ["name", "intro"]
}

guided_json_schema=GuidedDecodingParams(json=guided_json)

sampling_params = SamplingParams(
    max_tokens=1024,
    temperature=0.7,
    top_p=0.9,
    stop=["<|im_end|>", "<|endoftext|>"],  # Qwen 的结束标记
    # --- 关键:启用 JSON 引导 ---
    guided_decoding=guided_json_schema,
)

这样,LLM返回的就是json格式的文本了:
json响应
GuidedDecodingParams本身还支持其他的结构化输出,比如Regex(正则表达式),BNF(巴斯克范式,也就是输出标准的代码),碍于篇幅,这里就不一一列举了。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐