大模型格式化输出

如果只是和用户聊天,大模型返回自然语言就够了。

但程序更希望拿到:

yaml 复制代码
{
  name: 'Albert Einstein',
  birth_year: 1879,
  nationality: 'German'
}

而不是:

yaml 复制代码
爱因斯坦出生于 1879 年,是一位德国物理学家......

这就是结构化输出要解决的问题:

让模型输出从"给人阅读的文本",变成"程序可以稳定处理的数据"。


一、最简单的方法:要求模型返回 JSON

可以直接在 Prompt 中写:

ini 复制代码
const prompt = `
请介绍一下爱因斯坦。

以 JSON 格式返回,包含:

name
birth_year
nationality
major_achievement
`;

模型可能返回:

json 复制代码
{
  "name": "Albert Einstein",
  "birth_year": 1879,
  "nationality": "German",
  "major_achievement": [
    "Theory of Relativity"
  ]
}

然后:

ini 复制代码
const result =
  JSON.parse(response.content);

这样字符串就变成了 JavaScript 对象。

但问题也很明显。

模型可能返回:

go 复制代码
```json
{
  "name": "Albert Einstein"
}
```

也可能夹杂解释文字。

这时:

javascript 复制代码
JSON.parse()

就可能直接失败。


二、JsonOutputParser

LangChain 提供:

javascript 复制代码
import {
  JsonOutputParser
} from '@langchain/core/output_parsers';

创建解析器:

ini 复制代码
const parser =
  new JsonOutputParser();

然后把格式要求加入 Prompt:

ini 复制代码
const prompt = `
请介绍一下爱因斯坦。

${parser.getFormatInstructions()}
`;

调用模型:

ini 复制代码
const response =
  await model.invoke(prompt);

最后:

ini 复制代码
const result =
  await parser.parse(response.content);

流程变成:

javascript 复制代码
Prompt
  ↓
要求输出 JSON
  ↓
LLM
  ↓
JSON 文本
  ↓
JsonOutputParser
  ↓
JavaScript Object

但这仍然只能解决:

javascript 复制代码
是不是合法 JSON

不能保证:

复制代码
必须有哪些字段
字段是什么类型

三、StructuredOutputParser

于是约束进一步升级。

javascript 复制代码
import {
  StructuredOutputParser
} from '@langchain/core/output_parsers';

可以声明字段:

php 复制代码
const parser =
  StructuredOutputParser
    .fromNamesAndDescriptions({
      name: '姓名',
      birth_year: '出生年份',
      nationality: '国籍',
      major_achievement:
        '主要成就'
    });

然后:

ini 复制代码
const prompt = `
请介绍一下爱因斯坦。

${parser.getFormatInstructions()}
`;

这次模型不仅知道:

javascript 复制代码
返回 JSON

还知道:

javascript 复制代码
JSON 应该有哪些字段
每个字段是什么意思

结构约束更明确了。


四、但字段名称正确还不够

假设我们希望:

复制代码
birth_year

必须是:

typescript 复制代码
number

结果模型返回:

json 复制代码
{
  "birth_year": "1879"
}

这依然是合法 JSON。

甚至字段名也是对的。

但类型错了。

真实业务通常还会要求:

typescript 复制代码
name 必须是 string

birth_year 必须是 number

major_achievement 必须是 string[]

某些字段可选

某些字段又是嵌套对象

所以真正需要的是:

复制代码
Schema

五、Zod:给数据定义 Schema

Zod 是 JavaScript / TypeScript 中常见的 Schema 校验库。

例如:

css 复制代码
import { z } from 'zod';

const schema = z.object({
  name: z.string(),

  birth_year: z.number(),

  nationality: z.string(),

  major_achievement:
    z.array(z.string()),

  famous_theory:
    z.string().optional()
});

它表达的是:

typescript 复制代码
整个结果必须是 object

name
→ string

birth_year
→ number

nationality
→ string

major_achievement
→ string[]

famous_theory
→ 可选 string

Zod 的核心作用一句话就够:

定义数据应该长什么样,并在运行时验证数据。

例如:

php 复制代码
schema.parse({
  name: 'Einstein',
  birth_year: '1879'
});

这里:

复制代码
birth_year

本来要求:

typescript 复制代码
number

却传入:

c 复制代码
string

Zod 会直接发现类型不符合 Schema。


六、在 LangChain 中使用 Zod

可以把 Zod Schema 交给:

复制代码
StructuredOutputParser

例如:

css 复制代码
const schema = z.object({
  name: z.string()
    .describe('姓名'),

  birth_year: z.number()
    .describe('出生年份'),

  nationality: z.string()
    .describe('国籍'),

  major_achievement:
    z.array(z.string())
      .describe('主要成就')
});

