写在前面:这是一篇关于AI应用开发的思考笔记。如果你也在做LLM相关的项目,相信你会对这个问题有共鸣。

一、问题的提出:一个被忽视的痛点

最近在做AI相关应用时,我发现了一个普遍存在但很少被深入讨论的问题:历史对话消息的压缩

现在的AI框架确实很多,从LangChain到LlamaIndex,从AutoGen到Dify,功能越来越强大。但有一个基础问题,似乎大家都默认"以后再说"——那就是如何高效地处理多轮对话中的上下文。

我们知道,大模型本身是无状态的。要让模型知道之前的聊天内容,就必须把历史消息一起发过去。这个设计简单直接,但随着对话轮数增加,token消耗会呈线性甚至指数级增长。

第1轮:50 tokens
第5轮:250 tokens
第20轮:1000+ tokens
第50轮:可能超过模型的context window限制

这不仅仅是成本问题,更是可用性问题。

二、为什么压缩不是简单的"摘要一下"?

刚开始面对这个问题时,我也想过:用个摘要算法不就行了吗?后来发现,事情没那么简单。

核心矛盾:领域特性决定压缩策略

如果把历史消息拆分开看,它具有明显的领域数据特性。不同领域的对话,压缩方式应该完全不同:

场景 关键信息 错误压缩的后果
代码调试 具体代码行、错误堆栈 丢失细节导致无法复现问题
法律咨询 时间线、证据链、当事人关系 细节缺失影响判断准确性
日常闲聊 情感倾向、话题脉络 过于精简失去对话温度
数据分析 指标定义、计算逻辑 上下文缺失导致结论不可信

这意味着,不存在一套通用的压缩规则。你不能用总结小说的方式去总结一段代码审查对话,也不能用处理客服工单的方式去处理朋友间的闲聊。

三、现有框架能做什么,不能做什么

这里我想特别提一下目前业界比较成熟的方案,比如 LongLLMLingua 这类提示压缩框架。

LongLLMLingua的核心思路是通过统计特征(主要是困惑度)来识别哪些token可以安全删除,同时支持通过显式配置或标签来实现一定程度的领域适配。听起来很美好对吧?

但实际用起来你会发现几个硬伤:

  1. 它不会自动识别领域:系统不知道当前对话是医疗咨询还是代码调试,需要你提前告诉它"这是代码场景",然后加载对应的配置模板。
  2. 配置维护成本高:每个新领域都需要人工标注训练数据、调整参数阈值。你的业务从电商客服扩展到法律咨询,压缩策略就得重新调一遍。
  3. 统计特征≠语义理解:困惑度低不代表不重要。一段看似平淡的法律条款陈述,可能是整个案件的关键依据,但压缩算法可能会把它当成冗余信息删掉。

所以,有现成框架不等于解决了问题。这些工具更像是"半成品",帮你省去了从零造轮子的麻烦,但核心的领域适配难题,依然要你自己扛。

四、四个值得探索的方向

基于以上分析,我认为解决这个问题需要从以下几个方向入手:

方向一:分层压缩策略(聚焦“存储与加工范式”)

不要试图用单一粒度处理所有历史消息。当前LLM上下文管理已普遍采用**“工作记忆+长期记忆+分层路由与压缩优化”**的架构路线,具体可落地为三层设计:

  1. 短期记忆(最近5-10轮/当前会话):高保真保留,直接驻留于模型的上下文窗口。工程上常结合 LongLLMLingua 等提示压缩框架,在Token级进行细粒度优化。需明确:此类工具在此架构中仅充当“底层粗筛过滤器”,用于剔除明显冗余的停用词、重复标点或低困惑度片段,而非承担领域语义压缩的核心职责。真正的领域适配仍依赖中层摘要模型与上层路由机制兜底。
  2. 中期记忆(会话主题/关键决策):采用分层摘要与查询感知压缩。例如 RAPTOR 框架会将历史交互构建成"多层摘要树",推理时可根据问题复杂度动态检索粗粒度或细粒度内容;更智能的查询感知压缩会实时将 <历史片段 + 当前提问> 输入专用压缩模型,精准剔除无关轮次。
  3. 长期记忆(用户偏好/业务背景/事实沉淀):结构化存储与定期压缩提炼。借鉴人类认知机制,当对话历史触发阈值时,系统会自动调用压缩流水线。进阶方案如 A-MEM 借鉴卡片盒笔记法,新摘要生成时会回溯关联、修改旧记忆并建立语义链接;或结合 Mem0/知识图谱,将用户习惯、核心事实抽离为KV或图结构。

