Skip to content

fix(rag): 修复多意图检索 Chunk 归属丢失 - #105

Merged
magestacks merged 1 commit into
nageoffer:mainfrom
Niooooo:agent/fix-intent-chunk-attribution-cleanup
Aug 7, 2026
Merged

fix(rag): 修复多意图检索 Chunk 归属丢失#105
magestacks merged 1 commit into
nageoffer:mainfrom
Niooooo:agent/fix-intent-chunk-attribution-cleanup

Conversation

@Niooooo

@Niooooo Niooooo commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

说明

Issue #87 的多意图检索会在并行汇总后丢失 Chunk 与召回意图的对应关系,导致未命中的意图参与 Prompt 规划。

改动

  • 在定向向量检索中保留 Chunk 与意图的真实归属
  • 等价查询只执行一次,同时保留全部意图 owner
  • 后处理完成后仅保留最终入选 Chunk 的归属
  • 全局证据不激活候选意图的 snippet,但仍保留分类结果对应的自定义 promptTemplate
  • RetrievedChunkKey 放到 framework 层,去重、融合和归因共用同一身份规则
  • 接入最新 RetrievalScope、补充检索配额和单子问题调用结构
  • 补充共享 Chunk、截断后归属、全局证据和 Prompt 选择回归测试

边界

精确意图归属目前由向量定向检索提供。Keyword、Graph 和全局检索没有足够信息建立精确归属,因此保持为空,不根据 Collection、文档名或内容反推。排序、RRF、Rerank、Score、TopK 和 Prompt 模板资源不变。

验证

  • 定向回归:73 passed
  • 编译:passed
  • git diff --check origin/main...7937eee:passed
  • 全仓测试:241 tests,0 failures,24 errors,2 skipped
    • 21 个 Spring 集成用例因本机 127.0.0.1:6379 拒绝连接而无法加载上下文
    • 3 个错误来自既有 JdbcConversationMemorySummaryServiceTest 的 Mockito 严格桩检查

Closes #87

@Niooooo
Niooooo marked this pull request as ready for review August 5, 2026 07:12
@magestacks

Copy link
Copy Markdown
Collaborator

供参考:

核心思路(用真实召回归属替代「把 chunks 广播给每个候选意图」)是对的,RetrievedChunkKey 把去重 / 融合 / 归因三处的 key 规则收成一份也是净收益,顺手删掉了 DefaultContextFormatter 里那个拿 map size 当后缀的自造 key。但归属为空的那条路径上有一处连带影响得先定下来。

1. 全局兜底作用域下,意图的自定义 promptTemplate 会静默失效

intentChunks 这个 map 同时是两个决策的输入:

  • DefaultContextFormatter.retrievedIntents() 拿它过滤 snippet
  • RAGPromptService.planPrompt() 拿它过滤 retained,进而决定用不用意图自己的 promptTemplate

改前全局路径下 RetrievalEngine 把 chunks 挂到每个 kbIntent 上,两处都判「有命中」。改后全局路径不产归属,chunks 全部落到 MULTI_CHANNEL_KEY,两处都判「无命中」。

snippet 被抑制是有意为之(globalEvidenceDoesNotActivateCandidateSnippet 有断言),但同一条过滤规则在 RAGPromptService 里还管着模板选择,这一层没有测试覆盖。

在 merge base 和 PR HEAD 上各跑一遍同样输入(单个 KB 意图,score 0.65,带 snippet 与 promptTemplate,证据走全局路径):

snippet 进上下文 自定义模板生效 证据进上下文
merge base true true true
PR HEAD false false true

证据还在,规则和人设没了,没有日志也没有异

触发条件不窄。默认 confidence-threshold: ement-threshold: 0.8,单个 KB
意图只要分数落在 [0.6, 0.8) 就走全局兜底 —等」这个最常见的中间态正好踩上。

