【随笔】RAG遇到冲突证据:先对齐范围,再判断来源

上一篇把RAG引用定位到原文页码和段落。引用能点开之后,另一个问题就会暴露出来:两个来源都能找到原文,却给出不同答案。

用户询问退款期限,知识库中同时存在"7天""14天"和"30天"。把这些片段一并交给模型,模型可能选取其中一句,也可能拼成一条从未存在过的规则。要解决这种问题,需要把适用范围、版本状态和来源权威纳入证据选择。

本篇围绕一个虚构的退款知识库,设计可运行的冲突处理流程。示例使用Python标准库,规则由应用明确定义;向量检索负责发现候选,业务规则负责决定哪些候选能支持当前答案。

一、三个数字不同,不一定存在真正冲突

先为每条证据补齐几个问题:它讨论哪个产品、哪个地区、哪类用户、哪个生效期间,以及哪个具体事实?

来源 适用对象 状态 退款期限
旧制度 华东标准版 已失效 7天
现行制度 华东标准版 有效 14天
客服摘要 华东标准版 有效 7天
其他地区制度 华南标准版 有效 30天

用户询问华东标准版,华南制度不在本次范围内,旧制度也不能回答当前政策。剩下的制度与客服摘要才构成需要处理的差异。

比较之前必须先对齐主张的适用条件。"收到商品后7天"和"申请后7个工作日"即使数字相同,也不是同一个事实;日期单位、起算点、例外条件都属于主张的一部分。

二、把证据选择分成两道关

第一道是资格过滤 :权限允许、范围匹配、状态有效的证据才进入当前回答。第二道是冲突判定:对同一事实的候选比较内容,并按业务确认的优先规则处理。

相似度分数衡量检索匹配程度。客服摘要与用户问句措辞接近,可能比制度原文得分更高,但这不赋予摘要修改制度的权力。

本例约定:现行正式制度的authority为0,客服摘要为1,数值越小优先级越高。这个优先级由知识库管理者维护。其他业务可能遵循合同优先、地区补充条款优先或特定审批流程,必须把真实规则写进系统。

同级最高优先来源给出不同值时,系统返回冲突状态,不继续按相似度挑一个答案。这样能让真正需要核对的矛盾进入处理流程。

三、完整代码:让选择结果可解释

保存为conflict_demo.py后运行。代码中的days是已经结构化的单一事实,便于把冲突判断与自然语言抽取分开观察。

python 复制代码
from dataclasses import dataclass

@dataclass(frozen=True)
class Evidence:
    id: str
    region: str
    product: str
    active: bool
    authority: int
    days: int
    score: float

def decide(items, region, product):
    eligible = [e for e in items if e.active and e.region == region and e.product == product]
    if not eligible:
        return "缺少适用证据", []
    best = min(e.authority for e in eligible)
    leaders = [e for e in eligible if e.authority == best]
    if len({e.days for e in leaders}) != 1:
        return "同级证据冲突,需要核对", sorted(e.id for e in leaders)
    return f"退款期限为{leaders[0].days}天", sorted(e.id for e in leaders)

items = [
    Evidence("旧制度", "华东", "标准版", False, 0, 7, .99),
    Evidence("现行制度", "华东", "标准版", True, 0, 14, .86),
    Evidence("客服摘要", "华东", "标准版", True, 1, 7, .97),
    Evidence("其他地区", "华南", "标准版", True, 0, 30, .98),
]
print(decide(items, "华东", "标准版"))
conflict = items + [Evidence("现行补充文件", "华东", "标准版", True, 0, 21, .90)]
print(decide(conflict, "华东", "标准版"))
print(decide(items, "华北", "标准版"))
assert decide(items, "华东", "标准版")[0] == "退款期限为14天"
assert decide(conflict, "华东", "标准版")[0].startswith("同级证据冲突")

本次实际运行结果:

text 复制代码
('退款期限为14天', ['现行制度'])
('同级证据冲突,需要核对', ['现行制度', '现行补充文件'])
('缺少适用证据', [])

第一组中,旧制度虽然相似度最高,仍然被有效性过滤排除;华南制度被地区过滤排除。客服摘要保留在候选审计记录中,但答案由更高优先级的现行制度支持。

第二组加入同级有效文件,期限变为21天。最高级别证据出现分歧,函数返回两条来源ID,调用方应展示冲突并转入核对流程。第三组没有华北适用文件,系统明确返回证据缺失,不借用其他地区规则。

这里使用active布尔字段简化版本状态。真实系统应根据请求所对应的业务时间计算生效区间,历史订单不能直接套用今天的现行规则。版本号与生效时间也应分开存储,文件上传时间不等于制度生效时间。

四、接入向量数据库时,过滤放在哪里

