LangChain设计模式之责任链模式
LangChain 里随手写出的
prompt | llm | parser,底层并不是三个对象简单相加,而是一条责任链。本文从设计模式定义、源码实现和实战场景三个层面,把它拆清楚。
一、先记住一句话
在 LangChain 中,责任链模式解决的核心问题是:
如何把多个可执行步骤串联起来,让请求按顺序正向传递。
它是最基础、最常用的执行骨架。你看到的 Prompt 构建、模型调用、输出解析、工具调用、格式化,最终都会被组织成一条链。
二、责任链模式是什么
责任链模式是行为型设计模式。经典定义是:
将多个处理者组成一条链,请求沿着链依次传递,直到某个处理者能够处理并返回结果。
它通常包含四个角色:
| 角色 | 含义 | LangChain 对应 |
|---|---|---|
| 抽象处理者 | 定义统一处理接口 | Runnable |
| 具体处理者 | 处理具体请求 | Prompt、LLM、Parser |
| 链 | 将处理者串联起来 | RunnableSequence |
| 请求 | 在链上传递的数据 | invoke(input, config) |
需要特别说明的是,LangChain 中的责任链不是“谁先处理完谁返回”的短链逻辑,而是更偏向固定管线:每个步骤都会执行,上一步的输出作为下一步的输入。它继承的是责任链“解耦步骤、串联执行”的思想,同时吸收管道模式对数据流的约束。
三、LangChain 里的实现:RunnableSequence
在 LangChain 中,| 运算符会把左右两个 Runnable 组合起来。多个组件连续使用 | 时,最终会被封装成 RunnableSequence。
prompt | llm | parser
↓ 底层等价于
RunnableSequence([prompt, llm, parser])
3.1 统一接口先行
责任链能成立,前提是链上的每个步骤都实现同一个接口。LangChain 用 Runnable 完成这件事:
from abc import ABC, abstractmethod
from typing import Generic, TypeVar, Optional, List
Input = TypeVar("Input")
Output = TypeVar("Output")
class Runnable(Generic[Input, Output], ABC):
input_type: type
output_type: type
@abstractmethod
def invoke(self, input: Input, config: Optional[dict] = None) -> Output:
raise NotImplementedError()
所有 Prompt、LLM、Parser、Chain、Tool,以及后续包装出来的中间件对象,都满足这个契约。
3.2 链的组装与执行
下面是贴合 LangChain 实际逻辑的简化版 RunnableSequence:
class RunnableSequence(Runnable[Input, Output]):
def __init__(self, steps: List[Runnable]):
self.steps = steps
self._validate_steps()
def _validate_steps(self) -> None:
for i in range(len(self.steps) - 1):
current_output = self.steps[i].output_type
next_input = self.steps[i + 1].input_type
if not issubclass(current_output, next_input):
raise ValueError(
f"步骤 {i} 的输出类型 {current_output} "
f"不匹配步骤 {i + 1} 的输入类型 {next_input}"
)
def invoke(self, input: Input, config: Optional[dict] = None) -> Output:
value = input
for i, step in enumerate(self.steps):
step_config = self._patch_config(config, step_idx=i)
value = step.invoke(value, step_config)
return value
def _patch_config(self, config: Optional[dict], step_idx: int) -> dict:
config = config or {}
return {
**config,
"step_idx": step_idx,
"step_tag": f"step{step_idx}",
"sequence_id": id(self),
}
3.3 这四点决定了它像责任链
- 线性串联:请求从第一个步骤进入,依次传到最后一个步骤。
- 步骤解耦:每个 Runnable 只关心自己的输入和输出,不感知前后步骤。
- 可扩展:通过
|可以随时增删步骤或调整顺序。 - 上下文传递:
config沿链传递,为日志、会话、状态回溯提供基础。
四、实战:从基础链到嵌套链
4.1 基础 Prompt-LLM-Parser 链
这是 LangChain 最常见的一条责任链:
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个专业的文本总结助手,总结内容需简洁明了,不超过50字。"),
("user", "{input}")
])
llm = ChatOpenAI(model="gpt-3.5-turbo")
parser = StrOutputParser()
chain = prompt | llm | parser
input_text = (
"LangChain 是一个用于构建大语言模型应用的框架,"
"它提供了丰富的组件和工具,支持快速开发复杂的 LLM 应用。"
)
result = chain.invoke({"input": input_text})
print(result)
执行顺序:
{"input": input_text}
↓
prompt
↓
llm
↓
parser
↓
result
4.2 嵌套责任链:责任链里的责任链
责任链的一个步骤本身也可以是一条责任链,于是形成树形结构:
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableSequence
summary_chain = prompt | llm | parser
translate_prompt = ChatPromptTemplate.from_messages([
("system", "将以下文本翻译成英文,保持原意,简洁准确。"),
("user", "{input}")
])
translate_chain = translate_prompt | llm | parser
# 先总结,再翻译总结结果
main_chain = RunnableSequence([summary_chain, translate_chain])
result = main_chain.invoke({"input": input_text})
print(result)
这种能力让复杂任务可以被拆成多级子链,而不是写一个巨大的处理函数。
五、它在四大模式中的位置
责任链模式不是孤立存在的。它在 LangChain 内核中负责“正向执行流的骨架”:
| 模式 | 作用 | 关系 |
|---|---|---|
| 命令模式 | 统一 Runnable 接口 | 责任链的前提 |
| 责任链模式 | 串联步骤、正向传递 | 核心骨架 |
| 装饰器模式 | 包装链或组件,增强功能 | 对责任链做非侵入扩展 |
| 管道模式 | 约束输入输出类型 | 让责任链的数据流更规范 |
一句话总结:命令模式定接口,责任链模式搭骨架,装饰器模式做增强,管道模式规数据流。
六、总结
LangChain 的责任链模式是怎么实现的:
- LangChain 通过
Runnable接口统一所有可执行组件。 |运算符把组件组合成RunnableSequence。RunnableSequence.invoke把输入依次传给每个步骤,前一步输出作为后一步输入。- 每个步骤只关注自身职责,通过
config传递上下文。 - 责任链既可以扁平串联,也可以嵌套成树形链,支撑复杂任务拆分。

1155

被折叠的 条评论
为什么被折叠?



