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 小时前
在小程序做海报的话,Painter就很给力
前端·微信小程序·canvas
匠心码员2 小时前
Git Commit 提交规范:让每一次提交都清晰可读
前端
骑斑马的李司凌2 小时前
调试时卡半天?原来127.0.0.1和localhost的区别这么大!
前端
哈哈O哈哈哈2 小时前
Electron + Vue 3 + Node.js 的跨平台桌面应用示例项目
前端
ycbing2 小时前
设计并实现一个 MCP Server
前端
千寻girling2 小时前
面试官: “ 说一下怎么做到前端图片尺寸的响应式适配 ”
前端·javascript·面试
大橘喵喵重2 小时前
FastAPI 实现国际化(i18n)和多语言支持的完整方案
后端
少莫千华2 小时前
【Web API】RESTful API接口规范
前端·后端·json·api·restful·rest
用户2345267009822 小时前
AI会不会悄悄把漏洞甚至后门写进你的代码里
后端·ai编程