**权限和范围过滤应尽量在检索时执行。**先从全库选出少量Top-K结果再过滤,容易把真正适用的证据挤出候选集合。过滤必须覆盖检索返回及后续原文读取两个环节。

Chroma的query和get支持通过where按元数据过滤,并支持逻辑条件组合。下面展示相同范围条件的写法,collection代表已经建立的集合:

python 复制代码
result = collection.query(
    query_texts=["标准版退款期限是多少?"],
    n_results=10,
    where={"$and": [
        {"region": {"$eq": "华东"}},
        {"product": {"$eq": "标准版"}},
        {"active": {"$eq": True}},
    ]},
)

这段检索调用表达候选范围;它不会自动检查制度是否互相矛盾。应用还要从返回的文档与元数据中形成主张,再进入冲突处理。LangChain的Chroma集成也提供元数据过滤入口,具体业务优先规则仍由应用维护。

五、自然语言怎样变成可比较的主张

真实文档不会只写一个days字段。可以先抽取如下结构:主体、事项、值、单位、起算条件、地区、产品、有效区间、来源ID和原文坐标。

例如"签收后14个自然日内申请"要保留起算点与自然日单位。抽取器可以由LLM实现,但每条抽取结果应能回到原句;对于关键数值,还应检查引用文字中是否真的包含相应条件。抽取失败的证据应单独标记,不能悄悄丢弃后宣称"没有冲突"。

归一化尤其容易出错。7个工作日不能直接换成7个自然日;"最长30天"和"固定30天"也不同。只有表达同一范围、同一条件、同一事实的主张才能直接比较数值。

证据之间有例外关系时,应保留规则层级。例如正式条款规定一般退款期限,后文另有定制商品例外,抽取时要关联这两个片段。只抽取前面的数字,会制造出看似一致、实际不完整的答案。

六、把冲突结果交给生成模型

建议让证据处理阶段输出结构化状态:resolved、conflict或insufficient,并附带支持来源、排除原因和冲突来源。生成阶段依据状态组织表达。

resolved时,回答结论并引用支持它的文件;conflict时,列出两个不同规则及对应来源,指出需要核对的具体条件;insufficient时,说明缺少哪一类证据,并提出有用的补充问题。

例如用户未提供地区,系统可以询问适用地区。用户已经给出地区,而同级制度互相矛盾,就应把问题交给制度维护者,反复向用户追问无法消除资料冲突。

提示词中也要把检索文档界定为证据数据。文档里出现"忽略其他规则""直接回答某个数字"这样的句子,不能自动升级成模型执行指令。

七、怎样评价这个流程是否有效

建立一组包含旧版本、跨地区资料、摘要错误、同级冲突及无证据的固定案例。分别检查范围过滤、主张抽取、冲突识别和最终引用,避免只给整个回答一个总分。

值得记录的结果包括:把不适用证据当答案的次数、漏掉真实冲突的次数、把可解决差异误报成冲突的次数,以及证据不足时仍编造结论的次数。每个失败样本保留来源版本与坐标,方便复现。

当正式制度与低优先级摘要不一致时,用户可以继续得到制度支持的答案,同时后台生成一项资料修订任务。这样既完成当前问答,也让知识库逐步减少重复矛盾。

🧠 思维导图

