课程目标
精读 Runnable 抽象类的核心设计:invoke / stream / batch 三大方法,以及 withRetry / withFallbacks / withConfig 三大增强方法。
4.1 Runnable 的设计哲学
Runnable 是整个 LangChain.js 的万物基石。一个简单但深刻的设计决策:
框架中的一切组件 — 模型、提示词、解析器、工具、检索器 — 都是 Runnable。
这意味着任何两个组件都可以通过 pipe() 组合,不需要特殊的适配器或胶水代码。
4.2 类定义与继承关系
Serializable (序列化基类)
└── Runnable<RunInput, RunOutput, CallOptions> (核心抽象)
├── BaseChatModel (模型)
├── BasePromptTemplate (提示词)
├── BaseOutputParser (解析器)
├── StructuredTool (工具)
├── BaseRetriever (检索器)
├── RunnableSequence (管道组合)
├── RunnableParallel (并行执行)
├── RunnableLambda (函数包装)
├── RunnableBranch (条件分支)
├── RunnableBinding (配置绑定)
├── RunnableRetry (重试包装)
├── RunnableWithFallbacks (降级包装)
└── ...
源码位置: libs/langchain-core/src/runnables/base.ts:124
export abstract class Runnable<
RunInput = any,
RunOutput = any,
CallOptions extends RunnableConfig = RunnableConfig,
>
extends Serializable
implements RunnableInterface<RunInput, RunOutput, CallOptions>
{
protected lc_runnable = true; // 标识这是一个 Runnable
name?: string; // 可选的名称(用于调试和追踪)
// 唯一的抽象方法 — 子类必须实现
abstract invoke(
input: RunInput,
options?: Partial<CallOptions>
): Promise<RunOutput>;
// 下面的方法都有默认实现,子类可选择重写
// ...
}
关键观察:invoke() 是唯一的抽象方法。batch()、stream()、pipe() 等都有默认实现。这意味着实现一个自定义 Runnable 的最低要求是只实现 invoke()。
4.3 三大核心方法
4.3.1 invoke — 单次调用
abstract invoke(
input: RunInput,
options?: Partial<CallOptions>
): Promise<RunOutput>;
- 语义:接收输入,返回输出。最基本的调用方式。
- 异步:始终返回
Promise,支持异步操作(如 HTTP 调用 LLM)。 - options:可选的运行时配置(callbacks、timeout、signal 等)。
4.3.2 batch — 批量调用
async batch(
inputs: RunInput[],
options?: Partial<CallOptions> | Partial<CallOptions>[],
batchOptions?: RunnableBatchOptions
): Promise<(RunOutput | Error)[]>
默认实现(base.ts:261):
async batch(inputs, options, batchOptions) {
const configList = this._getOptionsList(options ?? {}, inputs.length);
const maxConcurrency =
configList[0]?.maxConcurrency ?? batchOptions?.maxConcurrency;
const caller = new AsyncCaller({ maxConcurrency, onFailedAttempt: (e) => { throw e; } });
const batchCalls = inputs.map((input, i) =>
caller.call(async () => {
try {
const result = await this.invoke(input, configList[i]);
return result;
} catch (e) {
if (batchOptions?.returnExceptions) {
return e as Error; // 收集异常而不是中断
}
throw e; // 默认:第一个异常就中断
}
})
);
return Promise.all(batchCalls);
}
设计要点:
- 默认实现是"并行调用 N 次
invoke()",通过AsyncCaller控制并发 maxConcurrency限制并发数(防止 API 限流)returnExceptions: true模式:收集异常而不是 fail-fast- 子类可以重写
batch()提供更高效的批处理(如一次 API 调用处理多个请求)
4.3.3 stream — 流式调用
async stream(
input: RunInput,
options?: Partial<CallOptions>
): Promise<IterableReadableStream<RunOutput>>
默认实现(base.ts:310):
async stream(input, options) {
const config = ensureConfig(options);
const wrappedGenerator = new AsyncGeneratorWithSetup({
generator: this._streamIterator(input, config),
config,
});
await wrappedGenerator.setup;
return IterableReadableStream.fromAsyncGenerator(wrappedGenerator);
}
实际的流式逻辑在 _streamIterator() 中(base.ts:297):
async *_streamIterator(input, options) {
// 默认实现:直接 yield invoke 的结果(一次性输出)
yield this.invoke(input, options);
}
设计要点:
- 默认的
_streamIterator()没有真正的"流"——它 yield 了整个 invoke 结果 - 子类(如
BaseChatModel)重写_streamIterator()实现真正的逐 token 流式 stream()将AsyncGenerator包装为IterableReadableStream,兼容 Web APIAsyncGeneratorWithSetup会预先消费第一个 chunk,让初始化错误立即暴露
4.4 三大增强方法
4.4.1 withRetry — 自动重试
withRetry(fields?: {
stopAfterAttempt?: number;
onFailedAttempt?: RunnableRetryFailedAttemptHandler;
}): RunnableRetry<RunInput, RunOutput, CallOptions>
使用方式:
const reliableModel = model.withRetry({
stopAfterAttempt: 3,
onFailedAttempt: (error, input) => {
console.log(`Attempt failed: ${error.message}`);
},
});
实现原理(base.ts:156):
- 返回一个
RunnableRetry包装器 RunnableRetry持有原始 Runnable 的引用- 调用时,如果失败就重试,直到达到
stopAfterAttempt次数 - 底层使用
p-retry库
4.4.2 withFallbacks — 降级策略
withFallbacks(
fields: { fallbacks: Runnable<RunInput, RunOutput>[] } | Runnable<RunInput, RunOutput>[]
): RunnableWithFallbacks<RunInput, RunOutput>
使用方式:
const resilientModel = primaryModel.withFallbacks([backupModel1, backupModel2]);
// 调用时:
// 1. 先尝试 primaryModel
// 2. 如果失败,尝试 backupModel1
// 3. 如果还失败,尝试 backupModel2
// 4. 全部失败才抛异常
实现原理(base.ts:192):
- 返回
RunnableWithFallbacks包装器 - 依次尝试 primary + fallbacks,第一个成功就返回
- 常见场景:主力模型超时/限流时降级到备选模型
4.4.3 withConfig — 配置绑定
withConfig(
config: Partial<CallOptions>
): Runnable<RunInput, RunOutput, CallOptions>
使用方式:
const verboseModel = model.withConfig({
tags: ["production"],
metadata: { user: "test" },
maxConcurrency: 5,
});
实现原理(base.ts:175):
- 返回
RunnableBinding包装器 RunnableBinding在每次调用时将绑定的 config 与运行时 config 合并- 不改变原始 Runnable
4.5 pipe — 管道组合
pipe<NewRunOutput>(
coerceable: RunnableLike<RunOutput, NewRunOutput>
): Runnable<RunInput, Exclude<NewRunOutput, Error>>
这是 LangChain.js 最重要的方法之一。
const chain = prompt.pipe(model).pipe(parser);
// 等价于
const chain = RunnableSequence.from([prompt, model, parser]);
pipe() 接受一个 RunnableLike,这意味着它可以接受:
RunnableInterface— 标准 Runnable 对象RunnableFunc— 普通函数(自动包装为RunnableLambda)RunnableMapLike— 对象字面量(自动包装为RunnableParallel)
// 三种都可以
chain.pipe(parser); // Runnable 对象
chain.pipe((output) => output.toUpperCase()); // 普通函数
chain.pipe({ a: runnableA, b: runnableB }); // 对象字面量
4.6 RunnableConfig 详解
每次调用 Runnable 时,都可以传入运行时配置:
export interface RunnableConfig<ConfigurableFieldType = Record<string, any>>
extends BaseCallbackConfig {
configurable?: ConfigurableFieldType; // 可配置字段(自定义 key-value)
recursionLimit?: number; // 递归深度限制(默认 25)
maxConcurrency?: number; // 最大并发数
timeout?: number; // 超时时间(毫秒)
signal?: AbortSignal; // 中止信号
}
// 继承自 BaseCallbackConfig
interface BaseCallbackConfig {
callbacks?: CallbackManager | Callbacks; // 回调处理器
tags?: string[]; // 标签(用于过滤和追踪)
metadata?: Record<string, unknown>; // 元数据(附加到追踪记录)
runName?: string; // 运行名称(用于追踪)
runId?: string; // 运行 ID
}
config 合并策略(config.ts:49 mergeConfigs()):
metadata:浅合并(...spread)tags:去重合并(Set)configurable:浅合并timeout:取最小值(更严格的约束)signal:用AbortSignal.any()组合(任一信号触发就中止)callbacks:链式传递
4.7 Serializable 基类
Runnable 继承自 Serializable,获得序列化能力:
abstract class Serializable {
lc_serializable = false; // 默认不可序列化,子类按需开启
lc_namespace: string[]; // 命名空间(如 ["langchain_core", "prompts"])
toJSON(): SerializedNotImplemented | SerializedConstructor {
// 序列化为 JSON
}
toJSONNotImplemented(): SerializedNotImplemented {
// 不支持序列化时的回退
}
}
这让 Runnable 可以被保存、传输和恢复(第 34 课详讲)。
4.8 实战练习:自定义 Runnable
实现一个简单的 UpperCaseRunnable:
import { Runnable, RunnableConfig } from "@langchain/core/runnables";
class UpperCaseRunnable extends Runnable<string, string> {
lc_namespace = ["custom"];
async invoke(input: string, _options?: Partial<RunnableConfig>): Promise<string> {
return input.toUpperCase();
}
// 可选:重写 _streamIterator 实现真正的流式
async *_streamIterator(input: string): AsyncGenerator<string> {
for (const char of input) {
yield char.toUpperCase();
await new Promise((resolve) => setTimeout(resolve, 50)); // 模拟延迟
}
}
}
// 使用
const upper = new UpperCaseRunnable();
// invoke
const result = await upper.invoke("hello world");
console.log(result); // "HELLO WORLD"
// stream
for await (const chunk of await upper.stream("hello")) {
process.stdout.write(chunk); // H E L L O(逐字符)
}
// batch
const results = await upper.batch(["hello", "world"]);
console.log(results); // ["HELLO", "WORLD"]
// pipe 组合
const chain = upper.pipe((s) => s.split("").reverse().join(""));
const reversed = await chain.invoke("hello");
console.log(reversed); // "OLLEH"
// withRetry
const reliable = upper.withRetry({ stopAfterAttempt: 3 });
// withFallbacks
const fallback = upper.withFallbacks([new UpperCaseRunnable()]);
观察:
- 只实现了
invoke(),但batch()、stream()、pipe()等全部可用 - 重写
_streamIterator()就获得了真正的流式能力 withRetry()、withFallbacks()无需额外代码
4.9 源码精读路线
| 优先级 | 文件 | 关注点 |
|---|---|---|
| P0 | runnables/types.ts | RunnableInterface 接口定义、RunnableConfig 类型 |
| P0 | runnables/base.ts:120-320 | Runnable 抽象类、invoke/batch/stream 实现 |
| P1 | runnables/config.ts | mergeConfigs()、ensureConfig()、config 合并策略 |
| P2 | runnables/base.ts:1302 | RunnableBinding(withConfig 的实现) |
| P2 | runnables/base.ts:1727 | RunnableRetry(withRetry 的实现) |
| P2 | runnables/base.ts:2922 | RunnableWithFallbacks(withFallbacks 的实现) |
本课收获总结
| 级别 | 你应该掌握的 |
|---|---|
| 🟢 基础 | 理解 invoke/stream/batch 三大核心方法的语义;能自定义一个简单 Runnable |
| 🔵 中阶 | 掌握 RunnableConfig 的字段和作用;理解 withRetry/withFallbacks/withConfig |
| 🟡 高阶 | 理解 _streamIterator() 的默认实现与重写机制;理解模板方法模式 |
| 🟠 资深 | 分析 Runnable 继承 Serializable 的设计决策;理解 config 合并策略的细节 |
| 🔴 架构 | 能评估"统一接口 + 默认实现"模式的扩展性边界 |
下一课预告
第 5 课深入 RunnableSequence — 管道组合的核心。理解 pipe() 背后的 first → middle → last 结构和流式传播机制。