AgentDepthAgent 工程学习指南
输入概念或问题,检索正文和待编写目录。
第一次学习?从 L0 总览开始 →

父子块、RAPTOR 与 GraphRAG

我的学习 →

正在读取学习记录…

客户问“生鲜签收第10天能退吗”,检索到“7天内可退”还不够,商品例外可能在旁边一段。高级 RAG 关心怎样组织资料、找回相关片段,再把足够的上下文交给回答步骤。RAG 是先检索资料、再据此生成答案的流程。

父子块把文档分成两层:小的子块便于精确定位,大的父块保留完整条件。像先在目录中找到一条条目,再翻开那一页。RAPTOR 将相关片段逐层聚合成摘要树,适合从具体事实走向跨段概括;GraphRAG 用实体与关系组成图,适合追查“订单属于谁、商品受哪条规则约束”。

同一个问题需要哪些证据

固定资料包含退货期限、商品例外和退款时间三个子块。问题中的“生鲜”可以直接命中例外;“第10天”需要期限信息。混合检索把关键词检索与语义检索的结果合并:前者擅长准确词语,后者可以找含义接近的表述。

下面用两份固定排序演示融合算法。语义排序是给定的输入,便于看清融合步骤;真实向量检索需要先计算文本向量,再按相似度排序。两份结果都含例外块,因此融合前要按子块标识去重,避免同一条证据占两席。

小块定位与完整规则:关键词排序:例外 → 期限,语义排序:期限 → 例外 → 到账;RRF按名次累加,去重后取例外、期限;例外 ↘ 退货规则 ↙ 期限,只展开一次父块;签收7天内;生鲜除外,依据完整条件回答
图1:小块定位与完整规则。

跟着固定样本运行

先在 PowerShell 创建并进入学习目录:

Bash
mkdir "F:\学习\l5-advanced-rag" -Force
cd "F:\学习\l5-advanced-rag"

保存下面的文件后,在这个终端运行 dir 核对文件名,再执行本节给出的运行命令。

新建 F:\学习\l5-advanced-rag,将下面完整代码保存为 advanced_rag_demo.py。

先在 PowerShell 创建并进入学习目录:

Bash
mkdir "F:\学习\l5-advanced-rag" -Force
cd "F:\学习\l5-advanced-rag"

保存下面的文件后,在这个终端运行 dir 核对文件名,再执行本节给出的运行命令。

Python
"""融合两个检索排序,再展开命中子块的完整父规则。"""

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 中进入保存目录并运行:

Bash
cd "F:\学习\l5-advanced-rag"
ls
python -X utf8 advanced_rag_demo.py

输出为:

TXT
融合排序:例外、期限、到账
展开父块:已发货订单在签收7天内可退货;生鲜商品除外。
客户答复:生鲜商品属于例外,不能按无理由退货规则申请。

“例外”和“期限”各在一份排序中排第一、另一份排第二,两者得分相同。程序按标识固定同分顺序,所以例外在前。RRF 是倒数排名融合,按名次贡献分数;参数60降低前几名之间的分差。它能合并分值尺度不同的检索器,因为它使用名次而非原始相似度。

前两个子块都指向“退货规则”。展开后只有一个父块,其中包含期限与例外,回答函数才能根据完整条件回复。融合解决“候选怎样合并”,父块展开解决“候选需要哪些相邻上下文”。

三种资料结构怎样选择

父子块适合条款、手册和局部上下文:子块命中“生鲜”,父块补回期限。父块也会带来更多文字,需要限定展开数量,防止整个文档进入请求。

RAPTOR 的摘要树底层是原文片段,上层是多个片段的汇总,再上一层可以概括整个主题。问“几类售后规则有什么差别”时,可以先命中摘要,再下钻查看原文。下钻是沿父子关系读取更具体的节点。生成摘要时应保存被汇总的来源,数字和例外仍需回到原文核对。

GraphRAG 中,节点是实体,如商品、订单、政策;边是实体之间的关系,如“订单包含商品”“商品适用政策”。用户问“这个订单为什么受生鲜例外限制”,可以沿订单→商品→规则的关系读取证据。关系图需要抽取、去重、版本管理,新增一条规则也可能要求更新相关边。

左侧RAPTOR从售后总摘要到退货摘要再到期限和例外原文;右侧GraphRAG沿订单包含生鲜商品与商品受规则限制的关系查证
图2:摘要树向下找到具体来源,关系图沿实体之间的关系找到证据。
问题优先尝试的结构需要带回的证据
某条款有何例外子块定位、父块展开完整条款
多章节共同讲了什么摘要树汇总与底层来源
两个实体为什么有关联关系图实体、关系及原文

这些结构可以组合使用。摘要树帮助定位主题,关键词和向量检索选具体条款,关系图连接订单与商品,最后把原文作为回答依据。是否值得建立树或图,要用真实问题比较召回与维护成本,不能按名称判断效果。

改动一个固定输入

把主程序中的 answer(10, "生鲜", contexts) 改为 answer(10, "普通", contexts),再运行。最后一行变成“客户答复:超过7天期限。”恢复“生鲜”后回到原结果。

如果只保留“到账”子块,父块展开拿到退款规则,回答会显示“缺少完整退货规则”。这说明找到一段与售后有关的文字,还不足以回答退货条件。

现象检查位置
同一证据出现多次融合前的子块标识和展开后的父块去重
命中了词但回答缺条件子块到父块的映射
主题摘要与原文矛盾摘要来源与版本
关系路径有边却无原文实体关系的证据记录

检查实现中的六种情况

在同一目录将下面测试保存为 test_advanced_rag_demo.py。测试覆盖正文中的正常路径与边界条件。

测试代码:test_advanced_rag_demo.py
Python
"""检查融合、去重及完整条件对回答的影响。"""
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()
Bash
cd "F:\学习\l5-advanced-rag"
python -X utf8 -m unittest -v test_advanced_rag_demo

应显示6项通过。将主程序恢复到上面的固定输入后,可以再次对照完整输出。

课后练习展开习题收起习题

4 道练习 · 客观题规则评分 · 其余题目待批改

正在读取本地记录…

不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。

练习 1 / 4 · v1.0.1

选择题 · 先判断边界

关于“父子块、RAPTOR 与 GraphRAG”,哪种做法更稳妥?

对应知识点:L5-advanced-rag

选择一个答案
练习 2 / 4 · v1.0.0

简答题 · 解释失败

写出一个失败输入并说明记录字段。

对应知识点:L5-advanced-rag

练习 3 / 4 · v1.0.0

代码题 · 边界测试

补充正常、边界、失败三组输入和预期结果。

对应知识点:L5-advanced-rag

练习 4 / 4 · v1.0.0

场景题 · 结果未知

证据不足时如何设计状态和人工接管?

对应知识点:L5-advanced-rag

图片查看器
100%

文章配图 · 按 Esc 关闭