RAG引用一致性保障:局部映射模式的设计与实践

RAG引用一致性保障:局部映射模式的设计与实践

最后更新:


962 字 约 5 分钟

1.绪言

事情的起因是这样,今天在推RAG构建的时候,遇到了这样一个问题:

定义了一些problems去给Agent做测试看整个RAG问答过程中搜索、匹配以及引用召回的过程有没有什么问题,然后发现Agent的返回答案中出现了一些异常的现象,引用的citation_id超出了范围,根本就不存在对应的chunk

这个问题还是比较严重的,为什么?它会把原本的异常伪装成成功的回答,给用户构造了一个错误的,或者说根本就没有的源头,丧失可信度,胡言乱语,看到召回率以为很高,其实都是假数据

所以我们现在来尝试解决这个问题


2.问题根因

其实源头就是Chunk编号导致的,我原本在项目中对Chunk是这样编号的

Python
chunk_id = stable_hash(
    document_id,
    content_hash,
    parsed.heading_path,
    str(index),
)

然后stable_hash据此生成64位十六进制哈希值,我试图让Agent稳定地引用这个chunk_id,这显然是做不到的

为什么?回顾LLM的思考机制,经过Tokenizer,这个长串的chunk_id就会被切分成几个语义毫不相干的Token,当LLM尝试预测连续多个非语义Token,其命中正确chunk_id的概率就会显著下降

以及形式化外推和经典的Lost in Middle问题,当缺乏精确的上下文记忆,LLM宁愿胡说八道也不愿意说它实际上不清楚,因为被套上了遵守格式的硬约束


3.方案选择

接下来我们来开始解决这个问题,大概有三种方案:Prompt约束、重试机制以及局部映射

首先来讲Prompt约束,这个实现算是比较简单,不外乎就是在RAG系统中的Prompt里加上几句话,但是它的可靠性怎么样?仍然非常低,因为你就算三令五申让它不要瞎猜,指不定你这个提示词本身都被Lost了,谁还管你会不会幻觉,这个就是典型的软约束方案,也是之前的Prompt Engineering的痛点,无法行之有效地对模型产生硬约束,治标不治本

随后是重试机制,这个比较好理解,在运行期,看到你这轮回答引用的citation_id越界了,就告诉模型去尝试重试。可靠性还行,对降低偶发率有一定的效果,但是有一个比较致命的问题就是显著带来了运行负担,一次重试两次重试,浪费的都是时间和资源,它可以作为一个辅助,但绝对不能作为解决这个问题的主力

最后则是我们今天要介绍的重点,短局部标签+服务端映射,我们接下来详细讲一下


4.局部映射

首先对于RAG,我们对给定的query,在预处理好的向量数据库中进行召回

Python
retrieved = await self.retrieve(retrival_query or query, mode=mode, limit=limit)

然后这里召回的Chunk的编号就是其在向量数据库中的编号,即上面所提到的64位十六进制SHA-256编码

我们现在肯定不能直接拿着这个编码去给LLM处理了,所以考虑局部映射,具体地:

Python
citation_labels = {
    f"R{index}": citation for index, citation in enumerate(retrieved.citations, start=1)
}
evidence = build_evidence_context(
    retrieved.citations,
    citation_labels=citation_labels,
)

通过这种方式,最终LLM看到的就是:

Python
citation_labels = {
    "R1": citation_a,
    "R2": citation_b,
    "R3": citation_c,
}

然后只能这样引用:

Python
{
  "answer": "...",
  "citation_ids": ["R1", "R3"],
  "evidence_status": "supported"
}

最后通过服务端这边再将对应的citations_ids转换成原始的citation

使用这种R1类的编号,取代了原有一长串的随机编码,将其有效性从模型的不可预测行为转为了服务端可预测的问题

将一次完整的问答整理一下,现在就是这样的流程:

sequenceDiagram
    participant U as 用户
    participant S as 检索服务
    participant M as 局部映射
    participant L as LLM
    participant V as 校验与还原

    U->>S: 用户问题
    S->>M: retrieved.citations
    M->>M: 生成本次请求的局部映射
    Note over M: R1 → CitationSnapshot A<br/>R2 → CitationSnapshot B<br/>R3 → CitationSnapshot C
    M->>L: 放入 R1/R2/R3 上下文
    L-->>V: 返回 citation_ids
    V->>V: 校验 citation_ids 是否属于本次映射
    V->>V: 将 R1/R2/R3 还原为真实 CitationSnapshot
    V-->>U: 返回内部 chunk_id 和完整引用快照

5.小结

最后,看到一句话特别适合今天的这个场景:

把复杂留给服务端,把简单留给LLM

以上

评论

评论会在审核后展示

正在加载人机验证...

查看原图