LangChain_LCEL

LCELLangChain Expression Language

一、RunnableSequence(LCEL的基础结构)

1.1 介绍

  • LCEL 核心概念:

    • 内容:作为通用接口的 Runnable 协议,用于顺序组合的 RunnableSequence

    • Runnable 协议:在LCEL的世界里,万物皆Runnable。从最基础的提示词模板(PromptTemplate)和语言模型(LLM/ChatModel)到功能性的组件比如输出解析器(OutputParser)和文档检索器(Retriever)都实现了 Runnable 接口;

    • RunnableSequenceRunnableSequenceLangChain 中最重要的组合操作符,因为它几乎在每个链中都使用。

  • 管道符|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 可以作为一个信使,将原始数据携带到需要他的地方;
    • 作为并行分支的占位符:在 RunnableParallelRunnablePassthrough 通常被用来将原始输入原封不动的包含在并行处理的输出结果中。
  • assign() 方法:
    • RunnablePassthroughassign() 方法允许在保留原始输入字典的同时,动态的计算并添加新的键值对;
    • assign() 接收一个或多个关键字参数,其值可以是 Runnablecallable 或普通值,对于每个关键字参数assign() 都会并行地执行其对应的 Runnable(以原始输入作为其输入),然后将执行结果作为新的值,关键字作为新的键,合并到原始输入字典中,最后返回这个扩充后的新字典。

2.2 示例

检索增强生成(RAG)是 RunnablePassthrough.assign() 发挥巨大作用的典型场景:在一个 RAG 流程中,需要根据用户的问题(question)去检索相关文档(context),然后将问题和文档一起提供给 LLM 生成答案。这意味着,最终传给提示模板的必须同时包含 questioncontext

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 包装器,它可以将任何 Pythoncallable 对象(如普通函数、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)
    

四、RunnableParallelLCEL的并发)

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": "..."}
posted @ 2026-03-07 06:53  Neur0toxin  阅读(6)  评论(0)    收藏  举报