分块、元数据与权限继承
正在读取学习记录…
分块是把长文档拆成能单独检索的小段。元数据是伴随正文的说明,如文档编号、版本、字符位置和可见范围。块越短越容易定位,条件与结论也越容易被拆开;重叠让相邻块共享一部分文字,减少边界处的信息断裂。
用退款资料观察分块
退款资料依次说明期限、生鲜例外和申请所需订单号。程序按固定字符窗口切分,相邻窗口重叠6个字符,同时把店铺和可见范围复制到每个块。
原文共32字符。size=18表示每块最多18字符,overlap=6表示相邻块共享6字符,因此每次前进12字符。文档是A店铺的私有资料,版本1;切成三块后,来源身份与权限也会各自存入三条记录。
保存完整程序并运行
先在 PowerShell 创建并进入学习目录:
mkdir "F:\学习\l5-chunking" -Force
cd "F:\学习\l5-chunking"保存下面的文件后,在这个终端运行 dir 核对文件名,再执行本节给出的运行命令。
新建学习目录,例如 F:\学习\l5-chunking。将下面代码完整保存为 chunking_demo.py;代码包含固定资料和运行入口,保存后就能执行。
先在 PowerShell 创建并进入学习目录:
mkdir "F:\学习\l5-chunking" -Force
cd "F:\学习\l5-chunking"保存下面的文件后,在这个终端运行 dir 核对文件名,再执行本节给出的运行命令。
"""切分退款规则,保留重叠、字符位置与文档权限。"""
DOCUMENT = {"id": "refund-A", "version": 1, "tenant": "A",
"visibility": "private",
"text": "退款通常三至五个工作日到账。生鲜商品除外。申请退款需要订单编号。"}
def split_document(doc, size=18, overlap=6):
if type(size) is not int or type(overlap) is not int:
raise ValueError("窗口与重叠必须是整数")
if size <= 0 or not 0 <= overlap < size:
raise ValueError("重叠应小于正数窗口")
chunks = []
# 每次前进size-overlap个字符,相邻块保留共同文字。
for start in range(0, len(doc["text"]), size - overlap):
end = min(start + size, len(doc["text"]))
chunks.append({
"id": f"{doc['id']}:v{doc['version']}:{start}",
"doc_id": doc["id"], "version": doc["version"],
"tenant": doc["tenant"], "visibility": doc["visibility"],
"start": start, "end": end, "text": doc["text"][start:end],
})
if end == len(doc["text"]):
break
return chunks
def main():
for chunk in split_document(DOCUMENT):
print(f"{chunk['id']} [{chunk['start']}:{chunk['end']}] "
f"{chunk['tenant']}/{chunk['visibility']}:{chunk['text']}")
if __name__ == "__main__":
main()在保存文件的文件夹地址栏输入 powershell 并回车,再执行:
macOS:先在 Python 篇的学习文件夹中打开终端,执行 source .venv/bin/activate。再输入 cd ,把当前例子的文件夹拖入终端并回车,运行下面的命令。
cd "F:\学习\l5-chunking"
ls
python -X utf8 chunking_demo.py使用其他目录时,把第一行换成自己的路径。dir 应能列出 chunking_demo.py。终端输出如下:
refund-A:v1:0 [0:18] A/private:退款通常三至五个工作日到账。生鲜商品
refund-A:v1:12 [12:30] A/private:账。生鲜商品除外。申请退款需要订单编
refund-A:v1:24 [24:32] A/private:款需要订单编号。编号中的v1说明来源版本,最后的数字是该块在原文中的起点。区间采用左闭右开规则,例如[0:18]包含第0到17个字符。每个块都保持A/private,切小后不会突然变成公开资料。相邻块共享6字符,能在输出中逐字对上。
为什么需要位置、重叠与权限
split_document先检查窗口与重叠,再以size-overlap为步长移动窗口。每次记录原文起止位置和正文,同时继承文档元数据。最后一个窗口到达末尾就停止,避免额外生成只包含尾部重叠的小块。
固定字符窗口容易切在句子中间,重叠只能降低断裂概率。退款例外条件若比重叠更长,仍可能离开主条款。段落切分可保留句子结构;递归切分先按标题、段落、句子拆,过长部分再按长度拆;语义切分根据主题变化决定边界,需要检查是否把条件和结论放在一起。
窗口增大通常提供更完整的语境,也会带来更多无关文字;重叠增大增加索引体积和重复候选。演示以字符计数,模型请求还需按对应分词器统计Token。图片、代码和表格适合保留结构,不宜机械拆成同一字符窗口。
权限继承后,检索仍需按当前用户权限筛选每个块。资料变更时,用doc_id定位旧块,以版本区分新旧,再更新索引;引用时利用start/end回到原文。可追溯位置能帮助检查答案依据,不能自动证明分块保住了业务含义。
改一处输入,核对机制变化
将函数默认 overlap=6 改为 overlap=0,运行 python -X utf8 chunking_demo.py。窗口前进18字符,只得到[0:18]和[18:32]两块,权限仍是A/private。恢复6并重跑,回到三块和6字符重叠。字符区间决定切片位置,权限继承不随窗口变化。
运行配套检查
把下面测试保存为 test_chunking_demo.py,与主程序放在同一目录。5项测试检查上面的实际处理结果,也覆盖重叠、权限、位置和非法窗口。
查看测试代码:test_chunking_demo.py
"""核对分块、元数据与权限继承的正常结果与具体边界。"""
import unittest
import chunking_demo as demo
class DemoTests(unittest.TestCase):
def test_every_chunk_inherits_permissions(self):
# 每个块都继承原文的可见范围。
for chunk in demo.split_document(demo.DOCUMENT):
self.assertEqual((chunk["tenant"], chunk["visibility"]), ("A", "private"))
self.assertEqual(chunk["doc_id"], "refund-A")
def test_offsets_point_to_original_text(self):
# 位置记录能回到原文取出相同文字。
for chunk in demo.split_document(demo.DOCUMENT):
self.assertEqual(chunk["text"], demo.DOCUMENT["text"][chunk["start"]:chunk["end"]])
def test_overlap_is_preserved(self):
# 相邻块确实共享六个字符。
chunks = demo.split_document(demo.DOCUMENT)
for left, right in zip(chunks, chunks[1:]):
self.assertEqual(left["text"][-6:], right["text"][:6])
def test_invalid_window_rejected(self):
# 非法窗口拒绝运行,避免零步长。
for size, overlap in ((0, 0), (4, 4), (4, -1), (True, 0)):
with self.assertRaises(ValueError):
demo.split_document(demo.DOCUMENT, size, overlap)
def test_empty_and_short_documents(self):
# 空文档没有块,短文档只有一块。
self.assertEqual(demo.split_document(dict(demo.DOCUMENT, text="")), [])
self.assertEqual(len(demo.split_document(dict(demo.DOCUMENT, text="退款"))), 1)
if __name__ == "__main__":
unittest.main()python -X utf8 -m unittest -v test_chunking_demo.py测试名称后应显示 ok,末尾是 Ran 5 tests 和 OK。
运行时怎样定位问题
| 看到的情况 | 检查与操作 |
|---|---|
| 找不到 chunking_demo.py | 运行 dir,切换到保存主程序的目录 |
| 切分一直不结束 | overlap必须小于size,步长要为正数 |
| 检索块丢了生鲜例外 | 按完整条款切分或增加邻接块,核对原文位置 |
| 私有原文出现公开块 | 切分时复制权限,检查每块tenant与visibility |
每个块有了可追溯身份,索引才能按编号更新和删除。
参考阅读:LlamaIndex 源码与检索流程。阅读时可以沿文档、节点、索引和检索入口对照这里的数据流。
课后练习展开习题收起习题
正在读取本地记录…
不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。
选择题 · 先判断边界
关于“分块、元数据与权限继承”,哪种做法更稳妥?
对应知识点:L5-chunking
简答题 · 解释失败
写出一个失败输入并说明记录字段。
对应知识点:L5-chunking
代码题 · 边界测试
补充正常、边界、失败三组输入和预期结果。
对应知识点:L5-chunking
场景题 · 结果未知
证据不足时如何设计状态和人工接管?
对应知识点:L5-chunking
下一篇:索引、增量更新与删除。
描述会发送给本站维护者,用于修订文章,保存90天。请只填写文章问题,省略个人信息、密钥和内部资料。