路由调度机制:分层压缩的核心不在于"怎么压",而在于"怎么取"。系统通过预算控制器或轻量级路由模型,动态决策当前请求应该"读缓存、查向量、还是按需解压原始记录",在Token成本与信息完整性之间取得最优解。

方向二:领域适配机制(这才是真正的难点)

如何让系统自动识别当前对话的领域,并选择合适的压缩策略?

可能的实现路径:

  1. 显式标注:用户在对话开始时指定场景类型。优点是准确,缺点是用户体验割裂。
  2. 隐式识别:通过关键词、句式模式自动分类。优点是无缝,缺点是需要大量标注数据训练分类器。
  3. 混合模式:结合两者,允许用户覆盖自动判断结果。这可能是最务实的方案。

这里有个工程上的挑战:领域分类器本身的准确性如何保证? 如果分类错了,压缩策略也会跟着错,而且这种错误往往是静默的——模型输出看起来正常,但关键信息已经丢了。


方向三:“上下文分页”调度架构(聚焦“检索与召回机制”)

传统认知中,开发者常追求历史消息的“可逆压缩”,即压缩后还能无损还原。但需明确:方向一解决的是“历史数据如何分层沉淀”,而本方向解决的是“推理时如何按需拉取”。两者共同构成 Context Paging(上下文分页)完整闭环。

在LLM上下文中,语义压缩本质是不可逆的(摘要过程必然伴随细节丢失)。工业界早已放弃对“算法级可逆”的执念,转而采用 “有损摘要 + 无损索引 + 按需召回” 的工程等价方案,标准术语为 Context PagingHierarchical Memory

已有框架对该范式的落地支持
框架/项目 实现机制 如何实现“细节还原/按需召回”
Letta (原MemGPT) 上下文分页:主存摘要,外存原始对话。Agent检测到信息不足时,自动拉取原始片段 平时看摘要,质疑时自动“解压”
LlamaIndex (AutoMergingRetriever) 构建多层摘要树,检索时先命中高层摘要,置信度低则下钻召回底层原文 动态粗细粒度切换
Zep 对话流式写入,后台异步生成摘要/事实图谱,但原始消息永久保留 索引定位原始片段,支持增量拉取
LangGraph + Checkpointer Agent状态快照机制,支持回滚到任意检查点并加载完整原始上下文 增量更新+状态回滚
典型工程架构
[用户输入] → [LLM推理]
       ↑          ↓
[动态路由层] ← [压缩摘要注入]
   │
   ├─ 预算充足/细节关键 → 直取原始片段 (Raw Retrieval)
   └─ 预算紧张/宏观决策 → 仅注入摘要 (Summary Injection)
       ↑
[外部存储] (向量库 + 原始消息JSON/DB) ← 异步写入

关键认知:这不是“压缩算法”的优化,而是**“上下文调度架构”**的设计。所有主流框架均已将“摘要驻留+原始按需拉取”封装为标准化组件。工程重心应从“如何压得更狠”转向“何时该调取原文”。


方向四:评估标准——从“绝对分数”到“业务容忍度驱动”

历史消息压缩的评估,工业界已形成明确范式:不追求通用阈值,而是建立“成本-质量帕累托前沿”与“领域定制化验收标准”。压缩评估的核心不是“算法得了多少分”,而是“业务能接受多少信息损失”。

1. 评估指标分层:算法、任务与体验的三重验证
评估层级 核心目标 推荐方法/指标 适用工具
算法层 压缩比 vs 语义漂移 Token Compression Ratio、关键实体/数值留存率、Semantic Similarity LLMLingua 内置评估、BERTScoreSimCSE
任务层(黄金标准) 下游性能衰减可控 业务测试集的 Accuracy/F1/Pass@k 随压缩率的变化曲线 自定义压测脚本、LongBench(QA/摘要子集)
体验层 用户无感知断点 多轮追问率、人工接管率、会话中断率、意图识别错误率 LangSmithArize Phoenix、线上埋点监控

💡 注意FaithfulnessContext Recall 本质是 RAG 检索评估指标。在压缩场景中,可通过“将压缩后文本作为 context 输入”进行二次适配,用于量化事实保真度,但不能替代下游任务压测。

2. 可直接落地的评估方案

(1)基于 RAGAS v0.2+ 的保真度量化

⚠️ 注意:此脚本不直接评估压缩算法本身,而是量化“压缩后上下文对下游推理的衰减影响”。
工业标准做法是:分别用「原始全文」和「压缩文本」作为 context 生成答案,计算 Faithfulness 差值 Δ,
结合压缩率绘制 成本-质量 帕累托曲线,寻找业务容忍拐点。
from ragas import EvaluationDataset, evaluate
from ragas.metrics import Faithfulness, AnswerRelevancy
from langchain_openai import ChatOpenAI, OpenAIEmbeddings

