父子块、RAPTOR 与 GraphRAG
正在读取学习记录…
客户问“生鲜签收第10天能退吗”,检索到“7天内可退”还不够,商品例外可能在旁边一段。高级 RAG 关心怎样组织资料、找回相关片段,再把足够的上下文交给回答步骤。RAG 是先检索资料、再据此生成答案的流程。
父子块把文档分成两层:小的子块便于精确定位,大的父块保留完整条件。像先在目录中找到一条条目,再翻开那一页。RAPTOR 将相关片段逐层聚合成摘要树,适合从具体事实走向跨段概括;GraphRAG 用实体与关系组成图,适合追查“订单属于谁、商品受哪条规则约束”。
同一个问题需要哪些证据
固定资料包含退货期限、商品例外和退款时间三个子块。问题中的“生鲜”可以直接命中例外;“第10天”需要期限信息。混合检索把关键词检索与语义检索的结果合并:前者擅长准确词语,后者可以找含义接近的表述。
下面用两份固定排序演示融合算法。语义排序是给定的输入,便于看清融合步骤;真实向量检索需要先计算文本向量,再按相似度排序。两份结果都含例外块,因此融合前要按子块标识去重,避免同一条证据占两席。
跟着固定样本运行
先在 PowerShell 创建并进入学习目录:
mkdir "F:\学习\l5-advanced-rag" -Force
cd "F:\学习\l5-advanced-rag"保存下面的文件后,在这个终端运行 dir 核对文件名,再执行本节给出的运行命令。
新建 F:\学习\l5-advanced-rag,将下面完整代码保存为 advanced_rag_demo.py。
先在 PowerShell 创建并进入学习目录:
mkdir "F:\学习\l5-advanced-rag" -Force
cd "F:\学习\l5-advanced-rag"保存下面的文件后,在这个终端运行 dir 核对文件名,再执行本节给出的运行命令。
"""融合两个检索排序,再展开命中子块的完整父规则。"""
CHILDREN = {
"期限": ("退货规则", "已发货订单在签收7天内可退货。"),
"例外": ("退货规则", "生鲜商品不支持无理由退货。"),
"到账": ("退款规则", "退款审核通过后3个工作日到账。"),
}
PARENTS = {
"退货规则": "已发货订单在签收7天内可退货;生鲜商品除外。",
"退款规则": "退款审核通过后3个工作日到账。",
}
def fuse(rankings, k=60):
# RRF:每份排序贡献 1/(k+名次),同一子块累加得分。
scores = {}
for ranking in rankings:
seen = set()
for rank, chunk_id in enumerate(ranking, 1):
if chunk_id in seen:
continue
seen.add(chunk_id)
scores[chunk_id] = scores.get(chunk_id, 0) + 1 / (k + rank)
# 同分时按标识排序,让重跑的顺序固定。
return sorted(scores, key=lambda key: (-scores[key], key))
def expand(chunk_ids):
# 多个子块属于同一父块时,父规则也只放一次。
parent_ids = list(dict.fromkeys(CHILDREN[key][0] for key in chunk_ids))
return [PARENTS[key] for key in parent_ids]
def answer(day, product, contexts):
# 只有同时拿到期限和例外,才给出完整条件判断。
rule = PARENTS["退货规则"]
if rule not in contexts:
return "缺少完整退货规则"
if product == "生鲜":
return "生鲜商品属于例外,不能按无理由退货规则申请。"
return "期限内可申请。" if day <= 7 else "超过7天期限。"
if __name__ == "__main__":
keyword = ["例外", "期限"]
semantic = ["期限", "例外", "到账"]
merged = fuse([keyword, semantic])
print("融合排序:" + "、".join(merged))
contexts = expand(merged[:2])
print("展开父块:" + ";".join(contexts))
print("客户答复:" + answer(10, "生鲜", contexts))在 PowerShell 中进入保存目录并运行:
cd "F:\学习\l5-advanced-rag"
ls
python -X utf8 advanced_rag_demo.py输出为:
融合排序:例外、期限、到账
展开父块:已发货订单在签收7天内可退货;生鲜商品除外。
客户答复:生鲜商品属于例外,不能按无理由退货规则申请。“例外”和“期限”各在一份排序中排第一、另一份排第二,两者得分相同。程序按标识固定同分顺序,所以例外在前。RRF 是倒数排名融合,按名次贡献分数;参数60降低前几名之间的分差。它能合并分值尺度不同的检索器,因为它使用名次而非原始相似度。
前两个子块都指向“退货规则”。展开后只有一个父块,其中包含期限与例外,回答函数才能根据完整条件回复。融合解决“候选怎样合并”,父块展开解决“候选需要哪些相邻上下文”。
三种资料结构怎样选择
父子块适合条款、手册和局部上下文:子块命中“生鲜”,父块补回期限。父块也会带来更多文字,需要限定展开数量,防止整个文档进入请求。
RAPTOR 的摘要树底层是原文片段,上层是多个片段的汇总,再上一层可以概括整个主题。问“几类售后规则有什么差别”时,可以先命中摘要,再下钻查看原文。下钻是沿父子关系读取更具体的节点。生成摘要时应保存被汇总的来源,数字和例外仍需回到原文核对。
GraphRAG 中,节点是实体,如商品、订单、政策;边是实体之间的关系,如“订单包含商品”“商品适用政策”。用户问“这个订单为什么受生鲜例外限制”,可以沿订单→商品→规则的关系读取证据。关系图需要抽取、去重、版本管理,新增一条规则也可能要求更新相关边。
| 问题 | 优先尝试的结构 | 需要带回的证据 |
|---|---|---|
| 某条款有何例外 | 子块定位、父块展开 | 完整条款 |
| 多章节共同讲了什么 | 摘要树 | 汇总与底层来源 |
| 两个实体为什么有关联 | 关系图 | 实体、关系及原文 |
这些结构可以组合使用。摘要树帮助定位主题,关键词和向量检索选具体条款,关系图连接订单与商品,最后把原文作为回答依据。是否值得建立树或图,要用真实问题比较召回与维护成本,不能按名称判断效果。
改动一个固定输入
把主程序中的 answer(10, "生鲜", contexts) 改为 answer(10, "普通", contexts),再运行。最后一行变成“客户答复:超过7天期限。”恢复“生鲜”后回到原结果。
如果只保留“到账”子块,父块展开拿到退款规则,回答会显示“缺少完整退货规则”。这说明找到一段与售后有关的文字,还不足以回答退货条件。
| 现象 | 检查位置 |
|---|---|
| 同一证据出现多次 | 融合前的子块标识和展开后的父块去重 |
| 命中了词但回答缺条件 | 子块到父块的映射 |
| 主题摘要与原文矛盾 | 摘要来源与版本 |
| 关系路径有边却无原文 | 实体关系的证据记录 |
检查实现中的六种情况
在同一目录将下面测试保存为 test_advanced_rag_demo.py。测试覆盖正文中的正常路径与边界条件。
测试代码:test_advanced_rag_demo.py
"""检查融合、去重及完整条件对回答的影响。"""
import unittest
from advanced_rag_demo import CHILDREN, PARENTS, fuse, expand, answer
class AdvancedRagTests(unittest.TestCase):
def test_fusion(self):
self.assertEqual(fuse([["例外", "期限"], ["期限", "例外", "到账"]]),
["例外", "期限", "到账"])
def test_duplicate_in_one_list(self):
self.assertEqual(fuse([["期限", "期限"]]), ["期限"])
def test_empty_rankings(self):
self.assertEqual(fuse([[], []]), [])
def test_parent_deduplication(self):
self.assertEqual(expand(["期限", "例外"]), [PARENTS["退货规则"]])
def test_missing_parent(self):
self.assertEqual(answer(10, "生鲜", []), "缺少完整退货规则")
def test_deadline_and_exception(self):
contexts = expand(["期限"])
self.assertEqual(answer(10, "普通", contexts), "超过7天期限。")
self.assertEqual(answer(7, "普通", contexts), "期限内可申请。")
self.assertIn("属于例外", answer(3, "生鲜", contexts))
if __name__ == "__main__":
unittest.main()cd "F:\学习\l5-advanced-rag"
python -X utf8 -m unittest -v test_advanced_rag_demo应显示6项通过。将主程序恢复到上面的固定输入后,可以再次对照完整输出。
课后练习展开习题收起习题
正在读取本地记录…
不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。
选择题 · 先判断边界
关于“父子块、RAPTOR 与 GraphRAG”,哪种做法更稳妥?
对应知识点:L5-advanced-rag
简答题 · 解释失败
写出一个失败输入并说明记录字段。
对应知识点:L5-advanced-rag
代码题 · 边界测试
补充正常、边界、失败三组输入和预期结果。
对应知识点:L5-advanced-rag
场景题 · 结果未知
证据不足时如何设计状态和人工接管?
对应知识点:L5-advanced-rag
描述会发送给本站维护者,用于修订文章,保存90天。请只填写文章问题,省略个人信息、密钥和内部资料。