651 字
3 分钟
LLM 结构化输出
2025-04-08

什么是结构化输出?#

通常情况下,LLM 的输出是自然语言,但是有时候我们需要 LLM 输出特定的结构化数据,比如 JSON、XML 等。

让大语言模型(LLM)输出格式清晰、结构明确的数据,而不是一大段自然语言描述。

如何使用结构化输出?#

1. Prompt 中指定输出格式#

import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
const completion = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [
{
role: 'system',
content: '你是一个专业的历史学家,你应该能够以简洁和准确的方式总结一个人的生活。',
},
{
role: 'user',
content: `请总结10个${name}生活中的重要事件。
以 JSON 格式输出,字段包括:
{
"name": "string", // name
"gender": "string", // gender
"type": "string", // type
"profile": "string", // profile 100 words
"timeline": [
{
"year": "string", // year
"title": "string", // title
"description": "string", // description 50 words
}
]
}
`,
},
],
});
// 期望输出
```json
{
"name": "string", // name
"gender": "string", // gender
"type": "string", // type
"profile": "string", // profile 100 words
"timeline": [
{
"year": "string", // year
"title": "string", // title
"description": "string", // description 50 words
}
]
}

2. 使用 zod 定义结构,配合 OpenAI 的 zodResponseFormat 函数#

import OpenAI from 'openai';
import { zodResponseFormat } from 'openai/helpers/zod';
import { z } from 'zod';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
const BiographySchema = z.object({
name: z.string(),
gender: z.string(),
type: z.string(),
profile: z.string(),
timeline: z.array(z.object({ year: z.string(), title: z.string(), description: z.string() })),
});
const completion = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [
{
role: 'system',
content: '你是一个专业的历史学家,你应该能够以简洁和准确的方式总结一个人的生活。',
},
{
role: 'user',
content: `请总结10个${name}生活中的重要事件。`,
},
],
response_format: zodResponseFormat(BiographySchema, 'biography'),
});
// 期望输出
{
"name": "string", // name
"gender": "string", // gender
"type": "string", // type
"profile": "string", // profile 100 words
"timeline": [
{
"year": "string", // year
"title": "string", // title
"description": "string", // description 50 words
}
]
}

其他#

部分兼容 openai sdk 的服务不支持 zodResponseFormat 函数#

  • deepseek-chat 模型不支持 zodResponseFormat 函数

    解决方法:response_format: model.includes('deepseek-chat') ? { type: 'json_object' } : zodResponseFormat(BiographySchema, 'biography'),

  • deepseek-reasoner 模型不支持 json_object

    解决方法:response_format: model.includes('deepseek-reasoner') ? { type: 'text' } : zodResponseFormat(BiographySchema, 'biography'),

最终处理#

const response_format = model.includes('deepseek') ?
{ type: model.includes('chat') ? 'json_object' : 'text', }
: zodResponseFormat(BiographySchema, 'biography'),

总结#

方法优点缺点
Prompt 中指定输出格式简单易用,自然语言输入1. 如果输出格式复杂,可能需要多次调整
2. 不同的模型可能对于输出格式的理解不同导致输出格式错误
3. 输出的文本一般被 ```jsonn ``` 包裹,需要自行匹配真正的 JSON 文本1
zod + zodResponseFormat结构清晰,易于维护,支持复杂类型,高可控需要额外引入 zod

Footnotes#

  1. 匹配方法 text.match(/```json\s*([\s\S]*?)\s*```/)?.[1] || ''

LLM 结构化输出
https://www.mihouo.com/posts/front/llm-structured-outputs/
作者
发布于
2025-04-08
许可协议
CC BY-NC-SA 4.0