【Langchian】Runnable是什么?以及Langchian记忆管理的不同方式。

Runnable 是啥?

Runnable 是 LangChain 的基类(langchain_core.runnables.Runnable),规定所有"可执行"对象的标准。让 LangChain 的零件(LLM、Prompt、Chain 等)统一操作,能单独跑,也能连成流水线。如果每个模块用自己的方式跑(llm.callprompt.generate),没法统一,连不起来。

Runnable规定了什么

  • 标准化:都用 invoke 跑。(invoke 单次执行,batch 批量,stream` 流式。)
  • 组合化:都用 | 连成链。(用 | 组成链,输出接输入。)
  • 扩展化:新模块只要继承 Runnable,就能加入。

Runnable的子类

既然Runnable 是个抽象基类,自然本身不干活,具体工作交给子类。它的家族分成几大类,按功能划分:

  • 直接子类:继承 Runnable,自己实现逻辑,干具体活。比如ChatOpenAIRunnablePassthrough
  • 封装子类:不直接继承,而是包装其他 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.assignRunnableWithMessageHistory 这类工具。前者通过手动分配上下文来传递历史信息,适合简单的场景;后者则是更强大的方案,专门为对话历史管理设计,能更方便地集成消息记录。

方案 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 字段。
    • 实现:lambdaConversationBufferMemory (内存)加载历史,塞进输入字典。
    • 输入示例:{"input": "我叫啥?"}{"input": "我叫啥?", "history": [历史消息]}
  • ConversationBufferMemory
    • 存储:内存(RAM),程序运行期间有效。
    • 方法:load_memory_variables 读,save_context 写。
    • 限制:程序关闭后历史丢失。
  • 无状态链chain 本身不保存记忆,每次 invokeassign 临时提供。
  • 手动管理 :需显式调用 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

    • 类比:像助手自带账本,自动查记历史,你只管问。
    • 场景:生产环境、多会话应用、需要持久化。
相关推荐
xilu06 小时前
MCP与RAG:增强大型语言模型的两种路径
人工智能·llm·mcp
小杨4047 小时前
LLM大语言模型一(概述篇)
人工智能·llm·aigc
X20469 小时前
HippoRAG2:仿人脑检索的RAG,超越GraphRAG、LightRAG和KAG,成本骤降12倍!
人工智能·llm·aigc
RWKV元始智能9 小时前
RWKV 社区 2 月动态:10 篇新学术论文!
人工智能·llm·aigc
xiaohezi12 小时前
大模型核心概念科普:Token、上下文长度、最大输出,一次讲透
llm
阿正的梦工坊15 小时前
STaR(Self-Taught Reasoner)方法:让语言模型自学推理能力(代码实现)
人工智能·深度学习·机器学习·语言模型·自然语言处理·llm
Just_Paranoid17 小时前
DeepSeek 202502 开源周合集
chatgpt·开源·llm·openai·qwen·deepseek
昵称不能为null1 天前
大模型微调入门(Transformers + Pytorch)
人工智能·python·机器学习·llm
leitiannet2 天前
大语言模型:Linux系统下源码编译Ollama指南
llm·ollama