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 官方推荐的模式。

相关推荐
艾斯特_17 分钟前
Echarts常用配置项及解释
前端·javascript·echarts
m0_5027249526 分钟前
飞书真机调试
开发语言·前端·javascript
我只会写Bug啊1 小时前
复制可用!纯前端基于 Geolocation API 实现经纬度获取与地图可视化
前端·高德地图·地图·百度地图·经纬度
刘一说1 小时前
Vue3 模块语法革命:移除过滤器(Filters)的深度解析与迁移指南
前端·vue.js·js
qq_12498707531 小时前
基于小程序中医食谱推荐系统的设计(源码+论文+部署+安装)
java·spring boot·后端·微信小程序·小程序·毕业设计·计算机毕业设计
lkbhua莱克瓦242 小时前
JavaScript核心语法
开发语言·前端·javascript·笔记·html·ecmascript·javaweb
Trae1ounG2 小时前
这是什么dom
前端·javascript·vue.js
比老马还六2 小时前
Bipes项目二次开发/扩展积木功能(八)
前端·javascript
易营宝2 小时前
全球建站SaaS平台能提升SEO评分吗?是否值得切换?
大数据·前端·人工智能
513495922 小时前
在Vue.js项目中使用docx和file-saver实现Word文档导出
前端·vue.js·word