Skip to content

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)

基于 LangChain.js v1.x 版本