1. 一个组件要具备哪些行为?
最简模型是:输入 Input → 处理 → 输出 Output。如果每种组件都各自定义同步、异步、批量、流式的方法名,组合起来会很困难。Runnable 对这些执行方式提供统一词汇:
| 操作 | 用途 |
|---|---|
invoke(x) |
处理一份输入 |
ainvoke(x) |
在 async 程序中处理一份输入 |
batch(xs) |
对多份输入进行批量/并发处理 |
stream(x) |
随结果产生逐步返回输出 |
并非所有 Runnable 都原生支持高性能 async、批处理和流式。Base 类有通用实现,具体实现可按后端特性覆盖优化。
2. 从 Python 函数变成 Runnable
python
from langchain_core.runnables import RunnableLambda
def word_count(text: str) -> int:
return len(text.split())
counter = RunnableLambda(word_count)
print(counter.invoke("LangChain components can compose")) # 4
print(counter.batch(["one two", "three", "four five six"])) # [2, 1, 3]
print(list(counter.stream("one two three"))) # [3]
RunnableLambda 适合把普通函数放进链路。此处的 stream 默认是一次性产出完整结果,并不意味着函数本身可以逐 token 生成。
3. 通用包装:给现有组件添加策略
python
from langchain_core.runnables import RunnableLambda
runnable = RunnableLambda(lambda value: value * 2)
configured = runnable.with_config(tags=["lesson-04"], metadata={"feature": "demo"})
print(configured.invoke(21)) # 42
schema = runnable.get_input_jsonschema()
print(schema)
config 里的 tags 和 metadata 可供执行上下文与观测系统使用。Runnable 还支持 .with_retry(...)、.with_fallbacks(...)、.with_listeners(...) 等统一包装,而不必让每个原始函数重新实现。
4. 异步调用示例
python
import asyncio
from langchain_core.runnables import RunnableLambda
runnable = RunnableLambda(lambda value: value + 1)
async def main() -> None:
result = await runnable.ainvoke(41)
print(result) # 42
asyncio.run(main())
这个示例可在 Python 脚本中运行,无需网络。默认 ainvoke 可将同步 invoke 放进 executor;provider-native async 实现可以覆盖它以避免阻塞线程池。
5. 源码设计解析:统一接口背后的默认实现
Runnable 是抽象基类,不是一种会自动完成业务逻辑的对象。它定义单输入到单输出的核心操作 invoke;组件继承这一接口,就能进入 LangChain 的组合与执行体系。比如 RunnableLambda(word_count) 包装器负责接收输入、调用 Python 函数并把结果作为输出返回。
python
@abstractmethod
def invoke(self, input, config=None, **kwargs):
"""Transform a single input into an output."""
async def ainvoke(self, input, config=None, **kwargs):
return await run_in_executor(config, self.invoke, input, config, **kwargs)
这段基类逻辑说明同步和异步之间的关系:invoke 是最小必需的同步契约;如果子类没有高效的原生异步实现,基类会把同步调用交给 executor 执行,避免直接阻塞当前 async 事件循环。它提供的是通用兼容层,并不意味着原函数变成了真正的异步 I/O。需要原生异步的 provider 可以覆写 ainvoke 或其底层生成逻辑。
同理,基础 batch 会在默认线程池中并发执行多次 invoke,并非自动调用某个供应商的批处理 API。支持真正批量请求的子类可以覆写 batch,把多条输入合并为后端的一次请求,减少网络开销。统一方法带来可替换性,具体性能仍由实现决定。
通用配置、重试和 fallback 则采用"返回新 Runnable"的装饰思路,而非修改原组件。例如 .with_config(tags=[...]) 会创建 RunnableBinding,将配置与原 Runnable 绑定;.with_retry(...) 会创建 RunnableRetry,负责失败后的重试策略。于是原本的函数只负责计算,重试、追踪元数据等横切策略由可组合的包装器管理。
设计收益与边界 :只实现一个清楚的核心行为,组件就能获得一致入口;通用默认实现减少接入工作;高性能或供应商原生语义则保留给具体子类。另一方面,batch、stream 和 ainvoke 的存在,不等于每个实现都以原生并行、细粒度 token 流或非阻塞 I/O 的方式运行。
源码位置(以下路径相对 LangChain 仓库根目录):
libs/core/langchain_core/runnables/base.py:Runnable定义统一执行契约及ainvoke、batch等默认行为;RunnableLambda将 Python callable 包装为 Runnable;RunnableSequence负责顺序执行各子步骤。
6. 边界与错误认识
.batch不等价于供应商批量 API。默认行为可能是并发多次 invoke。.stream有统一入口,不保证组件每个都能产生细粒度 chunks。- 配置可沿组合流程传递,但不应把敏感凭证写进 tags 或 metadata。
- Runnable 的类型提示和 JSON Schema 帮助表达契约,但动态 Python 类型检查并不会因此自动覆盖所有运行时错误。
小结
Runnable 是 LangChain 框架"可以组合"的基础契约。它把单次执行、批处理、异步、流式和内省放到相似接口下;子类可以继承默认行为,也能按后端能力优化。
本系列导航
- 上一篇:03 BaseChatModel 与 Provider 适配器------模型如何接入 LangChain
- 下一篇:待更新
- 全系列:LangChain 源码入门