Skip to content

RAG的查询扩展/自查询/提示压缩

查询扩展

查询扩展就是把用户的原始问题补全,加上同义词、相关术语、隐含意图等这些信息,让检索能够覆盖更多相关文档,用户输入减肥,扩展后可能变成 健康减肥方法、饮食控制、运动减脂等

image.png

RAG的核心是 先检索后生成

检索如果没有查到高质量的东西,生成自然效果也不会好,查询扩展解决了两个痛点

  • 词汇不匹配:用户说新冠,知识库里存的是COVID-19,如果不扩展就搜不到
  • 查询太模糊:用户就输入怎么理财,扩展后就变成新手理财入门、低风险投资等,方向更明确

image.png

查询扩展发生在用户输入之后,检索之前,属于查询预处理阶段

常见的扩展方式有三种

  • 基于规则的扩展
    • 维护一份同义词词典,减肥对应瘦身、控制体重等,简单粗暴,适合垂直领域
  • 基于LLM的扩展
    • 让大模型直接改写查询,效果最好但是有延迟和成本
  • 基于检索反馈的扩展
    • 先用原始查询检索一轮,从top结果里提取高频词作为扩展词,即PRF,不依赖外部模型

扩展的度要把握好,扩展太少没效果,扩展太多引入噪音,生产环境一般控制在3-5个扩展词

用大模型做查询扩展会增加延迟,有没有办法优化

几个常用的优化手段

  • 用小模型做扩展
  • 扩展结果做缓存,同样的查询不重复调模型
  • 异步扩展,原始查询先跑一路检索,扩展查询同时在跑,两路结果合并
  • 热门查询预扩展,把高频的扩展结果提前存起来

查询扩展和混合检索的关系

二者可以配合使用

  • 查询扩展解决的是查询词不准的问题
  • 混合检索解决的是检索方式单一的问题

扩展后的查询即可以跑向量检索抓语义相关的,也可以走关键词检索,用BM25,然后两个结果在用加权求和或者RRF合并

如果扩展出来的词引入了噪声,怎么处理

  • 事前在扩展的时候就控制质量,prompt明确要求只输出高度相关词汇
  • 事后靠重排序兜底,rerank会把不相关的文档排到后面去

自查询

自查询(Self-Query)是RAG系统自动解析用户查询中的隐含条件的机制,把模糊的自然语言转成结构化查询语句

比如用户说“26年帆哥的用户报告”,自查询会拆成两部分

  • 语义匹配:用户报告
  • 元数据过滤:作者=帆哥,时间=2026

为什么需要自查询

用户提问往往带着隐含条件,传统向量检索只管语义相似度,压根不看元数据,结果就是检索出一堆语义相关但时间、作者、标签都不对的文档,自查询通过“解析+过滤”,让检索同时满足语义相关性和元数据条件,直接解决检索不准的痛点

自查询的工作流程

自查询发生在用户输入 到检索文档之间的意图解析阶段

image.png

python
{
  "vector_search": {
    "query_text": "风险控制策略 监管趋势",
    "embedding_model": "text-embedding-3-large"
  },
  "metadata_filter": {
    "must": [
      {"field": "author", "operator": "=", "value": "李四"},
      {"field": "date", "operator": ">=", "value": "2025-01-01"}
    ],
    "should": [
      {"field": "tags", "operator": "in", "value": ["金融", "风控"]}
    ]
  },
  "options": {
    "limit": 10,
    "score_threshold": 0.75
  }
}

自查询与查询扩展的对比

image.png

实际项目里可以两者配合用,比如先自查询过滤出“2026的用户报告”,再通过查询扩展补充其他关键词

如果知识库没有做元数据标注,自查询还能用吗

用不了,自查询的前提是文档入库的时候就打好了元数据标签,没有元数据,llm解析出来的过滤条件也没地方用,这种情况只能退回到纯语义检索,或者先补一轮元数据抽取,用NER模型或者LLM从文档内容里自动提取时间、作者、主题等字段

提示压缩

提示压缩是对检索出来的文档内容做精简处理,提取核心信息、过滤无关文本,让最终给大模型的内容保持关键信息,又能节省上下文空间

比如检索到一篇10页的技术白皮书,压缩后只保留跟用户问题相关的2个核心章节和关键数据,无关段落直接砍掉

为什么需要提示压缩

RAG的生成效果全靠喂给模型的文档质量,检索出来的文档通常有三个问题

  • 上下文窗口有限,比如gpt-4 turbo是128k tokens, claude是200k tokens,听着很大,但是实际业务里面检索10篇文档很容易就超了,因此压缩能把关键信息浓缩进有限的token里
  • 无关内容稀释重点,用户如果问”如何优化代码“,文档里80%是算法原理,只有20%是优化技巧,不压缩的话模型容易被原理部分带跑偏
  • 省钱省时间,可以减少无关token

image.png

压缩方案落地

一般用小模型或者规则算法来做压缩

LLMLingua是微软开源的方案,基于信息熵评估每个token的重要性,把冗余内容直接砍掉,可以快速用LlamaIndex集成

python
from llama_index.indices.postprocessor import LongLLMLinguaPostprocessor

node_postprocessor = LongLLMLinguaPostprocessor(
    instruction_str="根据上下文回答问题"
)
contexts = retriever.retrieve(question)
compressed_contexts = node_postprocessor.process(contexts)
# instruction_str是压缩的指导方向,比如如下
# 强调保留关键实体
instruction_str = "保留文档中的人名、日期和数字"

# 任务导向压缩
instruction_str = "根据问题提取相关事实"

# 图文场景
instruction_str = "压缩文本时保留与图片中logo相关的描述"

压缩会不会把关键信息压没了,如何保证压缩质量

核心是让压缩模块知道什么是关键

  • 把用户问题作为压缩的锚点,跟问题相关性高的内容优先保留
  • 设置压缩率上限,保留30%-50%是比较安全的区间
  • 用NER提取关键实体,比如人名、日期、数字等硬性信息
  • 上线前最好多测测,对比压缩前后的回答质量

长上下文模型出来了,还需要压缩吗

  • 成本问题,窗口大了不代表免费
  • 上下文越长,模型越容易走神,压缩掉无意义的文本意义还是很大的

最后更新于: