Runnable:LangChain 的"万能接口"
在 LangChain 的世界里,Runnable 是一个统一的、标准的执行接口 。所有能够"被调用、被组合、被传输"的组件,无论是模型(ChatTongyi)、提示模板(ChatPromptTemplate)、输出解析器(StrOutputParser)、链(Chain),甚至是整个 LangGraph 图,都实现了 Runnable 接口。
一句话定义 :
Runnable是 LangChain 表达语言(LCEL)的基石,它定义了一个"如何执行一个任务"的通用契约。任何实现了Runnable的对象,都可以用统一的 API 去调用它,并可以用管道符|去和另一个Runnable串联。
为什么需要 Runnable?(解决了什么问题?)
在 Runnable 出现之前,LangChain 中存在 LLMChain、ConversationChain、RetrievalQA 等多种"硬编码"的 Chain 类,它们各自的调用方式各不相同,无法统一组合。
Runnable 提供了一套标准化的接口,使得:
- 所有组件可以无缝组合(LCEL)。
- 所有组件支持统一的调用方式(
invoke、stream、batch)。 - 所有组件天然可观测(可与 LangSmith 集成)。
- 所有组件可被包装和增强(如
RunnableWithMessageHistory)。
Runnable 的核心方法
任何实现了 Runnable 的对象,都至少实现了以下三个核心方法:
| 方法 | 作用 | 适用场景 |
|---|---|---|
invoke(input, config=None) |
同步执行,返回完整结果。 | 常规单次调用。 |
ainvoke(input, config=None) |
异步执行,返回完整结果。 | 异步环境(如 FastAPI)。 |
stream(input, config=None) |
同步流式执行,逐个产出结果。 | 需要逐步输出长文本时。 |
astream(input, config=None) |
异步流式执行,逐个产出结果。 | 异步流式输出。 |
batch(inputs, config=None) |
批量执行,返回结果列表。 | 并发处理多个输入。 |
abatch(inputs, config=None) |
异步批量执行。 | 高并发场景。 |
所有
*async*方法是*sync*方法的异步版本,前缀a代表async。
哪些东西是 Runnable?(几乎一切!)
| 组件类型 | 示例 |
|---|---|
| 模型 | ChatTongyi、ChatOpenAI、Tongyi(LLM) |
| 提示模板 | PromptTemplate、ChatPromptTemplate、MessagesPlaceholder |
| 输出解析器 | StrOutputParser、PydanticOutputParser、JsonOutputParser |
| 检索器 | Chroma.as_retriever() 返回的 Retriever 对象 |
| 向量库 | Chroma 本身(直接调用) |
| 工具 | @tool 装饰的函数(如 get_weather) |
| 链 | 所有用 ` |
| Agent | create_react_agent 返回的图(编译后) |
| Graph | StateGraph.compile() 返回的图 |
| 自定义函数 | 用 RunnableLambda 包装的任意函数 |
如何创建一个 Runnable?
1. 最直接:用 | 管道符组合 LCEL 链
python
chain = prompt | model | str_parser
chain 自动成为一个 RunnableSequence,支持所有 Runnable 方法。
2. 用 RunnableLambda 包装普通函数
python
from langchain_core.runnables import RunnableLambda
def my_func(x):
return x.upper()
runnable_func = RunnableLambda(my_func)
# 然后可以放进链里
chain = prompt | model | runnable_func | str_parser
3. 用 RunnableParallel 并行执行
python
from langchain_core.runnables import RunnableParallel
# 同时执行两个任务,返回字典结果
parallel_chain = RunnableParallel(
answer=chain1,
context=chain2
)
Runnable 的核心原则
1. 输入类型决定输出类型
Runnable 是类型安全的。例如,ChatPromptTemplate 接受 dict(包含变量值),输出 PromptValue;ChatModel 接受 PromptValue,输出 AIMessage。如果类型不匹配,| 管道会报错。
2. 配置(config)是可传递的
在调用 invoke(input, config=...) 时,config 可以携带 metadata、tags、callbacks、max_concurrency 等参数,这些会贯穿整个链的执行过程,特别适合可观测性(LangSmith)和限流。
3. 所有 Runnable 都可绑定回调
python
from langchain_core.callbacks import StdOutCallbackHandler
chain.invoke(input, config={"callbacks": [StdOutCallbackHandler()]})
这样会打印链中每一步的执行日志,极其有助于调试。
Runnable 与 LangGraph 的关系
LangGraph 中编译后的 graph 本身就是一个 Runnable,因此你可以:
- 像调用普通链一样
graph.invoke(input)或graph.stream(input)。 - 把
graph作为子节点嵌入到更大的 LCEL 链中(prompt | graph | str_parser)。 - 给
graph挂载回调、配置thread_id等。 - 利用
astream_events获取更细粒度的事件(这是 LangGraph 对Runnable接口的扩展)。
这也是为什么 LangGraph 与 LangChain 无缝集成:它本质上是在 LangChain 的 Runnable 体系之上增加了"状态图"的编排能力。
Runnable 在面试中的高频追问
Q1:RunnableSequence 和普通的 Chain 有什么区别?
A :RunnableSequence 是 LCEL 方式的链,它是 Runnable 接口的统一实现;而传统 Chain(如 LLMChain)是硬编码类,无法用 | 灵活组合。LangChain 官方推荐弃用旧 Chain,全面转向 LCEL。
Q2:invoke 和 stream 的区别是什么?
A :invoke 是一次性返回完整结果;stream 是逐步产出结果(生成器),适合长文本流式输出。两者都是 Runnable 的标准方法,所有组件都可选支持。
Q3:batch 和 abatch 的优势是什么?
A :可以批量处理多个输入,且自动并发执行,提升吞吐量。比如同时处理 10 条用户消息,batch 会比循环调用快数倍。
Q4:自定义 Runnable 应该怎么做?
A :通常不需要显式实现 Runnable,只需用 RunnableLambda 包装函数;若需要更复杂的逻辑(如状态管理),可继承 Runnable 并实现 invoke、stream 等方法。
总结
Runnable 是 LangChain 生态的"基石接口",它统一了所有组件的执行方式,定义了 invoke、stream、batch 三大核心方法,并通过 LCEL(|)实现了组件的无限组合。无论是提示、模型、解析器、工具、检索器,还是 LangGraph 图,最终都归一为同一个 Runnable 接口,这是 LangChain 能够保持高度灵活性和扩展性的根本原因。理解 Runnable,就理解了 LangChain 的"哲学"。