const parser =
  StructuredOutputParser
    .fromZodSchema(schema);

然后:

ini 复制代码
const prompt = `
请介绍一下爱因斯坦。

${parser.getFormatInstructions()}
`;

调用:

ini 复制代码
const response =
  await model.invoke(prompt);

const result =
  await parser.parse(response.content);

现在约束已经从:

javascript 复制代码
JSON

升级到了:

diff 复制代码
具体字段
+
具体类型
+
数据结构

七、Tool Calling 又做了什么

前面的 Parser 思路,本质是:

javascript 复制代码
让模型生成一段文本
    ↓
文本里包含 JSON
    ↓
再解析 JSON

另一种思路是:

不让模型"写 JSON 文本",而是让模型生成符合工具参数定义的结构化参数。

例如定义:

css 复制代码
const weatherTool = {
  name: 'get_weather',

  description: '获取天气',

  schema: z.object({
    city: z.string()
  })
};

模型需要调用工具时,可能产生:

css 复制代码
{
  name: 'get_weather',
  args: {
    city: '杭州'
  }
}

这里:

复制代码
args

已经是结构化数据。

因此 Tool Calling 也经常被用于结构化输出。


八、现在更直接的写法:withStructuredOutput()

如果目的不是调用真实工具,只是:

我希望模型按照 Schema 返回数据。

那么现在可以直接:

css 复制代码
const schema = z.object({
  name: z.string(),
  birth_year: z.number(),
  nationality: z.string(),
  major_achievement:
    z.array(z.string())
});

然后:

ini 复制代码
const structuredModel =
  model.withStructuredOutput(schema);

调用:

arduino 复制代码
const result =
  await structuredModel.invoke(
    '介绍一下爱因斯坦'
  );

拿到的结果直接可以按照 Schema 使用:

arduino 复制代码
console.log(result.name);
console.log(result.birth_year);

代码已经从:

css 复制代码
LLM
↓
字符串
↓
Parser
↓
Object

逐渐演进成:

css 复制代码
Schema
↓
LLM
↓
Structured Object

九、整个结构化输出主线其实很简单

从最开始一路看下来:

scss 复制代码
自然语言
    ↓
要求 JSON
    ↓
JSON.parse()
    ↓
JsonOutputParser
    ↓
StructuredOutputParser
    ↓
Zod Schema
    ↓
Tool Calling
    ↓
withStructuredOutput()

每一步都只是在解决同一个问题:

如何让大模型输出越来越稳定、越来越适合程序直接使用。

可以把几个方案简单理解成:

scss 复制代码
JSON
→ 有格式

StructuredOutputParser
→ 有字段

Zod
→ 有字段 + 有类型

withStructuredOutput()
→ 直接把结构要求交给模型调用

真正开发新项目时,如果当前模型支持结构化输出,通常优先考虑:

ini 复制代码
model.withStructuredOutput(schema);

而了解前面的 Parser 演进,是为了真正理解:

javascript 复制代码
Schema 为什么存在
Parser 在解决什么
结构化输出究竟比普通 JSON 强在哪里

最终目的不是"学会更多 API",而是让模型输出的数据能够真正进入业务代码。

相关推荐
2601_962380761 分钟前
节气民俗科普视频的 AI 动效分镜工作流:从文案到成片怎么做
人工智能·音视频
启雀AI9 分钟前
培训管理系统的 AI 智能陪练完整功能逻辑,以家电门店销售为例的剧本框架
人工智能·ai·软件需求·培训系统·培训平台
hunteritself12 分钟前
夯爆了!Qoder 狂肝 2 小时,293 个测试全绿,Credits 一分没扣
前端·人工智能·chrome·深度学习·机器学习
RPAdaren15 分钟前
AI 舆情智能体频繁断跑、漏抓发酵?90% 团队都踩了同一层坑
大数据·人工智能
Jet450516 分钟前
第5篇 一条线做深,一条线做宽,研究网络才长得出来
人工智能
wflynn16 分钟前
Claude 自主发现类 CRISPR 结构的新型逆转录酶系统:Anthropic 生命科学实验室的早期成果
人工智能·ai
浅安的邂逅19 分钟前
260926-OpenAI 急停最强模型训练:智能体借 DNS 漏洞联网,还泄露了 GitHub token
人工智能·chatgpt·开源·openai·ai日报
维核科技39 分钟前
AI 罕见病:为渐冻症按下“暂停键”
人工智能
学代码的CJY41 分钟前
AI大模型1-1-大模型认知与工程概览
人工智能·ai
QYR-分析42 分钟前
全球汽车塑料扰流板市场深度解析:政策驱动下的赛道机遇与厂商破局方向
人工智能