最近在做一个基于 RAG 的问答系统,检索这块用的向量库,top-k 设了 5,但有些文档 chunk 特别大,几段拼起来直接超了模型上下文。试过调小 chunk size,结果语义又被切碎了,召回效果反而变差。想问问大家,检索后到丢给 LLM 之前,这段中间处理一般怎么做?是再做一层 rerank 截断,还是用滑动窗口压缩?有没有比较成熟的框架或者思路推荐,谢谢。
RAG 检索回来的内容太长,塞不进 LLM 上下文,大家一般怎么处理?
全部回复
共 4 条先做rerank按相关性截断,再对高分段做句子级压缩,比调chunk size靠谱。
这个坑我也踩过,top-k 设 5 其实不算多,问题往往出在 chunk 本身粒度不统一。我后来是把 chunk 分成两层,检索用小的,喂给 LLM 之前再按 doc_id 把相邻的 chunk 合并回去,这样召回精度和上下文完整性都能兼顾一点。rerank 截断确实有用,但别直接按分数砍,容易把答案所在的那段切掉,最好按句子或者段落边界来裁。滑动窗口压缩我也试过,效果一般,主要是压缩过程中容易丢关键实体和数字,尤其是做业务问答的时候特别致命。现在比较常见的做法是检索后加一个 summarizer 或者 extractor,把召回的 chunk 先抽成要点再塞给 LLM,等于用一次小模型换上下文空间。框架的话可以看看 LlamaIndex 的 node postprocessor,里面 sentence window 和 auto-merging 这俩模式基本就是为这个问题设计的。另外上下文超了不一定要硬塞,先算一下 token 预算,给检索内容留固定比例,剩下的让模型基于要点回答,反而更稳。
我一般会在检索后加一层 rerank,用 cross-encoder 或者 Cohere 的 rerank 接口把 top-k 缩到 2-3 条,顺便按相关性截断,能砍掉不少冗余内容。另外可以试试 LangChain 里的 ContextualCompressionRetriever,它支持用 LLM 或嵌入模型对 chunk 做句子级压缩,比单纯截断保留的语义更完整。滑动窗口我踩过坑,窗口太小反而丢关键信息,不如直接上压缩。
我一般会在检索后加一层 rerank,用交叉编码器把 top-k 重排一遍,然后只取前 2-3 个最相关的 chunk 塞进去,通常就够用了。chunk size 别一刀切,可以按段落或语义切,配合一点重叠,召回会稳很多。实在还超的话,再上 LLMLingua 这类压缩,或者让模型先对长 chunk 做摘要再喂给主模型。langchain 和 llama_index 里都有现成的 context 压缩模块,可以直接拿来试。