History Truncation
History Truncation 是在对话历史超过上下文窗口限制时,截断或移除旧消息的策略。它在保留关键上下文和控制 token 消耗之间找到平衡。
#type / concept
#status / evergreen
#tech / ai
[!info] related notes
History Truncation
一句话定义
History Truncation 是在对话历史超过上下文窗口限制时,截断或移除旧消息的策略。不是简单地”删掉最早的”,而是要保留关键信息、生成摘要、维护上下文连贯性。
核心原理
截断策略
| 策略 | 原理 | 优缺点 |
|---|---|---|
| 时间截断 | 删掉最早的 N 条 | 简单但可能丢关键信息 |
| Token 截断 | 从最新消息开始,累计 token 直到超限 | 精确控制 token |
| 重要性截断 | 保留重要消息,删掉不重要的 | 复杂但效果好 |
| 摘要截断 | 旧消息生成摘要替代 | 保留信息但增加一次 LLM 调用 |
Token 截断实现
def truncate_by_token(messages: list, max_tokens: int, tokenizer) -> list:
"""从最新消息开始,累计 token 直到超限"""
# 始终保留 system message
system_msgs = [m for m in messages if m.role == "system"]
other_msgs = [m for m in messages if m.role != "system"]
system_tokens = sum(tokenizer.count(m.content) for m in system_msgs)
remaining_tokens = max_tokens - system_tokens
# 从最新消息开始保留
kept = []
for msg in reversed(other_msgs):
msg_tokens = tokenizer.count(msg.content)
if remaining_tokens - msg_tokens < 0:
break
kept.insert(0, msg)
remaining_tokens -= msg_tokens
return system_msgs + kept
摘要截断实现
async def truncate_with_summary(messages: list, max_tokens: int, llm) -> list:
"""旧消息生成摘要,新消息保留原文"""
system_msgs = [m for m in messages if m.role == "system"]
other_msgs = [m for m in messages if m.role != "system"]
# 分离旧消息和新消息
split_point = len(other_msgs) - 10 # 保留最近 10 条
old_msgs = other_msgs[:split_point]
new_msgs = other_msgs[split_point:]
# 旧消息生成摘要
if old_msgs:
summary = await llm.chat(f"请用 100 字总结这段对话的要点:\n{format_messages(old_msgs)}")
summary_msg = {"role": "system", "content": f"之前的对话摘要: {summary}"}
return system_msgs + [summary_msg] + new_msgs
return system_msgs + new_msgs
常见坑
- 只删最早的: 可能删掉了关键的用户偏好信息
- 不保留 system message: 截断时把 system prompt 也删了
- 不做摘要: 直接丢弃旧消息,信息丢失
- 截断太频繁: 每轮都截断,增加延迟