要定的是:全局兜底时该不该保留意图人设。我「不敢把检索范围收窄到这几个库」,不等于「这
个意图没命中」,意图分类结果本身还有效。如nPrompt` 那条链得显式处理,不能靠 map
为空顺带生效。

2. 关键词 / 图谱通道也做意图收窄,但不

SearchChannelResult.intentIdsByChunkKey 」,但
KeywordSearchChannel.resolveCollections()xtractIntentCollections()
一样会收窄到命中意图的库 —— 它们也是定向检

后果是某意图只被关键词命中、向量没命中时,,snippet 和模板一起丢。当前
keyword.enabled: falsegraph.type: none,默认配置不触发,但配置一开就有这个 释改成「仅向量定向检索填写」并写清后果。

3. RetrievedChunkKey 的位置

IntentParallelRetrievervector/strategRetrievedChunkKey,方向是模态包 →
编排层,和「依赖恒为单向 retrieval → {vec 。RetrieveRequest`
已经是这个例外,所以不算新破例,但又多一个

RetrievedChunkKeyRetrievedChunk 的到 framework 里 RetrievedChunk
旁边更顺,两边都向下依赖,不用开例外。

4. 分支落后 main 15 个提交

RAGPromptServiceTest.includesCitationRulesFromKnowledgePrompt 在 PR 分支上是红的,但在 merge
base(dc0d001)上就已经红了,不是这个 PR answer-citation-rules.st一起更新过。除此之外 PR 自己的测试全绿,跟 main 也能干净合并。

建议 rebase 到 main 再验一次。17eaaa6 引 snippet /模板这套语义有交叉,合到一起之后第 1 条的取舍可能要重新看。

一处可以顺手简化

