一. LCEL是什么?
LCEL = LangChain Expression Language(LangChain 表达式语言)
它不是一门新语言,而是一套用管道操作符 | 把各种组件串起来的声明式写法。你可以把它理解成 LangChain 里的 "管道语法" ,类似 Unix 的 cat file | grep xxx | sort。
他只是一种简便写法,LCEL 就是实现了 Runnbale 接口的一些 api 组合成 chain,然后统一执行。
1.1 对比
普通代码
// 1. 手动格式化 prompt
const messages = await prompt.formatMessages({ input: '25°C' });
// 2. 手动调模型
const response = await model.invoke(messages);
// 3. 手动解析输出
const text = response.content;
console.log(text);
LCEL的代码
import { StringOutputParser } from '@langchain/core/output_parsers';
const chain = prompt | model | new StringOutputParser();
const result = await chain.invoke({ input: '25°C' });
console.log(result); // 直接拿到字符串 "77°F"
就这三行,完成了 格式化 → 调模型 → 解析输出 的全部流程。
LCEL 的本质:把 LangChain 里所有组件(Prompt、Model、Parser、Retriever、Tool…)统一成同一个接口
Runnable,然后用|把它们串起来。任何实现了Runnable接口的东西,都能互相 pipe。
二.案例
1.1 基础案例
import 'dotenv/config';
import { StructuredOutputParser } from "@langchain/core/output_parsers";
import { PromptTemplate } from "@langchain/core/prompts";
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,
},
});
// 定义输出结构 schema
const schema = z.object({
translation: z.string().describe("翻译后的英文文本"),
keywords: z.array(z.string()).length(3).describe("3个关键词")
});
const outputParser = StructuredOutputParser.fromZodSchema(schema);
const promptTemplate = PromptTemplate.fromTemplate(
'将以下文本翻译成英文,然后总结为3个关键词。\n\n文本:{text}\n\n{format_instructions}'
);
const input = {
text: 'LangChain 是一个强大的 AI 应用开发框架',
format_instructions: outputParser.getFormatInstructions()
};
const response = await model.invoke(await promptTemplate.format(input));
console.log("调用模型后的response",response.content)
// 步骤 1: 格式化 prompt
// console.log("input:",input)
// const formattedPrompt = await promptTemplate.format(input);//就是把iput里面的对象格式化成prompt里面的模板形式,并把format_instructions换成实际的指令。
// console.log("格式haunt以后的formattedPrompt",formattedPrompt)
// // 步骤 2: 调用模型
// const response = await model.invoke(formattedPrompt);
// console.log("调用模型后的response",response.content)
// // 步骤 3: 解析输出
// const result = await outputParser.invoke(response);//把response里面的内容MD形式,解析成我们想要的格式,并把结果赋值给result
// console.log("格式化以后的response",result);
model.invoke(); 里面的参数需要用 promptTemplate 格式化,invoke 的结果 response 用 outputParser 去格式化。
1.2 使用RunnableSequence
RunnableSequence 是 LangChain 中用于按顺序串联多个 Runnable 组件的组合原语——前一个组件的输出自动作为下一个组件的输入,形成一条线性处理管道。
统一接口:生成的 RunnableSequence 本身也是 Runnable,自动支持 .invoke()、.batch()、.stream()(流式)、.ainvoke()(异步)。
管道符 和 显式构造
import { RunnableSequence } from "@langchain/core/runnables";
import { PromptTemplate } from "@langchain/core/prompts";
import { ChatOpenAI } from "@langchain/openai";
import { StringOutputParser } from "@langchain/core/output_parsers";
import 'dotenv/config';
const prompt = PromptTemplate.fromTemplate("讲个关于{topic}的笑话");
const model = new ChatOpenAI({
modelName: 'qwen-max',
apiKey: process.env.OPENAI_API_KEY,
temperature: 0,
configuration: {
baseURL: process.env.OPENAI_BASE_URL,
},
timeout: 60000,
maxRetries: 3,
});
const parser = new StringOutputParser();
// 写法一:管道符(推荐,简洁)
//const chain1 = prompt.pipe(model).pipe(parser);
//const result1 = await chain1.invoke({ topic: "程序员" });
console.log(result1);
// 写法二:显式构造 RunnableSequence
const chain2 = RunnableSequence.from([prompt, model, parser]);
const result2 = await chain2.invoke({ topic: "程序员" });
console.log(result2);
把基础案例写成管道符
import 'dotenv/config';
import { StructuredOutputParser } from "@langchain/core/output_parsers";
import { PromptTemplate } from "@langchain/core/prompts";
import { RunnableSequence } from "@langchain/core/runnables";
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,
},
});
// 定义输出结构 schema
const schema = z.object({
translation: z.string().describe("翻译后的英文文本"),
keywords: z.array(z.string()).length(3).describe("3个关键词")
});
const outputParser = StructuredOutputParser.fromZodSchema(schema);
const promptTemplate = PromptTemplate.fromTemplate(
'将以下文本翻译成英文,然后总结为3个关键词。\n\n文本:{text}\n\n{format_instructions}'
);
//const chain = RunnableSequence.from([promptTemplate, model, outputParser]);
const chain = promptTemplate.pipe(model).pipe(outputParser);
const input = {
text: 'LangChain 是一个强大的 AI 应用开发框架',
format_instructions: outputParser.getFormatInstructions()
};
const result = await chain.invoke(input);
console.log(result)
// //步骤 1: 格式化 prompt
// console.log("input:",input)
// const formattedPrompt = await promptTemplate.format(input);//就是把iput里面的对象格式化成prompt里面的模板形式,并把format_instructions换成实际的指令。
// console.log("格式haunt以后的formattedPrompt",formattedPrompt)
// // 步骤 2: 调用模型
// const response = await model.invoke(formattedPrompt);
// console.log("调用模型后的response",response.content)
// // 步骤 3: 解析输出
// const result = await outputParser.invoke(response);//把response里面的内容MD形式,解析成我们想要的格式,并把结果赋值给result
// console.log("格式化以后的response",result);
1.3 使用RunnableLambda
RunnableLambda:把单个简单函数包装成 Runnable
const toUpper = RunnableLambda.from((s: string) => s.toUpperCase());
基本用法
import "dotenv/config";
import {RunnableLambda,RunnableSequence} from "@langchain/core/runnables";
const addOne = RunnableLambda.from(x => x + 1);
const multiplyRwo = RunnableLambda.from(x => x * 2);
const chain = RunnableSequence.from([addOne, multiplyRwo, addOne]);
const result = await chain.invoke(5);
console.log(result);
RunnableSequence和RunnableLambda联合使用
const chain = RunnableSequence.from([
RunnableLambda.from((input) => ({ topic: input })), // ← 转换器(RunnableLambda)
prompt, // ← 组件
model, // ← 组件
RunnableLambda.from((output) => output.trim()), // ← 转换器(RunnableLambda)
]);
// ↑ 整个是一条链(RunnableSequence),里面嵌入了两个 RunnableLambda
1.4 使用RunnableMap
RunnableMap 是 LangChain 中用于并行执行多个 Runnable 并将结果收集到一个对象中的组合原语。
简单说:它让链中的多个分支同时跑,最后把每个分支的输出拼成一个
{ key: value }对象返回。
基本用法
import { RunnableMap, RunnableLambda } from "@langchain/core/runnables";
const chain = RunnableMap.from({
uppercase: RunnableLambda.from((text: string) => text.toUpperCase()),
length: RunnableLambda.from((text: string) => text.length),
reversed: RunnableLambda.from((text: string) =>
text.split("").reverse().join("")
),
});
const result = await chain.invoke("hello");
console.log(result);
// {
// uppercase: "HELLO",
// length: 5,
// reversed: "olleh"
// }
高级用法
import 'dotenv/config';
import { RunnableMap, RunnableLambda } from "@langchain/core/runnables";
import { PromptTemplate } from "@langchain/core/prompts";
const addOne = RunnableLambda.from((input) => input.num + 1);
const multiplyTwo = RunnableLambda.from((input) => input.num * 2);
const square = RunnableLambda.from((input) => input.num * input.num);
const greetTemplate = PromptTemplate.fromTemplate("你好,{name}!");
const weatherTemplate = PromptTemplate.fromTemplate("今天天气{weather}。");
// 创建 RunnableMap, 并行执行多个 runnable
const runnableMap = RunnableMap.from({
// 数学运算
add: addOne,
multiply: multiplyTwo,
square: square,
// prompt 格式化
greeting: greetTemplate,
weather: weatherTemplate,
});
// 测试输入
const input = {
name: "神光",
weather: "多云",
num: 5,
};
// 执行 RunnableMap
const result = await runnableMap.invoke(input);
console.log(result);
RunnableSequence和RunnableMap的区别
RunnableSequence和RunnableMap联合使用
a.经典使用场景
const fullChain =
// 第一步:并行准备
RunnableMap.from({
context: retriever,
question: new RunnablePassthrough(),
})
// 第二步:串行生成
.pipe(prompt)
.pipe(model)
.pipe(new StringOutputParser());
const answer = await fullChain.invoke("LangChain 支持哪些模型?");
1.5 使用RunnableBranch
RunnableBranch 是 LangChain 中实现条件路由(Conditional Routing) 的组合原语——它像
if-elif-else一样,根据条件函数从多个分支中选择第一个匹配的分支执行,实现动态工作流路由。
基本写法
from langchain_core.runnables import RunnableBranch
branch = RunnableBranch(
(lambda x: x > 100, chain_a), # 条件1成立 → 执行chain_a
(lambda x: x > 10, chain_b), # 条件2成立 → 执行chain_b
default_chain # 都不匹配 → 执行default
)
高级写法
import 'dotenv/config';
import { RunnableBranch, RunnableLambda } from "@langchain/core/runnables";
// 创建条件判断函数
const isPositive = RunnableLambda.from((input) => input > 0);
const isNegative = RunnableLambda.from((input) => input < 0);
const isEven = RunnableLambda.from((input) => input % 2 === 0);
// 创建分支处理函数
const handlePositive = RunnableLambda.from((input) => `正数: ${input} + 10 = ${input + 10}`);
const handleNegative = RunnableLambda.from((input) => `负数: ${input} - 10 = ${input - 10}`);
const handleEven = RunnableLambda.from((input) => `偶数: ${input} * 2 = ${input * 2}`);
const handleDefault = RunnableLambda.from((input) => `默认: ${input}`);
// 创建 RunnableBranch
const branch = RunnableBranch.from([
[isPositive, handlePositive],
[isNegative, handleNegative],
[isEven, handleEven],
handleDefault
]);
// 测试不同的输入
const testCases = [5, -3, 4, 0];
for (const testCase of testCases) {
const result = await branch.invoke(testCase);
console.log(`输入: ${testCase} => ${result}`);
}
1.6 使用RouterRunnable
RouterRunnable 是 LangChain 中用于按 key 显式路由到对应 Runnable 的组件——类似
switch-case,根据输入中的key字段选择执行哪条已命名的子链,并返回该链的输出。
基本用法
import { RouterRunnable, RunnableLambda } from "@langchain/core/runnables";
const router = new RouterRunnable({
runnables: {
toUpperCase: RunnableLambda.from((text: string) => text.toUpperCase()),
reverseText: RunnableLambda.from((text: string) =>
text.split("").reverse().join("")
),
},
});
// 路由到 reverseText
const r1 = await router.invoke({ key: "reverseText", input: "Hello" });
console.log(r1); // "olleH"
// 路由到 toUpperCase
const r2 = await router.invoke({ key: "toUpperCase", input: "Hello" });
console.log(r2); // "HELLO"
高级用法
import 'dotenv/config';
import { RouterRunnable, RunnableLambda } from "@langchain/core/runnables";
// 创建两个简单的 RunnableLambda
const toUpperCase = RunnableLambda.from((text) => text.toUpperCase());
const reverseText = RunnableLambda.from((text) => text.split("").reverse().join(""));
// 创建 RouterRunnable: 根据 key 选择要调用的 runnable
const router = new RouterRunnable({
runnables: {
toUpperCase,
reverseText,
},
});
// 测试: 调用 reverseText
const result1 = await router.invoke({ key: "reverseText", input: "Hello World" });
console.log('reverseText 结果:', result1);
// 测试: 调用 toUpperCase
const result2 = await router.invoke({ key: "toUpperCase", input: "Hello World" });
console.log('toUpperCase 结果:', result2);
1.7 使用RunnablePassthrough
RunnablePassthrough = "什么都不做,在并行分支中需要把原始输入也作为一个 key 传下去
基本用法
import { RunnablePassthrough } from "@langchain/core/runnables";
const passthrough = new RunnablePassthrough();
//const passthrough = RunnableSequence.from()
const result = await passthrough.invoke("hello");
console.log(result); // "hello" — 原样返回
高级案例
import 'dotenv/config';
import { RunnablePassthrough, RunnableLambda, RunnableSequence, RunnableMap } from "@langchain/core/runnables";
const chain = RunnableSequence.from([
RunnableLambda.from((input) => ({ concept: input })),
RunnableMap.from({
original: new RunnablePassthrough(),
processed: RunnableLambda.from((obj) => ({
concept: input,
upper: obj.concept.toUpperCase(),
length: obj.concept.length,
}))
})
]);
const input = "你在说什么?听说你会原样返回的。";
const result = await chain.invoke(input);
console.log(result);
和RunnableMap联合使用
import { RunnableMap, RunnablePassthrough, RunnableLambda } from "@langchain/core/runnables";
const chain = RunnableMap.from({
// 分支1:对输入做大写转换
uppercase: RunnableLambda.from((text: string) => text.toUpperCase()),
// 分支2:对输入求长度
length: RunnableLambda.from((text: string) => text.length),
// 分支3:原样保留原始输入
original: new RunnablePassthrough(),
});
const result = await chain.invoke("hello");
console.log(result);
// {
// uppercase: "HELLO",
// length: 5,
// original: "hello" ← 原始输入被原样保留
// }
调试打印日志
import { RunnablePassthrough } from "@langchain/core/runnables";
const debugPassthrough = new RunnablePassthrough({
func: (input) => {
console.log("🔍 当前输入:", JSON.stringify(input));
return input; // 仍然原样返回
},
});
const chain = prompt.pipe(debugPassthrough).pipe(model).pipe(parser);
1.8 使用RunnableEach
RunnableEach 是 LangChain 中用于对数组中的每个元素逐一应用同一条 Runnable 链的组合原语。
基本用法
import 'dotenv/config';
import { RunnableEach, RunnableLambda, RunnableSequence } from "@langchain/core/runnables";
const toUpperCase = RunnableLambda.from((input) => input.toUpperCase());
const addGreeting = RunnableLambda.from((input) => `你好, ${input}!`);
const processItem = RunnableSequence.from([
toUpperCase,
addGreeting,
]);
// 使用 RunnableEach 对数组中的每个元素应用这个链
const chain = new RunnableEach({
bound: processItem,
});
const input = ["alice", "bob", "carol"];
const result = await chain.invoke(input);
console.log('✅ RunnableEach - 数组元素处理:');
console.log('输入:', input);
console.log('输出:', result);
对比RunnableEach和RunnableMap
RunnableEach = LCEL 世界里的 Array.map(),专门用来对数组中的每一项跑同一条处理链。
1.9 使用RunnablePick
RunnablePick 是 LangChain 中用于从字典(dict / object)输入中提取指定字段的 Runnable 组件——它从上游传来的字典中按指定 key 提取字段,并把提取出的字段(或字段值)传给下游。
基本用法
import { RunnablePick } from "@langchain/core/runnables";
const inputData = {
name: "John",
age: 30,
city: "New York",
};
// 提取多个 key
const pickBasic = new RunnablePick(["name", "city"]);
console.log(await pickBasic.invoke(inputData)); // { name: "John", city: "New York" }
// 提取单个 key → 返回值本身
const pickName = new RunnablePick("name");
console.log(await pickName.invoke(inputData)); // "John"
高级案例
import 'dotenv/config';
import { RunnablePick, RunnableSequence } from "@langchain/core/runnables";
const inputData = {
name: "snow",
age: 30,
city: "浙江",
country: "中国",
email: "545442765@example.com",
phone: "+86-13800189777",
};
const chain = RunnableSequence.from([
(input) => ({
...input,
fullInfo: `${input.name}, ${input.age}岁, 来自${input.city}`,
}),
new RunnablePick(["name", "fullInfo"]),
]);
const result = await chain.invoke(inputData);
console.log(result);
1.10 使用RunnableWithMessageHistory对象
RunnableWithMessageHistory 是 LangChain 中用来给任意 LCEL 链附加多轮对话记忆(Memory) 的包装器。它让原本无状态的链自动具备"读取历史 → 注入 Prompt → 执行 → 保存新消息"的能力,且包装后依然是标准 Runnable,可继续用
|拼接或嵌套
他已经被启用了。官方现在的战略是全面转向 LangGraph 的持久化(Persistence / Checkpointer)机制。
替换办法
// 旧方式 (你截图中的代码)
const chain = new RunnableWithMessageHistory({ runnable: simpleChain, ... });
// 新方式 (LangGraph 持久化)
import { StateGraph, MessagesAnnotation, MemorySaver } from "@langchain/langgraph";
const memory = new MemorySaver(); // 替代 InMemoryChatMessageHistory
const graph = new StateGraph(MessagesAnnotation)
.addNode("callModel", callModel)
.addEdge("__start__", "callModel")
.compile({ checkpointer: memory });
// 调用时直接传 thread_id 即可自动记忆
await graph.invoke({ messages: [...] }, { configurable: { thread_id: "user-123" } });
核心机制
它解决的核心痛点是:原生 LCEL 链(如 prompt | model | parser)是无状态的,每次调用互相独立,无法记住前文。
其工作流程可以理解为:
- 调用时传入
session_id(会话唯一标识) - 通过
get_session_history函数按session_id加载该会话的历史消息 - 把历史消息通过
MessagesPlaceholder注入 Prompt - 执行被包装的原始链
- 把本轮「用户提问 + AI 回复」追加保存到该
session_id对应的历史存储中
1.11 Runnable-WithRetry
在 LangChain(langchain-core) 中,RunnableWithRetry 通常指通过
Runnable.with_retry()方法生成的带自动重试能力的 Runnable 包装对象(底层为RunnableRetry/RunnableBinding),用于在调用失败时进行自动重试,常用于处理网络抖动、API 限流
案例
在管道里面使用,所有的Runnable类里面的对象都可以使用这个方法with_retry(),进行重试。
import { ChatOpenAI } from "@langchain/openai";
import { PromptTemplate } from "@langchain/core/prompts";
const model = new ChatOpenAI({ temperature: 0.7 });
// 模板
const prompt = PromptTemplate.fromTemplate(
"用 {tone} 的语气,用一句话解释:{concept}"
);
// ✅ 只对模型加重试,不对整个链包
const chain = prompt.pipe(
model.withRetry({
stopAfterAttempt: 3, // 最多尝试 3 次(含首次)
onFailedAttempt: (e) => {
console.log(`🔄 模型第 ${e.attemptNumber} 次失败,重试中...`);
},
})
);
async function main() {
const res = await chain.invoke({
tone: "幽默",
concept: "指数退避重试策略",
});
console.log(res.content);
}
main();
你也可以在简单函数上使用,前提条件是用RunnableLambda包含普通函数,生成Runnable对象,然后调用withRetry方法即可。
import { RunnableLambda } from "@langchain/core/runnables";
let callCount = 0;
const flakyFn = new RunnableLambda({
func: async (input) => {
callCount++;
if (callCount < 3) {
throw new Error(`模拟瞬时失败 #${callCount}`);
}
return `成功!输入=${input},共尝试 ${callCount} 次`;
},
});
const retryFn = flakyFn.withRetry({
stopAfterAttempt: 5,
onFailedAttempt: (e) => {
console.log(`❌ 第 ${e.attemptNumber} 次失败:${e.message}`);
},
});
// 测试
const result = await retryFn.invoke("hello");
console.log(result);
// ❌ 第 1 次失败:模拟瞬时失败 #1
// ❌ 第 2 次失败:模拟瞬时失败 #2
// 成功!输入=hello,共尝试 3 次
1.12使用Runnable-WithFallbacks
Runnable-WithFallbacks 是 LangChain 中通过
.withFallbacks()方法创建的一个 Runnable 包装器,它的作用是:当主 Runnable 调用失败时,自动按顺序尝试备用 Runnable,直到有一个成功为止。
用法
const primary = new ChatOpenAI({ model: "gpt-4" });
const fallback1 = new ChatOpenAI({ model: "gpt-3.5-turbo" }); // 次选
const fallback2 = new ChatAnthropic({ model: "claude-3-haiku" }); // 最后兜底
const chain = prompt.pipe(
primary.withFallbacks([fallback1, fallback2])
);
// 尝试顺序:gpt-4 → gpt-3.5-turbo → claude-3-haiku
primary.withFallbacks([fallback1, fallback2]) 在主大模型里面添加上2个备用模型
案例
import { ChatOpenAI } from "@langchain/openai";
import { ChatAnthropic } from "@langchain/anthropic";
import { PromptTemplate } from "@langchain/core/prompts";
// 主模型(故意用个不存在的模型名,让它必挂)
const primary = new ChatOpenAI({
model: "gpt-99-turbo", // 不存在 → 必失败
temperature: 0,
maxRetries: 0,
});
// 备用模型
const fallback = new ChatAnthropic({
model: "claude-3-haiku-20240307",
temperature: 0,
});
const prompt = PromptTemplate.fromTemplate(
"用一句话解释:{concept}"
);
// ✅ 主 → 备
const chain = prompt.pipe(
primary.withFallbacks([fallback])
);
async function main() {
try {
const res = await chain.invoke({
concept: "RunnableWithFallbacks",
});
console.log("✅ 成功(来自备用模型):");
console.log(res.content);
} catch (err) {
console.error("❌ 主备全部失败:", err.message);
}
}
main();
你也可以在Runnable对象后面使用写备用方案
import "dotenv/config";
import { RunnableLambda } from "@langchain/core/runnables";
// 模拟三个"翻译服务",优先级从高到低
const premiumTranslator = RunnableLambda.from(async (text) => {
console.log("[Premium] 尝试翻译...");
// 模拟高级服务不可用
throw new Error("Premium 服务超时");
});
const standardTranslator = RunnableLambda.from(async (text) => {
console.log("[Standard] 尝试翻译...");
// 模拟标准服务也挂了
throw new Error("Standard 服务限流");
});
const localTranslator = RunnableLambda.from(async (text) => {
console.log("[Local] 使用本地词典翻译...");
const dict = { hello: "你好", world: "世界", goodbye: "再见" };
const words = text.toLowerCase().split(" ");
return words.map((w) => dict[w] ?? w).join("");
});
// withFallbacks: 依次尝试 premium -> standard -> local
const translator = premiumTranslator.withFallbacks({
fallbacks: [standardTranslator, localTranslator],
});
const result = await translator.invoke("hello world");
console.log("翻译结果:", result);
1.13使用Runnable-WithConfig
你看到的 Runnable-WithConfig 指的是 Runnable.with_config()(JS 里是 .withConfig()) ——它返回的是一个 RunnableBinding(把配置绑到 Runnable 上的装饰器),作用是给 Runnable 预绑定一份 RunnableConfig,以后调用不用再重复传。
案例
在大模型上配置信息后,直接调用invoke()
import { ChatOpenAI } from "@langchain/openai";
import { PromptTemplate } from "@langchain/core/prompts";
const model = new ChatOpenAI({ model: "gpt-3.5-turbo", temperature: 0 });
// ✅ 用 withConfig 预绑配置:以后 invoke 不用再传
const tracedModel = model.withConfig({
run_name: "TracedGPTCall",
tags: ["prod", "chat"],
metadata: { owner: "backend-team" },
maxConcurrency: 2,
});
const prompt = PromptTemplate.fromTemplate("用一句话解释:{topic}");
const chain = prompt.pipe(tracedModel);
// 调用时干干净净,配置已经在链上了
const res = await chain.invoke({ topic: "RunnableBinding" });
console.log(res.content);
你还可以给Runnable函数配置withConfig
import "dotenv/config";
import { RunnableLambda, RunnableSequence } from "@langchain/core/runnables";
// 模拟一个简单的"用户数据库"
const mockUsers = new Map([
[
"user-123",
{
id: "009",
name: "snow",
email: "snow@qq.com",
},
],
]);
// 节点1: 根据 config.configurable.userId 查用户
const fun1 = RunnableLambda.from(async (input, config) => {
const userId = config?.configurable?.userId;
const user = userId ? mockUsers.get(userId) : null;
if (!user) {
throw new Error("未找到用户,无法发送通知");
}
return {
user,
notification: input,
};
});
// 节点2: 根据 config.configurable.role 做权限判断
const fun2 = RunnableLambda.from(async (state, config) => {
const role = config?.configurable?.role ?? "普通用户";
const canSend =
role === "管理员" ||
role === "运营" ||
role === "系统";
if (!canSend) {
throw new Error(`角色「${role}」无权限发送系统通知`);
}
return {
...state,
role,
};
});
// 节点3: 根据 locale 生成最终通知文案
const fun3 = RunnableLambda.from(async (state, config) => {
const locale = config?.configurable?.locale ?? "zh-CN";
let content;
if (locale === "en-US") {
content = `Dear ${state.user.name},\n\n${state.notification}\n\n(from role: ${state.role})`;
} else {
content = `亲爱的 ${state.user.name}, \n\n${state.notification}\n\n(发送人角色: ${state.role})`;
}
return {
...state,
locale,
finalContent: content,
};
});
// 把三个节点串起来
const chain = RunnableSequence.from([fun1, fun2, fun3]);
const chainWithConfig = chain.withConfig({
tags: ["demo", "withConfig", "notification"],
metadata: {
demoName: "RunnableWithConfig",
},
configurable: {
userId: "user-123",
role: "管理员",
locale: "zh-CN",
},
});
const result = await chainWithConfig.invoke("你有一条新的系统通知,请及时查看。");
console.log("✅ 最终通知内容:\n", result.finalContent);
console.log("\n--- chainWithConfig2 ---\n");
// 再创建一个不同配置的 chainWithConfig2,使用英文 locale
const chainWithConfig2 = chain.withConfig({
tags: ["demo", "withConfig", "notification-en"],
metadata: {
demoName: "RunnableWithConfig2",
},
configurable: {
userId: "user-123",
role: "运营",
locale: "en-US",
},
});
// 输入为要发送的通知文案
const result2 = await chainWithConfig2.invoke("System maintenance scheduled tonight.");
console.log("✅ 最终通知内容:\n", result2.finalContent);
1.14使用Runnable-WithCallbacks
它指的是
Runnable.withCallbacks()(JS 里是.withCallbacks()) ——给 Runnable 绑定回调函数,在链执行的不同阶段自动触发(如开始、结束、出错时),常用于日志、监控、追踪、Token 统计等。
一个类继承BaseCallbackHandler后,你可以监听以下事件:
| 事件 | 触发时机 |
|---|---|
handleChainStart | 链/Runnable 开始执行 |
handleChainEnd | 链/Runnable 执行完成 |
handleChainError | 链/Runnable 执行出错 |
handleLLMStart | LLM 开始生成 |
handleLLMNewToken | LLM 流式输出新 token |
handleLLMEnd | LLM 生成结束 |
handleToolStart / handleToolEnd | 工具调用开始/结束 |
handleRetrieverStart / handleRetrieverEnd | 检索开始/结束 |
class案例
import { ChatOpenAI } from "@langchain/openai";
import { PromptTemplate } from "@langchain/core/prompts";
import { BaseCallbackHandler } from "@langchain/core/callbacks/base";
import "dotenv/config";
// 自定义回调处理器
class LogCallbackHandler extends BaseCallbackHandler {
constructor() {
super();
this.name = "LogCallbackHandler";
}
async handleChainStart(serialized, inputs, runId) {
console.log(`🔵 [ChainStart] runId=${runId} inputs=`, inputs);
}
async handleChainEnd(outputs, runId) {
console.log(`✅ [ChainEnd] runId=${runId} outputs=`, outputs);
}
async handleChainError(err, runId) {
console.log(`❌ [ChainError] runId=${runId} error=`, err.message);
}
async handleLLMStart(serialized, prompts, runId) {
console.log(`🤖 [LLMStart] runId=${runId} prompt=`, prompts[0]?.slice(0, 50));
}
async handleLLMNewToken(token, runId) {
process.stdout.write(`📝 [Token] ${token}`);
}
async handleLLMEnd(output, runId) {
const text = output.generations?.[0]?.[0]?.text ?? "";
console.log(`\n🏁 [LLMEnd] runId=${runId} totalLength=${text.length}`);
}
}
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 prompt = PromptTemplate.fromTemplate("用一句话解释:{topic}");
// ✅ 修正:使用 withConfig 绑定回调
const chain = prompt.pipe(model).withConfig({
callbacks: [new LogCallbackHandler()],
});
async function main() {
const result = await chain.invoke({ topic: "RunnableWithCallbacks" });
console.log("\n\n📦 最终结果:", result.content);
}
main();
执行结果
object案例
可以把他的执行函数写到一个对象里面,加到callback里面。
import "dotenv/config";
import { RunnableLambda, RunnableSequence } from "@langchain/core/runnables";
// 文本处理链:清洗 -> 分词 -> 统计
const clean = RunnableLambda.from((text) => {
return text+'你是谁?';
});
const tokenize = RunnableLambda.from((text) => {
return text+'他是猪八戒';
});
const count = RunnableLambda.from((tokens) => {
return { tokens, wordCount: tokens.length };
});
const chain = RunnableSequence.from([clean, tokenize, count]);
// 用 callbacks 观测每一步的输出
const callback = {
handleChainStart(chain) {
const step = chain?.id?[chain.id.length - 1] : "unknown";
console.log(`[START] ${step}`);
},
handleChainEnd(output) {
console.log(`[END] 输出=${JSON.stringify(output)}\n`);
},
handleChainError(err) {
console.log(`[ERROR] ${err.message}\n`);
},
};
const result = await chain.invoke("hello", {
callbacks: [callback],
});
console.log("最后结果:", result.content);
三.总结
RunnableSequence 是"串联器",RunnableLambda 是"转换器"。
| 组件 | 执行方式 | 类比 | 典型用途 |
|---|---|---|---|
| RunnableSequence | 串行 — 一步一步,上一步输出作为下一步输入 | 工厂流水线 | Prompt → LLM → Parser 标准三步链 |
| RunnableLambda | 转换 — 对输入执行自定义函数,返回处理结果 | 流水线上的加工工位 | 数据清洗、字段提取、格式化、打日志 |
| RunnableMap | 并行 — 所有分支同时执行,共享同一输入,结果收集为对象 | 分兵并进 / Promise.all() | 同时准备多路数据(如检索 docs + 历史摘要 + 原始问题) |
| RunnableBranch | 条件路由 — 按顺序评估条件,执行第一个匹配的分支 | if-elif-else | 根据输入内容/数值走不同处理逻辑(如数学题 vs 代码题) |
| RouterRunnable | Key 路由 — 按输入中的 key 字段精确匹配,执行对应分支 | switch-case | Intent 已分类好,直接分发到对应 handler(如客服多意图路由) |
| RunnablePassthrough | 透传 — 原样返回输入,不做任何修改 | 透明传送带 | 在 RunnableMap 中保留原始输入、调试插入点、占位对齐接口 |
| RunnableEach | 批量逐一 — 对数组中的每个元素独立应用同一条链 | Array.map() | 批量处理消息列表、逐条翻译、对多个文档逐一生成摘要 |
| RunnablePick | 字段提取 — 从字典中按指定 key 提取字段,向下游传递 | 从文件夹里挑出指定文件 | 从并行分支输出中只挑选 Prompt 需要的字段(如只取 context + question) |
| RunnableWithMessageHistory ⚠️ 已弃用 | 包装增强 — 加载历史 → 注入 Prompt → 执行链 → 保存新消息 | 给无状态链装上"记忆芯片" | 多轮对话场景(已被 LangGraph Checkpointer 取代,新项目不建议使用) |