LangChain_LCEL
LCEL(LangChain Expression Language)
一、RunnableSequence(LCEL的基础结构)
1.1 介绍
-
LCEL核心概念:-
内容:作为通用接口的
Runnable协议,用于顺序组合的RunnableSequence。 -
Runnable协议:在LCEL的世界里,万物皆Runnable。从最基础的提示词模板(PromptTemplate)和语言模型(LLM/ChatModel)到功能性的组件比如输出解析器(OutputParser)和文档检索器(Retriever)都实现了Runnable接口; -
RunnableSequence:RunnableSequence是LangChain中最重要的组合操作符,因为它几乎在每个链中都使用。
-
-
管道符
|与RunnableSequence的关系:-
RunnableSequence的作用非常纯粹(将多个Runnable组件按照顺序链接起来,形成一个线性的处理管道,在这个管道中前一个组件的输出会直接作为后一个组件的输入);可以通过如下方式组成链:#type1 RunnableSequence(first=..., middle=..., last=...) #type2 RunnableSequence(steps=[...]) #type3 一般选择用管道符 chain = prompt | model | output_parser -
|操作符和其背后的“强制转换”(Coercion)机制是LCEL优秀用户体验的核心。当|的右侧是一个非Runnable对象时,例如一个普通的Python函数或一个字典,LCEL会尝试将其自动转换为一个合适的Runnable类型;例如,函数会被包装成RunnableLambda,字典会被包装成RunnableParallel。
-
1.2 示例
# 1. 导入必要的库
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# 2. 初始化模型(请确保已设置 OPENAI_API_KEY 环境变量)
model = ChatOpenAI(model="gpt-3.5-turbo")
# 3. 创建提示模板
prompt = ChatPromptTemplate.from_template("讲一个关于 {topic} 的笑话。")
# 4. 创建输出解析器
output_parser = StrOutputParser()
# 5. 使用 | 操作符构建 RunnableSequence
chain = prompt | model | output_parser
# 6. 调用链(invoke 方法会同步执行整个链)
response = chain.invoke({"topic": "程序员"})
print(response)
二、RunnablePassthrough(传递结果和原始数据)
2.1 介绍
- 成因:
RunnableSequence的默认行为是替换,每一步的输入完全取代上一步的输出,但时常会遇到这样的需求:怎么在一个线性的数据流中既处理数据又保留原始数据?这个时候就要用到RunnablePassthrough; - 作用:
- 在链中传递数据:当链的后续步骤需要用到之前某个步骤的原始输入,而不仅仅是其直接上游的输出时,
RunnablePassthrough可以作为一个信使,将原始数据携带到需要他的地方; - 作为并行分支的占位符:在
RunnableParallel中RunnablePassthrough通常被用来将原始输入原封不动的包含在并行处理的输出结果中。
- 在链中传递数据:当链的后续步骤需要用到之前某个步骤的原始输入,而不仅仅是其直接上游的输出时,
assign()方法:RunnablePassthrough的assign()方法允许在保留原始输入字典的同时,动态的计算并添加新的键值对;assign()接收一个或多个关键字参数,其值可以是Runnable、callable或普通值,对于每个关键字参数assign()都会并行地执行其对应的Runnable(以原始输入作为其输入),然后将执行结果作为新的值,关键字作为新的键,合并到原始输入字典中,最后返回这个扩充后的新字典。
2.2 示例
检索增强生成(RAG)是 RunnablePassthrough 和 .assign() 发挥巨大作用的典型场景:在一个 RAG 流程中,需要根据用户的问题(question)去检索相关文档(context),然后将问题和文档一起提供给 LLM 生成答案。这意味着,最终传给提示模板的必须同时包含 question 和 context。
from langchain_core.runnables import RunnablePassthrough
from operator import itemgetter
# 假设 retriever 和其他组件已定义
# retriever =...
# prompt =...
# model =...
# output_parser =...
# 使用.assign() 扩充数据流
rag_chain_v2 = RunnablePassthrough.assign(
context=itemgetter("question") | retriever
) | prompt | model | output_parser
# 调用链
# response = rag_chain_v2.invoke({"question": "LCEL 是什么?"})
# print(response)
三、RunnableLambda(将函数转化为Runable实例)
3.1 介绍
- 内容:
RunnableLambda是一个Runnable包装器,它可以将任何Python的callable对象(如普通函数、lambda表达式等)转换成LCEL链的一个合法部分;一旦将一个函数包装成RunnableLambda,它就继承了所有Runnable的标准方法:.invoke(input, config=None): 对单个输入执行函数;.batch(inputs, config=None): 对一批输入执行批量处理;.stream(input, config=None): (如果可能)流式地输出结果;.with_config(config): 为 Runnable 添加配置;|(管道操作符): 用于将多个 Runnable 连接成一个序列。
- 主要用途:
- 数据预处理/后处理:在将输入传递给
LLM之前对其进行格式化,或者在将LLM的输出传递给下一个步骤之前进行解析; - 自定义逻辑:在链的执行流程中插入任何你需要的
Python逻辑; - 简化集成:将现有的、非
LangChain的函数快速改造为可以与LangChain生态其他部分协作的组件。
- 数据预处理/后处理:在将输入传递给
- 约束:
RunnableLambda有一个很重要的使用约束:被包装的函数必须只接收一个参数;- 为了实现更高级的控制和可观测性,
RunnableLambda包装的函数可以选择性地接收第二个参数:config: RunnableConfig。
3.2 示例
创建 RunnableLambda 有两种方式:
-
显式创建:
RunnableLambda(my_function); -
隐式创建: 在
|管道中直接使用函数名... | my_function |...,LCEL会自动将其强制转换为一个RunnableLambda,隐式创建是更常见、更简洁的做法:# 原始的、需要多个参数的业务逻辑函数 def combine_text(text1: str, text2: str) -> str: """将两个文本用连字符拼接起来""" return f"{text1} - {text2}" # 兼容 LCEL 的包装函数,接收单个字典参数 def wrapped_combine_text(inputs: dict) -> str: """从输入字典中解包参数并调用原始函数""" return combine_text(inputs['key1'], inputs['key2']) # 在链中使用 # 假设 runnable1 和 runnable2 是两个上游组件 #... # 它们的输出将被 RunnableParallel 组合成一个字典 multi_param_chain = { "key1": runnable1, "key2": runnable2 } | RunnableLambda(wrapped_combine_text)
四、RunnableParallel(LCEL的并发)
4.1 介绍
- 内容:
- 工作流程:
RunnableParallel是一个组合原语,它接收一个输入,并将这个输入同时分发给它所包含的多个子Runnable;它会并行地执行所有这些子Runnable,等待它们全部完成后,将结果收集到一个字典中返回; - 输入: 单一输入,与
RunnableSequence的输入类似; - 输出: 一个字典,该字典的键是在构造
RunnableParallel时指定的键,值是其对应的子Runnable的执行结果。
- 工作流程:
4.2 示例
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableParallel
# --- 通用组件 ---
model = ChatOpenAI(model="gpt-3.5-turbo")
parser = StrOutputParser()
# --- 分支 A: 笑话生成链 ---
prompt_joke = ChatPromptTemplate.from_template("讲一个关于 {topic} 的简短笑话。")
joke_chain = prompt_joke | model | parser
# --- 分支 B: 诗歌生成链 ---
prompt_poem = ChatPromptTemplate.from_template("写一首关于 {topic} 的四行短诗。")
poem_chain = prompt_poem | model | parser
# --- 使用 RunnableParallel (或字典字面量) 组合 ---
# map_chain = RunnableParallel(joke=joke_chain, poem=poem_chain)
# 或者更简洁地:
map_chain = {
"joke": joke_chain,
"poem": poem_chain
}
# --- 调用并行链 ---
result = map_chain.invoke({"topic": "机器人"})
# 打印结果
print("--- 笑话 ---")
print(result['joke'])
print("\n--- 诗歌 ---")
print(result['poem'])
# result 的结构会是: {"joke": "...", "poem": "..."}
LangChain LCEL的部分内容。
浙公网安备 33010602011771号