对话太长怎么办?Agent上下文窗口的滑动与摘要配置
Agent运行一段时间后,对话历史会越来越长,直接全部发给模型很容易超出上下文窗口,导致调用失败或响应变慢。
解决这个问题不需要复杂的框架,核心是配置一套上下文管理策略:保留最近对话、压缩早期内容、固定关键指令。
先明确上下文预算
在配置前,先确认两个数值:模型支持的上下文窗口大小,以及你愿意为单次请求分配的最大Token数。
例如模型窗口是128K,但为了控制成本和延迟,可以只分配8K给对话历史。
建议把预算拆成三部分:
- 系统提示词:固定占用,不参与压缩,通常几百Token
- 最近对话:保留最近N轮原文,保证连贯性
- 历史摘要:把更早的对话压缩成一段摘要,控制在一千Token以内
这个拆分方式适用于大多数客服、助手类Agent。
如果你的任务需要严格引用早期细节,摘要部分要保留更多原文,不能一味压缩。
用滑动窗口保留最近对话
滑动窗口的意思是只保留最近若干条消息,更早的消息移出窗口。
配置时可以用一个列表存储消息,每次新增后检查总长度,超出阈值就从头部删除。
下面是一个Python示例,按消息条数控制窗口:
MAX_MESSAGES = 20
def add_message(history, message):
history.append(message)
if len(history) > MAX_MESSAGES:
removed = history.pop(0)
return removed
return None
按条数控制简单,但不同消息长度差异大,更稳妥的方式是按Token估算。
可以用tiktoken等库计算Token数,累计超过预算时从头部移除。
注意:滑动窗口不能把系统提示词移出去,系统提示词要单独存放,每次请求时拼接在最前面。
用摘要压缩早期对话
滑动窗口丢弃的早期消息可能包含用户偏好、已确认的订单号等信息。
直接丢弃会导致Agent“失忆”。
解决办法是在移除消息前,把被移除的内容交给模型生成摘要,追加到摘要变量中。
summary = ""
def compress(removed_messages):
global summary
text = "\n".join(m["content"] for m in removed_messages)
prompt = f"请用三句话总结以下对话中的关键事实和用户需求:\n{text}"
new_summary = call_llm(prompt)
summary = summary + "\n" + new_summary
摘要本身也要控制长度,超过预算时对摘要再做一次压缩。
摘要内容应聚焦事实和结论,不要保留寒暄和重复确认。
固定系统提示词和工具说明
系统提示词包含Agent的角色、输出格式和工具调用规则,这部分不应该被滑动窗口影响。
配置时把它作为独立字段,每次请求都完整发送。
如果Agent需要调用工具,工具说明也放在系统提示词或单独字段中,不要混进对话历史。
否则窗口裁剪时可能把工具定义裁掉,导致模型无法正确调用。
一个完整的请求结构大致如下:
messages = [
{"role": "system", "content": system_prompt},
{"role": "system", "content": "历史摘要:" + summary},
*recent_messages
]
验证配置是否生效
配置完成后,用一段长对话做验证。
连续发送二十条以上消息,观察每次请求的Token数是否稳定在预算内,以及模型是否还能正确回答早期提到的信息。
检查点包括:
- 请求Token数是否超过设定上限
- 摘要是否包含关键事实,比如用户姓名、订单号
- 系统提示词是否每次完整发送
- 工具调用是否正常
如果模型开始遗忘早期信息,优先检查摘要生成是否失败,或者摘要是否被过度压缩。
常见坑和边界
滑动窗口按条数裁剪时,一条长消息可能就占满预算,建议同时做Token级检查。
摘要压缩会引入额外模型调用,增加延迟和成本,对实时性要求高的场景可以降低摘要频率。
另外,摘要本身也可能出错,关键字段如订单号建议单独结构化存储,不要只依赖摘要。
如果对话需要严格审计,滑动窗口会丢失原始消息,应把完整历史写入数据库,窗口只用于构造请求。