入选理由引用要可核:入库时按文档哈希、页码、块序生成不可变块 ID,给模型只发 [1] 这类小标号,再按引用原文逐句判定支持与否。
RAG 引用溯源的问题:我最后为什么把分块 ID 放到框架外面自己管
The RAG citation problem: Why I ended up tracking chunk IDs outside the framework
我在生产环境里搭 RAG 系统有一段时间了(工作中主要用 LangChain 加 Elasticsearch),一直撞上的最难的问题并不是检索质量,而是数据在流水线里一路流转时,来源归属会彻底失效。
等答案送到用户手上时,所谓的“引用”通常最多只是一个宽泛的文档级链接。没人能真正核实某条具体说法是否有原文支撑。
我最后做了个副业项目来专门解决这个问题,其中有几条经验几乎能套用到任何技术栈上,包括 LangChain:
- 确定性地生成分块 ID: 在数据接入时一次性生成(例如
{doc_hash}:{page}:{chunk_index}),并在后续每个阶段都把它们当作不可变值。溯源信息丢失大多发生在流水线中途重新生成或重新设置 ID 键的时候。 - 绝不要让 LLM 看到真实 ID: 给它小整数标签,比如 [1]、[2],并把标签到 ID 的映射保存在你的请求作用域里。最好的副作用是:如果你只发了 5 个分块,而模型却引用了 [7],你就能立刻发现这是编造的引用,而不是悄悄把它解析成一堆垃圾。
- 向量库只是索引: 把真正的分块文本存在一个朴素、可靠的可信数据源里(我用的是 SQLite)。校验和报告绝不应该依赖向量库的内部实现。
- 加一道忠实度校验: 我会把每条生成的论断与它所引用分块的原文逐字比对。判定结果分四种:有支撑、部分支撑、无支撑、未引用。用 LLM 来评判 LLM 确实有问题,但把有问题的论断标出来,总比什么都不做强。
- 尊重分页边界: 如果你的分块从不跨页,引用就会有用得多,因为每条论断都能对应到一个确切的页码。检索质量会略有损失,但对审计相关的应用来说,这点代价是 100% 值得的。
这个项目是开源的,想研究实现细节的人可以去看:auditrag(https://github.com/aryanSharmaGithub/auditRag)。
先提醒一句,我是刻意不用框架来搭的,这样才能端到端严格守住 ID 的各项不变量。我确实很好奇——这里有人在 LangChain 里做到过严格的块级溯源吗?我当时没找到干净的做法(也许用自定义 Document 元数据加回调?)。
关于这些设计选择,欢迎随时提问!