增强LangChain交互体验:消息历史(记忆)功能详解

一、食用说明

在构建聊天机器人时,将对话状态传入和传出链至关重要。 LangGraph 实现了内置的持久层,允许链状态自动持久化在内存或外部后端(如 SQLite、Postgres 或 Redis)中。在本文我们将演示如何通过将任意 LangChain runnables 包装在最小的 LangGraph 应用程序中来添加信息历史功能,这使我们能够持久化消息历史记录和链状态的其他元素,从而简化多轮应用程序的开发。它还支持多线程,使单个应用程序能够与多个用户分别交互。

温馨提示:本文搭配 Jupyter notebooks 食用更佳,在交互式环境中学习是更好地理解它们的好方法。

二、使用 Qwen3 聊天模型

bash 复制代码
pip install -qU "langchain[openai]"
python 复制代码
from pydantic import SecretStr
import os

os.environ["OPENAI_BASE_URL"] = "https://api.siliconflow.cn/v1/"
os.environ["OPENAI_API_KEY"] = "sk-xxx"

from langchain.chat_models import init_chat_model

llm = init_chat_model("Qwen/Qwen3-8B", model_provider="openai")

三、示例:消息输入

聊天模型接受消息列表作为输入并输出消息。 LangGraph 包括一个内置的 MessagesState,我们可以将其用于此目的。下面,我们将图状态定义为消息列表,向图中添加一个调用聊天模型的节点,使用内存检查点编译器编译图,以在运行之间存储消息。

python 复制代码
from langchain_core.messages import HumanMessage
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import START, MessagesState, StateGraph

# Define a new graph
workflow = StateGraph(state_schema=MessagesState)


# Define the function that calls the model
def call_model(state: MessagesState):
    response = llm.invoke(state["messages"])
    # Update message history with response:
    return {"messages": response}


# Define the (single) node in the graph
workflow.add_edge(START, "model")
workflow.add_node("model", call_model)

# Add memory
memory = MemorySaver()
app = workflow.compile(checkpointer=memory)

可以查看一下图结构:

python 复制代码
from IPython.display import Image, display

display(Image(app.get_graph().draw_mermaid_png()))

当我们运行应用程序时,我们传入一个配置 dict,其中指定了 thread_id。此 ID 用于区分对话线程(例如,不同用户之间)。

python 复制代码
config = {"configurable": {"thread_id": "abc123"}}

然后我们可以调用应用程序

python 复制代码
query = "hi 我是小智"

input_messages = [HumanMessage(query)]
output = app.invoke({"messages": input_messages}, config)
output["messages"][-1].pretty_print()  # output contains all messages in state
python 复制代码
query = "我是谁?"

input_messages = [HumanMessage(query)]
output = app.invoke({"messages": input_messages}, config)
output["messages"][-1].pretty_print()

请注意,状态对于不同的线程是分开的。如果我们向具有新 thread_id 的线程发出相同的查询,模型会指示它不知道答案

python 复制代码
query = "我是谁?"
config = {"configurable": {"thread_id": "abc234"}}

input_messages = [HumanMessage(query)]
output = app.invoke({"messages": input_messages}, config)
output["messages"][-1].pretty_print()

四、示例:字典输入

LangChain runnables 通常通过单个 dict 参数中的单独键接受多个输入。一个常见的例子是具有多个参数的提示模板。

之前的 runnable 是聊天模型,这里我们将提示模板和聊天模型链接在一起。

python 复制代码
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "请用 {language} 回答问题"),
        MessagesPlaceholder(variable_name="messages"),
    ]
)

runnable = prompt | llm

对于这种情况,我们将图状态定义为包括这些参数(除了消息历史记录之外)。然后,我们以与之前相同的方式定义单节点图。

请注意,在下面的状态中:

  • 对 messages 列表的更新将附加消息;
  • 对 language 字符串的更新将覆盖该字符串。
python 复制代码
from typing import Sequence

from langchain_core.messages import BaseMessage
from langgraph.graph.message import add_messages
from typing_extensions import Annotated, TypedDict

"""‌作为状态机StateGraph的状态字段,用于跟踪对话历史,序列化时会保留消息的时间顺序和完整内容"""
class State(TypedDict):
    # Annotated包装器允许附加元数据add_messages,这是LangGraph提供的消息合并函数,当状态更新时,add_messages会自动处理新旧消息序列的合并逻辑
    messages: Annotated[Sequence[BaseMessage], add_messages]
    language: str

workflow = StateGraph(state_schema=State)


def call_model(state: State):
    response = runnable.invoke(state)
    # Update message history with response:
    return {"messages": [response]}


workflow.add_edge(START, "model")
workflow.add_node("model", call_model)

memory = MemorySaver()
app = workflow.compile(checkpointer=memory)
python 复制代码
from IPython.display import Image, display

display(Image(app.get_graph().draw_mermaid_png()))
python 复制代码
config = {"configurable": {"thread_id": "abc345"}}

input_dict = {
    "messages": [HumanMessage("hi 我是小智")],
    "language": "粤语",
}
output = app.invoke(input_dict, config)
output["messages"][-1].pretty_print()

五、管理消息历史记录

消息历史记录(和应用程序状态的其他元素)可以通过 .get_state 访问

python 复制代码
state = app.get_state(config).values

print(f'Language: {state["language"]}')
for message in state["messages"]:
    message.pretty_print()

我们还可以通过 .update_state 更新状态。例如,我们可以手动附加新消息

python 复制代码
from langchain_core.messages import HumanMessage

_ = app.update_state(config, {"messages": [HumanMessage("今天天气很好")]})
python 复制代码
state = app.get_state(config).values

print(f'Language: {state["language"]}')
for message in state["messages"]:
    message.pretty_print()

参考文档

相关推荐
用户711283928478 小时前
什么?大模型删库跑路了?
langchain·llm
金汐脉动16 小时前
LangChain × Elasticsearch:手把手教你搭建智能向量数据库
langchain
金汐脉动16 小时前
LangChain × PGVector:手把手教你搭建智能向量数据库
langchain
耿玉17 小时前
什么是 AI AGENT?与大语言模型的区别?
langchain·ai编程
AI大模型学习教程1 天前
前端学AI之LangChain.js入门教程:实现智能对话机器人
人工智能·langchain
金汐脉动2 天前
解锁聊天模型的隐藏能力:工具调用全指南
langchain
牛大姐2 天前
DeepSeek+LangChain:搭建一个基础的本地agent
langchain
hshpy3 天前
LangChain in java
java·langchain·flask
大尾巴青年3 天前
07 一分钟搞懂langchain如何调用tool
langchain·llm