06.AI应用搭建--langchain实现历史对话记录(多轮对话基础)
从05.AI应用搭建–langchain输出解析器后,基本介绍完一个简单AI应用的基本流程。但是,大家很容易发现前面的内容,更多是一问一答形式,无法实现追问,那么对于这种场景应该如何解决呢?立刻有人想到:既然可以拼接强化AI和user的提示词内容,那我直接将AI的输出和用户的历史提问记录下来,然后拼接进每次的提问不就可以了,那么恭喜你,已经掌握了多轮对话的实现基础。1、实现多轮对话的基础在于存储历
文章目录
前言
提示:承上启下,系列文章,通过前言会议一下上篇章内容,引入本文内容:
从05.AI应用搭建–langchain输出解析器后,基本介绍完一个简单AI应用的基本流程。但是,大家很容易发现前面的内容,更多是一问一答形式,无法实现追问,那么对于这种场景应该如何解决呢?
立刻有人想到:既然可以拼接强化AI和user的提示词内容,那我直接将AI的输出和用户的历史提问记录下来,然后拼接进每次的提问不就可以了,那么恭喜你,已经掌握了多轮对话的实现基础。
一、如何实现多轮对话存储
通过一段代码简单理解下基础原理:
# 构造提示词。其中MessagesPlaceholder是消息占位符,它的作用是可以动态插入历史对话记录
'''
MessagesPlaceholder的消息结构如下:
[
("human", "你好,我叫小明"),
("system", "你好小明!有什么我能帮助你的吗?。"),
("human", "我最喜欢红色,帮我选一种适合圣诞节的礼物"),
("system", "圣诞帽"),
]
'''
from langchain_community .chat_models import MessagesPlaceholder
#1、构造提示词模板,可以看出和之前讲的没什么不同,只是加了个MessagesPlaceholder,按照其数据结构,很容易理解,就是将历史对话拼接进了提示词中。
# variable_name="conversation",这就是声明历史对话通过哪个参数传入提示词
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个友好的助手,根据对话历史回答问题。"),
MessagesPlaceholder(variable_name="conversation",optional = True ), # 核心:动态插入对话历史,其中optional 默认 False,设为 True 则占位符无内容时不会报错
("human", "{input}"), # 当前用户输入
])
# 2、 模拟对话历史(可来自用户与AI的交互记录)
conversation_history = [
HumanMessage(content="你好,我叫小明"),
AIMessage(content="你好小明!有什么我能帮助你的吗?"),
HumanMessage(content="我忘记我叫什么了"),
]
# 3. 构造链,调用模型。可以看到传入历史记录和原来的填入用户输入没什么不同
chain = prompt | llm
response = chain.invoke({
"conversation": conversation_history, # 填充占位符
"input": "提醒我一下我的名字" # 当前输入
})
#从输出结果可以看出来,大模型从历史对话中找到了自己不知道的内容,并回答了用户的问题
print(response.content)
# 输出示例:你的名字是小明呀~
从这个例子可以看出:
1、想要传入历史对话,只需要在通过MessagesPlaceholder在提示词内拼接历史对话记录即可
2、历史对话记录存储时,需要记录对话的角色信息如system、human等
3、大模型会从历史对话记录中获取信息回答用户提问
但是,上面的例子有个大问题,对话记录存在内存里的,服务重启对话记录就清空了,若要实现大型、长期的对话应用,这肯定不行。有无办法想数据库一样,将数据存在本地/服务器上,要用的时候去获取出来? 有的,可以用FileChatMessageHistory、RedisChatMessageHistory将记录存储到文件或Redis内(本文只讲FileChatMessageHistory,RedisChatMessageHistory的原理差不多,只是调用Redis的方法存放在不同位置而已,可自行补充)
二、FileChatMessageHistory的使用方法
若相同代码import时报错,可能是langchain的版本不一致导致的,我使用的版本如下
pip install -i https://mirrors.aliyun.com/pypi/simple/ langchain==0.2.10 langchain-openai langchain-community python-dotenv
1.代码(为了演示多轮对话,使用了函数)
import os
from dotenv import load_dotenv # 补充:加载环境变量
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
from langchain_community.chat_message_histories import FileChatMessageHistory
from langchain.memory import ConversationBufferMemory
# 加载.env文件(国内用户必加,否则API Key获取不到)
load_dotenv()
# 大模型配置
MODULE_API_KEY = os.getenv("DASHSCOPE_API_KEY")
MODULE_BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1"
MODULE_NAME = "qwen-plus"
# ====================== 1. 初始化Qwen-plus模型 ======================
def init_qwen_plus_cn():
return ChatOpenAI(
api_key=MODULE_API_KEY,
model=MODULE_NAME,
base_url=MODULE_BASE_URL,
temperature=0.6,
max_tokens=2048,
request_timeout=30, # 补充:国内网络超时配置(避免卡死)
max_retries=2 # 补充:失败重试(适配国内网络波动)
)
# ====================== 2. 本地文件存储 ======================
def file_based_chat_demo():
try:
client = init_qwen_plus_cn()
except ValueError as e:
print(f"初始化失败:{e}")
return
# 按用户ID分文件存储(国内合规)
user_id = "user_001"
history_file = f"./chat_history/{user_id}_qwen_history.json"
os.makedirs("./chat_history", exist_ok=True)
# 初始化文件存储 + 内存管理。message_history可简单理解为存储得对话内容
message_history = FileChatMessageHistory(file_path=history_file)
# ConversationBufferMemory 用于管理历史对话记录
'''
前提:ConversationBufferMemory存储的是json格式的数据,所以数据的存储和获取都是用key进行操作
chat_memory:消息存储在底层存储介质的位置;
memory_key :该历史记录对应的key值
input_key : 用户输入内容的key值;
output_key:AI输出内容的key值;
'''
memory = ConversationBufferMemory(
chat_memory=message_history,
return_messages=True,
memory_key="chat_history", # 关键:与load_memory_variables的键名对齐
input_key="human_input", # 关键:与save_context的inputs键名对齐
output_key="ai_output" # 关键:与save_context的outputs键名对齐
)
print("===== 【国内版】Qwen-plus 文件存储对话 Demo =====")
print("输入 '退出' 结束对话(输入内容会本地保存)")
while True:
query = input("\n请输入你的问题:")
if query.strip() in ["退出", "exit", "quit"]:
print(f"对话结束,历史已保存至:{history_file}")
break
if not query.strip(): # 处理空输入(避免调用空字符串)
print("输入不能为空,请重新输入!")
continue
# 加载历史消息load_memory_variables方法获取内存里的历史对话记录,通过chat_history是对应的历史记录的key值。通过这个可以精准获取指定文件里的内容
history_msgs = memory.load_memory_variables({})["chat_history"]
# 构造国内合规的系统提示词
system_msg = SystemMessage(content="""
你是通义千问Qwen-plus,严格遵守中国法律法规,拒绝回答敏感问题。
回答简洁、专业,符合国内用户的使用习惯,禁止输出无关内容。
""")
# 拼接上下文(系统消息 + 历史 + 当前输入)。*history_msgs就是把history_msgs解包。简单理解就是就列表里的每个元素取出来放在新的列表current_context里(即两个列表的合并)
current_context = [system_msg, *history_msgs, HumanMessage(content=query)]
# 调用模型
try:
response = client.invoke(current_context)
# 关键:兼容Qwen-plus的返回格式(可能是字符串/AIMessage)
if isinstance(response, str):
ai_content = response
else:
ai_content = response.content
except Exception as e:
print(f"模型调用失败:{str(e)}")
continue
# 保存历史(键名与memory配置完全对齐)。这一步会向文件内写入这轮(AI中每轮一般包含用户提问+ai回答)对话数据
memory.save_context(
inputs={"human_input": query},
outputs={"ai_output": ai_content}
)
print(f"千问回答:{ai_content}")
if __name__ == "__main__":
file_based_chat_demo()
2. 运行结果
2.1 可以看到,AI可以通过历史记录回答原本不知道的问题

