在 LangChain 框架中,Runnable 是一个最核心的"底层协议(Protocol)"或"标准接口"。
简单来说,LangChain 里几乎所有的核心组件(如提示词模板、大模型、输出解析器、检索器、自定义函数等),在底层都继承或实现了 Runnable 接口。它的核心思想是:"任何组件,本质上都是一个'接受输入,经过处理,产生输出'的独立任务单元。"
1. 为什么设计出 Runnable?(统一接口)
在早期的 LangChain 中,调用不同组件的命令非常混乱。调用 LLM 用 llm("..."),调用 Chain 用 chain.run(),调用检索器用 retriever.get_relevant_documents()。这导致组件之间很难优雅地拼接。
为了解决这个痛点,LangChain 提出了 Runnable 协议。它强制规定:所有组件必须统一实现以下 6 个最核心的调用方法:
| 同步方法 | 异步方法(Async) | 核心作用 |
|---|---|---|
invoke() |
ainvoke() |
传入单个 输入,等待并获取单个输出。 |
batch() |
abatch() |
批量传入一组 输入,内部自动利用线程池并发处理,返回一组输出。 |
stream() |
astream() |
流式传输,大模型吐出一个词,这里就立刻返回一个词,常用于打字机流式前端交互。 |
2. 谁实现了 Runnable 接口?
在现代 LangChain(LCEL)里,你会发现处处皆为 Runnable:
-
PromptTemplate:它是一个Runnable。输入是dict(变量),输出是PromptValue(格式化后的文本)。 -
ChatOpenAI / Ollama:它是一个Runnable。输入是PromptValue或消息列表,输出是AIMessage。 -
OutputParser:它是一个Runnable。输入是AIMessage,输出是解析后的结构化数据(如list或dict)。 -
甚至你用管道符
|拼出来的整个Chain:它也是一个全新的RunnableSequence(依然是Runnable的子类)。
因为大家都是 Runnable,拥有完全相同的接口方法,所以它们才能用 | 像乐高积木一样完美咬合。
3. Runnable 协议带来的天然红利
由于整个大模型链条里的每个节点都严格遵循 Runnable,LangChain 自动赋予了你的应用三大极其强大的底层能力:
① 自动获得流式传输(Streaming)和并发(Batching)
即便你自己写了一个复杂的 Chain,只要里面所有的子组件都是 Runnable,你的整个 Chain 就会自动无缝支持 chain.stream() 和 chain.batch(),不需要你手动去写复杂的异步多线程或生成器代码。
② 显式的数据校验(Input/Output Schema)
每个 Runnable 组件都内置了 input_schema 和 output_schema 属性。在运行时或调试时,你可以直接通过 Pydantic 结构查看这个组件期望接收什么格式的数据 以及会吐出什么数据,极大地降低了 Debug 成本。
③ 完美的异步支持
大模型应用包含大量的网络 I/O(等待大模型响应、等待数据库检索)。Runnable 原生自带的 ainvoke、abatch、astream 能够完美融入 Python 的 asyncio 生态,让你的高并发后端服务性能最大化。
一句话总结:
Runnable是 LangChain 世界的统一标准度量衡。有了它,框架才能实现"万物皆可连接";有了它,你拼装出来的 AI 工作流才能天然具备高并发和流式输出的能力。