转写说明

本文基于已校验的公开原文进行结构化转写与事实梳理,非原文转载。 转写保留可核验的技术事实,并将工程建议与来源观点明确分开。

核心结论

LLM 默认输出自由文本,而非结构化 JSON。直接调用 JSON.parse() 会因 markdown 包裹或格式偏差而失败。将 LLM 驯服为结构化输出是一个渐进过程:从手搓正则剥除外衣,到 LangChain OutputParser 体系,再到 Zod Schema 类型约束,最后到 Tool Call 原生能力,约束强度逐级提升。核心目的是为下游业务提供字段名固定、类型精确、结构可靠的数据。

能力机制

第一级手搓正则通过 /```json\s*([\s\S]*?)\s*```/ 捕获 markdown 代码块内的 JSON 字符串,需手动处理 ```json ``` 包裹、try/catch 异常及多种格式变体。

第二级 JsonOutputParser 是 LangChain 基础解析器,parser.getFormatInstructions() 在 prompt 中追加格式约束文本,parser.parse() 内部完成 markdown 剥离与 JSON.parse 转换,但仅保证返回合法 JSON,不约束字段名和类型。

第三级 StructuredOutputParser.fromNamesAndDescriptions 通过字段名加描述的方式固定输出结构,强制 LLM 使用指定字段名,避免用同义中文替代。类型仍依赖描述约束。

第四级 StructuredOutputParser.fromZodSchema 以 Zod Schema 定义精确类型系统,支持 z.string()、z.number()、z.array()、z.object() 及嵌套结构,配合 .describe() 向 LLM 传递字段语义。parse() 内部执行 Zod 运行时验证,类型不符会抛错。

第五级 Tool Call 利用 LLM 原生工具调用能力,model.bindTools([{name, description, schema}]) 绑定工具后,LLM 直接在 response.tool_calls[0].args 中返回结构化参数,无需 getFormatInstructions() 和 parse() 步骤,可靠性高于 prompt 约束。

快速开始

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 第二级:JsonOutputParser
import { JsonOutputParser } from '@langchain/core/output_parsers';
const parser = new JsonOutputParser();
const prompt = `描述任务 ${parser.getFormatInstructions()}`;
const result = await parser.parse(response.content);

// 第三级:固定字段名
import { StructuredOutputParser } from '@langchain/core/output_parsers';
const parser = StructuredOutputParser.fromNamesAndDescriptions({
    name: '姓名',
    birth_year: '出生年份'
});

// 第四级:Zod Schema 精确类型
import { z } from 'zod';
import { StructuredOutputParser } from '@langchain/core/output_parsers';
const schema = z.object({
    name: z.string().describe('姓名'),
    birth_year: z.number().describe('出生年份')
});
const parser = StructuredOutputParser.fromZodSchema(schema);

// 第五级:Tool Call
import { z } from 'zod';
const schema = z.object({ name: z.string(), birth_year: z.number() });
const modelWithTool = model.bindTools([{ name: 'extract', schema }]);
const response = await modelWithTool.invoke('任务描述');
const result = response.tool_calls[0].args;

环境变量名称按需配置,不在示例中展示。

适用边界

JsonOutputParser 适用于仅需合法 JSON、不关心字段名的场景。fromNamesAndDescriptions 适用于需要固定字段名但类型要求宽松的场景。fromZodSchema 适用于类型严格、结构复杂、需运行时验证的生产环境。Tool Call 适用于模型已支持 tool calling 能力的场景,可靠性最高但依赖模型特性。

手搓正则适合临时调试或无 LangChain 依赖的简单场景,不建议在生产代码中维护。OutputParser 体系的优势在于通用性,任何返回文本的 LLM 均可使用;Tool Call 的优势在于可靠性和简洁性,但受限于模型能力。

核验清单

开发阶段需确认:prompt 中的格式约束指令已通过 getFormatInstructions() 自动添加;parser.parse() 能够正确处理 markdown 包裹的响应;使用 Zod Schema 时验证 birth_year 等数值字段返回的是数字而非字符串;数组字段如 awards 返回的是对象数组而非字符串拼接。

生产阶段需确认:模型是否支持 tool calling,是则优先采用 bindTools 方案;异常处理已覆盖 LLM 返回非 JSON 格式的兜底逻辑;复杂嵌套结构的字段名和类型与下游消费端定义一致;测试用例覆盖字段名错误、类型错误、结构缺失等失败场景。

来源与核验

  • 原始文章
  • 页面事实以原始来源及其引用的官方资料为准;版本、星标和模型能力会随时间变化。
  • AI Stack 不公开抓取到的全文快照,只发布独立转写与来源入口。

站内链接

相关文章