Appearance
DeepSeek API 是无状态的,每次请求需要将完整对话历史一起传入。本文介绍多轮对话的正确实现方式、上下文窗口管理策略,以及在 1M 超长上下文下如何控制 token 成本。
DeepSeek API 多轮对话
核心原则:API 无状态,上下文自己维护
DeepSeek API 的 /chat/completions 接口不记录任何对话状态。要实现多轮对话,需要在每次请求时把完整的历史消息传入 messages 数组。
基本实现
typescript
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.deepseek.com",
apiKey: process.env.DEEPSEEK_API_KEY,
});
// 维护对话历史
const messages: OpenAI.ChatCompletionMessageParam[] = [
{ role: "system", content: "You are a helpful assistant." },
];
async function chat(userInput: string): Promise<string> {
// 添加用户消息
messages.push({ role: "user", content: userInput });
const response = await client.chat.completions.create({
model: "deepseek-v4-pro",
messages,
});
const assistantMessage = response.choices[0].message;
// 把模型回复加入历史
messages.push(assistantMessage);
return assistantMessage.content ?? "";
}
// 使用
const reply1 = await chat("世界最高峰是哪座山?");
console.log(reply1); // 珠穆朗玛峰
const reply2 = await chat("它的海拔是多少?");
console.log(reply2); // 8848.86 米(模型知道"它"指上一轮的主题)Python 版本:
python
from openai import OpenAI
client = OpenAI(api_key="<your api key>", base_url="https://api.deepseek.com")
messages = [{"role": "system", "content": "You are a helpful assistant."}]
def chat(user_input: str) -> str:
messages.append({"role": "user", "content": user_input})
response = client.chat.completions.create(
model="deepseek-v4-pro",
messages=messages,
)
assistant_msg = response.choices[0].message
messages.append(assistant_msg)
return assistant_msg.content
print(chat("世界最高峰是哪座山?"))
print(chat("它的海拔是多少?"))上下文长度管理
DeepSeek V4 支持 1M token 上下文,理论上可以保留极长的对话历史。但随着对话轮数增加,每次请求的输入 token 越来越多,成本也线性增长。
推荐策略:
方案一:保留最近 N 轮(简单)
typescript
const MAX_HISTORY = 20; // 保留最近 20 条消息
function trimHistory(
messages: OpenAI.ChatCompletionMessageParam[],
systemPrompt: string,
): OpenAI.ChatCompletionMessageParam[] {
const system = [{ role: "system" as const, content: systemPrompt }];
const history = messages.filter((m) => m.role !== "system");
return [...system, ...history.slice(-MAX_HISTORY)];
}方案二:token 预算控制(精确)
typescript
function trimByTokenBudget(
messages: OpenAI.ChatCompletionMessageParam[],
maxTokens = 50000,
): OpenAI.ChatCompletionMessageParam[] {
// 估算:中文约 0.6 token/字符,英文约 0.3 token/字符
const estimateTokens = (text: string) => text.length * 0.6;
let total = 0;
const kept: OpenAI.ChatCompletionMessageParam[] = [];
// 从最新消息往前保留
for (let i = messages.length - 1; i >= 0; i--) {
const msg = messages[i];
const tokens = estimateTokens(String(msg.content));
if (total + tokens > maxTokens && msg.role !== "system") break;
kept.unshift(msg);
total += tokens;
}
return kept;
}与 KV Cache 配合使用
DeepSeek 的 KV Cache 在多轮对话中特别有价值:每次请求传入的历史消息前缀相同,就会触发缓存命中,将缓存部分的费用降至 0.025元/M token(pro)。
建议:
- 保持 system prompt 不变:同样的 system prompt 每次都命中缓存
- 不要修改历史消息:只在末尾追加新消息,保证前缀稳定
详见:上下文硬盘缓存
常见问题
Q: 可以修改历史消息中的 assistant 回复吗?
A: 技术上可以,但会破坏 KV Cache 的前缀匹配,增加成本。也可能让模型产生混淆,不建议随意修改历史消息。
Q: 怎么实现"记住用户偏好"这类长期记忆?
A: DeepSeek API 本身不支持持久化记忆。通常做法是:把关键信息提炼成摘要放在 system prompt 里,或使用外部数据库(如向量库)检索相关历史注入上下文。
Q: 多轮对话中 token 突然暴增怎么排查?
A: 检查 response.usage.prompt_tokens。如果历史越来越长,说明没有做 history trimming;如果单次暴增,检查是否有大文件/长文档被附加进了 messages。