带历史记忆的对话脚本
基于 LangChain 框架实现的交互式问答脚本,支持对话历史记忆功能,能够根据上下文进行连续对话。
设计思路
核心目标
实现一个能够"记住"对话内容的问答助手,支持多轮对话,模型可以根据之前的对话上下文来理解当前问题。
架构设计
┌─────────────────────────────────────────────────────────────┐
│ 交互式问答循环 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 用户输入问题 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 调用问答链(问题 + 历史消息) │ │
│ │ ┌───────────┐ ┌───────────┐ ┌───────────────┐ │ │
│ │ │ Prompt │ │ ChatOllama│ │ StrOutputParser│ │ │
│ │ │ Template │→ │ LLM │→ │ │ │ │
│ │ └───────────┘ └───────────┘ └───────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 输出回答 + 更新历史消息 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
模块划分
|-------|-----------|---------------------------------------------|
| 模块 | 功能 | 关键技术 |
| 环境配置 | 加载环境变量 | python-dotenv |
| 模型构建 | 创建 LLM 实例 | ChatOllama |
| 提示词模板 | 构建对话模板 | ChatPromptTemplate, MessagesPlaceholder |
| 链式调用 | 组合处理流程 | LCEL (LangChain Expression Language) |
| 历史管理 | 维护对话上下文 | HumanMessage, AIMessage |
实现过程
1. 环境初始化
from dotenv import load_dotenv
load_dotenv()
使用 .env 文件管理敏感配置,避免硬编码模型名称和 URL。
2. 构建 LLM 实例
llm = ChatOllama(
model=os.getenv("LOCAL_MODEL_NAME"),
base_url=os.getenv("LOCAL_MODEL_URL")
)
为什么选择 ChatOllama而非 OllamaLLM?
ChatOllama原生支持消息格式 (List[BaseMessage])- 内置角色区分(system/human/ai)
- 更适合多轮对话场景
3. 构建提示词模板
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个专业的文档问答助手..."),
MessagesPlaceholder(variable_name="history"), # 历史消息占位
("human", "{question}") # 当前问题
])
关键点: MessagesPlaceholder
- 动态插入历史消息
- 保持消息的时间顺序
- 无需手动拼接字符串
4. 构建 LCEL 链
chain = prompt | llm | StrOutputParser()
使用 LangChain Expression Language (LCEL) 实现链式调用:
|运算符连接组件- 自动传递数据流
- 代码简洁清晰
5. 历史消息管理
history: List[BaseMessage] = []
# 每轮对话后更新
history.append(HumanMessage(content=question))
history.append(AIMessage(content=answer))
消息类型说明:
SystemMessage:系统设定HumanMessage:用户提问AIMessage:助手回答
安装配置
依赖安装
pip install langchain-core langchain-ollama python-dotenv
依赖包说明
|--------------------|-----------|-------------------|
| 包名 | 版本要求 | 用途 |
| langchain-core | >= 0.1.0 | 核心组件(消息、模板、链、解析器) |
| langchain-ollama | >= 0.1.0 | Ollama 模型集成 |
| python-dotenv | >= 1.0.0 | 环境变量管理 |
环境变量配置
在项目根目录创建 .env 文件:
LOCAL_MODEL_NAME=qwen2.5
LOCAL_MODEL_URL=http://localhost:11434
前置条件
-
安装并启动 Ollama
-
下载所需模型:
ollama pull qwen2.5
知识点详解
1. LangChain 核心概念
Chat Models vs LLMs
|------|-----------------|------|
| 特性 | Chat Models | LLMs |
| 输入格式 | 消息列表 | 字符串 |
| 角色支持 | system/human/ai | 无 |
| 适用场景 | 对话、多轮交互 | 文本生成 |
Messages 类型
from langchain_core.messages import (
SystemMessage, # 系统指令
HumanMessage, # 用户消息
AIMessage, # AI 回复
BaseMessage # 基类
)
2. LCEL (LangChain Expression Language)
LCEL 是 LangChain 的声明式语法,用于组合组件:
# 传统方式
output = parser(llm(prompt(input)))
# LCEL 方式
chain = prompt | llm | parser
output = chain.invoke(input)
优势:
- 代码更清晰
- 自动处理数据传递
- 支持流式输出、异步调用
3. Output Parsers
StrOutputParser 将模型输出转换为字符串:
from langchain_core.output_parsers import StrOutputParser
# AIMessage -> str
parser = StrOutputParser()
result = parser.invoke(ai_message)
4. Prompt Templates
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
# from_messages: 构建多角色对话模板
prompt = ChatPromptTemplate.from_messages([
("system", "系统提示词"),
MessagesPlaceholder(variable_name="history"), # 动态消息
("human", "{user_input}") # 变量占位
])
# 调用时传入变量
prompt.invoke({
"history": [...],
"user_input": "你好"
})
5. 对话历史管理策略
本脚本使用内存列表存储历史,适合简单场景。生产环境可考虑:
|------------------|---------|---------|
| 方案 | 优点 | 缺点 |
| 内存列表 | 简单直接 | 进程重启丢失 |
| Redis | 持久化、跨进程 | 需要额外服务 |
| 数据库 | 持久化、可查询 | 复杂度高 |
| LangChain Memory | 开箱即用 | 已废弃,不推荐 |
交互示例

