成本优化指南
裁剪聊天历史以降低成本:滑动窗口还是摘要
限制你每轮重发的内容:保留最近几条消息或一段简短摘要。50 轮对话的 token 算术和一个 Python 辅助函数。

你的支持机器人已经聊了 50 轮,每一条新消息都比上一条更贵。API 不记忆任何内容,所以你的代码每次都要重发整个对话。解决办法是限制你发送的内容:保留系统提示和最近几轮(滑动窗口),或者把旧轮次换成一段简短摘要。下面是 50 轮对话的算术,以及一个可以直接复制的辅助函数。
DeepSeek 的多轮对话指南(api-docs.deepseek.com,2026 年 11 月 1 日查阅)说得很直白:服务器不记录先前请求的上下文,所以客户端每次调用都要发送完整的 messages 列表。CheapTokens 上的 OpenAI 兼容格式也是同样的工作方式。如果这对你来说是新的,请先看为什么长对话更贵和messages 数组如何工作。
第 50 轮实际发送了什么#
假设你运行一个支持机器人,系统提示为 400 个 token。在典型的一轮中,用户写大约 60 个 token,机器人回答大约 190 个。一个完整轮次会给历史增加 250 个 token。这些是假设:请用你自己响应中 usage 字段的数字替换它们。
在第 50 轮,请求携带系统提示、49 个先前轮次和新消息:400 + 49 × 250 + 60 = 12,710 个输入 token,以得到 190 个 token 的回答。把整个对话加起来。系统提示和新消息每次都会发送:50 × 460 = 23,000。重复的历史增加 250 × 1,225 = 306,250。合计:23,000 + 306,250 = 329,250 个输入 token,对应 50 × 190 = 9,500 个输出 token。
每个请求中的历史每轮增加一轮,所以总量随对话长度的平方增长。从 50 轮增加到 100 轮,重复历史部分大约翻两番。
自己试试试一试:如果你只发送系统提示、最近 4 轮和新消息,第 50 轮会携带多少输入 token?显示答案
1,460(400 + 4 × 250 + 60),而完整历史是 12,710。同样的问题,同样的回答长度,输入只是其中一小部分。
方案 1:滑动窗口#
思路:始终发送系统提示,丢弃除最近 N 轮之外的所有内容。你在自己的存储中保留完整历史,只裁剪你发送的那份副本。
def trim_history(messages, keep_pairs=4):
"""System prompt + the last keep_pairs turns + the new user message."""
system = [m for m in messages if m["role"] == "system"]
rest = [m for m in messages if m["role"] != "system"]
return system + rest[-(keep_pairs * 2 + 1):]在你追加用户的新消息之后调用它。使用 keep_pairs=4 时,就是 4 对 user/assistant 加上新消息,共 9 条消息,而且切片总是从一条 user 消息开始。下面是它接入真实调用的样子:
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.cheaptokens.dev/v1",
api_key=os.environ["CHEAPTOKENS_API_KEY"],
)
MODEL = "deepseek-ai/DeepSeek-V4-Flash-0731"
history = [{"role": "system", "content": "You are a concise support assistant."}]
def ask(text):
history.append({"role": "user", "content": text})
resp = client.chat.completions.create(
model=MODEL,
messages=trim_history(history),
max_tokens=500,
)
answer = resp.choices[0].message.content
history.append({"role": "assistant", "content": answer})
print(resp.usage.prompt_tokens, resp.usage.completion_tokens)
return answerprint 这一行是有用的:观察第一个数字。不裁剪时它每轮都会上升。使用窗口后,一旦对话超过第 4 轮,它就不再增长。

用数字来说:从第 5 轮起,每个请求是 400 + 4 × 250 + 60 = 1,460 个输入 token。前四轮仍然正常增长,合计 3,340。整个对话:3,340 + 46 × 1,460 = 70,500 个输入 token,大约是完整历史发送的 329,250 的五分之一。输出保持 50 × 190 = 9,500。

