一.输出方式介绍
大模型的输出有2种处理方式,1.output parser 2.tool
Output Parser(输出解析器)
Tool(工具/函数调用) 。
它们是两个不同层级的东西,但经常一起出现在 Agent 的流程中

简单来说:
- Output Parser = 「模型吐出文本 → 我把它解析成对象」
- Tool = 「模型说要用某个工具 → 我去执行那个工具 → 把结果喂回模型」

你说的应该是 AI Agent 里的 Output Parser(输出解析器) 和 Tool(工具/函数调用) 。它们是两个不同层级的东西,但经常一起出现在 Agent 的流程中。下面帮你快速理清。
一、核心区别:Parser vs Tool
| 维度 | Output Parser(输出解析器) | Tool / Function Calling(工具调用) |
|---|---|---|
| 本质 | 把 LLM 返回的原始文本解析成程序可用的结构化数据(dict / Pydantic / 等) | LLM 决定调用哪个外部函数,由你的代码执行该函数 |
| 谁在执行 | 你的应用代码对模型输出字符串做解析 | 你的应用代码执行被调用的函数/API |
| 典型用途 | 从自然语言中提取结构化信息(如抽取用户画像 JSON) | 让 Agent 查天气、搜数据库、调 API、算数等 |
| 模型参与方式 | 模型只负责生成文本,Parser 是后处理 | 模型生成工具名+参数(tool_call),框架解析并执行 |
| LangChain 对应 | JsonOutputParser / PydanticOutputParser |
@tool 装饰器 / with_structured_output() / Tool Calling |
简单来说:
- Output Parser = 「模型吐出文本 → 我把它解析成对象」
- Tool = 「模型说要用某个工具 → 我去执行那个工具 → 把结果喂回模型」
二、它们在 Agent 中的关系
在一个典型的 ReAct Agent 循环里,两者是上下游协作的关系:
sql
用户提问
↓
LLM 思考 → 生成 Tool Call(工具名 + 参数)
↓
你的代码解析 Tool Call → 执行对应 Tool 函数
↓
Tool 返回原始结果(可能是 JSON / HTML / 字符串)
↓
【可选】在 Tool 内部对原始结果做格式化/清洗
↓
把结果作为 Observation 送回 LLM
↓
LLM 生成最终回答
- Tool Calling 负责「模型决定做什么动作」
- Output Parser(或在 Tool 内部对原始结果做格式化/清洗)负责「把模型输出或工具输出变成干净的结构化数据」
三、什么时候选择用JsonOutputParser,StructuredOutputParser,XMLOutputParser什么时候用withStructuredOutput
| 方式 | 底层机制 | 格式约束能力 | 模型要求 | 什么时候选它 |
|---|---|---|---|---|
JsonOutputParser |
Prompt 引导 + 后解析 | ⭐⭐ 弱(靠模型自觉) | 任何模型 | 简单 JSON,模型能力强,快速原型 |
StructuredOutputParser |
Prompt 引导 + 后解析 | ⭐⭐⭐ 中(带格式说明) | 任何模型 | 需要自定义字段描述、复杂 schema |
XMLOutputParser |
Prompt 引导 + 后解析 | ⭐⭐ 弱(XML 嵌套易错) | 任何模型 | 几乎不推荐,除非模型对 XML 特别友好 |
with_structured_output() |
原生 Tool/Function Calling | ⭐⭐⭐⭐⭐ 强(模型原生保证) | 支持 function calling 的模型 | 首选,生产环境、复杂 schema |
四、总结:
能走 with_structured_output() 就走它(它底层就是 Tool Calling,最稳)。
走不了才用 JsonOutputParser 兜底。
StructuredOutputParser 和 XMLOutputParser 基本是历史遗留,新项目不用特意学。
二.案例
1.使用JsonOutputParser
js
import "dotenv/config";
import {ChatOpenAI} from "@langchain/openai";
import {JsonOutputParser} from "@langchain/core/output_parsers";
import {HumanMessage} from '@langchain/core/messages';
const model= new ChatOpenAI({
modelName: process.env.MODEL_NAME,
apiKey: process.env.OPENAI_API_KEY,
temperature: 0,
configuration: {
baseURL: process.env.OPENAI_BASE_URL,
},
});
const parser = new JsonOutputParser();
const question = `请你介绍一下爱因斯坦的信息,以json的格式返回,具体包含的字段如下:
name(姓名),birth(出生日期),death(逝世日期),nationality(国籍),education(教育背景)famous_theory(著名理论)
${parser.getFormatInstructions()}
`;
//response.content是一个markdown格式的字符串,包含了爱因斯坦的信息。我们希望将这些信息转换成json格式,以便于后续处理。为此,我们可以使用parser.parse()方法进行转换。
const response = await model.invoke([new HumanMessage(question)]);
console.log("格式化前"+response.content);
//使用parser的目的是为了将response.content转换成json格式,这样就可以直接使用result变量来访问解析后的数据了。
const result = await parser.parse(response.content);
console.log("格式化后"+JSON.stringify(result, null, 2));
JsonOutputParser把带有md格式的字符串,转化成了我们想要的json形式。这个json自动使用prompt提示语里面的字段。
2.使用StructuredOutputParser
js
import 'dotenv/config';
import { ChatOpenAI } from '@langchain/openai';
import {StructuredOutputParser} from '@langchain/core/output_parsers';
import {HumanMessage} from '@langchain/core/messages';
const model= new ChatOpenAI({
modelName: process.env.MODEL_NAME,
apiKey: process.env.OPENAI_API_KEY,
temperature: 0,
configuration: {
baseURL: process.env.OPENAI_BASE_URL,
},
});
const parser = StructuredOutputParser.fromNamesAndDescriptions({
name: '姓名',
birth: '出生日期',
death: '逝世日期',
nationality: '国籍',
main_work: '主要成就',
});
const question = `请你介绍一下爱因斯坦的信息, ${parser.getFormatInstructions()}`;
const response = await model.invoke([new HumanMessage(question)]);
console.log("格式化前"+response.content);
const output = await parser.parse(response.content);
console.log("格式化后"+JSON.stringify(output, null, 2));
fromNamesAndDescriptions 指定生成格式
3.StructuredOutputParser+zod
js
import 'dotenv/config';
import { ChatOpenAI } from '@langchain/openai';
import { StructuredOutputParser } from '@langchain/core/output_parsers';
import { HumanMessage } from '@langchain/core/messages';
import { z } from 'zod';
const model = new ChatOpenAI({
modelName: process.env.MODEL_NAME,
apiKey: process.env.OPENAI_API_KEY,
temperature: 0,
configuration: {
baseURL: process.env.OPENAI_BASE_URL,
},
});
const schema = z.object({
name: z.string().describe('姓名'),
birth: z
.string()
.describe('出生日期,格式为 YYYY-MM-DD'),
death: z
.string()
.optional()
.describe('逝世日期,格式为 YYYY-MM-DD,如果仍在世则不要输出此字段'),
nationality: z.string().describe('国籍'),
main_work: z
.array(z.string())
.describe('主要成就列表,必须是字符串数组,如 ["相对论", "光电效应"]'),
});
const parser = StructuredOutputParser.fromZodSchema(schema);
const question = `请你介绍一下爱因斯坦的信息。\n\n${parser.getFormatInstructions()}`;
const response = await model.invoke([new HumanMessage(question)]);
console.log('格式化前:', response.content);
const output = await parser.parse(response.content);
console.log('格式化后:', JSON.stringify(output, null, 2));
4.model用绑定json格式的tool形式处理结果
js
import 'dotenv/config';
import { ChatOpenAI } from '@langchain/openai';
import { HumanMessage } from '@langchain/core/messages';
import { z } from 'zod';
const model = new ChatOpenAI({
modelName: process.env.MODEL_NAME,
apiKey: process.env.OPENAI_API_KEY,
temperature: 0,
configuration: {
baseURL: process.env.OPENAI_BASE_URL,
},
});
const schema = z.object({
name: z.string().describe('姓名'),
birth: z
.string()
.describe('出生日期,格式为 YYYY-MM-DD'),
death: z
.string()
.optional()
.describe('逝世日期,格式为 YYYY-MM-DD,如果仍在世则不要输出此字段'),
nationality: z.string().describe('国籍'),
main_work: z
.array(z.string())
.describe('主要成就列表,必须是字符串数组,如 ["相对论", "光电效应"]'),
});
const modelWithTool = model.bindTools([ {
name: "extract_info",
description: "从输入的文本中提取信息,并返回一个 JSON 对象。",
schema: schema,
}]
);
const question = `请你介绍一下爱因斯坦的信息。`;
const response = await modelWithTool.invoke([new HumanMessage(question)]);
console.log('格式化前:', response.tool_calls[0].args);
console.log("格式化以后",JSON.stringify(response.tool_calls[0].args, null, 2))
5.withStructuredOutput
js
import 'dotenv/config';
import { ChatOpenAI } from '@langchain/openai';
import { z } from 'zod';
// 初始化模型,增加 timeout 配置 (例如设置为 60秒/60000毫秒)
const model = new ChatOpenAI({
modelName: process.env.MODEL_NAME,
apiKey: process.env.OPENAI_API_KEY,
temperature: 0,
configuration: {
baseURL: process.env.OPENAI_BASE_URL,
},
timeout: 60000, // ✅ 关键修复:将超时时间延长至 60 秒
maxRetries: 3, // ✅ 可选:开启内部自动重试次数
});
// 修复 Zod Schema
const schema = z.object({
name: z.string().describe('人物的中文名称,必须返回"爱因斯坦"'),
birth: z
.string()
.describe('出生日期,格式为 YYYY-MM-DD'),
death: z
.string()
.nullable()
.describe('逝世日期,格式为 YYYY-MM-DD,如果仍在世则输出 null'),
nationality: z.string().describe('国籍'),
main_work: z
.array(z.string())
.nullable()
.describe('主要成就列表,必须是字符串数组,如 ["相对论", "光电效应"]'),
});
const structuredOutputParser = model.withStructuredOutput(schema);
const question = `请你介绍一下爱因斯坦的信息,请用中文回复。`;
try {
const response = await structuredOutputParser.invoke(question);
console.log('格式化以后', JSON.stringify(response, null, 2));
} catch (error) {
console.error('发生未知错误:', error);
}
6.stream --流式输出
js
import 'dotenv/config';
import { ChatOpenAI } from '@langchain/openai';
const model = new ChatOpenAI({
modelName: process.env.MODEL_NAME,
apiKey: process.env.OPENAI_API_KEY,
temperature: 0,
configuration: {
baseURL: process.env.OPENAI_BASE_URL,
},
timeout: 60000,
maxRetries: 3,
});
const question = `请你介绍一下爱因斯坦的信息`;
const stream = await model.stream(question); // ✅ 注意变量名 steam(避免和 chunk 混)
let full = '';
for await (const chunk of stream) {
const content = chunk.content; // ✅ LangChain chunk 用 .content,不是 .text
if (typeof content === 'string') {
full += content;
process.stdout.write(content);
}
}
console.log('\n===== 完整内容 =====');
console.log(full);
7.stream 流式输出+withStructuredOutput格式化流式输出
js
import 'dotenv/config';
import { ChatOpenAI } from '@langchain/openai';
import { z } from 'zod';
const model = new ChatOpenAI({
modelName: process.env.MODEL_NAME,
apiKey: process.env.OPENAI_API_KEY,
temperature: 0,
configuration: {
baseURL: process.env.OPENAI_BASE_URL,
},
timeout: 60000,
maxRetries: 3,
});
// 1. 定义 Schema(建议字段全用 string,避免数字解析失败)
const schema = z.object({
name: z.string().describe('姓名'),
birth_year: z.string().describe('出生年份'),
death_year: z.string().nullable().describe('去世年份,在世则输出 null'),
nationality: z.string().describe('国籍'),
famous_works: z.array(z.string()).describe('著名作品或成就列表'),
});
// 2. 普通模型用于流式输出(不强制 JSON 模式,体验流畅不报错)
const baseModel = model;
// 3. 结构化模型用于最终提取(只走一次性调用 invoke)
const structuredModel = model.withStructuredOutput(schema);
const prompt = `请你详细介绍爱因斯坦的信息。`;
async function runStableStreamAndExtract() {
// ========== 第一阶段:流式输出给用户看 ==========
const stream = await baseModel.stream(prompt);
let fullText = '';
console.log('🎬 开始流式接收...\n');
for await (const chunk of stream) {
const content = chunk.content;
if (typeof content === 'string') {
process.stdout.write(content);//实时输出
fullText += content;
}
}
console.log('\n\n✅ 流式接收完毕,开始结构化提取...\n');
// ========== 第二阶段:对完整文本进行结构化提取 ==========
try {
// 将刚才的完整文本作为上下文,让模型提取结构化数据
const extractPrompt = `请根据以下文本,提取出结构化的 JSON 信息:\n\n${fullText}。要求:完全按照文件回答问题,不要胡编乱造`;
const result = await structuredModel.invoke(extractPrompt);
console.log('🎯 最终结构化对象:');
console.log(JSON.stringify(result, null, 2));
} catch (e) {
console.error('❌ 结构化提取失败:', e.message);
}
}
runStableStreamAndExtract();
process.stdout.write(content);//实时输出
await baseModel.stream(prompt);//流式输出
model.withStructuredOutput(schema);--json格式化
const result = await structuredModel.invoke(extractPrompt);---结构化
此时的流式输出里面你可以使用jsonoutputparser,structuredOutputParser,也可以用tool,都可以达到格式化数据的效果。