使用 LangChain 的 JsonOutputParser 与 Zod 强制 LLM 输出结构化 JSON

106 阅读4分钟

使用 LangChain 的 JsonOutputParser 与 Zod 强制 LLM 输出结构化 JSON

在构建基于大语言模型(LLM)的应用时,一个常见的痛点是:模型输出的内容格式不可控。虽然我们希望 LLM 返回结构化的 JSON 数据,但默认情况下,它往往会夹杂解释性文字、Markdown 格式,甚至字段缺失或命名不一致——这给后续程序处理带来极大困难。

LangChain 提供了强大的 OutputParser 机制,配合 Zod 定义的数据契约(Schema),可以强制 LLM 输出严格符合预期的 JSON 格式。本文将通过一个前端概念解析器的实战案例,带你掌握这一关键技术。


为什么需要 OutputParser?

假设你让 LLM 解释“Promise”这个前端概念,并希望得到如下结构:

{
  "name": "Promise",
  "core": "用于处理异步操作的对象",
  "useCase": ["网络请求", "定时任务", "并发控制"],
  "difficulty": "中等"
}

但实际返回可能是:

“Promise 是 JavaScript 中用于处理异步操作的一种对象。常见用法包括 fetch 请求、setTimeout 等。学习难度中等。”

这种自由文本无法直接被程序消费。我们需要一种机制,在调用模型前就声明期望的输出结构,并在模型响应后自动校验和解析。

这就是 JsonOutputParser 的价值所在。


技术栈准备

  • LangChain.js(@langchain/core)
  • Zod:TypeScript 优先的运行时校验库
  • DeepSeek API(或其他支持结构化输出的模型)

安装依赖:

npm install @langchain/core @langchain/deepseek zod dotenv

步骤一:定义 Zod Schema(前端契约)

Zod 允许我们以类型安全的方式定义数据结构,并自动生成描述信息:

import { z } from 'zod';

const FrontendContractSchema = z.object({
  name: z.string().describe("概念名称"),
  core: z.string().describe("核心要点"),
  useCase: z.array(z.string()).describe("常见使用场景"),
  difficulty: z.enum(['简单', '中等', '复杂']).describe("学习难度")
});

这个 Schema 不仅用于校验,还会被 JsonOutputParser 转换为自然语言指令,告诉 LLM 应该如何组织输出。


步骤二:创建 JsonOutputParser

import { JsonOutputParser } from '@langchain/core/output_parsers';

const jsonParser = new JsonOutputParser(FrontendContractSchema);

jsonParser.getFormatInstructions() 会生成类似这样的提示:

The output should be a markdown code snippet formatted in the following schema:

{
  "name": string // 概念名称,
  "core": string // 核心要点,
  "useCase": [string, ...] // 常见使用场景,
  "difficulty": "简单" | "中等" | "复杂" // 学习难度
}

步骤三:构建 Prompt + Chain

使用 PromptTemplate 将格式指令注入提示词,并强调“只输出 JSON”:

const prompt = PromptTemplate.fromTemplate(`
  你是一个只会输出 JSON 的 API,不允许输出任何解释性文字。

  ⚠️ 你必须【只返回】符合以下 Schema 的 JSON:
  - 不允许增加字段
  - 不允许减少字段
  - 字段名必须完全一致
  - 返回结果必须可被 JSON.parse 成功解析

  {format_instructions}

  前端概念:{topic}
`);

然后通过 .pipe() 构建执行链:

const chain = prompt.pipe(model).pipe(jsonParser);

这条链的流程是:

  1. 渲染提示词(含格式指令)
  2. 调用 LLM
  3. 自动解析并校验输出是否符合 Zod Schema

步骤四:调用并获取结构化结果

const response = await chain.invoke({
  topic: "Promise",
  format_instructions: jsonParser.getFormatInstructions(),
});

console.log(response);
// 输出:
// {
//   name: 'Promise',
//   core: '用于表示异步操作最终完成或失败的对象',
//   useCase: ['AJAX 请求', '异步任务链式调用', '错误统一处理'],
//   difficulty: '中等'
// }

如果 LLM 输出不符合 Schema(比如缺少字段或类型错误),JsonOutputParser 会抛出错误或触发重试机制(可配合 RunnableRetry 使用)。


优势总结

特性说明
✅ 类型安全Zod 提供 TypeScript 类型推导
✅ 自动校验非法输出会被拦截
✅ 提示增强自动将 Schema 转为 LLM 可理解的指令
✅ 易集成与 LangChain Chain 无缝衔接

扩展建议

  • 对于更复杂的场景,可结合 StructuredOutputParserPydanticOutputParser(Python 版)
  • 在生产环境中,建议添加重试逻辑和 fallback 机制
  • 可将 Schema 作为前后端共享的“API 契约”,实现真正的端到端类型安全

结语

通过 JsonOutputParser + Zod,我们不再需要手动清洗 LLM 的“脏数据”,而是从源头约束其输出行为。这不仅提升了系统稳定性,也让 AI 更像一个可靠的“API 服务”而非“聊天机器人”。

在构建下一代 AI 应用时,结构化输出能力将成为标配。掌握这一模式,是你迈向工程化 LLM 应用的关键一步。


如需完整可运行代码示例,欢迎留言!