在代码中,整个改动就是调用中的一个参数:
比较
一行改动
之前
messages=history之后
messages=trim_history(history)问题在于:窗口会遗忘。如果用户在第 40 轮问“我的订单号是多少?”,而该号码在第 3 轮,模型从未见过它。有两个低成本的修复方法。在你自己的代码中从对话里提取稳定事实(姓名、订单号、套餐),并把它们追加到系统提示中,窗口永远不会裁掉它们。或者添加摘要,也就是下一个方案。
方案 2:旧轮次的摘要#
不是丢弃旧轮次,而是压缩它们。每隔几轮,把刚掉出窗口的轮次加上当前摘要发送给模型,并要求生成一个新的简短摘要。然后把这个摘要放入系统消息。
def summarize(summary, old_messages):
transcript = "\n".join(f"{m['role']}: {m['content']}" for m in old_messages)
prompt = (
f"Summary so far:\n{summary}\n\nNew turns:\n{transcript}\n\n"
"Rewrite the summary in under 200 words. "
"Keep names, numbers, decisions and open questions."
)
resp = client.chat.completions.create(
model=MODEL,
messages=[{"role": "user", "content": prompt}],
max_tokens=400,
)
return resp.choices[0].message.content
def build_messages(history, summary, keep_pairs=4):
system = history[0]["content"]
if summary:
system += "\n\nEarlier in this chat: " + summary
recent = history[1:][-(keep_pairs * 2 + 1):]
return [{"role": "system", "content": system}] + recent摘要不是免费的:它是一次额外的模型调用。假设摘要约 300 个 token,每 5 轮刷新一次(50 轮中 9 次),每次刷新读取约 1,600 个 token(旧摘要加五轮加指令)并写入 300。为稳妥起见,从第 5 轮起在每个请求中都计入摘要(实际上它出现在第一次刷新之后,所以这是略微高估):400 + 300 + 4 × 250 + 60 = 1,760 个输入 token。
对话总计:主请求 3,340 + 46 × 1,760 = 84,300 个输入 token,摘要调用 9 × 1,600 = 14,400 个输入和 9 × 300 = 2,700 个输出。合计 84,300 + 14,400 = 98,700 个输入和 9,500 + 2,700 = 12,200 个输出 token。
摘要比单纯的窗口成本略高,作为回报,模型仍然知道第 3 轮发生了什么。
三种方案的成本#
下面是同样的 50 轮对话在 DeepSeek-V4-Flash 上,根据上述总量实时定价:
价格来自实时价格表,所以当资费变化时数字会更新。排序不会变:窗口发送最少,摘要居中,完整历史最贵。
对于这样的对话,起步用的 CheapTokens 模型是 DeepSeek-V4-Flash。它有 380K 上下文窗口(50 轮对话即使不裁剪也放得下),每次回答最多 32,768 个输出 token,一天中任何时段都是固定价格,而且总体而言,把输入和输出加在一起计算,它比 OpenRouter 更便宜。模型 id 是 deepseek-。如果你想在窗口方案上比较模型(平均每个请求 1,410 个输入 token,190 个输出,50 个请求),这里是实时表格:
| 模型 | 输入 $/1M | 输出 $/1M | 每次请求 | 50 次请求 | OpenRouter,50 次请求 |
|---|---|---|---|---|---|
| GLM-5.3-Flash | $0.02 | $0.04 | $0.000036 | $0.0018 | $0.0032 |
| DeepSeek-V4-Flash | $0.015 | $0.10 | $0.00004 | $0.002 | $0.0063 |
| MiniMax-M2.7 | $0.05 | $0.15 | $0.000099 | $0.005 | $0.0594 |
每次请求 1,410 个输入 token 和 190 个输出 token。每 100 万 token 的价格为当前价格,会随我们的资费一同变动;OpenRouter 列显示相同工作负载的参考价格。
GLM-5.3-Flash 有 390K 上下文,MiniMax-M2.7 有 180K,对于裁剪过的对话来说足够了。当前价格在定价页面上,文档展示了如何发送第一个请求。
该选哪一个#
窗口是默认选择:三行代码,没有额外调用,没有额外故障模式。把它用于支持机器人、辅导老师和角色扮演,在这些场景中最近几轮承载对话,系统提示承载规则。当用户回溯很久以前说过的事情时,添加摘要,比如一个永不结束对话的个人助理。让窗口足够小,使模型仍能看到即时上下文;又足够大,能容纳一个完整的问答交换。到底能容纳多少是一个单独的限制,在什么是上下文窗口中有所介绍。
快速检查
用户在第 40 轮询问第 3 轮给出的订单号。你使用 4 轮窗口且没有摘要。会发生什么?
显示答案
答案: 模型看不到它,因为第 3 轮已从请求中裁掉
API 是无状态的。模型只能看到请求中的内容,所以你仍然需要的事实必须留在系统提示或摘要中。



