# 聊天越长越贵还越笨?管好你的对话上下文
·
关键词:聚合 API、多轮对话、上下文窗口、对话压缩、Token 成本、滑动窗口
做带记忆的 AI 助手,新手最容易踩的坑:把每一轮 user/assistant 都无脑 append 到一个 messages 列表里。
问题有三个,而且会同时爆发:
- 成本飙升:上下文每轮累加,Token 消耗近似线性增长,长聊一天烧掉不少钱。
- 超窗口:再大的上下文也有上限,聊久了直接报错
context length exceeded。 - 越聊越笨:堆满历史后,模型被早期无关信息干扰,回答质量反而下降。
解法就两条,本文用 TokenPortal 聚合 API(一个 base_url + 一个 Key 接 150+ 大模型,OpenAI 兼容)给出最小可运行实现:
滑动窗口(只留最近几轮)+ 摘要压缩(把老对话压成一段)。换模型零改动,摘要还能用更便宜的模型跑。
一、滑动窗口:只保留最近 N 轮
最简单也最常用。超过窗口就把最老的几轮"砍掉",只留 system + 最近 K 轮。
import os
from openai import OpenAI
# 控制台获取:https://tokenportal.ai (BASE_URL / API_KEY 以控制台为准)
BASE_URL = "https://api.tokenportal.ai/v1" # 发布前替换为你的真实网关地址
API_KEY = os.getenv("TP_API_KEY", "YOUR_TOKENPORTAL_KEY")
client = OpenAI(base_url=BASE_URL, api_key=API_KEY)
SYSTEM = "你是一个乐于助人的中文助手。"
KEEP_TURNS = 4 # 始终保留的最近对话轮数
CHAT_MODEL = "deepseek-v4-pro" # 正式回答用强模型(模型 ID 以控制台为准)
history = [] # 仅存 user/assistant 轮次
def chat(user_msg: str) -> str:
global history
history.append({"role": "user", "content": user_msg})
# 超出窗口:丢弃最老的部分
if len(history) > KEEP_TURNS:
history = history[len(history) - KEEP_TURNS:]
resp = client.chat.completions.create(
model=CHAT_MODEL,
messages=[{"role": "system", "content": SYSTEM}] + history,
temperature=0.7
)
reply = resp.choices[0].message.content
history.append({"role": "assistant", "content": reply})
return reply
滑动窗口干净、零额外开销,但有个代价:砍掉的历史就彻底丢了,模型会"忘了"前面聊过什么。
二、摘要压缩:把老对话"折叠"起来
要保留长程记忆,就在砍之前先总结。把超出窗口的旧轮次交给模型压成一段摘要,替换进上下文。这样模型既不被旧信息淹没,又不丢关键事实。
context_summary = "" # 跨轮累积的对话摘要
def summarize(old_turns: list) -> str:
# 摘要用与主回答相同的模型即可;想更省可换成 deepseek-v4-flash(以控制台模型广场为准)
resp = client.chat.completions.create(
model=CHAT_MODEL,
messages=[
{"role": "system", "content": "把对话压缩成一段中文摘要,保留关键事实与未决问题,不超过 120 字。"},
{"role": "user", "content": str(old_turns)}
],
temperature=0
)
return resp.choices[0].message.content
def chat_with_summary(user_msg: str) -> str:
global history, context_summary
history.append({"role": "user", "content": user_msg})
# 超出窗口:把最老的部分摘要掉,再裁剪
if len(history) > KEEP_TURNS:
head = history[:len(history) - KEEP_TURNS]
context_summary = summarize(head) # 覆盖式压缩,始终是最新摘要
history = history[len(history) - KEEP_TURNS:]
sys_msg = SYSTEM + (f"\n(此前对话摘要:{context_summary})" if context_summary else "")
resp = client.chat.completions.create(
model=CHAT_MODEL,
messages=[{"role": "system", "content": sys_msg}] + history,
temperature=0.7
)
reply = resp.choices[0].message.content
history.append({"role": "assistant", "content": reply})
return reply
要点:
- 摘要是覆盖式的:每次只对"窗口外的最老部分"重新总结,不会无限累积。
- 摘要模型可降级:摘要任务对能力要求低,换成更便宜更快的
deepseek-v4-flash能再省一笔(ID 以控制台为准)。 - 主回答用强模型:关键生成仍走
deepseek-v4-pro,质量不受影响。
三、换模型零改动
上面 CHAT_MODEL 换成 qwen3.7-max / glm-5.2 / kimi-k3 任意一个,对话管理逻辑一行不用改——聚合 API 抹平了各厂商 SDK 差异,你只管切模型。
四、落地建议
- 短对话(客服单次咨询):滑动窗口就够,KEEP_TURNS=4~6。
- 长对话(陪聊、咨询、带状态的 Agent):滑动窗口 + 摘要双保险。
- 成本敏感:摘要用便宜模型,主回答按"难的任务用强模型"分级调用。
- 可观测:每次请求后打印
resp.usage,把 Token 当"电费"记账(见我主页另一篇《给 AI 应用装一个 Token 电表》)。
五、小结
- 多轮对话别无脑 append,否则又贵又容易超窗口。
- 滑动窗口管"近因",摘要压缩管"长程记忆",两者可叠加。
- 聚合 API 下换模型只改一个字段,上下文管理代码完全复用。
如果这篇对你有启发,来我主页看看更多聚合 API 实战。一个接口接 150+ 大模型,官方货源、正规可开票、稳定,代码不用为每个厂商重写。
更多推荐



所有评论(0)