# 构造测试集:question 为当前提问,context 为压缩后的历史,ground_truth 为预期依据
data = {
    "question": ["当前对话的上一轮结论是什么?"],
    "context": [["压缩后的历史文本片段"]],  # 注意:必须是 list[list[str]]
    "ground_truth": ["根据第3轮确认的方案A执行"]
}
dataset = EvaluationDataset.from_list(data)

result = evaluate(
    dataset=dataset,
    metrics=[Faithfulness(), AnswerRelevancy()],
    llm=ChatOpenAI(model="gpt-4o"),      # 指定 Judge LLM
    embeddings=OpenAIEmbeddings(),       # 指定 Embedding 模型
)
print(result)  # 输出 0-1 分数,量化“压缩是否丢失决策依据”

(2)工业界真实压测流程:绘制帕累托前沿
不要孤立看某个分数,而是通过自动化脚本跑通以下链路:

[构建领域测试集] → [设置压缩率梯度: 30%~90%] → [批量注入模型推理] 
       ↓
[记录下游任务准确率] → [绘制 压缩率-准确率 曲线] → [寻找业务容忍拐点]

例如:客服场景在 60% 压缩率时准确率仅下降 2%,可视为最优解;而医疗问诊在 30% 压缩率时关键症状遗漏率骤增,则必须保留更高保真度。

3. 行业共识现状:缺的不是工具,而是“业务锚点”

过去开发者常纠结于“Faithfulness 达到 0.8 还是 0.85 才能上线”,但当前一线团队的共识已发生本质转移:

  1. 放弃通用阈值,采用业务容忍度驱动:不同场景对信息丢失的容忍度差异极大。闲聊可接受高压缩+低保真,代码审查/法务合同则要求近无损压缩。
  2. 从离线分数转向线上行为指标:离线评估仅能筛除明显缺陷,最终验收必须依赖线上 A/B 测试。核心观测指标包括:用户重问率多轮对话打断率Agent 幻觉触发率人工兜底接管率
  3. 建立 Badcase 回流闭环:线上拦截到的压缩丢失案例,自动注入测试集并触发压缩策略/路由规则迭代。
4. 避坑指南:关于基准测试的常见误区
  • NeedleInAHaystack 不适合评估压缩:该基准用于测试模型原生长窗口注意力机制,压缩会破坏原始 token 分布与注意力路径,直接套用属于测试对象错配。
  • ✅ 正确做法:使用 LongBench 的下游任务子集,或基于自身业务构建“关键信息注入-压缩-召回推理”的定制压测集。学术界近年也更关注 CompBench 等面向 Prompt 压缩的专用基准,但工程落地仍以业务压测曲线为准。

小结:评估框架已成熟,但“合格线”必须由业务定义。工业界的标准工作流是:离线压测定拐点 → 小流量 AB 验体验 → 线上监控做兜底 → Badcase 回流促迭代。压缩策略的优劣,最终不体现在算法报告的分数上,而体现在用户是否觉得“对话依然连贯且高效”。

五、结语:这不是终点,而是起点

写到这里,我想说的是:历史消息压缩这个问题,远比我最初想的要复杂。它不是一个可以用某个算法"一键解决"的技术点,而是一个涉及系统设计、领域知识、用户体验的综合课题。

现有的框架如 LongLLMLingua 给了我们一个不错的起点,但它们解决的是"通用场景下的效率优化",而不是"特定领域的精准压缩"。真正的领域适配,仍然要靠我们自己去理解和构建。

好消息是,"上下文分页/按需召回"和"评估标准"这两个方向,工业界已经有相对成熟的方案了。Letta、LlamaIndex、Zep 等框架已经把"摘要驻留+原始按需拉取"封装成了标准化组件;RAGAS、DeepEval 等工具也提供了评估基础,但需要根据你的业务场景做二次适配,不能直接套用。

这标志着行业共识已从“追求单一压缩算法的突破”转向“构建分层存储+动态调度+业务容忍度评估”的系统工程。

坏消息是,组合这些模块、尤其是做好领域适配,这条路还得自己走。方向二中提到的"领域分类器准确性"问题,方向四中提到的"业务容忍度阈值"问题,都没有现成答案。

这篇文章只是一个初步的探讨,抛砖引玉而已。接下来,我计划针对每个方向单独写更深入的实践文章,分享一些具体的实现思路和踩过的坑。

如果你也在做类似的工作,欢迎交流。毕竟,在这个快速发展的领域里,没有人能独自走完全程。

Logo

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

更多推荐