Search

为什么 Runnable 会逐渐取代传统的 Chain

传统 Chain 基于继承封装流程,Runnable + LCEL 面向接口协议组合计算单元。本文讲清传统 Chain 的先天缺陷、Runnable 的核心改进与选型取舍。

Pluszzz3 min read4
为什么 Runnable 会逐渐取代传统的 Chain

如果你接触过早期 LangChain,一定写过 LLMChain、RetrievalQA。在新版本中,传统 Chain 被标记为废弃,Runnable + LCEL(LangChain Expression Language)成为整个框架底层标准。

很多人第一感觉只是语法变化:把 LLMChain(prompt, llm) 换成 prompt | llm | parser。但这远不止语法糖,是一次底层架构范式的重构。本文由浅入深讲清楚:传统 Chain 有什么先天缺陷、Runnable 解决了什么核心问题,以及二者的取舍。

澄清概念:

  • 传统 Chain(Legacy Chain):LangChain 早期面向继承的类体系:LLMChain、SequentialChain、RetrievalQA;需要继承基类,重写 _call() 方法实现自定义链路。
  • Runnable:一套接口协议,所有组件(Prompt、LLM、Parser、检索器、自定义函数、甚至 LangGraph 图)实现同一套契约;通过管道符 | 组合得到执行管道,等价于新版的“链”。
  • LCEL:是组合 Runnable 的表达式语法,|、RunnableParallel、RunnableBranch 都属于 LCEL 能力。

一、传统 Chain 的先天痛点

早期 Chain 设计思想:用面向类继承的方式封装工作流。当业务走向生产环境、复杂 Agent/RAG 场景,缺陷被持续放大。

1. 接口不统一,各组件调用方式割裂

旧版中每个组件拥有自己的调用入口:

  • Prompt:.format()
  • LLM:.generate()
  • Parser:.parse()
  • Chain:.run() / .__call__()

想要串联一条流程,开发者必须手写大量适配胶水代码。自定义 Chain 需要继承 BaseChain 并重写私有方法,门槛高。

2. 流式、异步、批处理支持残缺

  • 传统 Chain 流式能力依赖全局 Callbacks 回调,侵入业务代码
  • 原生没有 ainvoke 异步、batch 批处理接口
  • 想要拿到中间流式输出,需要写大量回调钩子,生产环境极易出错

3. 组合能力弱,很难实现并行、分支

  • SequentialChain 只支持简单串行
  • 想要并行执行多条子任务、条件分支路由,自定义成本极高
  • 很难做到“链里面嵌入子链,子链再嵌套子链”的灵活组合

4. 输入输出强绑定,扩展性差

每一个 Chain 预先写死输入 key、输出 key;如果链路中间需要透传原始参数、追加中间变量,非常别扭。

5. 可观测性差,调试困难

回调系统是全局的,很难给单条链路附加独立 tags、metadata;LangSmith 追踪链路经常断裂,子步骤追踪不完整,排障困难。

6. 和后续生态割裂

后期官方推出 LangGraph,LangGraph 图本身是 Runnable;旧 Chain 无法直接作为 LangGraph 的节点,两套体系互不互通,生态分裂。

举一个直观对比代码

旧版 LLMChain:

from langchain.chains import LLMChain
chain = LLMChain(prompt=prompt, llm=llm)
res = chain.run(topic="Runnable")

新版 Runnable LCEL:

chain = prompt | llm | parser
res = chain.invoke({"topic": "Runnable"})

看起来只是写法不同,但底层执行模型完全不一样。

二、Runnable 协议带来的核心改进

Runnable 的核心思想:定义统一契约,万物皆可 Runnable

只要实现 Runnable 接口,对象自动拥有这 5 套标准方法:

  • invoke():同步单次调用
  • ainvoke():异步单次调用
  • stream():同步流式输出
  • astream():异步流式输出
  • batch() / abatch():批量并发处理

1. 统一接口,消除组件之间的胶水代码

Prompt、大模型、解析器、检索器、普通 Python 函数(包装为 RunnableLambda)全部遵循同一套协议。用管道符 | 直接拼接,不用关心每个组件内部怎么调用。自定义逻辑不再需要继承类,普通函数套一层 RunnableLambda 即可接入链路。

2. 开箱即用流式、异步、批处理

整条链路任意节点,都可以直接调用 .stream() 拿到逐段输出;异步接口原生支持高并发后端服务;batch 内置并发控制,不用自己维护线程池。不需要写任何回调代码,能力是协议自带的。

