LangChain 多轮对话完全指南
目录
什么是多轮对话
对话历史管理
MessagesPlaceholder 占位符
对话记忆机制
多轮对话实战
进阶用法
什么是多轮对话
单轮对话 vs 多轮对话
单轮对话:每次独立问答,不记住之前的内容
# 单轮:每次都是全新的开始 response = model.invoke("北京天气如何?") response = model.invoke("那上海呢?") # AI不知道"那上海"指的是什么多轮对话:记住对话历史,上下文关联
# 多轮:记住之前的对话 messages = [ HumanMessage(content="北京天气如何?"), AIMessage(content="北京今天晴天,25度"), HumanMessage(content="那上海呢?") #AI知道"那上海"指的是上海的天气] response = model.invoke(messages)为什么需要多轮对话?
| 场景 | 单轮问题 | 多轮优势 |
|---|---|---|
| 追问 | "那上海呢?" | 自动理解指代上文 |
| 澄清 | "不对,是冬天" | 记住之前的错误修正 |
| 任务延续 | "继续写代码" | 记得之前写了什么 |
| 个性化 | "我的名字是小明" | 记住用户信息 |
对话历史管理
最简单的多轮对话
from langchain.chat_models import init_chat_model from langchain.messages import SystemMessage, HumanMessage, AIMessage import os from dotenv import load_dotenv load_dotenv(encoding='utf-8') model = init_chat_model( model="qwen-plus", model_provider="openai", api_key=os.getenv("aliQwen-api"), base_url="https://dashscope.aliyuncs.com/compatible-mode/v1" ) # 构建对话历史 messages = [ SystemMessage(content="你是一个旅游助手"), HumanMessage(content="推荐几个国内旅游城市"), AIMessage(content="我推荐:成都、杭州、厦门、丽江"), HumanMessage(content="成都有什么好吃的?"), ] response = model.invoke(messages) print(response.content)对话历史累积
# 初始化对话历史 history = [] # 第1轮对话 messages = history + [ HumanMessage(content="推荐几个国内旅游城市") ] response = model.invoke(messages) print(f"AI: {response.content}") # 更新历史 history.append(HumanMessage(content="推荐几个国内旅游城市")) history.append(AIMessage(content=response.content)) # 第2轮对话(带上历史) messages = history + [HumanMessage(content="成都有什么好吃的?")] response = model.invoke(messages) print(f"AI: {response.content}") # 更新历史 history.append(HumanMessage(content="成都有什么好吃的?")) history.append(AIMessage(content=response.content)) # 第3轮对话 messages = history + [HumanMessage(content="那杭州呢?")] response = model.invoke(messages) print(f"AI: {response.content}")MessagesPlaceholder 占位符
为什么需要占位符?
在构建 Prompt 模板时,对话历史的长度是动态的,不可能写死:
第1轮:0条历史
第10轮:10条历史
这时候需要MessagesPlaceholder占位符,在运行时动态插入历史消息。
显式使用 MessagesPlaceholder
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder # 构建模板,占位符用于插入对话历史 prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个资深的Python开发工程师,请认真回答我提出的Python相关的问题"), MessagesPlaceholder("memory"), # 动态插入对话历史 ("human", "{question}") ]) # 调用时传入历史消息 prompt_value = prompt.invoke({ "memory": [ HumanMessage(content="我的名字叫亮仔,是一名程序员"), AIMessage(content="好的,亮仔你好,很高兴认识你"), ], "question": "Python的装饰器是什么?" })隐式使用占位符
# 隐式写法:("placeholder", "{memory}") 等价于 MessagesPlaceholder("memory") prompt = ChatPromptTemplate.from_messages([ ("placeholder", "{memory}"), ("system", "你是一个资深的Python开发工程师"), ("human", "{question}") ]) prompt_value = prompt.invoke({ "memory": [ HumanMessage(content="我的名字叫亮仔"), AIMessage(content="好的,亮仔你好"), ], "question": "装饰器是什么?" })两种写法对比
# 显式(更清晰,推荐) MessagesPlaceholder("memory") # 隐式(更简洁) ("placeholder", "{memory}")对话记忆机制
什么是记忆机制?
记忆机制是AI 应用记住之前对话内容的能力。常见实现方式:
| 类型 | 说明 | 适用场景 |
|---|---|---|
| ConversationBufferMemory | 完整保存所有历史 | 对话短、上下文重要 |
| ConversationTokenBufferMemory | 按Token数限制 | 对话长、需要控制成本 |
| ConversationSummaryMemory | 自动总结历史 | 超长对话 |
| VectorStoreRetrieverMemory | 向量检索记忆 | 超长对话、语义检索 |
ConversationBufferMemory
最简单的记忆方式,保存完整对话历史:
from langchain.memory import ConversationBufferMemory from langchain.chains import ConversationChain # 创建记忆对象 memory = ConversationBufferMemory() # 添加对话历史 memory.save_context({"input": "我叫小明"}, {"output": "你好小明!"}) memory.save_context({"input": "我最喜欢的颜色是蓝色"}, {"output": "蓝色是很棒的选择!"}) # 加载历史 history = memory.load_memory_variables({}) print(history) # {'history': 'Human: 我叫小明\nAI: 你好小明!\nHuman: 我最喜欢的颜色是蓝色\nAI: 蓝色是很棒的选择!'}结合 LangChain 链使用
from langchain.chat_models import init_chat_model from langchain.memory import ConversationBufferMemory from langchain.chains import ConversationChain from langchain.prompts import PromptTemplate import os from dotenv import load_dotenv load_dotenv(encoding='utf-8') model = init_chat_model( model="qwen-plus", model_provider="openai", api_key=os.getenv("aliQwen-api"), base_url="https://dashscope.aliyuncs.com/compatible-mode/v1" ) # 创建记忆链 memory = ConversationBufferMemory() # 自定义Prompt template = """你是一个友好的AI助手。 历史对话: {history} 用户新消息:{input} 你的回复:""" prompt = PromptTemplate( template=template, input_variables=["history", "input"] ) # 创建对话链 chain = ConversationChain( llm=model, memory=memory, prompt=prompt, verbose=True ) # 对话 response = chain.invoke({"input": "我叫小明"}) print(response) response = chain.invoke({"input": "我叫什么名字?"}) print(response) # 会记住叫小明多轮对话实战
基础版本:手动管理历史
from langchain.chat_models import init_chat_model from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_core.messages import SystemMessage, HumanMessage, AIMessage import os from dotenv import load_dotenv load_dotenv(encoding='utf-8') model = init_chat_model( model="qwen-plus", model_provider="openai", api_key=os.getenv("aliQwen-api"), base_url="https://dashscope.aliyuncs.com/compatible-mode/v1" ) # 构建带记忆的模板 prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个旅游助手,为用户提供旅行建议"), MessagesPlaceholder("history"), ("human", "{question}") ]) def chat(question, history): """对话函数""" messages = prompt.format_messages(history=history, question=question) response = model.invoke(messages) # 更新历史 history.append(HumanMessage(content=question)) history.append(AIMessage(content=response.content)) return response.content, history # 开始对话 history = [] print("=== 第1轮 ===") answer1, history = chat("推荐几个国内旅游城市", history) print(f"AI: {answer1}") print("\n=== 第2轮 ===") answer2, history = chat("北京有什么好吃的?", history) print(f"AI: {answer2}") print("\n=== 第3轮 ===") answer3, history = chat("那上海呢?", history) # "那上海呢"需要结合上文理解 print(f"AI: {answer3}")进阶版本:支持上下文理解
from langchain.chat_models import init_chat_model from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_core.messages import SystemMessage, HumanMessage, AIMessage import os from dotenv import load_dotenv load_dotenv(encoding='utf-8') model = init_chat_model( model="qwen-plus", model_provider="openai", api_key=os.getenv("aliQwen-api"), base_url="https://dashscope.aliyuncs.com/compatible-mode/v1" ) # 改进的系统提示词,让AI更好地理解上下文 prompt = ChatPromptTemplate.from_messages([ ("system", """你是一个专业的旅游顾问。 请根据对话历史,理解用户的上下文。 如果用户用"那...呢"、"还有呢"等指代词,要结合之前的对话来理解。 例如: - 问"北京之后上海呢?" 要理解这是在问上海的旅游推荐"""), MessagesPlaceholder("history"), ("human", "{question}") ]) def chat(question, history): messages = prompt.format_messages(history=history, question=question) response = model.invoke(messages) history.append(HumanMessage(content=question)) history.append(AIMessage(content=response.content)) return response.content, history # 对话测试 history = [] answer1, history = chat("北京有什么景点?", history) print(f"AI: {answer1}") answer2, history = chat("那上海呢?", history) print(f"AI: {answer2}") # 应该理解是在问上海的景点生产版本:带用户信息记忆
from langchain.chat_models import init_chat_model from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_core.messages import SystemMessage, HumanMessage, AIMessage import os from dotenv import load_dotenv load_dotenv(encoding='utf-8') model = init_chat_model( model="qwen-plus", model_provider="openai", api_key=os.getenv("aliQwen-api"), base_url="https://dashscope.aliyuncs.com/compatible-mode/v1" ) # 构建带用户信息的模板 prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个私人旅行助手。用户信息:{user_info}"), MessagesPlaceholder("history"), ("human", "{question}") ]) class TravelChat: def __init__(self): self.history = [] self.user_info = { "name": "小明", "preferences": "喜欢美食和自然风光", "budget": "中等预算", "travel_style": "自由行" } def chat(self, question): messages = prompt.format_messages( user_info=self.user_info, history=self.history, question=question ) response = model.invoke(messages) self.history.append(HumanMessage(content=question)) self.history.append(AIMessage(content=response.content)) return response.content def reset(self): """重置对话历史""" self.history = [] # 使用 chat = TravelChat() print("=== 对话1 ===") print(chat.chat("我叫小明,喜欢吃辣,帮我推荐一个旅游城市")) print("\n=== 对话2 ===") print(chat.chat("那里有什么好吃的辣味美食?"))进阶用法
Token数限制(控制成本)
对话历史太长会导致 Token 消耗过大,需要限制:
from langchain.memory import ConversationTokenBufferMemory from langchain.chat_models import init_chat_model import os from dotenv import load_dotenv load_dotenv(encoding='utf-8') model = init_chat_model( model="qwen-plus", model_provider="openai", api_key=os.getenv("aliQwen-api"), base_url="https://dashscope.aliyuncs.com/compatible-mode/v1" ) # 限制最大Token数为2000 memory = ConversationTokenBufferMemory( llm=model, max_token_limit=2000 ) # 添加大量对话 for i in range(20): memory.save_context({"input": f"这是第{i}轮对话"}, {"output": f"这是第{i}轮回复"}) # 自动截断旧的历史 history = memory.load_memory_variables({}) print(f"历史长度: {len(history['history'])}") # 会自动保留最新的对话,删除旧的自动总结历史(超长对话)
from langchain.memory import ConversationSummaryMemory from langchain.chat_models import init_chat_model import os from dotenv import load_dotenv load_dotenv(encoding='utf-8') model = init_chat_model( model="qwen-plus", model_provider="openai", api_key=os.getenv("aliQwen-api"), base_url="https://dashscope.aliyuncs.com/compatible-mode/v1" ) # 自动总结历史 memory = ConversationSummaryMemory(llm=model) # 添加对话 memory.save_context({"input": "我叫小明,是一名程序员"}, {"output": "你好小明!"}) memory.save_context({"input": "我工作5年了"}, {"output": "经验丰富啊!"}) memory.save_context({"input": "我会Python、Java、Go"}, {"output": "技术栈很广!"}) # 获取总结后的历史 history = memory.load_memory_variables({}) print(history['history']) # 会自动总结为一段话,而不是保留所有对话向量记忆(语义检索)
from langchain.memory import VectorStoreRetrieverMemory from langchain.vectorstores import Chroma from langchain.embeddings import OpenAIEmbeddings from langchain.chat_models import init_chat_model import os from dotenv import load_dotenv load_dotenv(encoding='utf-8') # 创建向量存储 vectorstore = Chroma(embedding_function=OpenAIEmbeddings()) memory = VectorStoreRetrieverMemory( vectorstore=vectorstore, search_kwargs={"k": 3} # 返回最相关的3条记忆 ) # 保存记忆(带描述) memory.save_context( {"input": "用户喜欢日料"}, {"output": "已记录用户的日料偏好"} ) memory.save_context( {"input": "用户下周要去东京"}, {"output": "已记录用户的东京行程"} ) # 检索相关记忆 memory.load_memory_variables({"prompt": "用户有什么美食偏好?"}) # 会语义检索返回"喜欢日料"相关记忆常见问题
1. 对话历史越来越长怎么办?
# 方法1:限制Token数 from langchain.memory import ConversationTokenBufferMemory memory = ConversationTokenBufferMemory(llm=model, max_token_limit=2000) # 方法2:自动总结 from langchain.memory import ConversationSummaryMemory memory = ConversationSummaryMemory(llm=model) # 方法3:只保留最近N轮 def limit_history(history, max_turns=5): return history[-max_turns * 2:] # 每轮2条消息(问+答)
2. 如何让AI记住用户信息?
# 方法1:放在系统提示词里 prompt = ChatPromptTemplate.from_messages([ ("system", "用户叫{name},喜欢{preference}"), MessagesPlaceholder("history"), ("human", "{question}") ]) # 方法2:每次传入用户信息 messages = prompt.format_messages( user_info=user_info, history=history, question=question )3. 如何让AI理解指代词?
# 改进系统提示词 system_prompt = """你是一个专业的AI助手。 请注意理解对话中的指代词: - "那...呢" 通常指代之前提到的事物 - "他/她/它" 指代之前提到的人或物 - "然后呢" 通常继续之前的话题 结合上下文理解用户真实意图。"""
4. 如何持久化对话历史?
import json # 保存到文件 def save_history(history, filename="history.json"): with open(filename, "w", encoding="utf-8") as f: json.dump([msg.to_json() for msg in history], f, ensure_ascii=False) # 从文件加载 def load_history(filename="history.json"): with open(filename, "r", encoding="utf-8") as f: data = json.load(f) return [HumanMessage(**m) if m["type"]=="human" else AIMessage(**m) for m in data]
总结
多轮对话核心概念
| 概念 | 作用 | 代码 |
|---|---|---|
| 对话历史 | 记住之前的问答 | history.append(HumanMessage(...)) |
| MessagesPlaceholder | 动态插入历史到模板 | MessagesPlaceholder("memory") |
| ConversationBufferMemory | 完整保存历史 | memory.load_memory_variables({}) |
| ConversationTokenBufferMemory | 限制Token数 | max_token_limit=2000 |
| ConversationSummaryMemory | 自动总结历史 | memory.load_memory_variables({}) |
选择记忆类型
对话长度短 →ConversationBufferMemory(最简单) 对话长度中等 → ConversationTokenBufferMemory(控制成本) 对话长度长 → ConversationSummaryMemory(自动总结) 对话长度超长 →VectorStoreRetrieverMemory(向量检索)
多轮对话模板
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个AI助手"), MessagesPlaceholder("history"), # 对话历史 ("human", "{question}") ]) def chat(question, history): messages = prompt.format_messages(history=history, question=question) response = model.invoke(messages) history.append(HumanMessage(content=question)) history.append(AIMessage(content=response.content)) return response.content, history