做长对话的智能体,最先撞墙的就是上下文。聊到二三十轮,token蹭蹭涨,要么超模型窗口直接报错,要么账单看着肉疼。我折腾了大半个月,最后落到"滑动窗口+要点压缩"这套组合,实测token砍了快一半,关键信息基本没丢。记一下过程。

一开始走的弯路

我最早是粗暴截断——只留最近N轮,前面的全扔。结果用户聊着聊着,智能体就"失忆"了:开头说好的需求,到后面全忘。客诉直接来了。

后来改成全量塞,每轮把历史全带上。信息是不丢了,但一个稍微长点的会话,单次请求两万多token,慢且贵。

现在的做法:分层

我把上下文切成三块,分别处理。

近期窗口(原样保留)

最近的5到6轮对话,一个字不动,全须全尾带上。这部分最影响当前回答的连贯性,不能压。

中期记忆(压成要点)

再往前的对话,不直接扔,而是隔一段就触发一次压缩:让模型把这几轮聊了啥总结成几条要点。比如十轮关于"用户要订一张6月飞东京的机票,预算3000,偏好夜间航班"的来回,压完就剩三四行结论。原始一千多token,压完一百出头。

长期事实(抽出来单存)

像用户的偏好、确认过的关键决定,我单独抽成结构化的fact存到外面(就一个简单的KV),每轮按需取,不混在对话流里。

压缩的触发时机

不是每轮都压——那太费。我设的是:当历史token累计超过一个阈值(我设的4000),就把"近期窗口之前"的内容打包压一次,压完替换掉原文。相当于滑动窗口往前滚的时候,被滚出去的内容先过一道压缩再归档。

if history_tokens > 4000:
    old = messages[:-6]          # 近期6轮之外
    summary = compress(old)      # 让模型总结成要点
    messages = [summary] + messages[-6:]

一个没解决干净的坑

压缩本身也要调一次模型,是有成本和延迟的。我测下来,压一次大概多花半秒、几百token,但换来后续每轮都省,整体还是赚。另外压缩偶尔会丢掉一些当时看着不重要、后面又被问起的细节——比如用户随口提过的一个航司黑名单。这个我还没特别好的解法,目前是把"用户明确表达的排除项"强行进长期事实那一层,绕过压缩。

搭这套我没自己写调度,用了个零代码配智能体的平台,窗口、压缩节点、记忆存储都是拖出来连的,改阈值就是改个数字。

模型这层提一句:压缩和对话都得调大模型,我用讯飞的现成模型服务(MaaS)接的API,不用操心模型部署和算力,调用直接走。

你们的长上下文是怎么扛的?有没有上向量记忆做检索式召回的?我感觉那套对"很久以前提过一嘴"的场景更稳,但工程复杂度高,纠结要不要上,评论区给点意见。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