使用 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);
这条链的流程是:
- 渲染提示词(含格式指令)
- 调用 LLM
- 自动解析并校验输出是否符合 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 无缝衔接 |
扩展建议
- 对于更复杂的场景,可结合
StructuredOutputParser或PydanticOutputParser(Python 版) - 在生产环境中,建议添加重试逻辑和 fallback 机制
- 可将 Schema 作为前后端共享的“API 契约”,实现真正的端到端类型安全
结语
通过 JsonOutputParser + Zod,我们不再需要手动清洗 LLM 的“脏数据”,而是从源头约束其输出行为。这不仅提升了系统稳定性,也让 AI 更像一个可靠的“API 服务”而非“聊天机器人”。
在构建下一代 AI 应用时,结构化输出能力将成为标配。掌握这一模式,是你迈向工程化 LLM 应用的关键一步。
如需完整可运行代码示例,欢迎留言!