LangChain:从设计模式层面来理解链(Chain)的设计

LangChain 中的“链”(Chain)并非单一对应某一种设计模式,而是多种设计模式的巧妙融合。

LangChain 的 Chain 本质上责任链的现代化实现 —— 通过 Runnable 协议统一接口,用组合模式支持嵌套,借建造者模式简化构造,最终实现了声明式、可组合、延迟执行的 LLM 应用流水线。

LangChain:Chain

在 LangChain 中,链(Chain)的核心思想是将多个处理单元(如 LLM 调用、提示模板、解析器等)连接成一个可执行的工作流,并支持线性顺序条件路由等控制流。如果用设计模式来类比,最贴切的是 责任链模式(Chain of Responsibility Pattern),同时在某些场景下也体现了 管道模式(Pipeline Pattern)模板方法模式(Template Method Pattern)

在LangChain 中,链(Chain) 本质上责任链的现代化实现 —— 通过 Runnable 协议统一接口,用组合模式支持嵌套,借建造者模式简化构造,最终实现了声明式、可组合、延迟执行的 LLM 应用流水线。

这种设计让开发者可以用 a | b | c 这样直观的语法,构建出堪比 DAG (有向无环图) 的复杂工作流,而底层依然保持清晰的职责分离。

匹配的设计模式

责任链模式

