langchain学习总结-两个Runnable核心类的讲解与使用

两个Runnable核心类的讲解与使用

概述

本章节学习 LangChain 中两个核心的 Runnable 工具类:RunnableParallel 和 RunnablePassthrough。这两个工具类用于构建复杂的 LLM 应用流程,实现并行处理和数据传递。


1. RunnableParallel - 并行执行

是什么

RunnableParallel 是 LangChain 中的一个工具类,用于并行执行多个 Runnable 对象。它允许同时调用多个链,并将所有结果组合成一个字典返回。

有什么用

  • 提高执行效率:多个独立的任务可以并行执行,减少总等待时间
  • 结果聚合:将多个执行结果自动组合成一个统一的字典结构
  • 简化代码:避免手动管理多个异步或并发任务

使用场景

  • 同时生成多种类型的内容(如笑话、诗歌、摘要等)
  • 并行调用不同的模型或提示词模板
  • 需要将多个结果合并后传递给下一步骤

基础示例

python 复制代码
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableParallel
from langchain_openai import ChatOpenAI
import dotenv

dotenv.load_dotenv()

# 1. 编排prompt
joke_prompt = ChatPromptTemplate.from_template("请讲一个关于{subject}的冷笑话,尽可能短一些")
poem_prompt = ChatPromptTemplate.from_template("请写一篇关于{subject}的诗,尽可能短一些")

# 2. 创建大语言模型
llm = ChatOpenAI(model="moonshot-v1-8k")

# 3. 创建输出解析器
parser = StrOutputParser()

# 4. 编排链
joke_chain = joke_prompt | llm | parser
poem_chain = poem_prompt | llm | parser

# 5. 并行链
map_chain = RunnableParallel(joke=joke_chain, poem=poem_chain)

# 6. 调用
res = map_chain.invoke({"subject": "程序员"})

print(res)

输出结果

python 复制代码
{
    'joke': '问:为什么程序员总是混淆圣诞节和万圣节?\n答:因为他们喜欢 Oct 31(十月三十一日)胜过 Dec 25(十二月二十五日)。',
    'poem': '在数字世界里,代码编织梦,\n程序员,夜以继日,键盘敲击声。\n逻辑的海洋,算法的风,\n创造奇迹,于无形。'
}

另一种写法

python 复制代码
# 使用字典形式创建
map_chain = RunnableParallel({
    "joke": joke_chain,
    "poem": poem_chain,
})

执行流程

json 复制代码
输入: {"subject": "程序员"}
    ↓
RunnableParallel (并行执行)
    ├─ joke_chain → "冷笑话内容"
    └─ poem_chain → "诗歌内容"
    ↓
输出: {"joke": "...", "poem": "..."}

2. RunnableParallel 模拟检索

是什么

这是 RunnableParallel 在 RAG(检索增强生成)场景中的典型应用。通过并行执行检索函数和原始数据传递,为后续的 LLM 调用准备完整的上下文。

有什么用

  • 数据预处理:在调用 LLM 之前,先执行检索或其他数据处理
  • 保持原始数据:使用 itemgetter 保留原始输入字段
  • 数据增强:为原始数据添加额外的上下文信息

核心组件

itemgetter

Python 标准库 operator 模块中的函数,用于从字典中提取指定字段。在 LCEL 链中用于保留或选择特定的输入字段。

示例代码

python 复制代码
from operator import itemgetter
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
import dotenv

dotenv.load_dotenv()


def retrieval(query: str) -> str:
    """一个模拟的检索器函数"""
    print("正在检索:", query)
    return "我是慕小课"


# 1. 编排prompt
prompt = ChatPromptTemplate.from_template("""请根据用户的问题回答,可以参考对应的上下文进行生成。

<context>
{context}
</context>

用户的提问是: {query}""")

# 2. 构建大语言模型
llm = ChatOpenAI(model="moonshot-v1-8k")

# 3. 输出解析器
parser = StrOutputParser()

# 4. 构建链
chain = {
    "context": lambda x: retrieval(x["query"]),  # 执行检索
    "query": itemgetter("query"),                # 保留原始query字段
} | prompt | llm | parser

# 5. 调用链
content = chain.invoke({"query": "你好,我是谁?"})

print(content)

输出结果

makefile 复制代码
正在检索: 你好,我是谁?
你好,根据你提供的上下文,你是慕小课。

执行流程

erlang 复制代码
输入: {"query": "你好,我是谁?"}
    ↓
RunnableParallel (并行处理)
    ├─ context → retrieval函数 → "我是慕小课"
    └─ query → itemgetter("query") → "你好,我是谁?"
    ↓
Prompt模板 → 填充context和query
    ↓
LLM → 生成回答
    ↓
