实用笔记:RAG中的父子分块与层次索引:A
《实用笔记》操作指南:RAG中的父子分块与分层索引——A版:为采用该模式的团队提供的合同、检查清单及可直接使用的代码模板。
本指南将逐步构建从原始材料到可运行系统的完整流程,主题为《RAG中的父子分块与层次索引:提升检索效果的实用指南》。重点在于可操作的步骤、明确的检查点以及可直接放入代码库的代码,无需猜测其用途。 在概览阶段,应在修改代码之前明确输入参数、该步骤的负责人以及完成标准。操作人员应能够从已知的检查点重新运行该步骤,而无需推测隐藏状态。 需同时记录正常流程与异常恢复流程。重试机制、人工审核环节以及错误处理都是产品不可或缺的部分,而非后续需要补充的功能。
为何分块其实是RAG中最困难的部分
在探讨为何要分阶段处理任务时,首先需列出相关约定:所需的输入参数、成功标志,以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。 相较于冗长的脚本,应优先选择小型且可测试的单元。当某个步骤出现故障时,故障原因应能明确指向某个具体的功能模块,而非整个复杂的流程。 在调整提示词之前,先使用固定的问题集来测试信息的召回率。仅仅更换提示词往往无法解决信息检索效果不佳的问题。
一个典型的故障示例
在处理“典型故障示例”阶段时,首先写下相关契约:所需的输入、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。 将这一阶段视为输入与验证后输出之间的契约。为相关成果命名,明确成功判定标准,杜绝无声的半完成状态。 在调整提示词之前,先使用固定的问题集来测试召回率。仅仅更换提示词很难改善较差的检索效果。
from sentence_transformers import SentenceTransformer
import numpy as np
model = SentenceTransformer("all-MiniLM-L6-v2")
def cosine_similarity(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
query = "What is a risk of caching?"
chunk_a = "Caching improves system performance significantly."
chunk_b = "However, improper cache invalidation can lead to stale data issues."
query_embedding = model.encode(query)
score_a = cosine_similarity(query_embedding, model.encode(chunk_a))
score_b = cosine_similarity(query_embedding, model.encode(chunk_b))
print(f"Chunk A similarity: {score_a:.4f} - '{chunk_a}'")
print(f"Chunk B similarity: {score_b:.4f} - '{chunk_b}'")
Chunk A similarity: 0.5891 — 'Caching improves system performance significantly.'
Chunk B similarity: 0.5103 — 'However, improper cache invalidation can lead to stale data issues.'
父子分块
在处理父子分块阶段时,首先需写下规范:所需输入、成功信号以及部分失败时的处理方式。这份清单能确保后续的代码修改保持一致性。 在功能结果旁记录执行时间以及令牌或查询成本。提前了解成本情况,可避免在系统从演示环境转向共享环境时出现意外费用。 在调整提示词之前,先用固定的问题集测试召回率。仅仅更换提示词很难改善较差的检索效果。 在处理父子分块阶段时,首先需写下规范:所需输入、成功信号以及部分失败时的处理方式。这份清单能确保后续的代码修改保持一致性。 需同时记录正常流程和故障恢复流程。重试机制、人工审核环节以及错误处理都属于产品功能的一部分,而非后续需要补充的内容。
实际应用中的运作方式
将“分阶段实现原理”视为可度量的对象来处理时效果最佳。在扩大范围之前,先记录一个成功的案例、一个失败案例以及回滚说明。 优先选择小型且可测试的单元,而非庞大的脚本。当某个步骤失败时,故障应指向单一责任模块,而非复杂的流程链。 将分块策略与检索策略分开。当质量指标发生变化时,修改其中一项不应迫使重新编写另一项。
def build_parent_child_index(document_section, parent_text, child_splitter, embedding_model):
child_chunks = child_splitter(parent_text)
records = []
for child_text in child_chunks:
records.append({
"text": child_text,
"embedding": embedding_model.encode(child_text),
"parent_text": parent_text # full parent stored directly on every child
})
return records
parent_text = """Indexing improves query speed by reducing scan time across large tables.
Caching reduces repeated computation but may introduce staleness if not invalidated properly.
Query optimization involves rewriting SQL queries to use more efficient execution plans."""
child_texts = [
"Indexing improves query speed by reducing scan time across large tables.",
"Caching reduces repeated computation but may introduce staleness if not invalidated properly.",
"Query optimization involves rewriting SQL queries to use more efficient execution plans.",
]
records = [
{"text": t, "parent_text": parent_text} for t in child_texts
]
for r in records:
print(f"Child: {r['text'][:50]}...")
print(f" → linked to parent ({len(r['parent_text'])} chars)\n")
核心理念:先检索小型数据,再扩展处理规模
将检索阶段视为可度量的对象时,其效果最佳。在扩大范围之前,先记录一个成功的案例、一个失败案例以及回滚说明。把这一阶段视为输入与经过验证的输出之间的契约,为相关成果命名、明确成功标准,并拒绝默许部分完成的情况。应将分块策略与检索策略分开,当质量指标发生变化时,修改其中一项不应迫使重新编写另一项。
def parent_child_retrieve(query, child_records, embedding_model, top_k=3):
query_embedding = embedding_model.encode(query)
scored = [
(np.dot(query_embedding, r["embedding"]) /
(np.linalg.norm(query_embedding) * np.linalg.norm(r["embedding"])), r)
for r in child_records
]
scored.sort(key=lambda x: x[0], reverse=True)
top_children = scored[:top_k]
# Expand each matched child to its parent - but deduplicate first
seen_parents = set()
expanded_context = []
for score, record in top_children:
parent = record["parent_text"]
if parent not in seen_parents:
expanded_context.append(parent)
seen_parents.add(parent)
return expanded_context
for r in records:
r["embedding"] = model.encode(r["text"])
context = parent_child_retrieve("What is a risk of caching?", records, model, top_k=2)
print("Context sent to the LLM:\n")
for c in context:
print(c)
为何这种方法如此有效
之所以这种方法如此有效,是因为将该阶段视为可度量的对象能带来最佳效果。在扩大范围之前,先记录一个理想的处理结果、一个失败案例以及回滚说明。 在功能测试结果旁同时记录处理时间以及令牌或查询成本。提前了解成本情况,可避免在系统从演示环境过渡到共享环境时出现意外费用。 应将分块策略与检索策略分开。当质量指标发生变化时,调整其中一个不应迫使重新编写另一个。 之所以这种方法如此有效,是因为将该阶段视为可度量的对象能带来最佳效果。在扩大范围之前,先记录一个理想的处理结果、一个失败案例以及回滚说明。 需同时记录正常处理流程和故障恢复流程。重试机制、人工审核环节以及死信处理都是产品本身的组成部分,而非后续需要补充的功能。
分层索引:更进一步
在实施分层索引时,应在修改代码之前明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。相比冗长的脚本,更应采用小型且可测试的单元。当某个步骤失败时,故障原因应能指向单一责任主体,而非复杂的流程链。必须引用实际作为答案依据的段落;没有引用的话,操作人员就无法区分是幻觉内容还是索引缺失。
Document: "Cloud Architecture Guide"
Section: Networking
Subsection: Load Balancing
Chunk: Round-robin method
Chunk: Least connections method
Subsection: CDN usage
Section: Security
Subsection: IAM policies
Subsection: Encryption
为何分层结构很重要
在“为何层次结构重要”阶段,应在修改代码之前明确输入内容、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 将此阶段视为输入与已验证输出之间的契约。为相关成果命名,定义成功判定标准,并拒绝默许的半完成状态。 需引用实际作为答案依据的段落。没有引用的话,操作人员就无法区分幻觉内容与索引缺失问题。
分层检索的实际应用
在分层检索的运行阶段,修改代码之前需明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 在功能结果旁记录执行时间以及令牌或查询成本。提前显示成本可避免在系统从演示环境切换到共享环境时出现意外费用。 需注明实际作为答案依据的段落。没有引用的话,操作人员就无法区分幻觉内容与索引缺失问题。 在分层检索的运行阶段,修改代码之前需明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 需同时记录正常流程与故障恢复流程。重试机制、人工审核环节以及错误处理方式都是产品功能的一部分,而非额外补充内容。
进行最终润色。父子结构索引与层次化索引
在处理父子结构索引与层次化索引阶段时,首先明确相关约定:所需输入、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。 优先选择小型且可测试的单元,而非庞大的脚本。当某个步骤出现故障时,故障应指向单一责任点,而非复杂的流程链。 在调整提示词之前,先使用固定的问题集来测试召回率。频繁更换提示词往往无法改善较差的检索效果。
实现模式(实际场景中的RAG流程)
在实现现实世界中的 RAG 模式时,首先需明确相关契约:所需的输入参数、成功标志,以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。 将这一阶段视为输入与经过验证的输出之间的契约。为相关成果命名,定义成功判定标准,杜绝无声的半完成状态。 在调整提示词之前,先使用固定的问题集来测试召回率。仅仅更换提示词往往无法改善较差的检索效果。
实际系统中的常见错误
在处理实际环境中的常见错误时,首先需明确相关规范:所需输入、成功标志以及部分失败时的处理方式。这份清单能确保后续的代码修改始终符合预期。 在功能结果旁记录执行时间以及令牌或查询成本。提前了解成本情况,可避免从演示环境过渡到共享环境时出现意外费用。 在调整提示词之前,先使用固定的问题集测试系统的召回率。仅仅更换提示词往往无法改善较差的检索效果。 在处理实际环境中的常见错误时,首先需明确相关规范:所需输入、成功标志以及部分失败时的处理方式。这份清单能确保后续的代码修改始终符合预期。 需同时记录正常流程和异常恢复流程。重试机制、人工审核环节以及错误消息处理都是产品不可或缺的部分,而非后续才需要补充的功能。
这会在哪些方面带来最大差异
将此视为可测量的界面时,该框架才能发挥最佳作用。在扩大范围之前,先记录一个成功的案例、一个失败案例以及回滚说明。 优先选择小型且可测试的单元,而非庞大的脚本。当某个步骤失败时,故障应指向单一责任模块,而非复杂的流程链。 将分块策略与检索策略分开。当质量指标发生变化时,修改其中一项不应迫使重新编写另一项。
整体视角
将“整体视角”阶段视为可测量的对象来处理效果最佳。在扩大范围之前,先记录一个成功的案例、一个失败案例以及回滚说明。把这一阶段视为输入与经过验证的输出之间的契约,为相关成果命名,明确成功标准,绝不允许出现悄无声息的半完成状态。应将分块策略与检索策略分开,当质量指标发生变化时,修改其中一项不应迫使重新编写另一项。
最后思考
将“最终思考阶段”视为可度量的对象时,其效果最佳。在扩大范围之前,需记录一份理想的处理结果、一个失败案例以及回滚说明。在功能结果旁还需标注处理时间以及令牌或查询成本。提前了解这些成本,就能避免在系统从演示环境转向共享环境时出现意外费用。应将分块策略与检索策略分开处理,当质量指标发生变化时,调整其中一个不应迫使重新编写另一个。将“最终思考阶段”视为可度量的对象时,其效果最佳。在扩大范围之前,需记录一份理想的处理结果、一个失败案例以及回滚说明。需同时记录正常流程与故障恢复流程,重试机制、人工审核环节以及死信处理都属于产品功能的一部分,而非后续需要补充的内容。
操作检查清单
将操作检查清单阶段视为可度量的标准,效果最佳。在扩大范围之前,先记录一份最优处理方案、一个故障案例以及回滚说明。
将配置与应用程序代码分开。环境文件、密钥存储和功能开关应集中存放于一处,以便操作人员无需查看全部内容即可进行审计。
将分块策略与检索策略分开。当质量指标发生变化时,修改其中一项不应强制要求重新编写另一项。
需分别对单轮回复和多轮对话的响应进行评分。若仅汇总聊天评分,就会掩盖工具循环导致的故障。
编写简短的操作手册:说明如何轮换密钥、如何清空队列以及如何回滚最近的导入操作。
将此阶段视为输入与经过验证的输出之间的契约。为相关成果命名,明确成功标准,杜绝默默完成部分任务的状况。
在推广该技术栈之前,应先冻结版本,为关键路径生成标准记录,并明确回滚步骤。共享环境需要设置速率限制、租户验证机制,以及明确的密钥轮换负责人。与其展示花哨的一次性演示,不如注重扎实的可靠性。
关于de861b3bd2cb的批量说明:请将提供商密钥移出代码仓库,设定单会话令牌上限,并将记录存储在评估测试用例旁边,以便后续模型更换时保持可比性。
对于强化安全性的第0阶段,在修改代码之前需明确输入内容、该步骤的负责人以及完成标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。应将此阶段视为输入与验证后输出之间的契约,为相关文件命名、定义成功检测标准,并拒绝默许的半完成状态。
安全加固细节 0/954:为该记录测量运行时间、错误类型以及代币消耗情况,然后依据固定的问题清单而非个人经验来判断是否保留该变更。
在处理安全加固笔记的第一阶段时,首先写下合约的必要输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改更加规范。 应将配置信息置于应用程序代码之外。环境文件、密钥存储以及功能开关应集中存放于一个位置,以便操作人员无需查看整个系统结构即可进行审计。
安全加固细节 1/954:为该记录测量运行时间、错误类型以及代币消耗情况,然后依据固定的问题清单而非个人经验来判断是否保留该变更。
在将加固措施视为可测量的表面时,第二阶段的效果最佳。在扩大范围之前,先记录一份理想的测试用例、一个故障案例以及回滚说明。 相比庞大的脚本,应优先选择小型且可测试的单元。当某个步骤出现故障时,故障原因应能指向单一责任方,而非复杂的流程链。
加固细节 2/954:针对此条说明,需测量执行时间、错误类型以及令牌消耗情况,然后依据固定的问题集而非个人经验来判断是否保留该变更。