formatKbContext 现在同时收 rerankedByInrerankedChunks,但前者只用来判断「哪些意图有命中」(CollUtil.isNotEmpty(rerankedByIntent.get(id)))。传 Set retrievedIntentIds` 就够,参 入参可能不一致」的状态。

@Niooooo
Niooooo force-pushed the agent/fix-intent-chunk-attribution-cleanup branch 3 times, most recently from 94e0b62 to 6e535e5 Compare August 6, 2026 11:48
@magestacks

Copy link
Copy Markdown
Collaborator

不好意思,昨天没看到咱的提交。我重构了向量、关键字和图的检索相关代码, 看先来有冲突,辛苦合并下看看

@Niooooo
Niooooo force-pushed the agent/fix-intent-chunk-attribution-cleanup branch from 6e535e5 to 7937eee Compare August 7, 2026 05:22
@Niooooo

Niooooo commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

已合并最新 main,冲突已经解决,相关测试也重新跑过了,麻烦再看一下。

@magestacks
magestacks merged commit 9d80d7a into nageoffer:main Aug 7, 2026
@magestacks

Copy link
Copy Markdown
Collaborator

PR已合并, 同时检测出一些其他优化项,请问是否有兴趣跟进修复?

归因缺失导致的两个遗留问题

PR 解决了「按意图选提示词」的核心问题,但引入了一个静默的行为回退,以及暴露了一个关键词/图谱通道的已有缺陷。两个问题根因相同,修法不同,建议分开跟进。


问题 A:关键词/图谱通道交不出归因

影响面:仅开启 ES 或图谱的部署,默认配置碰不到。

向量通道按意图逐个查,chunk 天生带归属。关键词通道把所有库并成一次 ES 查询(KeywordSearchChannel.java:74,94),返回结果里不带来源库信息,RetrievedChunk 也没有 collectionName 字段,归因只能留空。图谱通道同理。

这会导致一个结果:同一个 chunk,向量通道捞出来就有归因、关键词通道捞出来就没有。同一个问题的行为取决于哪条通道先出货,从用户视角看是随机的。

极端情况下向量通道两个意图都空、只有 ES 出货,retrievedIntentIds 退化成空集合——直接掉进问题 B。

SearchChannelResult.java:53-55 的字段注释对此有自陈:「仅向量定向检索填写精确归属,关键词、图谱和全局结果保持为空」。ChannelAttribution 注释也明确写了框架层 DTO 不带来源字段,是刻意设计。所以这个不是忘了,是当时没需求。现在有了,改起来要动 ES 查询结构,不是顺手的事。


问题 B:「没查过」被当成「查了没查到」(行为回退)

影响面:默认配置就会踩。这是唯一的行为回退,优先级最高。

当意图最高分落在 [0.35, 0.6) 区间时,系统退化成全局作用域,不分意图查,retrievedIntentIds 恒空。这不是边界情况——0.35 是意图解析下限,0.6 是定向检索阈值,中间这一整段分数带都会触发。

问题出在空集合的语义。retrievedIntentIds 为空有两种截然不同的原因:

  1. 定向检索了某个意图的库,一条没召回 → 该意图确实没命中,应该剔除
  2. 走了全局作用域,压根没按意图查 → 无从判断,不该当"没命中"处理

但代码里两者都是 Set.of(),分不出来。

更麻烦的是,两层消费代码对这个空集合的读法是反的:

  • RAGPromptService.java:117:集合空 → isNotEmpty 为 false → 短路 → 不过滤 → 意图模板保留
  • DefaultContextFormatter.java:69:集合空 → isEmpty 为 true → 返回空列表 → 意图 snippet 全丢

结果就是同一个意图、同一次请求,人设模板生效了、规则 snippet 没了。改前两条都生效,所以这是一次回退。

实证:同场景在 main 和本分支分别跑 formatKbContext,main 输出里 <rules> 段正常包含 snippet,本分支输出里 <rules> 整段消失,只剩 <content>。模板侧同时验证了自定义模板照常生效——回退和内部不一致同时坐实。

PR 自己的两个测试其实各钉了一颗钉子:usesSingleCandidateTemplateForGlobalEvidence(模板生效)和 globalEvidenceDoesNotActivateCandidateSnippet(snippet 不生效),说明行为分叉是有意为之。但代码里没有注释解释为什么模板和 snippet 要分家,这个决策的理由需要补上。


根因是同一个

三种真实状态(查了有结果 / 查了没结果 / 没查过)挤进了两个格子(在集合里 / 不在集合里)。issue 要治的是第二种,第三种被顺带误伤。

A 在生产侧让「没查过」这种状态出现得更频繁(关键词通道永远交白卷),B 在消费侧误读了这种状态。修法完全不同:B 是消费侧判断逻辑,几行能改;A 要动 ES 查询结构,是独立的工作量。


其他小项

  • KnowledgeRetrievalResult.retrievedIntentIds()RetrievalContext.getRetrievedIntentIds() 重复推导同一个集合,后者靠 intentChunks.keySet() 减去 MULTI_CHANNEL_KEY 反推,耦合脆弱
  • MULTI_CHANNEL_KEY 语义被扩宽(原意「没识别出意图」,现兼任「无归属 chunk」,且能与真实意图 key 共存),RAGConstant.java:58 注释仍是旧含义。已查过 SourcesAssembler / GroundingChunksAssembler / EvalController 均摊平取值,引用链路不受影响
  • IntentParallelRetriever 类头 javadoc 被整段删;KnowledgeRetrievalResultRetrievedChunkKey 两个 public 类型无类头注释;MultiChannelRetrievalEngineDeduplicationPostProcessor 中若干条解释 why 的注释被削掉(RRF 按名次重复累加的坑、为什么不能按 collection 去重)

@magestacks

Copy link
Copy Markdown
Collaborator

@Niooooo 请问上述内容咱是否有兴趣提交PR?

@Niooooo

Niooooo commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

有兴趣,我准备先试着处理问题B,解决完了很乐意提PR

@magestacks

Copy link
Copy Markdown
Collaborator

@Niooooo 好的。今明两天我应该会重构一部分代码,咱改动前记得勤拉下代码。

juzi050 pushed a commit to juzi050/ragent that referenced this pull request Aug 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

多意图检索合并后 Chunk 归属丢失,导致未命中意图也参与 Prompt 规划

2 participants