Runnable 是啥?
Runnable
是 LangChain 的基类(langchain_core.runnables.Runnable
),规定所有"可执行"对象的标准。让 LangChain 的零件(LLM、Prompt、Chain 等)统一操作,能单独跑,也能连成流水线。如果每个模块用自己的方式跑(llm.call
、prompt.generate
),没法统一,连不起来。
Runnable规定了什么
- 标准化:都用
invoke
跑。(invoke单次执行,
batch批量,
stream` 流式。) - 组合化:都用
|
连成链。(用|
组成链,输出接输入。) - 扩展化:新模块只要继承
Runnable
,就能加入。
Runnable的子类
既然Runnable
是个抽象基类,自然本身不干活,具体工作交给子类。它的家族分成几大类,按功能划分:
- 直接子类:继承
Runnable
,自己实现逻辑,干具体活。比如ChatOpenAI
、RunnablePassthrough
。 - 封装子类:不直接继承,而是包装其他
Runnable
,加新功能。比如RunnableWithMessageHistory
。 - 组合子类多个
Runnable
用|
组成的新对象,本身也是Runnable
。比如Chain
(像prompt | llm
)。
以下是 LangChain 中最常见的 Runnable
子类,按功能分类,附代码和关系说明。
模型类 :比如ChatOpenAI
就是直接子类,继承 Runnable
,实现 invoke
调用 API。调用 OpenAI 模型生成回答。输入 :字符串或消息列表。输出 :AIMessage
。
python
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(openai_api_key="your-api-key")
result = llm.invoke("你好") # 输出:AIMessage(content="こんにちは")
提示词类 :比如ChatPromptTemplate
也是直接子类,继承 Runnable
,实现 invoke
生成提示。 输入 :字典(如 {"input": "你好"}
)。 输出:消息列表。
python
from langchain.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([("human", "{input}")])
result = prompt.invoke({"input": "你好"}) # 输出:[HumanMessage("你好")]
工具类 :比如RunnablePassthrough
也是直接子类,继承 Runnable
。用于透传输入,可用 assign
加字段。 输入 :任意。 输出 :原输入或增强后的输入。 RunnableLambda
也是直接子类,把普通函数变成 Runnable
。功能 是包装自定义函数。 输入 :任意。 输出:函数结果。
python
from langchain_core.runnables import RunnablePassthrough
passthrough = RunnablePassthrough.assign(extra=lambda x: "附加")
result = passthrough.invoke({"input": "你好"}) # 输出:{"input": "你好", "extra": "附加"}
--------------------------------------------------------------------------------------
from langchain_core.runnables import RunnableLambda
runnable = RunnableLambda(lambda x: x["input"] + "!")
result = runnable.invoke({"input": "你好"}) # 输出:"你好!"
输出类 :比如 StrOutputParser
,负责从 AIMessage
提取文本,也是直接子类,继承 Runnable
,实现 invoke
解析输出。 输入 :AIMessage
。 输出:字符串。
python
from langchain_core.output_parsers import StrOutputParser
parser = StrOutputParser()
result = parser.invoke(llm.invoke("你好")) # 输出:"こんにちは"
历史类 :比如RunnableWithMessageHistory
,属于封装子类,包住其他 Runnable
(如 llm
),加历史功能 输入 :字典 + 会话 ID。 输出:链的结果。
python
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import SQLChatMessageHistory
def get_session_history(session_id):
return SQLChatMessageHistory(session_id=session_id, connection_string="sqlite:///chat.db")
chain_with_history = RunnableWithMessageHistory(llm, get_session_history, input_messages_key="input")
result = chain_with_history.invoke("你好", config={"configurable": {"session_id": "user123"}})
组合类 :比如Chain
组合子类,由多个 Runnable
合成,它本身也是 Runnable
输入 :第一个节点的输入。 输出:最后一个节点的输出。
python
chain = prompt | llm | parser
result = chain.invoke({"input": "你好"}) # 输出:"こんにちは"
Langchian的记忆实现策略
在 LangChain 中,链(Chain)默认是无状态的,这意味着每次运行都是独立的,无法自动记住之前的对话历史。比如用户说"我叫小明",然后问"我是谁?",如果没有额外的记忆机制,系统无法回答"你叫小明",因为它不具备上下文记忆能力。要实现这种对话历史的保留,就需要引入记忆机制。而在具体实现时,你的部署方式和调用方式会直接影响是否需要自行处理记忆逻辑。
如果你是本地调用官方API,而不是本地部署模型,那么情况是这样的:官方API通常是由服务提供方(如xAI或其他平台)管理的,他们可能会在服务端内置一定的记忆管理机制,但这取决于API的具体设计。如果官方API已经提供了会话管理功能(比如通过会话ID或上下文参数来追踪对话历史),那么你在本地调用时就不需要额外操心记忆问题,只需按照API的规范传递相关参数即可。但如果API没有内置记忆功能,或者你希望自定义记忆行为(比如控制存储的内容或方式),那你还是得在本地代码中引入记忆机制。简单来说,只要API没有现成的上下文管理,你就得自己动手,而本地部署模型只是让这个问题变得更显而易见罢了。
反过来,如果你是本地部署模型(即模型完全运行在你的设备上),那么整个流程都在你的掌控之中,LangChain 的链默认无状态特性就会直接体现出来。这时,你必须自己实现记忆功能,比如通过 RunnablePassthrough.assign
或 RunnableWithMessageHistory
这类工具。前者通过手动分配上下文来传递历史信息,适合简单的场景;后者则是更强大的方案,专门为对话历史管理设计,能更方便地集成消息记录。
方案 1:RunnablePassthrough.assign
------ 手动输入增强
python
from langchain.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain.memory import ConversationBufferMemory
llm = ChatOpenAI(openai_api_key="your-api-key", model_name="gpt-3.5-turbo")
prompt = ChatPromptTemplate.from_messages([
("system", "你是助手,基于历史 {history} 回答"),
("human", "{input}")
])
parser = StrOutputParser()
memory = ConversationBufferMemory(return_messages=True) # 内存存储
chain = (
RunnablePassthrough.assign(history=lambda x: memory.load_memory_variables({})["chat_history"])
| prompt
| llm
| parser
)
result = chain.invoke({"input": "我叫小明"})
memory.save_context({"input": "我叫小明"}, {"output": result})
print(chain.invoke({"input": "我叫啥?"})) # 输出:你叫小明
RunnablePassthrough.assign
:- 作用:在链开头增强输入,添加
history
字段。 - 实现:
lambda
从ConversationBufferMemory
(内存)加载历史,塞进输入字典。 - 输入示例:
{"input": "我叫啥?"}
→{"input": "我叫啥?", "history": [历史消息]}
。
- 作用:在链开头增强输入,添加
ConversationBufferMemory
:- 存储:内存(RAM),程序运行期间有效。
- 方法:
load_memory_variables
读,save_context
写。 - 限制:程序关闭后历史丢失。
- 无状态链 :
chain
本身不保存记忆,每次invoke
靠assign
临时提供。 - 手动管理 :需显式调用
memory.save_context
保存历史。 - 存储位置:内存,非持久化。要持久化,需自己加逻辑(如存文件或数据库)。
- 优点:简单,适合调试或单次会话。
- 缺点:不自动持久化,长期使用需手动扩展。
方案 2:RunnableWithMessageHistory
------ 自动历史管理
python
from langchain.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import SQLChatMessageHistory
llm = ChatOpenAI(openai_api_key="your-api-key", model_name="gpt-3.5-turbo")
prompt = ChatPromptTemplate.from_messages([
("system", "你是助手,基于历史回答"),
MessagesPlaceholder(variable_name="chat_history"),
("human", "{input}")
])
parser = StrOutputParser()
chain = prompt | llm | parser # 原链
def get_session_history(session_id):
return SQLChatMessageHistory(session_id=session_id, connection_string="sqlite:///chat.db")
chain_with_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="input",
history_messages_key="chat_history"
)
result = chain_with_history.invoke(
{"input": "我叫小明"},
config={"configurable": {"session_id": "user123"}}
)
print(chain_with_history.invoke(
{"input": "我叫啥?"},
config={"configurable": {"session_id": "user123"}}
)) # 输出:你叫小明
RunnableWithMessageHistory
:- 作用:封装原链,自动加载和保存历史。
- 实现:通过
get_session_history
获取历史对象,运行前加到输入,运行后存回。 - 输入示例:
{"input": "我叫啥?"}
→{"input": "我叫啥?", "chat_history": [历史消息]}
。
SQLChatMessageHistory
:- 存储:数据库(SQLite),持久化。
- 机制:按
session_id
存取历史,程序重启仍可用。
- 参数 :
input_messages_key
:输入字段名。history_messages_key
:历史字段名,匹配MessagesPlaceholder
。
- 有状态链:封装后自动管理历史。
- 持久化:存数据库,跨会话有效。
- 自动化 :
invoke
自带历史加载和保存。 - 优点:省心,适合多用户、长期会话。
- 缺点:需配置数据库,稍复杂。
-
RunnablePassthrough.assign
:- 类比:像给助手递张便签,告诉它"之前聊了啥",但得自己记笔记。
- 场景:快速测试、单次对话、手动控制历史。
- 扩展 :搭配
ConversationBufferMemory
可存内存,需手动持久化到文件或数据库。
-
RunnableWithMessageHistory
:- 类比:像助手自带账本,自动查记历史,你只管问。
- 场景:生产环境、多会话应用、需要持久化。