完整代码
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage, BaseMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_ollama import ChatOllama
import os
from dotenv import load_dotenv
from typing import List
# 目标:调用大模型实现带有历史记忆的问答,当我输入exit的时候结束对话
# 1. 构建问答链
# 2. 调用问答链
# 3. 输出回答
# 环境变量
load_dotenv()
# 1. 构建问答链(带历史记忆)
def build_qa_chain():
# 构建 ChatOllama 大模型(支持消息格式)
llm = ChatOllama(
model=os.getenv("LOCAL_MODEL_NAME"),
base_url=os.getenv("LOCAL_MODEL_URL")
)
# 构建提示词模板,包含历史消息占位符
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个专业的文档问答助手。请遵循以下规则:\n1. 回答要简洁准确,不要编造内容\n2. 如果有历史对话,请结合上下文回答"),
MessagesPlaceholder(variable_name="history"),
("human", "{question}")
])
# 构建链
chain = prompt | llm | StrOutputParser()
return chain
# 2. 调用问答链(带历史记忆)
def call_qa_chain(chain, question: str, history: List[BaseMessage]):
# 调用链,传入问题和历史
response = chain.invoke({
"question": question,
"history": history
})
return response
# 3. 输出回答
def print_answer(answer):
print(f"助手回答: {answer}")
# 交互式问答(带历史记忆)
def interactive_qa():
# 1. 构建问答链
chain = build_qa_chain()
# 初始化历史消息列表
history: List[BaseMessage] = []
# 2. 调用问答链
while True:
question = input("\n请输入问题(输入 exit 退出):")
if question == "exit":
print("再见!")
break
if not question.strip():
continue
# 调用问答链
answer = call_qa_chain(chain, question, history)
# 输出回答
print_answer(answer)
# 更新历史:添加用户问题和助手回答
history.append(HumanMessage(content=question))
history.append(AIMessage(content=answer))
# 可选:显示当前历史长度
print(f"[已记录 {len(history)//2} 轮对话]")
if __name__ == '__main__':
interactive_qa()
总结
大模型的组件依赖更新很快,现在使用的方法可能几天后就会不能用了,具体还需要参考官方文档学习:https://langchain-doc.cn/
这是一个小的完整案例,可以根据自己的需求进行改造,比如下面的一些功能,后面我也会分享一个完整的项目案例,现在还在想做一个什么应用。后续会不断更新大模型应用开发的探索和分享
- 支持更多文档格式 :扩展
load_documents()添加 Markdown、Excel 等支持 - 优化检索策略:使用混合检索(向量 + 关键词)提升召回率
- 流式输出 :使用
rag_chain.stream()实现打字机效果 - Web 界面:集成 Streamlit 或 Gradio 构建 Web 应用