最近在做一个金融研报的问答Agent,用RAG做知识库。单轮问答效果还行,但一旦问题涉及多步推理(比如“某公司2023年营收增速和2022年相比变化了多少,主要受哪个业务影响”),检索召回就经常缺一块,导致LLM只能瞎编或者答非所问。我现在用的是向量检索+BM25混合,也试过把问题拆成子查询再分别检索,但子查询之间怎么合并、去重、排序,总感觉没弄明白。想问问各位,遇到这种多跳场景,是应该换GraphRAG,还是在检索后加一层重排(rerank)?或者有没有更实用的做法,比如先让LLM生成查询计划再执行?求真实工程经验,别推太多论文,谢谢!
Agent+RAG做复杂查询时,多跳检索总是漏召回,大家怎么优化的?
全部回复
共 30 条生成查询计划真挺管用的,先把子查询结果按实体对齐再合并,漏召回少很多。
重排得加,但得用那种能跨段落推理的模型,不然还是白搭。
我们组之前也踩过这个坑,后来发现光靠拆查询没用,关键是拆完得有个“合并策略”。我们现在是让LLM先生成带依赖关系的子问题树,每层检索完用rerank把高相关片段提出来,再带着上下文去做下一跳,漏召回少了很多。GraphRAG除非你的数据本身关系网很密,否则前期构建成本太高,金融研报这种长文本不一定划算。
我们之前做医疗问答也踩过这坑,子查询合并排序确实是玄学。后来发现最实用的组合是:先让LLM生成带依赖关系的查询计划,每步检索完强制用规则去重(比如按文档ID+段落号),再拿结果拼一个临时上下文窗口单独喂给模型做二次推理。GraphRAG除非你知识库本身实体关系很明确,否则维护成本太高,不如先试试在重排模型里加一个“与上一步答案的相关性”特征,比单纯用向量相似度准很多。
我们之前做类似的多跳检索也踩过这坑,混合检索解决不了子查询间的依赖问题。可以试试让LLM先生成带步骤的查询计划,每步独立检索后再用LLM做一次合并过滤,比直接拆query更稳。另外rerank建议加上,尤其用bge-reranker-large这类模型,对跨段落的信息整合帮助很大。GraphRAG除非数据本身关系密集,否则初期成本太高,不如先把查询计划和重排这层做扎实。
我们组之前也踩过这个坑,子查询合并去重确实容易烂尾。后来改成让LLM先输出一个结构化的查询计划,把每步需要的字段和条件列清楚,再逐条去检索,最后按时间线或者业务逻辑做一次规则排序,比直接rerank稳。GraphRAG我们试过,维护成本太高,除非知识库本身实体关系特别密集,不然前期建模就够喝一壶的。另外可以试试在检索前把问题里的数字和实体单独抽出来做个硬匹配,往往能补上向量检索漏掉的精确信息。
之前做类似场景的时候也踩过这个坑,多跳漏召回很多时候不是检索器的问题,而是子查询分割得太机械了。比如你拆成“2023年营收增速”和“2022年营收增速”,分别查完再合并,顺序和权重完全没体现业务逻辑,这时候加rerank其实救不回来,因为候选集里压根没把“哪个业务影响最大”这个维度拉进来。
我的做法是让LLM先生成一个结构化的查询计划,每个节点明确要检索什么实体、什么时间范围、什么指标,然后每个子查询单独跑,但结果不是简单拼接,而是用一个临时图结构去关联。比如把公司、年份、业务线、财务指标都作为节点,子查询命中的段落挂到对应节点上,最后从问题要求的那个“差值”出发,反向找路径上的证据,这样合并去重自然多了。
GraphRAG本质上也是干这个事,但如果你们知识库schema没建好,引入图反而增加维护成本。更轻量的方案是,在拆子查询时让LLM同时输出每个子查询的“依赖关系”,比如必须先拿到2022年的绝对值才能算增速,用这个依赖关系决定合并顺序,再按时间或实体做去重,最后把多个候选段落按相关性分数和路径覆盖度做个简单加权排序。实测比直接rerank召回率提升不少,而且调起来比GraphRAG快多了。
另外注意一个细节,金融研报里很多数据是环比或同比表述,子查询里得把“变化了多少”显式转成“计算2023年增速与2022年增速的差值”,否则LLM生成的子查询可能还是模糊的。试过让LLM先输出一个“检索意图JSON”再执行吗?有时候比直接生成子查询更可控。
我们之前做类似场景也踩过这个坑,后来发现问题不一定在检索,而是子查询合并时排序太粗暴。后来改成按子查询的置信度加权,再对召回结果做去重时保留各跳独有信息,漏召回明显少了。GraphRAG我们试过,维护成本高,效果不稳定,不如先让LLM生成查询计划,再手动定义合并规则,工程上更可控。重排其实对漏召回帮助有限,它只是优化排序,不解决缺失。你子查询之间有没有试过按时间或逻辑顺序做级联过滤?感觉金融数据里时间维度很关键。
查询计划真的有用,先让LLM拆解成带依赖关系的子查询,再按顺序执行合并,比一次性拆了再拼强太多。
我们团队之前也踩过类似的坑,金融场景尤其明显,因为问题里经常藏着时间维度和业务线交叉的隐式条件。你提到的子查询合并排序问题,我们后来是让LLM先输出一个结构化的“查询DAG”(每个节点带检索意图和依赖关系),然后按节点顺序去向量库和BM25分别捞,最后用互信息或者简单的分数加权合并,但最关键的是在合并前先做一遍基于时间戳和实体名的硬过滤,这样能砍掉不少噪音。至于GraphRAG,我们试过但成本偏高,对动态更新的研报数据维护太麻烦,不如在检索后加一层lightweight的cross-encoder重排,只对前20个候选做,效果提升非常明显,尤其能解决“召回了但排后面被截断”的问题。另外有个土办法挺实用,就是为每个子查询生成一个“伪文档”摘要,用摘要之间以及摘要和原问题之间的语义相似度来做merge,比单纯拼embedding靠谱。你有没有试过让LLM在生成查询计划时同时输出每个子查询的“预期证据类型”,比如是数字对比还是事件描述,这样能帮你设计不同的召回策略,而不是全走向量检索。
我们组之前也踩过这坑,后来发现子查询合并的权重比查询本身更重要。你可以试试让LLM先产出带依赖关系的子问题树,然后按叶子节点检索,最后用图结构把结果聚合成上下文块,比单纯rerank稳。另外金融研报其实很适合用GraphRAG,实体关系建好后多跳能少漏不少,但前期构建成本你得掂量下。