2025-12-30
LangChain
0
请注意,本文编写于 51 天前,最后修改于 51 天前,其中某些信息可能已经过时。

目录

1.多轮对话的真实需求
2.多轮对话整体流程
3.初始化 LLM
4.定义聊天提示模板(历史是怎么插进去的)
5.将 prompt 与 LLM 组成管道
6.连接 PostgreSQL 数据库(持久化前提)
7.初始化聊天记录表(会自动在数据库创建相应的表)
8.定义获取聊天历史的函数(关键)
9.RunnableWithMessageHistory:多轮对话的核心
10.调用示例
11.小结

LangChain 的多轮对话,本质不是“模型记住了历史”,而是 Runnable 在每次调用前,把历史消息重新拼进 Prompt,再把新结果写回存储。

这一篇将基于一个完整的多轮对话流程,从零梳理:

  • 多轮对话在 LangChain 中是如何工作的
  • RunnableWithMessageHistory 到底做了什么
  • 使用 PostgreSQL或其他数据库进行持久化存储
  • 多用户 / 多会话是如何隔离的

1.多轮对话的真实需求

多轮对话通常至少要满足:

  • 同一个用户,多次提问能接着聊
  • 不同用户 / 不同会话互不干扰
  • 服务重启后,对话不能丢
  • Prompt、LLM、存储可以独立替换

下面我们用完整代码 + 调用流程,学习 LangChain 的多轮对话机制

2.多轮对话整体流程

python
用户输入 ↓ RunnableWithMessageHistory ↓ get_session_history(user_id, conversation_id) ↓ ChatMessageHistory / PostgresChatMessageHistory ↓ Prompt + LLM ↓ 模型输出(并写回历史)

3.初始化 LLM

这里以通义千问 ChatTongyi 为例,开启流式 + 思考模式:

python
from langchain_community.chat_models import ChatTongyi from pydantic import SecretStr llm = ChatTongyi( model="qwen3-plush api_key=SecretStr("sk-xxx"), streaming=True, # 思考模式必须启用流式输出 model_kwargs={ "enable_thinking": True, # 开启思考模式 "incremental_output": True } )

参数说明:

  • streaming=True: 开启流式输出,否则思考模式不生效
  • enable_thinking=True # 启用“思考”模式(流式调用才有效)
  • incremental_output=True # 模型 token 级增量返回,适合 SSE / WebSocket

4.定义聊天提示模板(历史是怎么插进去的)

使用 ChatPromptTemplate.from_messages 创建多轮对话模板

python
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder prompt = ChatPromptTemplate.from_messages( [ ( "system", "你是一个擅长写作的智能聊天助手", ), MessagesPlaceholder(variable_name="history"), # 历史记录占位符,实现多轮对话关键的一行 ("human", "{input}"), # 最新用户输入占位符 ] )

参数说明:

  • system: 系统指令,指定模型身份或能力
  • MessagesPlaceholder: 历史消息占位符,自动填充历史对话
  • human: 用户输入

5.将 prompt 与 LLM 组成管道

通过 | 操作符可以将 Prompt 输出作为 LLM 输入

python
runnable = prompt | llm

6.连接 PostgreSQL 数据库(持久化前提)

python
import psycopg sync_conn = psycopg.connect( "postgresql://user:password@host:5432/db" )

7.初始化聊天记录表(会自动在数据库创建相应的表)

python
from langchain_postgres import PostgresChatMessageHistory PostgresChatMessageHistory.create_tables( sync_conn, "message_store" )

说明:

  • LangChain 会自动创建表结构
  • 只需要在项目初次启动时执行一次
  • 后续直接复用表

8.定义获取聊天历史的函数(关键)

python
import uuid def get_session_history(user_id: str, conversation_id: str): # 会话的唯一标识 session_id = str(uuid.uuid5(uuid.NAMESPACE_DNS, f"{user_id}_{conversation_id}")) return PostgresChatMessageHistory( "message_store", # table_name session_id, # session_id sync_connection=sync_conn )

这里完成了三件非常重要的事:

  • 同一个 user + conversation → 同一个会话
  • 不同用户 / 会话自动隔离
  • 历史记录真正落库,可跨服务实例

9.RunnableWithMessageHistory:多轮对话的核心

python
from langchain_core.runnables import ConfigurableFieldSpec, RunnableWithMessageHistory with_message_history = RunnableWithMessageHistory( runnable, # type: ignore get_session_history, input_messages_key="input", # 最新输入的 key history_messages_key="history", # 历史消息的 key history_factory_config=[ ConfigurableFieldSpec( id="user_id", # 对应工厂函数参数名 annotation=str, name="用户 ID", description="用户的唯一标识符。", default="", is_shared=True, ), ConfigurableFieldSpec( id="conversation_id", # 对应工厂函数参数名 annotation=str, name="对话 ID", description="对话的唯一标识符。", default="", is_shared=True, ), ], )

参数说明:

  • runnable: 定义大模型的管道
  • get_session_history: 历史工厂函数(定义获取聊天历史的函数)
  • input_messages_key: 指定输入消息的 key,对应 prompt 中的 {input}
  • history_messages_key: 指定历史消息的 key,对应 MessagesPlaceholder 的 variable_name
  • history_factory_config: 配置需要传入工厂函数的参数

它在每一次 invoke 时,固定做 4 件事:

  • 根据 config 调用 get_session_history
  • 读取历史消息
  • 把历史注入 Prompt,再调用 runnable
  • 把「本轮问 + 答」写回存储

10.调用示例

python
# 第一次调用 result = with_message_history.invoke( {"input": "你好"}, config={"configurable": {"user_id": "123", "conversation_id": "1"}}, ) print(result) # 第二次调用(自动带上历史) result2 = with_message_history.invoke( {"input": "我刚才说了什么"}, config={"configurable": {"user_id": "123", "conversation_id": "1"}}, ) print(result2)

11.小结

  • Prompt 是无状态的
  • Runnable 是无状态的
  • 状态只存在于 MessageHistory
  • RunnableWithMessageHistory 负责“粘合”
  • PostgreSQL 让对话真正可持久化

本文作者:鑫 · Dev

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!