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