2.2 看看对话记录怎么存储的(就是按照固定格式以json格式存储)
{
"type": "human",
"data": {
"content": "你好,我叫什么名字?",
"additional_kwargs": {},
"response_metadata": {},
"type": "human",
"name": null,
"id": null,
"example": false
}
},
{
"type": "ai",
"data": {
"content": "你好,我无法知道你的名字呢。你可以告诉我你的名字,我会尊重并礼貌地与你交流。",
"additional_kwargs": {},
"response_metadata": {},
"type": "ai",
"name": null,
"id": null,
"example": false,
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": null
}
},
{
"type": "human",
"data": {
"content": "我叫幽奇,男,100岁,请你记住我",
"additional_kwargs": {},
"response_metadata": {},
"type": "human",
"name": null,
"id": null,
"example": false
}
},
{
"type": "ai",
"data": {
"content": "你好,幽奇!虽然你说100岁,但依然精神矍铄、童心未泯,真让人佩服!我会记住你的名字,也感谢你的信任。有什么问题或需要帮助,随时告诉我哦~",
"additional_kwargs": {},
"response_metadata": {},
"type": "ai",
"name": null,
"id": null,
"example": false,
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": null
}
},
{
"type": "human",
"data": {
"content": "你好,我叫什么名字?",
"additional_kwargs": {},
"response_metadata": {},
"type": "human",
"name": null,
"id": null,
"example": false
}
},
{
"type": "ai",
"data": {
"content": "你好,你叫幽奇。很高兴再次见到你!有什么我可以帮你的吗?",
"additional_kwargs": {},
"response_metadata": {},
"type": "ai",
"name": null,
"id": null,
"example": false,
"tool_calls": [],
"invalid_tool_calls": [],
"usage_metadata": null
}
},
三、历史记录截留
每次访问携带历史记录问AI,实现了多轮对话,但是,带来了个新问题,token浪费,随着对话论述增多,将浪费大量token,所以特定场景(如长期的多轮对话),需要现在最多取多少轮的历史记录或最长token
实现方式(举个例子):
# 自己实现个函数截留历史记录,这里面的3就是只截留最近的3轮对话
def truncate_chat_history(chat_history: FileChatMessageHistory, max_rounds: int = 3):
all_messages = chat_history.messages
keep_count = 2 * max_rounds
if len(all_messages) > keep_count:
#接取最近的6条数据(即3轮)
truncated_messages = all_messages[-keep_count:]
#清除历史数据
chat_history.clear()
for msg in truncated_messages:
chat_history.add_message(msg)
return len(chat_history.messages)
总结
1、实现多轮对话的基础在于存储历史数据
2、存储历史数据的方法,可用MessagesPlaceholder自己实现存储过程,也可以用FileChatMessageHistory自动化管理历史记录
3、为了防止多轮对话历史记录太长导致大量浪费token,可以截留历史记录
更多推荐



所有评论(0)