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完成这件事:
fromabcimportABC,abstractmethodfromtypingimportGeneric,TypeVar,Optional,List Input=TypeVar("Input")Output=TypeVar("Output")classRunnable(Generic[Input,Output],ABC):input_type:typeoutput_type:type@abstractmethoddefinvoke(self,input:Input,config:Optional[dict]=None)->Output:raiseNotImplementedError()所有 Prompt、LLM、Parser、Chain、Tool,以及后续包装出来的中间件对象,都满足这个契约。
3.2 链的组装与执行
下面是贴合 LangChain 实际逻辑的简化版RunnableSequence:
classRunnableSequence(Runnable[Input,Output]):def__init__(self,steps:List[Runnable]):self.steps=steps self._validate_steps()def_validate_steps(self)->None:foriinrange(len(self.steps)-1):current_output=self.steps[i].output_type next_input=self.steps[i+1].input_typeifnotissubclass(current_output,next_input):raiseValueError(f"步骤{i}的输出类型{current_output}"f"不匹配步骤{i+1}的输入类型{next_input}")definvoke(self,input:Input,config:Optional[dict]=None)->Output:value=inputfori,stepinenumerate(self.steps):step_config=self._patch_config(config,step_idx=i)value=step.invoke(value,step_config)returnvaluedef_patch_config(self,config:Optional[dict],step_idx:int)->dict:config=configor{}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 最常见的一条责任链:
fromlangchain_core.promptsimportChatPromptTemplatefromlangchain_openaiimportChatOpenAIfromlangchain_core.output_parsersimportStrOutputParser 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 ↓ result4.2 嵌套责任链:责任链里的责任链
责任链的一个步骤本身也可以是一条责任链,于是形成树形结构:
fromlangchain_core.promptsimportChatPromptTemplatefromlangchain_core.runnablesimportRunnableSequence 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传递上下文。 - 责任链既可以扁平串联,也可以嵌套成树形链,支撑复杂任务拆分。