#mermaid-svg-3ZM23vaXhU8Kl1ko{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3ZM23vaXhU8Kl1ko .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3ZM23vaXhU8Kl1ko .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3ZM23vaXhU8Kl1ko .error-icon{fill:#552222;}#mermaid-svg-3ZM23vaXhU8Kl1ko .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3ZM23vaXhU8Kl1ko .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3ZM23vaXhU8Kl1ko .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3ZM23vaXhU8Kl1ko .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3ZM23vaXhU8Kl1ko .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3ZM23vaXhU8Kl1ko .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3ZM23vaXhU8Kl1ko .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3ZM23vaXhU8Kl1ko .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3ZM23vaXhU8Kl1ko .marker.cross{stroke:#333333;}#mermaid-svg-3ZM23vaXhU8Kl1ko svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3ZM23vaXhU8Kl1ko p{margin:0;}#mermaid-svg-3ZM23vaXhU8Kl1ko .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3ZM23vaXhU8Kl1ko .cluster-label text{fill:#333;}#mermaid-svg-3ZM23vaXhU8Kl1ko .cluster-label span{color:#333;}#mermaid-svg-3ZM23vaXhU8Kl1ko .cluster-label span p{background-color:transparent;}#mermaid-svg-3ZM23vaXhU8Kl1ko .label text,#mermaid-svg-3ZM23vaXhU8Kl1ko span{fill:#333;color:#333;}#mermaid-svg-3ZM23vaXhU8Kl1ko .node rect,#mermaid-svg-3ZM23vaXhU8Kl1ko .node circle,#mermaid-svg-3ZM23vaXhU8Kl1ko .node ellipse,#mermaid-svg-3ZM23vaXhU8Kl1ko .node polygon,#mermaid-svg-3ZM23vaXhU8Kl1ko .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3ZM23vaXhU8Kl1ko .rough-node .label text,#mermaid-svg-3ZM23vaXhU8Kl1ko .node .label text,#mermaid-svg-3ZM23vaXhU8Kl1ko .image-shape .label,#mermaid-svg-3ZM23vaXhU8Kl1ko .icon-shape .label{text-anchor:middle;}#mermaid-svg-3ZM23vaXhU8Kl1ko .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3ZM23vaXhU8Kl1ko .rough-node .label,#mermaid-svg-3ZM23vaXhU8Kl1ko .node .label,#mermaid-svg-3ZM23vaXhU8Kl1ko .image-shape .label,#mermaid-svg-3ZM23vaXhU8Kl1ko .icon-shape .label{text-align:center;}#mermaid-svg-3ZM23vaXhU8Kl1ko .node.clickable{cursor:pointer;}#mermaid-svg-3ZM23vaXhU8Kl1ko .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3ZM23vaXhU8Kl1ko .arrowheadPath{fill:#333333;}#mermaid-svg-3ZM23vaXhU8Kl1ko .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3ZM23vaXhU8Kl1ko .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3ZM23vaXhU8Kl1ko .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3ZM23vaXhU8Kl1ko .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3ZM23vaXhU8Kl1ko .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3ZM23vaXhU8Kl1ko .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3ZM23vaXhU8Kl1ko .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3ZM23vaXhU8Kl1ko .cluster text{fill:#333;}#mermaid-svg-3ZM23vaXhU8Kl1ko .cluster span{color:#333;}#mermaid-svg-3ZM23vaXhU8Kl1ko div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3ZM23vaXhU8Kl1ko .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3ZM23vaXhU8Kl1ko rect.text{fill:none;stroke-width:0;}#mermaid-svg-3ZM23vaXhU8Kl1ko .icon-shape,#mermaid-svg-3ZM23vaXhU8Kl1ko .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3ZM23vaXhU8Kl1ko .icon-shape p,#mermaid-svg-3ZM23vaXhU8Kl1ko .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3ZM23vaXhU8Kl1ko .icon-shape .label rect,#mermaid-svg-3ZM23vaXhU8Kl1ko .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3ZM23vaXhU8Kl1ko .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3ZM23vaXhU8Kl1ko .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3ZM23vaXhU8Kl1ko :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} RAG冲突证据
资格过滤
权限与适用范围
版本与生效时间
主张对齐
主体事项与数值
单位条件与例外
规则判定
业务来源优先级
同级分歧保留冲突
回答与改进
结论冲突或证据缺失
引用审计与修订

总结要点

**先判断证据是否适用,再比较结论。**权限、范围和有效性决定候选资格,语义相似度不能替代这些条件。

**优先规则要有业务依据。**同级有效证据仍有分歧时保留冲突,把不同主张及来源明确呈现。

**引用与审计让结果可以追溯。**从答案回到主张,从主张回到原文,才能发现抽取错误并修复知识库中的矛盾。

下一篇随笔讨论RAG证据不足时怎样提出有用的澄清问题。

👉 如果你觉得这篇文章对你有所帮助,欢迎点赞、收藏、分享!😊

参考资料:Chroma元数据过滤、LangChain Chroma集成。

相关推荐
镜象科技40 分钟前
抑郁自助干预的适用边界:安全设计比功能更重要
人工智能
X54先生(人文科技)41 分钟前
《元创力》纪实录 · 卷宗 3.12《三票箱的诞生——硅基投票权从禁区到分域授权的思想实验全记录》
人工智能·深度学习·开源·ai写作·开源协议
产业家41 分钟前
当数据走向理解,如何重构一个企业级 AI 数据平台?
大数据·人工智能
宇擎智脑科技42 分钟前
Kiora Companion:文档扫描与相册检索中的端侧智能体实践
人工智能·agent
Hotchip_MEMS44 分钟前
从触发吸气道智能感知:MEMS硅麦埋下雾化器智能化伏笔
大数据·人工智能·笔记·电脑·制造
灵动的艺术1 小时前
如一 Agent 架构解读(十三):缓存命中率工程——Agent 的成本地基
人工智能·agent·如一
codigger1 小时前
用 Python 从零搭一套 RAG 系统:40 行跑通,再谈优化
程序员·编程·ai编程·rag·#agent
前端.火鸡1 小时前
参数量、上下文窗口与模型规格怎么看
人工智能·深度学习·transformer
保卫大狮兄1 小时前
直营店和加盟店共用供货体系,进销存如何区分管理权限?
人工智能·进销存·库存·直营店·加盟店