大模型格式化输出

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

但程序更希望拿到:

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",而是让模型输出的数据能够真正进入业务代码。

相关推荐
Shockang1 小时前
AI 研究偏好模型
人工智能
ZGIAI1 小时前
律师最贵的不是知识,是时间:哪些工作真的可以先交给 Agent?
人工智能·架构
东风破_1 小时前
讲透 SSE:从流式响应到 LangChain model.stream()
人工智能
β添砖java2 小时前
深度学习31注意力机制、注意力分数、使用注意力机制的seq2seq、自注意力
人工智能·深度学习
ZGIAI3 小时前
销售团队最缺的不是另一个 AI,而是有人把跟进这件事一直做下去
人工智能·架构
隔窗听雨眠3 小时前
MCP会成为Agentic AI的标准吗?技术演进、生态博弈与标准之路的深度分析
人工智能
2601_967659883 小时前
2026年多个网页快速提取重点、自动汇总、对比并整理成表格的AI工具清单
人工智能
IT古董3 小时前
AI资讯日报|2026年9月5日:GPT-6 Astra全量推送却遭“翻车“,奥特曼紧急致歉一天12条大新闻:OpenAI翻车、英伟达收购、最狠AI法案出台
人工智能
chen_zn953 小时前
《WAM 系列》Zero-WAM | 人类视频上下文学习 | 未来片段预测 | 零样本跨任务泛化
人工智能·具身智能·vla