3.3 对话能力集成:多轮对话管理与上下文持久化实现
一、多轮对话核心
多轮对话需要维护会话上下文,将历史消息在每次请求时一并发送给模型。模型依赖完整上下文才能理解"你"、"它"等指代,以及延续之前的话题。本节介绍上下文管理、长度控制与持久化方案,并提供可直接运行的完整代码。
二、上下文管理
2.1 内存管理
单机、单进程场景下,可用字典按session_id存储每个会话的messages:
# 会话级messages(内存存储)
session_messages = {}
def get_or_create_messages(session_id: str, system_prompt: str = "你是有帮助的助手"):
if session_id not in session_messages:
session_messages[session_id] = [
{"role": "system", "content": system_prompt}
]
return session_messages[session_id]
def append_and_chat(session_id: str, user_input: str, client) -> str:
messages = get_or_create_messages(session_id)
messages.append({"role": "user", "content": user_input})
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=messages,
temperature=0.7,
max_tokens=500
)
reply = response.choices[0].message.content
messages.append({"role": "assistant", "content": reply})
return reply
注意:进程重启后内存清空,生产环境需持久化。
2.2 长度控制策略
当对话轮次增多,messages可能超出模型的上下文窗口(如gpt-3.5-turbo的16K)。常用策略:
| 策略 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
| 滑动窗口 | 只保留最近N轮(如10轮) | 简单、成本可控 | 早期信息丢失 |
| 摘要压缩 | 将早期对话用LLM摘要,替换为一条摘要消息 | 保留关键信息 | 摘要可能失真、额外API调用 |
| Token计数截断 | 用tiktoken估算,超限时从最早的消息开始删除 | 精确控制长度 | 实现稍复杂 |
| 关键信息提取 | 只保留对后续有用的片段(如用户偏好、任务目标) | 信息密度高 | 提取逻辑复杂 |
2.3 滑动窗口实现示例
def trim_to_last_n_turns(messages: list, n: int = 10) -> list:
"""保留system + 最近n轮对话"""
if len(messages) <= 1 + n * 2: # system + n轮(user+assistant)
return messages
result = [messages[0]] # 保留system
result.extend(messages[-(n * 2):]) # 最近n轮
return result
2.4 基于Token的截断
import tiktoken
def trim_by_tokens(messages: list, max_tokens: int = 4000, model: str = "gpt-3.5-turbo") -> list:
"""从最早的消息开始删除,直至总Token不超过max_tokens"""
enc = tiktoken.encoding_for_model(model)
total = sum(len(enc.encode(m.get("content", ""))) for m in messages)
if total <= max_tokens:
return messages
result = [messages[0]] # 保留system
for m in reversed(messages[1:]):
total -= len(enc.encode(m.get("content", "")))
result.insert(1, m)
if total <= max_tokens:
break
return result
三、持久化方案
3.1 Redis存储
适合分布式、高并发场景。将messages序列化为JSON存储,设置过期时间避免内存占满。
import json
import redis
r = redis.Redis(host='localhost', port=6379, db=0)
def save_messages(session_id: str, messages: list, ttl: int = 86400):
"""保存会话,默认24小时过期"""
r.setex(f"chat:{session_id}", ttl, json.dumps(messages, ensure_ascii=False))
def load_messages(session_id: str) -> list:
data = r.get(f"chat:{session_id}")
return json.loads(data) if data else None
3.2 数据库存储
适合需长期保存、审计、分析的场景。可设计表结构:
| 字段 | 类型 | 说明 |
|---|---|---|
| session_id | varchar | 会话ID |
| role | varchar | system/user/assistant |
| content | text | 消息内容 |
| created_at | datetime | 创建时间 |
查询时按session_id、created_at排序,组装为messages列表。
3.3 方案选型
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 内存 | 开发、单机 | 简单、无依赖 | 重启丢失、无法扩展 |
| Redis | 分布式、高并发 | 快速、支持过期 | 需维护Redis |
| 数据库 | 长期保存、审计 | 持久、可分析 | 读写较慢、需设计表结构 |
四、完整示例:带持久化的多轮对话服务
"""
多轮对话服务(支持Redis持久化与长度控制)
运行: pip install openai python-dotenv redis tiktoken
"""
import json
import os
from dotenv import load_dotenv
from openai import OpenAI
import tiktoken
load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
# 若未安装redis,可改用内存字典
try:
import redis
r = redis.Redis(host=os.getenv("REDIS_HOST", "localhost"), port=6379, db=0)
USE_REDIS = True
except:
USE_REDIS = False
memory_store = {}
def get_messages(session_id: str) -> list:
if USE_REDIS:
data = r.get(f"chat:{session_id}")
return json.loads(data) if data else [{"role": "system", "content": "你是有帮助的助手"}]
return memory_store.get(session_id, [{"role": "system", "content": "你是有帮助的助手"}])
def save_messages(session_id: str, messages: list):
if USE_REDIS:
r.setex(f"chat:{session_id}", 86400, json.dumps(messages, ensure_ascii=False))
else:
memory_store[session_id] = messages
def trim_messages(messages: list, max_tokens: int = 3500) -> list:
enc = tiktoken.encoding_for_model("gpt-3.5-turbo")
total = sum(len(enc.encode(m.get("content", ""))) for m in messages)
if total <= max_tokens:
return messages
result = [messages[0]]
for m in reversed(messages[1:]):
total -= len(enc.encode(m.get("content", "")))
result.insert(1, m)
if total <= max_tokens:
break
return result
def chat(session_id: str, user_input: str) -> str:
messages = get_messages(session_id)
messages.append({"role": "user", "content": user_input})
messages = trim_messages(messages)
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=messages,
temperature=0.7,
max_tokens=500
)
reply = response.choices[0].message.content
messages.append({"role": "assistant", "content": reply})
save_messages(session_id, messages)
return reply
五、小结
多轮对话的核心是上下文管理与长度控制。根据业务需求选择内存、Redis或数据库持久化,并做好Token与轮次限制,避免超长上下文导致的错误与高成本。下一节将介绍GPT-4V图像理解与多模态集成。
下一节预告:3.4 文本生成与多模态能力集成:GPT-4V图像理解实战