07-输出解析器:结构化输出
💡 注意: 本教程使用 Zod v4 (
zod@^4.3.6),基础用法与 v3 相同
🎯 本章目标
- ✅ 理解输出解析器的作用
- ✅ 掌握手动解析 JSON 的方法
- ✅ 使用 Zod 验证输出数据
🤔 为什么需要输出解析器?
没有解析器时
javascript
const response = await model.invoke([
new HumanMessage("请返回一个 JSON,包含姓名和年龄")
]);
console.log(response.content);
// 输出:{
// "name": "小明",
// "age": 25
// }
// ❌ 问题:这是字符串,不是真正的对象!
// 需要手动 JSON.parse(),还可能解析失败使用解析器后
javascript
// LangChain v1.x 推荐方式:手动解析 + Zod 验证
const result = JSON.parse(response.content);
const validated = schema.parse(result); // Zod 验证
// ✅ 得到验证后的对象:{ name: "小明", age: 25 }📦 安装 Zod
Zod 是一个 TypeScript 优先的模式验证库:
bash
npm install zod💡 完整示例(LangChain v1.x 推荐方式)
javascript
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
import { z } from "zod";
async function main() {
const model = new ChatOpenAI({
modelName: "MiniMax/MiniMax-M2.5",
apiKey: process.env.MODELSCOPE_API_KEY,
temperature: 0, // 结构化输出时建议用 0
configuration: {
baseURL: "https://api-inference.modelscope.cn/v1",
},
});
// 步骤 1:定义输出模式(Zod Schema)
const personSchema = z.object({
name: z.string().describe("人物姓名"),
age: z.number().describe("年龄"),
occupation: z.string().describe("职业")
});
// 步骤 2:创建 Prompt,指示模型返回 JSON
const messages = [
new SystemMessage("你是一个数据提取助手。请从文本中提取信息,并只返回 JSON 格式,不要其他内容。"),
new HumanMessage(`
从以下文本中提取人物信息,返回 JSON 格式:
{
"name": "姓名",
"age": 年龄(数字),
"occupation": "职业"
}
文本:张三今年 28 岁,是一名软件工程师
`)
];
// 步骤 3:调用模型
const response = await model.invoke(messages);
console.log("AI 原始回复:", response.content);
// 步骤 4:手动解析 JSON
try {
// 提取 JSON 部分(可能包含在代码块中)
const jsonMatch = response.content.match(/\{[\s\S]*\}/);
const jsonStr = jsonMatch ? jsonMatch[0] : response.content;
const result = JSON.parse(jsonStr);
// 步骤 5:使用 Zod 验证
const validated = personSchema.parse(result);
console.log("\n✅ 解析成功!");
console.log("姓名:", validated.name);
console.log("年龄:", validated.age);
console.log("职业:", validated.occupation);
} catch (e) {
console.error("解析失败:", e.message);
}
}
main();📋 Zod 常用类型
| Zod 类型 | 说明 | 示例 |
|---|---|---|
z.string() | 字符串 | z.string().email() |
z.number() | 数字 | z.number().min(0).max(100) |
z.boolean() | 布尔值 | z.boolean() |
z.array() | 数组 | z.array(z.string()) |
z.enum() | 枚举 | z.enum(["positive", "negative", "neutral"]) |
z.object() | 对象 | z.object({ name: z.string() }) |
z.optional() | 可选 | z.string().optional() |
z.null() | 可为空 | z.string().null() |
🔧 链式调用(推荐方式)
javascript
import { ChatOpenAI } from "@langchain/openai";
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
import { PromptTemplate } from "@langchain/core/prompts";
import { z } from "zod";
async function main() {
const model = new ChatOpenAI({
modelName: "MiniMax/MiniMax-M2.5",
apiKey: process.env.MODELSCOPE_API_KEY,
temperature: 0,
configuration: {
baseURL: "https://api-inference.modelscope.cn/v1",
},
});
// 定义 Schema
const schema = z.object({
term: z.string(),
definition: z.string(),
example: z.string()
});
// 创建 Prompt
const prompt = PromptTemplate.fromTemplate(
"请用 JSON 格式解释什么是{topic},包含 term、definition、example 三个字段。只返回 JSON,不要其他内容。"
);
// 创建链:Prompt → Model
const chain = prompt.pipe(model);
// 调用
const response = await chain.invoke({ topic: "机器学习" });
// 手动解析
const jsonMatch = response.content.match(/\{[\s\S]*\}/);
const jsonStr = jsonMatch ? jsonMatch[0] : response.content;
const result = schema.parse(JSON.parse(jsonStr));
console.log("术语:", result.term);
console.log("定义:", result.definition);
console.log("例子:", result.example);
}
main();🎯 实际应用场景
1. 情感分析
javascript
const sentimentSchema = z.object({
sentiment: z.enum(["positive", "negative", "neutral"]),
confidence: z.number().min(0).max(1),
keywords: z.array(z.string())
});
const messages = [
new SystemMessage("分析以下文本的情感,只返回 JSON 格式。"),
new HumanMessage(`
分析以下文本的情感,返回 JSON:
{
"sentiment": "positive/negative/neutral",
"confidence": 0-1 之间的数字,
"keywords": ["关键词 1", "关键词 2"]
}
文本:这个产品真的很棒,我非常喜欢!
`)
];
const response = await model.invoke(messages);
const jsonStr = response.content.match(/\{[\s\S]*\}/)[0];
const result = sentimentSchema.parse(JSON.parse(jsonStr));2. 实体提取
javascript
const entitySchema = z.object({
persons: z.array(z.string()),
organizations: z.array(z.string()),
locations: z.array(z.string()),
dates: z.array(z.string())
});3. 文章摘要
javascript
const summarySchema = z.object({
title: z.string(),
summary: z.string(),
keyPoints: z.array(z.string()),
category: z.string()
});⚠️ 注意事项
1. Temperature 设为 0
结构化输出需要确定性:
javascript
const model = new ChatOpenAI({
temperature: 0, // ✅ 推荐
// ...
});2. 明确指示返回 JSON
javascript
// ✅ 好的 Prompt
new SystemMessage("只返回 JSON 格式,不要其他内容。")
// ❌ 不好的 Prompt
new SystemMessage("请分析一下...") // 模型可能返回多余的文字3. 处理解析错误
javascript
try {
const jsonMatch = response.content.match(/\{[\s\S]*\}/);
const result = JSON.parse(jsonMatch[0]);
const validated = schema.parse(result);
} catch (e) {
console.error("解析失败:", e.message);
// 可以重试或返回默认值
}4. 处理代码块
模型可能返回 Markdown 代码块:
javascript
// 回复可能是:```json {...} ```
const jsonStr = response.content
.replace(/```json\s*/g, '')
.replace(/```\s*/g, '')
.trim();📊 完整工具函数
javascript
import { z } from "zod";
/**
* 解析并验证 AI 返回的 JSON
* @param {string} content - AI 的原始回复
* @param {z.ZodSchema} schema - Zod 验证模式
* @returns {object} 验证后的对象
*/
function parseStructuredOutput(content, schema) {
try {
// 1. 提取 JSON 部分
const jsonMatch = content.match(/\{[\s\S]*\}/);
if (!jsonMatch) {
throw new Error("未找到 JSON 内容");
}
// 2. 清理 Markdown 代码块
let jsonStr = jsonMatch[0]
.replace(/```json\s*/g, '')
.replace(/```\s*/g, '')
.trim();
// 3. 解析 JSON
const parsed = JSON.parse(jsonStr);
// 4. Zod 验证
return schema.parse(parsed);
} catch (e) {
console.error("解析失败:", e.message);
throw e;
}
}
// 使用
const result = parseStructuredOutput(response.content, personSchema);📝 本章小结
- LangChain v1.x 中推荐手动解析 JSON + Zod 验证
- 使用
SystemMessage指示模型返回 JSON 格式 temperature设为 0 提高确定性- Zod 用于验证和类型安全
🏃 下一章
[08-链 (Chain) →](./08-链 (Chain).md)