输出: "你好,根据你提供的上下文,你是慕小课。"

3. RunnablePassthrough - 数据透传

是什么

RunnablePassthrough 是一个特殊的 Runnable,主要用于在链中传递原始输入数据。配合 assign 方法使用时,可以在保留原始数据的同时添加新字段。

有什么用

  • 简化链的构建:避免显式使用 RunnableParallel 和 itemgetter
  • 保留原始输入:自动将所有输入字段传递到下一步
  • 动态添加字段:通过 assign 方法在执行过程中添加新字段

与 RunnableParallel 的区别

特性 RunnableParallel RunnablePassthrough.assign
原始数据传递 需要手动指定每个字段 自动保留所有原始输入
添加新字段 通过字典指定 通过 assign 方法添加
代码简洁度 较冗长 更简洁
适用场景 需要精确控制字段 只需添加少量字段

示例代码

python 复制代码
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI
import dotenv

dotenv.load_dotenv()


def retrieval(query: str) -> str:
    """一个模拟的检索器函数"""
    print("正在检索:", query)
    return "我是慕小课"


# 1. 编排prompt
prompt = ChatPromptTemplate.from_template("""请根据用户的问题回答,可以参考对应的上下文进行生成。

<context>
{context}
</context>

用户的提问是: {query}""")

# 2. 构建大语言模型
llm = ChatOpenAI(model="moonshot-v1-8k")

# 3. 输出解析器
parser = StrOutputParser()

# 4. 构建链
chain = RunnablePassthrough.assign(
    context=lambda x: retrieval(x["query"])
) | prompt | llm | parser

# 5. 调用链
content = chain.invoke({"query": "你好,我是谁?"})

print(content)

输出结果

makefile 复制代码
正在检索: 你好,我是谁?
你好,根据你提供的上下文,你是慕小课。

执行流程

css 复制代码
输入: {"query": "你好,我是谁?"}
    ↓
RunnablePassthrough.assign
    ├─ 保留原始字段 → {"query": "你好,我是谁?"}
    └─ 添加context字段 → {"query": "...", "context": "我是慕小课"}
    ↓
Prompt模板 → 填充context和query
    ↓
LLM → 生成回答
    ↓
输出: "你好,根据你提供的上下文,你是慕小课。"

代码对比

使用 RunnableParallel(方式1)
python 复制代码
chain = {
    "context": lambda x: retrieval(x["query"]),
    "query": itemgetter("query"),
} | prompt | llm | parser
使用 RunnablePassthrough(方式2)
python 复制代码
chain = RunnablePassthrough.assign(
    context=lambda x: retrieval(x["query"])
) | prompt | llm | parser

方式2的优势:

  • 更简洁,不需要导入 itemgetter
  • 自动保留所有原始输入字段
  • 代码可读性更好

总结

工具类对比

工具类 主要用途 典型场景
RunnableParallel 并行执行多个链,聚合结果 同时生成多种内容、并行调用不同模型
RunnablePassthrough 透传数据,动态添加字段 RAG检索、数据增强处理

选择建议

  1. 需要并行执行多个独立任务时,使用 RunnableParallel
  2. 只需要在原始数据基础上添加少量字段时,使用 RunnablePassthrough.assign
  3. 需要精确控制传递哪些字段时,使用 RunnableParallel + itemgetter

RAG 模式推荐写法

在检索增强生成场景中,推荐使用 RunnablePassthrough.assign:

python 复制代码
chain = RunnablePassthrough.assign(
    context=lambda x: retrieval(x["query"])
) | prompt | llm | parser

这种写法简洁明了,易于维护,是 LangChain 官方推荐的模式。

相关推荐
星星在线2 小时前
MusicFree:一个「All in One」的个人音乐服务器,让听歌回归简单
前端·后端
IT_陈寒3 小时前
Redis的SETNX并发问题让我加了三天班
前端·人工智能·后端
demo007x3 小时前
Docling 文档转换以及技术架构分析
前端·后端·程序员
京东云开发者4 小时前
京东市民服务又“上新”!这次是黑龙江“龙易办”
前端
袋鱼不重5 小时前
我的神奇同事,AI 用多了居然写了个 Open In Codex
前端·后端·ai编程
用户8356290780515 小时前
使用 Python 操作 Word 内容控件
后端·python
像我这样帅的人丶你还5 小时前
啥? 前端也要会干Java?🛵🛵🛵
后端
Hommy885 小时前
【剪映小助手】添加贴纸接口(Add Sticker)
后端·github·剪映小助手·视频剪辑自动化·剪映api
Fireworks5 小时前
深入vue3源码解读 -- 1、响应式的基础概念
前端
程序员黑豆5 小时前
JDK 下载安装与配置详细教程
java·前端·ai编程