在 LangChain 中,Runnable 是一个核心概念,指的是可以被执行(“run”)的对象,通常用于构建和组合工作流。Runnable
是 LangChain Expression Language(LCEL)的基石,允许开发者通过声明式的方式将提示(Prompt)、语言模型(LLM)、解析器(Output Parser)等组件组合成链(Chain),并以统一的方式调用。以下基于 LangChain 0.3.x,详细解释 Runnable
的定义、作用、类型及使用场景,并提供代码示例。
什么是 Runnable
?
Runnable
是 LangChain 中定义在 langchain_core.runnables
模块中的抽象接口,代表一个可执行的单元。任何实现 Runnable
接口的对象都可以通过标准方法(如 invoke
、stream
、batch
)运行,处理输入并生成输出。Runnable
的设计目标是:
- 统一接口:为不同组件(如 LLM、提示模板、解析器)提供一致的调用方式。
- 模块化组合:通过 LCEL(如
|
运算符)将多个Runnable
组合成复杂工作流。 - 灵活性:支持同步、异步、流式和批量调用,适配各种场景。
核心方法:
invoke(input)
:同步调用,处理单个输入,返回输出。ainvoke(input)
:异步调用,适合高并发场景。stream(input)
:流式调用,逐步返回输出(例如逐字生成)。batch(inputs)
:批量调用,处理多个输入。abatch(inputs)
:异步批量调用。
典型实现:
- 提示模板(
ChatPromptTemplate
) - 语言模型(
ChatOpenAI
、ChatOllama
) - 解析器(
StrOutputParser
、JsonOutputParser
) - 检索器(
VectorStoreRetriever
) - 自定义链(
RunnableLambda
、RunnableSequence
)
Runnable
的作用
Runnable
在 LangChain 中的作用包括:
- 组件抽象:将复杂功能(LLM 调用、提示生成、数据检索)封装为可复用的模块。
- 链式组合:通过 LCEL 的
|
运算符,将多个Runnable
串联,形成从输入到输出的完整工作流。 - 扩展性:支持自定义
Runnable
,允许开发者实现特定逻辑。 - 一致性:提供统一的调用接口,简化调试和维护。
- 动态处理:支持条件分支(
RunnableBranch
)、并行执行(RunnableParallel
)等高级功能。
Runnable
的主要类型
LangChain 提供了多种 Runnable
实现,位于 langchain_core.runnables
模块,常见类型包括:
1. RunnableSequence
- 功能:按顺序执行多个
Runnable
,前一个的输出作为后一个的输入。 - 表示:通过
|
运算符创建(如prompt | llm | parser
)。 - 适用场景:线性工作流,如提示生成 → LLM 调用 → 输出解析。
- 示例:
chain = prompt | llm | StrOutputParser()
2. RunnableParallel
- 功能:并行执行多个
Runnable
,将结果组合为字典。 - 表示:使用字典定义并行任务(如
RunnableParallel({"key1": runnable1, "key2": runnable2})
)。 - 适用场景:需要同时执行多个任务,如检索文档和处理用户输入。
- 示例:
chain = RunnableParallel({"context": retriever, "question": RunnablePassthrough()})
3. RunnablePassthrough
- 功能:直接传递输入,不做任何修改。
- 适用场景:占位符或保持输入不变,如在并行链中传递原始问题。
- 示例:
chain = {"question": RunnablePassthrough()} | prompt
4. RunnableLambda
- 功能:将 Python 函数封装为
Runnable
,支持自定义逻辑。 - 适用场景:需要插入自定义处理逻辑,如数据清洗、格式转换。
- 示例:
from langchain_core.runnables import RunnableLambda runnable = RunnableLambda(lambda x: x.upper())
5. RunnableBranch
- 功能:根据条件选择执行不同的
Runnable
分支。 - 适用场景:动态路由,如根据输入类型选择不同处理路径。
- 示例:
branch = RunnableBranch( (lambda x: "question" in x, question_chain), (lambda x: "command" in x, command_chain), default_chain )
6. RunnableWithMessageHistory
- 功能:为链添加对话历史管理,跟踪多轮对话。
- 适用场景:聊天机器人、上下文相关的问答。
- 示例:
chain = RunnableWithMessageHistory(runnable, get_session_history)
7. 内置组件的 Runnable
- 提示模板(如
ChatPromptTemplate
):将输入格式化为消息。 - 语言模型(如
ChatOpenAI
):生成文本或结构化输出。 - 解析器(如
StrOutputParser
):将 LLM 输出转换为特定格式。 - 检索器(如
VectorStoreRetriever
):从向量存储中检索文档。
使用场景与代码示例
以下展示 Runnable
在不同场景中的应用,使用 langchain_openai.ChatOpenAI
和 LCEL 构建链。
1. 简单问答(RunnableSequence
)
构建一个简单的问答链,组合提示、LLM 和解析器。
import os
os.environ["OPENAI_API_KEY"] = "Your OpenAI API Key"
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# 初始化 LLM
llm = ChatOpenAI(temperature=0, model="gpt-4o-mini")
# 定义提示模板
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个助手,请准确回答问题。"),
("human", "{question}")
])
# 创建 RunnableSequence
chain = prompt | llm | StrOutputParser()
# 调用链
response = chain.invoke({"question": "什么是人工智能?"})
print(response)
输出示例:
人工智能(AI)是计算机科学的一个分支,旨在创建能够执行需要人类智能的任务的系统,例如学习、推理、问题解决和决策。
说明:
prompt | llm | StrOutputParser
创建一个RunnableSequence
。- 每个组件(
ChatPromptTemplate
、ChatOpenAI
、StrOutputParser
)都是Runnable
。 - 输入为字典,输出为字符串。
2. 语义搜索(RunnableParallel
)
结合向量存储和并行处理,构建语义搜索链。
import os
os.environ["OPENAI_API_KEY"] = "Your OpenAI API Key"
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_community.vectorstores import Chroma
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
# 初始化嵌入模型和 LLM
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
llm = ChatOpenAI(temperature=0, model="gpt-4o-mini")
# 准备文档
docs = [
"人工智能(AI)是计算机科学的一个分支,专注于创建智能系统。",
"机器学习是 AI 的子领域,涉及从数据中学习模型。"
]
# 创建向量存储
vectorstore = Chroma.from_texts(docs, embeddings)
retriever = vectorstore.as_retriever(search_kwargs={"k": 1})
# 定义提示模板
prompt = ChatPromptTemplate.from_messages([
("system", "根据以下上下文回答问题:\n{context}"),
("human", "{question}")
])
# 创建 RunnableParallel
chain = (
RunnableParallel({"context": retriever, "question": RunnablePassthrough()})
| prompt
| llm
| StrOutputParser()
)
# 调用链
response = chain.invoke("什么是人工智能?")
print(response)
输出示例:
人工智能(AI)是计算机科学的一个分支,专注于创建能够执行需要人类智能的任务的系统。
说明:
RunnableParallel
并行执行检索(retriever
)和输入传递(RunnablePassthrough
)。- 结果作为字典(
{"context": ..., "question": ...}
)传入prompt
。
3. 对话历史(RunnableWithMessageHistory
)
使用 RunnableWithMessageHistory
构建支持对话历史的链。
import os
os.environ["OPENAI_API_KEY"] = "Your OpenAI API Key"
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
# 初始化 LLM
llm = ChatOpenAI(temperature=0, model="gpt-4o-mini")
# 定义提示模板
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个助手,请根据对话历史回答问题。"),
MessagesPlaceholder(variable_name="history"),
("human", "{input}")
])
# 创建 RunnableSequence
runnable = prompt | llm
# 初始化消息历史存储
store = {}
def get_session_history(session_id: str) -> ChatMessageHistory:
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
# 创建 RunnableWithMessageHistory
chain = RunnableWithMessageHistory(
runnable,
get_session_history,
input_messages_key="input",
history_messages_key="history"
)
# 调用链
session_id = "user1"
response = chain.invoke(
{"input": "我叫鲍勃"},
config={"configurable": {"session_id": session_id}}
)
print(response.content)
response = chain.invoke(
{"input": "我的名字是什么?"},
config={"configurable": {"session_id": session_id}}
)
print(response.content)
输出示例:
你好,鲍勃!很高兴认识你!有什么可以帮助你的?
你的名字是鲍勃。
说明:
RunnableWithMessageHistory
包装RunnableSequence
,自动管理对话历史。ChatMessageHistory
存储消息,session_id
区分会话。
4. 自定义逻辑(RunnableLambda
)
使用 RunnableLambda
插入自定义处理逻辑。
import os
os.environ["OPENAI_API_KEY"] = "Your OpenAI API Key"
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableLambda
# 初始化 LLM
llm = ChatOpenAI(temperature=0, model="gpt-4o-mini")
# 定义提示模板
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个助手。"),
("human", "{question}")
])
# 定义自定义 Runnable
uppercase = RunnableLambda(lambda x: x.upper())
# 创建链
chain = prompt | llm | StrOutputParser() | uppercase
# 调用链
response = chain.invoke({"question": "什么是人工智能?"})
print(response)
输出示例:
人工智能(AI)是计算机科学的一个分支,旨在创建能够执行需要人类智能的任务的系统,例如学习、推理、问题解决和决策。
说明:
RunnableLambda
将输出转换为大写,作为链的最后一步。- 自定义逻辑可处理任意 Python 函数。
注意事项
- API 密钥安全:
- 避免硬编码密钥,推荐使用
.env
文件:from dotenv import load_dotenv load_dotenv() # 加载 OPENAI_API_KEY
- 确保密钥支持指定模型(如
gpt-4o-mini
)。
- 避免硬编码密钥,推荐使用
- 输入输出格式:
Runnable
的输入和输出需匹配链中各组件的期望格式。- 使用
prompt.format()
或chain.input_schema
调试输入格式。
- 性能:
- 流式调用(
stream
)适合实时交互,异步调用(ainvoke
)适合高并发。 - 批量调用(
batch
)可提高效率。
- 流式调用(
- Ollama 支持:
langchain_ollama.ChatOllama
也是Runnable
,可替换ChatOpenAI
,无需 OpenAI API:from langchain_ollama import ChatOllama llm = ChatOllama(model="qwen3:1.7b")
- 调试:
- 设置
verbose=True
或使用chain.get_graph()
可视化链结构。 - 打印中间结果(如
prompt.invoke(input)
)检查问题。
- 设置
常见问题
Q1:Runnable
和传统链有什么区别?
A:传统链(如 LLMChain
)是旧 API,代码复杂且不灵活。Runnable
使用 LCEL,提供统一接口、模块化组合和高级功能(如流式、并行)。
Q2:如何调试 Runnable
链?
A:使用 verbose=True
查看执行日志,或单独调用每个 Runnable
(如 prompt.invoke()
、llm.invoke()
)检查中间输出。
Q3:Runnable
支持工具调用吗?
A:支持,ChatOpenAI
等模型通过 bind_tools
实现工具调用,链中可解析 tool_calls
。
Q4:可以自定义 Runnable
吗?
A:可以,使用 RunnableLambda
或继承 Runnable
实现自定义逻辑。
总结
Runnable
是 LangChain 的核心抽象,表示可执行的组件,支持 LCEL 构建复杂工作流。主要类型包括:
RunnableSequence
:顺序执行(|
运算符)。RunnableParallel
:并行执行(字典形式)。RunnablePassthrough
:传递输入。RunnableLambda
:自定义逻辑。RunnableBranch
:条件分支。RunnableWithMessageHistory
:对话历史管理。