DeepSeek API 会话记忆实现:从问题复现到代码落地
问题现象:AI 为什么「失忆」了
很多开发者第一次调用 DeepSeek API 时会遇到这样的困惑:第一轮问「北京今天天气怎么样」,AI 回答得挺好;紧接着追问「那上海呢」,AI 却一脸茫然,仿佛刚才的对话从未发生。
这不是 bug,而是大模型 API 的默认行为。我们用 APIFOX 做个对比实验:
第一轮请求(正常)
{
"messages": [
{"role": "system", "content": "你是一个 helpful 的助手"},
{"role": "user", "content": "推荐一本 Python 入门书"}
]
}AI 返回:《Python编程:从入门到实践》是一本经典选择……
第二轮请求(异常)
{
"messages": [
{"role": "system", "content": "你是一个 helpful 的助手"},
{"role": "user", "content": "作者是谁"}
]
}AI 返回:请问您指的是哪本书的作者?
问题很明显:第二轮请求里完全没有提到「Python 入门书」,AI 自然不知道我们在问什么。
根源:大模型的「无状态」设计
DeepSeek API 本质上是一个 HTTP 接口,每次请求都是独立的。服务器不会为你的对话开辟一块内存空间,也不会自动关联你的上一次提问。
这种设计有几个现实考量:
- 成本:为每个用户维护会话状态需要大量服务器资源
- 并发:无状态架构更容易水平扩展,应对高并发
- 灵活性:开发者可以自定义记忆策略,比如只保留最近 3 轮、或者按 token 数截断
代价就是:记忆功能必须由调用方实现。
实现原理:messages 列表的完整传递
解决方案很简单:每次请求时,把历史对话全部塞进 messages 数组。
正确的第二轮请求应该是这样:
{
"messages": [
{"role": "system", "content": "你是一个 helpful 的助手"},
{"role": "user", "content": "推荐一本 Python 入门书"},
{"role": "assistant", "content": "《Python编程:从入门到实践》是一本经典选择……"},
{"role": "user", "content": "作者是谁"}
]
}注意数组里的第三项:我们必须把 AI 的第一轮回答原样传回去,角色标记为 assistant。这样 DeepSeek 才能理解「作者是谁」是在追问那本书。
格式规范
messages 数组必须满足以下规则:
| 要求 | 说明 |
|---|---|
| 顺序严格交替 | user → assistant → user → assistant,不能连续两个 user |
| 以 system 开头(可选) | 如果有系统提示词,必须放在最前面 |
| 以 user 结尾 | 最后一个必须是用户的新问题 |
| 内容原样保留 | 不要修改历史对话的原文,包括 AI 的废话 |
APIFOX 实操验证
打开 APIFOX,在之前的 DeepSeek 接口配置基础上,修改 Body 部分:
步骤 1:构造完整上下文
先手动模拟一轮对话。在「前置操作」里添加一个「自定义脚本」,或者直接编辑 Body:
{
"model": "deepseek-chat",
"messages": [
{"role": "system", "content": "你是资深 Python 开发者"},
{"role": "user", "content": "解释下 Python 的 GIL"},
{"role": "assistant", "content": "GIL(Global Interpreter Lock)是 CPython 解释器的全局锁……"},
{"role": "user", "content": "这对多线程有什么影响"}
],
"stream": false
}步骤 2:发送并观察响应
点击发送后,AI 的回答应该是这样的:
由于 GIL 的存在,同一时刻只有一个线程能执行 Python 字节码。这意味着……
关键词「由于 GIL 的存在」说明 AI 成功关联了上文。如果它重新解释「什么是 GIL」,说明你的 messages 顺序有问题。
步骤 3:流式输出同样适用
把 stream 改为 true,messages 的格式不变。只是响应变成逐字推送,需要前端做累积显示。
Python 代码封装示例
实际项目中,你需要在服务端维护每个用户的对话历史。这里给一个最小可用的 Flask 示例:
from flask import Flask, request, jsonify
import requests
app = Flask(__name__)
# 简易内存存储:生产环境请用 Redis
user_sessions = {}
DEEPSEEK_API_KEY = "your-api-key"
DEEPSEEK_URL = "https://api.deepseek.com/chat/completions"
@app.route('/chat', methods=['POST'])
def chat():
user_id = request.json.get('user_id')
new_message = request.json.get('message')
# 获取或初始化该用户的会话历史
if user_id not in user_sessions:
user_sessions[user_id] = [
{"role": "system", "content": "你是一个 helpful 的助手"}
]
# 追加用户新消息
user_sessions[user_id].append(
{"role": "user", "content": new_message}
)
# 调用 DeepSeek API
resp = requests.post(
DEEPSEEK_URL,
headers={"Authorization": f"Bearer {DEEPSEEK_API_KEY}"},
json={
"model": "deepseek-chat",
"messages": user_sessions[user_id],
"stream": False
}
)
result = resp.json()
assistant_reply = result['choices'][0]['message']['content']
# 关键:把 AI 回复也存进历史
user_sessions[user_id].append(
{"role": "assistant", "content": assistant_reply}
)
return jsonify({"reply": assistant_reply})
if __name__ == '__main__':
app.run(debug=True)测试方式:
curl -X POST http://localhost:5000/chat \
-H "Content-Type: application/json" \
-d '{"user_id": "user_001", "message": "你好"}'
curl -X POST http://localhost:5000/chat \
-H "Content-Type: application/json" \
-d '{"user_id": "user_001", "message": "刚才说了什么"}'第二次请求应该返回「你好,我是……」之类的内容,证明 AI 记得第一轮对话。
避坑指南
1. 顺序错误是最常见的坑
// 错误:连续两个 user
[
{"role": "user", "content": "你好"},
{"role": "user", "content": "天气怎么样"}
]
// 错误:assistant 在 user 前面
[
{"role": "assistant", "content": "你好"},
{"role": "user", "content": "你好"}
]DeepSeek 会返回 400 错误,提示 messages 格式非法。
2. 上下文太长会爆 token
DeepSeek 的上下文窗口是 64K,但超过 8K 后价格会上涨。实际建议:
- 保留最近 10-20 轮对话
- 或者按 token 数截断,保留 4000 token 左右
- 超长对话可以考虑摘要压缩:把早期对话让 AI 总结成一段背景描述
3. 敏感信息别往上下文里塞
如果你实现了「查看历史」功能,注意用户可能在对话中输入过密码、身份证号。要么做前端脱敏,要么定期清理历史记录。
4. 多用户必须隔离
上面的 Flask 示例用 user_id 做 key,生产环境请换成 Redis 或数据库,并且注意:
- 不要用 IP 地址做 key(NAT 环境下多个用户共享 IP)
- 设置合理的过期时间(比如 30 分钟无操作清空)
- 考虑用户主动「清空对话」的功能
5. 系统提示词别放中间
有些开发者想动态修改系统提示,结果把新的 system 消息插到数组中间。这会导致 API 报错,或者 AI 行为异常。系统提示只能出现在 messages[0]。
速查表
| 场景 | 操作 |
|---|---|
| 开启新对话 | 只传 system + 用户第一条消息 |
| 继续对话 | 追加历史:user → assistant → user(新) |
| 清空记忆 | 丢弃该用户的 messages 数组,重新初始化 |
| 控制长度 | 截断数组,保留尾部 N 条,或按 token 数裁剪 |
| 多用户隔离 | 用 user_id 做 key,分别存储 |
| 调试格式问题 | 打印完整 messages,检查角色交替和顺序 |
下一步
会话记忆搞定后,你的 AI 助手已经具备了基础对话能力。接下来可以思考:
- 如何让 AI 调用外部工具(function calling)
- 如何实现 RAG,让 AI 基于私有知识库回答
- 如何优化首 token 延迟和流式体验
这些我们会在后续篇章继续展开。