课程目标
将前面学过的所有核心组件(Prompt、Model、Parser、Tools、History)组合成完整的应用链。掌握链的类型匹配、流式执行、错误处理与优雅降级策略。
16.1 组件回顾与类型链路
在前面的课程中,我们已经学习了所有核心组件。现在看它们在链中的类型流转:
ChatPromptTemplate Record<string, any> → ChatPromptValue
.pipe(ChatModel) ChatPromptValue → AIMessage
.pipe(StringOutputParser) AIMessage → string
每个组件都是 Runnable,pipe() 将它们串联成 RunnableSequence(第 5 课),泛型保证类型安全:
// 链的完整类型签名
RunnableSequence<Record<string, any>, string>
16.2 基础链:Prompt + Model + Parser
16.2.1 最简单的链
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { ChatOpenAI } from "@langchain/openai";
import { StringOutputParser } from "@langchain/core/output_parsers";
const prompt = ChatPromptTemplate.fromMessages([
["system", "You are a helpful translator. Translate the following to {language}."],
["human", "{text}"],
]);
const model = new ChatOpenAI({ model: "gpt-4o-mini" });
const parser = new StringOutputParser();
// 用 pipe 串联
const chain = prompt.pipe(model).pipe(parser);
// invoke:一次性获取完整结果
const result = await chain.invoke({
language: "Chinese",
text: "Hello, how are you?",
});
// "你好,你好吗?"
16.2.2 结构化输出链
import { JsonOutputParser } from "@langchain/core/output_parsers";
interface TranslationResult {
translation: string;
confidence: number;
alternatives: string[];
}
const structuredPrompt = ChatPromptTemplate.fromMessages([
["system", `You are a translator. Respond in JSON with fields:
- translation: the translated text
- confidence: confidence score 0-1
- alternatives: array of alternative translations`],
["human", "Translate to {language}: {text}"],
]);
const structuredChain = structuredPrompt
.pipe(model)
.pipe(new JsonOutputParser<TranslationResult>());
const result = await structuredChain.invoke({
language: "Japanese",
text: "Good morning",
});
// { translation: "おはようございます", confidence: 0.95, alternatives: ["おはよう", "..."] }
16.3 带工具的链
16.3.1 Model + bindTools
import { tool } from "@langchain/core/tools";
import { z } from "zod";
import { HumanMessage, AIMessage, ToolMessage } from "@langchain/core/messages";
// 定义工具
const weatherTool = tool(
async ({ city }) => {
// 实际项目中调用天气 API
const data: Record<string, string> = {
"Beijing": "25°C, Sunny",
"Tokyo": "22°C, Cloudy",
"London": "15°C, Rainy",
};
return data[city] ?? "Weather data not available";
},
{
name: "get_weather",
description: "Get the current weather for a city",
schema: z.object({
city: z.string().describe("The city name in English"),
}),
}
);
const calculatorTool = tool(
async ({ expression }) => {
try {
return String(eval(expression));
} catch {
return "Invalid expression";
}
},
{
name: "calculator",
description: "Calculate a math expression",
schema: z.object({
expression: z.string().describe("A math expression"),
}),
}
);
// 绑定工具到模型
const modelWithTools = model.bindTools([weatherTool, calculatorTool]);
16.3.2 完整的工具调用循环
async function runWithTools(question: string): Promise<string> {
const tools = [weatherTool, calculatorTool];
const toolMap = Object.fromEntries(tools.map((t) => [t.name, t]));
let messages = [new HumanMessage(question)];
// 循环直到模型不再调用工具
while (true) {
const response = await modelWithTools.invoke(messages);
messages.push(response);
// 没有工具调用,直接返回文本回复
if (!response.tool_calls || response.tool_calls.length === 0) {
return typeof response.content === "string"
? response.content
: JSON.stringify(response.content);
}
// 执行工具调用
for (const toolCall of response.tool_calls) {
const selectedTool = toolMap[toolCall.name];
if (selectedTool) {
const toolResult = await selectedTool.invoke(toolCall);
messages.push(toolResult);
}
}
}
}
const answer = await runWithTools(
"What's the weather in Beijing? Also, what is 15 * 37?"
);
16.4 带对话历史的链
结合第 10 课的 RunnableWithMessageHistory:
import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts";
import { RunnableWithMessageHistory } from "@langchain/core/runnables";
import { ChatMessageHistory } from "langchain/stores/message/in_memory";
// 1. 创建带历史插槽的 Prompt
const prompt = ChatPromptTemplate.fromMessages([
["system", "You are a helpful assistant. Answer concisely."],
new MessagesPlaceholder("chat_history"),
["human", "{input}"],
]);
const chain = prompt.pipe(model).pipe(new StringOutputParser());
// 2. 用 Map 管理每个 session 的历史
const messageHistories = new Map<string, ChatMessageHistory>();
const getMessageHistory = (sessionId: string) => {
if (!messageHistories.has(sessionId)) {
messageHistories.set(sessionId, new ChatMessageHistory());
}
return messageHistories.get(sessionId)!;
};
// 3. 用 RunnableWithMessageHistory 包装
const chainWithHistory = new RunnableWithMessageHistory({
runnable: chain,
getMessageHistory,
inputMessagesKey: "input",
historyMessagesKey: "chat_history",
});
// 4. 使用——同一 session 自动维护上下文
const config = { configurable: { sessionId: "user-001" } };
const r1 = await chainWithHistory.invoke({ input: "My name is Alice" }, config);
// "Nice to meet you, Alice!"
const r2 = await chainWithHistory.invoke({ input: "What is my name?" }, config);
// "Your name is Alice."
// 不同 session 互不影响
const r3 = await chainWithHistory.invoke(
{ input: "What is my name?" },
{ configurable: { sessionId: "user-002" } }
);
// "I don't know your name yet."
16.5 链的流式执行
16.5.1 chain.stream()
const chain = prompt.pipe(model).pipe(new StringOutputParser());
// stream() 返回 AsyncGenerator,逐 chunk 输出
const stream = await chain.stream({
language: "Chinese",
text: "Tell me a story",
});
for await (const chunk of stream) {
process.stdout.write(chunk); // 逐 token 输出
}
流式传播原理(第 5 课 RunnableSequence):
Prompt.invoke() → 完整的 ChatPromptValue
→ Model._streamIterator() → AIMessageChunk 流
→ Parser.transform() → string chunk 流
16.5.2 chain.streamEvents()
streamEvents 提供更细粒度的事件流,能观察到链中每个节点的执行状态:
const eventStream = chain.streamEvents(
{ language: "Chinese", text: "Hello" },
{ version: "v2" }
);
for await (const event of eventStream) {
if (event.event === "on_llm_stream") {
// LLM 输出的每个 token
process.stdout.write(event.data.chunk.content);
} else if (event.event === "on_chain_start") {
console.log(`Chain started: ${event.name}`);
} else if (event.event === "on_chain_end") {
console.log(`Chain ended: ${event.name}`);
}
}
常见事件类型:
| 事件 | 触发时机 |
|---|---|
on_chain_start / on_chain_end | 链/子链 开始/结束 |
on_llm_start / on_llm_end | LLM 调用 开始/结束 |
on_llm_stream | LLM 输出每个 token |
on_tool_start / on_tool_end | 工具调用 开始/结束 |
on_prompt_start / on_prompt_end | Prompt 格式化 开始/结束 |
on_parser_start / on_parser_end | Parser 解析 开始/结束 |
16.6 withRetry — 自动重试
// 为整条链添加重试策略
const robustChain = chain.withRetry({
stopAfterAttempt: 3, // 最多重试 3 次
onFailedAttempt: (error, attempt) => {
console.log(`Attempt ${attempt} failed: ${error.message}`);
},
});
// 也可以只为特定节点添加重试
const robustModel = model.withRetry({ stopAfterAttempt: 2 });
const chain = prompt.pipe(robustModel).pipe(parser);
16.7 withFallbacks — 降级方案
import { ChatAnthropic } from "@langchain/anthropic";
const primaryModel = new ChatOpenAI({ model: "gpt-4o" });
const fallbackModel = new ChatAnthropic({ model: "claude-sonnet-4-20250514" });
// 主模型失败时自动切换到备用模型
const reliableModel = primaryModel.withFallbacks({
fallbacks: [fallbackModel],
});
const chain = prompt.pipe(reliableModel).pipe(parser);
16.8 withConfig — 运行时配置
// 为链绑定默认配置
const configuredChain = chain.withConfig({
tags: ["production", "translation"],
metadata: { version: "1.0" },
runName: "TranslationChain",
});
16.9 实战模式汇总
模式 1:问答链
const qaChain = ChatPromptTemplate.fromMessages([
["system", "Answer the question based on the context.\n\nContext: {context}"],
["human", "{question}"],
])
.pipe(model)
.pipe(new StringOutputParser());
模式 2:提取链
const extractChain = ChatPromptTemplate.fromMessages([
["system", "Extract structured information from the text. Respond in JSON."],
["human", "{text}"],
])
.pipe(model)
.pipe(new JsonOutputParser());
模式 3:翻译链
const translateChain = ChatPromptTemplate.fromMessages([
["system", "Translate the following text to {target_language}. Only output the translation."],
["human", "{text}"],
])
.pipe(model)
.pipe(new StringOutputParser());
16.10 综合实战:智能客服链
import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts";
import { ChatOpenAI } from "@langchain/openai";
import { JsonOutputParser } from "@langchain/core/output_parsers";
import { RunnablePassthrough, RunnableParallel } from "@langchain/core/runnables";
import { RunnableWithMessageHistory } from "@langchain/core/runnables";
import { ChatMessageHistory } from "langchain/stores/message/in_memory";
// 1. 定义输出格式
interface CustomerServiceResponse {
answer: string;
confidence: number;
sources: string[];
needsHumanReview: boolean;
}
// 2. 模拟知识库检索
async function searchFAQ(question: string): Promise<string> {
const faq: Record<string, string> = {
"refund": "Refunds are processed within 7 business days.",
"shipping": "Standard shipping takes 3-5 business days.",
"return": "Items can be returned within 30 days of purchase.",
};
const key = Object.keys(faq).find((k) => question.toLowerCase().includes(k));
return key ? faq[key] : "No FAQ found for this question.";
}
// 3. 构建并行检索 + 问题传递
const contextRetriever = RunnableParallel.from({
faq: async (input: { input: string }) => searchFAQ(input.input),
question: (input: { input: string }) => input.input,
});
// 4. 构建 Prompt
const prompt = ChatPromptTemplate.fromMessages([
["system", `You are a customer service assistant. Use the FAQ information to answer.
Always respond in JSON with: answer, confidence (0-1), sources (array), needsHumanReview (boolean).
FAQ Information:
{faq}`],
new MessagesPlaceholder({ variableName: "chat_history", optional: true }),
["human", "{question}"],
]);
// 5. 组合完整链
const model = new ChatOpenAI({ model: "gpt-4o-mini" });
const parser = new JsonOutputParser<CustomerServiceResponse>();
const coreChain = contextRetriever
.pipe(prompt)
.pipe(model)
.pipe(parser);
// 6. 添加对话历史
const histories = new Map<string, ChatMessageHistory>();
const fullChain = new RunnableWithMessageHistory({
runnable: coreChain,
getMessageHistory: (sessionId: string) => {
if (!histories.has(sessionId)) {
histories.set(sessionId, new ChatMessageHistory());
}
return histories.get(sessionId)!;
},
inputMessagesKey: "input",
historyMessagesKey: "chat_history",
});
// 7. 添加重试和降级
const robustChain = fullChain.withRetry({ stopAfterAttempt: 2 });
// 8. 使用
const config = { configurable: { sessionId: "customer-001" } };
const response = await robustChain.invoke(
{ input: "How long does shipping take?" },
config
);
// {
// answer: "Standard shipping takes 3-5 business days.",
// confidence: 0.95,
// sources: ["FAQ"],
// needsHumanReview: false
// }
// 流式执行,观察中间过程
const events = robustChain.streamEvents(
{ input: "What about returns?" },
{ version: "v2", ...config }
);
for await (const event of events) {
if (event.event === "on_parser_end") {
console.log("Parsed result:", event.data.output);
}
}
16.11 链的错误传播
在 RunnableSequence 中,错误会沿链传播并中断执行。处理策略:
| 策略 | 方法 | 适用场景 |
|---|---|---|
| 自动重试 | withRetry() | 网络抖动、API 限流 |
| 降级备选 | withFallbacks() | Provider 故障 |
| 错误捕获 | try-catch + OutputParserException | 输出格式错误 |
| 默认值 | RunnableLambda 包装 | 非关键节点失败 |
16.12 源码精读路线
| 优先级 | 文件 | 关注点 |
|---|---|---|
| P0 | runnables/base.ts | RunnableSequence——理解 pipe() 串联的内部结构 |
| P0 | runnables/base.ts | stream(), _streamIterator() 流式传播 |
| P1 | runnables/config.ts | RunnableConfig 在链中的传递 |
| P1 | runnables/base.ts | withRetry(), withFallbacks() |
| P2 | runnables/passthrough.ts | RunnablePassthrough.assign() 数据编排 |
本课收获总结
| 级别 | 你应该掌握的 |
|---|---|
| 🟢 基础 | 完成第一个从 Prompt → Model → Parser 的完整链,理解 invoke 和 stream |
| 🔵 中阶 | 理解链中每个节点的输入输出类型如何匹配;掌握 RunnableWithMessageHistory 包装 |
| 🟡 高阶 | 掌握 streamEvents 观察链的完整执行过程;理解流式传播原理 |
| 🟠 资深 | 分析链的错误传播机制;设计 withRetry + withFallbacks 的优雅降级策略 |
| 🔴 架构 | 建立"组件化 AI 应用"的系统思维:Prompt 模块化 + Model 可替换 + Parser 可插拔 + 工具可扩展 |
下一课预告
第 17 课讲上下文变量、单例系统与错误类型——Callbacks 是怎么在深层嵌套的 Runnable 链中传播的?框架的错误为什么有结构?