责任链模式(Chain of Responsibility):使多个对象都有机会处理请求,将它们连成一条链,并沿着链传递请求,直到有一个对象处理它为止。

  • LangChain 中的体现
    • 基础链/顺序链:每个组件(如 Runnable)依次执行,并将输出传递给下一个。这类似于责任链中每个处理器处理请求后必须传递给下一个(除非是终结点)。

      当创建一个 RunnableSequence 时,数据会像流水线一样,依次通过链上的每一个组件。前一个组件的输出自动成为后一个组件的输入,每个组件都负责处理流程中的一个特定环节。这种线性的、有序的数据传递和处理过程,正是责任链模式的精髓。

    • 路由链:根据输入条件动态选择下一个链,这是责任链的典型特征——每个节点判断自己是否能处理请求,不能则转发。LangChain 的 RunnableBranch 本质上就是一个条件责任链。

    • 链的可组合性:你可以将整个链视为一个节点,嵌入到更大的链中,形成嵌套的责任链。

  • 为什么最合适
    • 解耦:链中的每个组件只关心自己的输入/输出,无需知道整个工作流的全貌。
    • 灵活性:可以在运行时动态改变链的结构(如添加、删除中间步骤)。
    • 单一职责:每个链节点只完成一个明确的任务,符合责任链中“处理器仅处理自己关心的部分”的原则。
  • 代码示例映射

    1
    2
    3
    4
    5
    6
    7
    8
    # 责任链风格:每个函数判断是否处理
    def chain_step_1(x):
    # 1. 自己处理
    return self.process(x)
    # 2. 决定是否传递给下一个
    if self.next_chain:
    return self.next_chain(output)
    return default

    LangChain 的 RunnableSequenceRunnableBranch 正是这种思想的封装。

    责任链特性 LangChain 实现
    解耦请求发送者和接收者 Runnable 接口统一输入输出
    多个对象有机会处理请求 LLMChainTransformChainRouterChain
    链式传递 `chain1

管道模式

管道模式(Pipeline Pattern):也称管道与过滤器模式,将数据处理看作一系列“过滤器”,数据在管道中流动,每个过滤器对数据进行转换。

  • LangChain 中的体现

    • LCEL(LangChain Expression Language)使用管道符 | 连接组件,如 prompt | model | parser,这几乎是管道模式的教科书式实现
    • 数据单向流动,每个阶段是独立的过滤器。
  • 区别:管道模式通常所有阶段都会执行,没有条件跳过或路由。而 LangChain 的路由链允许分支,超出了经典管道模式的范围。因此管道模式更适合描述顺序链,但不足以覆盖路由链

1
2
# 类似管道-过滤器的实现逻辑:chain = prompt | model | parser
input → 过滤器1 → 过滤器2 → ... → 过滤器N → output

组合模式

组合模式 (Composite Pattern):通过树形结构组织对象,使客户端能够以统一方式处理单个对象和对象组合,适用于整体与部分具有层次关系的场景。

  • LangChain 中的体现

    • 链允许将简单的组件(如提示词模板、大语言模型、输出解析器)组合成更复杂的、功能统一的对象。

      可以像搭积木一样,通过管道符 |.pipe() 方法将多个 Runnable 对象串联起来,形成一个 RunnableSequence。这个序列本身也是一个 Runnable 对象,可以被继续组合或调用。这完美体现了组合模式“部分-整体”的层次结构思想,让客户端可以统一地对待单个对象和组合对象。

    • 链可以嵌套链,统一了接口调用。Runnable 接口让原子组件复杂链条对外暴露相同的调用方式。

    • SimpleSequentialChain 和 SequentialChain 形成树形结构,

1
2
3
4
5
6
7
8
9
10
11
# 组合模式:统一对待单个组件和组合组件
from langchain.schema.runnable import RunnableParallel, RunnableSequence

# 单个组件 (Leaf)
llm = ChatOpenAI()

# 组合组件 (Composite) - 视为一个整体
chain = RunnableParallel({
"branch_a": prompt_a | llm,
"branch_b": prompt_b | llm # 内部又是链
}) | final_prompt | llm

模板方法模式

模板方法模式(Template Method Pattern):父类定义算法的骨架(模板方法),子类实现某些具体步骤。

  • LangChain 中的体现

    • 基类 Chain(老版本)或 Runnable 定义了 invokebatchstream 等公共方法,子类需要实现 _call_invoke 等钩子方法。BaseChain._call() 定义骨架,子类定制具体步骤,但保持执行流程的一致性。
    • 例如,LLMChain固定了调用流程:格式化提示词→调用LLM→解析输出。自定义链时只需要实现 _call 逻辑,框架会处理错误重试、回调、异步等通用行为。
  • 为什么不是最贴切:模板方法模式侧重于继承算法步骤的固定顺序,而 LangChain 的链更强调组合(将一个链作为另一个链的组件),且路由链的步骤选择是动态的,并非编译时确定。

装饰模式

装饰模式(Decorator Pattern),又称装饰者模式,属于结构型设计模式。该模式通过组合而非继承的方式动态扩展对象功能,在不修改原类结构的前提下,由装饰类包裹真实对象并叠加附加职责,其核心设计遵循开闭原则。装饰对象与原始对象共享接口,通过持有引用在请求转发前后实现功能增强,常用于需运行时灵活扩展功能的场景。

  • LangChain 中的体现

    • LangChain 提供了一系列便捷的方法来“包装”一个现有的链,为其增加新的行为。例如:

      • with_retry(): 为主链增加失败重试的能力。

      • with_fallbacks(): 为主链添加降级方案,当主链执行失败时尝试备用链。

      • with_types(): 为链附加类型信息,用于类型检查。

结论

综上所述,LangChain 的“”是一个复合设计模式的典范:

  • 它使用责任链模式组合模式来构建灵活、线性的工作流。
  • 它使用模板方法模式来规范具体链的实现框架。
  • 它使用装饰器模式来优雅地扩展链的功能。
设计模式 在 LangChain 中的体现 适用场景
责任链模式 完美匹配路由链的条件转发,也能解释顺序链的线性传递。 动态选择执行路径
管道模式 完美匹配LCEL的管道语法,但无法涵盖条件分支。 大多数顺序处理链
组合模式 完美匹配了链嵌套链,链可以灵活组合实现完整的功能 SequentialChain,
复杂工作流
模板方法模式 匹配 LangChain 的基类骨架,但不是链组合的核心思想。 标准化链的执行流程
装饰模式 匹配对现有的链进行包含,增加新的行为 失败重试、回退

设计模式的表现差异

设计模式在不同软件开发语言的表现差异。

本人直观的感觉是设计模式在Java语言的程序设计中表现很明显,但在 Python,Golang开发语言就表现不明显。

语言的特性差异

语言 特性 设计哲学
Java 静态强类型语言,语法严谨,但表达方式单一
需要显式声明类型和接口,结构化的代码是显式的,
导致必须通过设计模式来解耦类型依赖
企业级、
规范优先,
严谨结构
Python 动态类型,关注对象行为而非类型,
类型运行时决定,减少抽象层(接口,实现)
函数是一等公民,函数作为参数传递,替代策略模式
语言特性内化了许多设计模式
实用主义
简洁至上
Golang 简单设计,没有类和继承,
只有组合,用组合替代复杂模式
隐匿接口,不需要显示声明实现
追求 简单、可读、务实,避免抽象过度
极简主义、
组合优先

对OOP依赖程度

  • Java推崇纯OOP:一切皆对象,设计模式大多基于“继承”和“多态”构建。必须显式地画出类图,写出层次结构,这放大了模式的存在感。
  • Python是“多范式”语言:可以用OOP,也可以用函数式、过程式。很多模式可以用更轻量的闭包或高阶函数代替。
  • Go是“实用主义”:它没有类和继承,只有struct和接口。它选择用goroutinechannel解决并发问题,而不是用Observer模式或Future模式。在并发领域,Go的模式表现非常“明显”,只是换了个赛道。

观点引用

  • Java 的语法约束迫使开发者显式使用设计模式,而 Python 和 Go 的语言特性让这些模式”隐形”或”简化”了。

  • 在静态类型、缺乏高阶抽象的语言(如 Java 早期)中,开发者需手动实现如工厂、策略、观察者等模式来弥补语言表达力的不足。

  • 在支持‌高阶函数、鸭子类型、多重分发、宏系统‌等特性的语言(如 Lisp、Python、Julia、Go)中,这些模式要么被语言原生支持,要么可通过更简单的方式实现,从而“消失”或“简化”。

  • 在某些编程语言中,设计模式是对语言局限性的权宜之计。而在更强大的语言(如 Lisp 或 Python)中,很多模式要么变得简单,要么根本不需要。 —— Peter Norvig《动态语言设计模式》

  • 因此,‌设计模式并非绝对必要,而是特定语言生态下对缺陷的适应性回应‌。

  • “设计模式是语言缺陷的补丁” —— Peter Norvig(Google 研发总监、知名人工智能专家)

  • ”设计模式是经验的结晶,不是代码的枷锁“——GoF成员Erich Gamma

  • Java 需要显式模式来弥补语言的静态约束,Python/Go 通过语言演进吸收了模式的本质,抛弃了模式的仪式

  • 并不是在Python/Go中看不到设计模式,是因为它们有更直接、更轻量的语言特性来实现同一目的。设计模式被语言特性重新表达了,模式本身就溶解在了语言特性中。以至于不再称之为”模式”,而称之为”写法”。

  • 这反映了编程语言的演进趋势:从”模式化编程”到”语言特性化”,从”显式结构”到”隐式简化”。

  • 许多模式本质上是弥补语言特性不足的解决方案,当语言本身提供更优雅的替代方案时,模式就变得不必要。

  • 设计模式的思想是永恒的(如“组合优于继承”、“针对接口编程”),但具体实现形式是依赖于语言的

LangChain:从设计模式层面来理解链(Chain)的设计

http://blog.gxitsky.com/2026/04/12/AI-LangChain-037-Chain-Design-Mode/

作者

光星

发布于

2026-04-12

更新于

2026-04-13

许可协议

评论