3. 极强的可组合性:串行、并行、条件分支

  • | 管道:顺序执行(RunnableSequence)
  • RunnableParallel:同时并行执行多条分支
  • RunnableBranch:条件路由,根据输入选择不同子链路

Runnable 可以无限嵌套:一条 Runnable 可以作为另一条 Runnable 内部的子单元。

RAG 典型示例:

rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)

4. 配置透传:RunnableConfig 统一上下文

通过 config 参数统一传递回调、标签、元数据、超时、重试、会话 ID。每一段子 Runnable 都可以拿到配置,方便日志、监控、LangSmith 追踪,链路完整不中断。

5. 和 LangGraph 生态打通

LangGraph 编译后的 Graph 本身就是 Runnable 实例。你可以把 LCEL 写好的 Runnable 直接作为图中的节点;也可以把 Graph 嵌入 LCEL 管道。实现简单链路用 LCEL,复杂状态机、多轮 Agent 交给 LangGraph,两套体系无缝互通。

6. 更好的可测试性

每一个 Runnable 单元都可以独立执行 invoke() 做单元测试,不用 mock 整条链路。调试时可以在链路中间插入 RunnableLambda 打印中间变量。

三、新旧体系对比表

维度 Legacy Chain(传统链) Runnable + LCEL
编程模型 继承 BaseChain,重写 _call 接口协议,管道符组合;普通函数包装即可接入
调用入口 run() / call() 统一 invoke/ainvoke/stream/astream/batch
流式支持 依赖全局 callback,侵入高 原生支持整条链路流式输出
异步能力 有限支持,需要手动处理 原生完整异步
并行 / 分支 实现复杂,需要大量自定义 原生提供 RunnableParallel / RunnableBranch
可观测性 全局回调,链路容易断裂 RunnableConfig 携带 tags、metadata,LangSmith 完整追踪
生态兼容 无法嵌入 LangGraph 可直接作为 LangGraph 节点,双向嵌入
自定义成本 高,需要继承重写类 低,RunnableLambda 包装普通 Python 函数

四、常见误区澄清

误区 1:Runnable 只是语法糖,底层还是 Chain

不完全对。 旧 Chain 是业务类;Runnable 是一套通用接口契约。LCEL 组合出来的 RunnableSequence 可以理解为新版 Chain,但它不是通过继承实现,而是组合模式,设计理念完全不同。官方不再鼓励手写继承 BaseChain。

误区 2:用 Runnable 之后,就不需要 LangGraph

恰恰相反。

  • LCEL(Runnable):适合无状态、DAG 式管道,适合 RAG、简单抽取、摘要;不适合循环、状态回写、人工介入 HITL
  • LangGraph:适合带状态、循环、多步 Agent、人工审核;编译后的 Graph 本身就是 Runnable,可以嵌入 LCEL 链路

二者互补,不是替代关系。简单管道写 LCEL;有状态循环交给 LangGraph。

误区 3:老项目必须全部立刻迁移

官方没有强制瞬间迁移,只是标记旧 API Deprecation。存量 Chain 还可以运行;新项目直接优先使用 Runnable LCEL。长期维护的项目建议逐步迁移,旧版 API 未来版本会移除。

五、什么时候选择什么方案

✅ 使用 Runnable (LCEL)

  • RAG、文档摘要、结构化抽取、简单多步无状态工作流
  • 需要流式输出、后端高并发异步服务
  • 需要单元测试各个子步骤
  • 后续有可能接入 LangGraph

❌ LCEL 不擅长

  • 需要循环执行、保存会话状态、人工介入 HITL
  • 复杂 Agent 思考循环

这类场景直接上 LangGraph。

六、写在最后

传统 Chain 的问题不是“不好用”,而是它诞生于大模型应用早期,只解决简单串联场景。当 LLM 应用走向生产,流式、并发、并行分支、可观测、和状态图编排生态互通成为硬性需求,基于继承的 Chain 架构就遇到天花板。

  • 传统 Chain:面向类继承,封装一套完整业务流程
  • Runnable:面向接口协议,把每一步计算变成可自由拼接的积木

Runnable 取代传统 Chain,本质是一次架构升级:从面向“流程封装”转向面向“可组合的计算单元”,为后续 LangGraph、复杂 Agent 生态打下统一底层基础。

PLUSZZZ*ZHANGJIA*