第一部分:元数据(Metadata)在RAG中的核心作用
在RAG中,元数据是"关于数据块的数据"。它不仅仅是附加信息,而是实现精准检索、可信溯源和高效过滤的关键。
核心价值:
1.增强检索精度 :在检索时,可以同时根据查询的语义(向量相似度)和元数据(如文档类型、日期、作者)进行过滤,确保返回的上下文更相关、更精准。
- 例如:"查找张三在2023年发布的财务报告中关于AI投资的章节。" 这里"张三"、"2023年"、"财务报告"都可以作为元数据过滤器。
2.实现可信溯源:这是RAG系统的"生命线"。当大模型生成答案时,必须能够指出答案的来源文档、具体页码甚至段落。这能:
- 增加用户信任:用户可以核对原文。
- 方便错误排查:当答案有误时,可快速定位问题源。
- 满足合规要求:许多行业(如金融、法律)要求信息来源可追溯。
3.优化分块与上下文管理:元数据可以帮助理解文档结构(如章节标题),从而指导更合理的分块,或在回答时提供更丰富的上下文。
第二部分:需要保留的关键元数据字段
根据源文档类型,需要提取和保留的元数据通常包括:
| 类别 | 元数据字段 | 说明与示例 |
|---|---|---|
| 基础标识 | source |
文档唯一标识,如文件路径、URL、数据库ID。 |
document_id |
内部为文档分配的唯一ID。 | |
title / filename |
文档标题或文件名。 | |
author |
文档作者。 | |
created_date / last_modified |
创建或最后修改日期。 | |
| 内容定位 | page_number |
至关重要。文本块所在的起始页码(有时也需结束页码)。 |
chunk_id / chunk_index |
文档内分块的顺序索引(如 0, 1, 2...)。 | |
section_header |
该块所属的章节或小标题。 | |
paragraph_index |
在页面或章节内的段落索引。 | |
| 文档属性 | document_type |
文档类型,如 pdf, word, 网页, 邮件。 |
tags / keywords |
用户或系统为文档添加的标签。 | |
language |
文档语言。 | |
summary |
文档摘要(通常用于文档级元数据)。 |
第三部分:如何在分块过程中保留这些信息
这是技术实现的核心。关键思想是:在文本被切割成块的同时,必须为每一块生成并绑定其对应的元数据。
1. 分块前的元数据提取
在文档加载(使用LangChain的DocumentLoader、LlamaIndex的Reader等工具)后,首先提取文档级元数据。
代码实现:
python
# 使用自己的PDF文件
from langchain_community.document_loaders import PyPDFLoader
import pprint
# 请将下面的路径替换为您自己的PDF文件路径
pdf_path = "2506.10380v2.pdf" # 替换为您的PDF文件路径
try:
# 使用PyPDFLoader加载PDF
print("加载PDF文件...")
loader = PyPDFLoader(pdf_path)
documents = loader.load()
print(f"\n成功加载了 {len(documents)} 个Document对象")
print("每个Document对应PDF的一页\n")
# 查看第一个Document对象
print("=== 第一个Document对象的内容预览 ===")
if len(documents) > 0:
first_doc = documents[0]
# 显示前300个字符的内容
content_preview = first_doc.page_content[:300]
print(f"内容预览:\n{content_preview}...\n")
print("=== 第一个Document对象的元数据 ===")
pprint.pprint(first_doc.metadata)
print()
# 显示所有元数据键的详细信息
print("=== 所有元数据字段的解释 ===")
for key, value in first_doc.metadata.items():
if key == 'source':
print(f"- {key}: 文档来源路径 - {value}")
elif key == 'page':
print(f"- {key}: 页码(从0开始)- {value}")
else:
print(f"- {key}: {value}")
# 演示如何提取和使用这些元数据
print("\n=== 使用元数据进行分块 ===")
print("在实际的RAG应用中,分块时会继承这些元数据。")
print("例如:")
print(f"源文件: {first_doc.metadata['source']}")
print(f"页码: {first_doc.metadata['page']}")
# 如果有更多页,显示第二页的元数据作为示例
if len(documents) > 1:
print(f"\n=== 第二页的元数据作为对比 ===")
print(f"页码: {documents[1].metadata.get('page')}")
except FileNotFoundError:
print(f"错误: 找不到文件 '{pdf_path}'")
print("请将 pdf_path 变量替换为您自己的PDF文件路径")
except Exception as e:
print(f"加载PDF时出错: {e}")
结果:
bash
加载PDF文件...
成功加载了 20 个Document对象
每个Document对应PDF的一页
=== 第一个Document对象的内容预览 ===
内容预览:
TableRAG: A Retrieval Augmented Generation Framework for
Heterogeneous Document Reasoning
Xiaohan Yu∗, Pu Jian∗, Chong Chen/envel⌢pe
Huawei Cloud BU, Beijing
{yuxiaohan5, jinapu2, chenchong55}@huawei.com
https://github.com/yxh-y/TableRAG
Abstract
Retrieval-Augmented Generation (RAG) has
demonstrated...
=== 第一个Document对象的元数据 ===
{'arxivid': 'https://arxiv.org/abs/2506.10380v2',
'author': 'Xiaohan Yu; Pu Jian; Chong Chen',
'creationdate': '',
'creator': 'arXiv GenPDF (tex2pdf:)',
'doi': 'https://doi.org/10.48550/arXiv.2506.10380',
'license': 'http://arxiv.org/licenses/nonexclusive-distrib/1.0/',
'page': 0,
'page_label': '1',
'producer': 'pikepdf 8.15.1',
'ptex.fullbanner': 'This is pdfTeX, Version 3.141592653-2.6-1.40.28 (TeX Live '
'2025) kpathsea version 6.4.1',
'source': '2506.10380v2.pdf',
'title': 'TableRAG: A Retrieval Augmented Generation Framework for '
'Heterogeneous Document Reasoning',
'total_pages': 20,
'trapped': '/False'}
=== 所有元数据字段的解释 ===
- producer: pikepdf 8.15.1
- creator: arXiv GenPDF (tex2pdf:)
- creationdate:
- author: Xiaohan Yu; Pu Jian; Chong Chen
- doi: https://doi.org/10.48550/arXiv.2506.10380
- license: http://arxiv.org/licenses/nonexclusive-distrib/1.0/
- ptex.fullbanner: This is pdfTeX, Version 3.141592653-2.6-1.40.28 (TeX Live 2025) kpathsea version 6.4.1
- title: TableRAG: A Retrieval Augmented Generation Framework for Heterogeneous Document Reasoning
- trapped: /False
- arxivid: https://arxiv.org/abs/2506.10380v2
- source: 文档来源路径 - 2506.10380v2.pdf
- total_pages: 20
- page: 页码(从0开始)- 0
- page_label: 1
=== 使用元数据进行分块 ===
在实际的RAG应用中,分块时会继承这些元数据。
例如:
源文件: 2506.10380v2.pdf
页码: 0
=== 第二页的元数据作为对比 ===
页码: 1
此时,一个基础的Document对象可能已有page、source信息。
2. 分块时保留和继承元数据
使用分块器(如RecursiveCharacterTextSplitter、SemanticSplitter)时,必须确保在切割文本时,新产生的文本块能正确继承并更新其来源块的元数据。
重点与难点: page_number****等定位信息的处理
- 简单场景(按页分块):如果整个页面作为一个块,则直接继承该页的页码。
- 复杂场景(跨页或页内细分) :
- 策略1:记录起始和结束位置 :除了
page_number,还可添加start_char/end_char(在页面文本中的字符偏移量),或start_index/end_index。 - 策略2:使用重叠分块并标注 :如果块A在第1页末尾和第2页开头,可以将其
page_number标记为[1, 2], 并在块内容中插入软标记如[接上页]。 - 策略3:保留上下文窗口 :分块时设置
chunk_overlap(如200字符),这不仅能保持语义连贯,也能让边界信息(如页码)在重叠区得到体现。
- 策略1:记录起始和结束位置 :除了
代码实现:
python
"""
RAG系统中文档分块与元数据继承示例
本代码展示如何使用RecursiveCharacterTextSplitter对PDF文档进行分块,并自动继承元数据
"""
import os
from pathlib import Path
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
def main():
print("=" * 70)
print("RAG系统分块与元数据继承演示")
print("=" * 70)
pdf_path = '2506.10380v2.pdf'
try:
# 1. 加载PDF文档
print(f"\n📥 正在加载PDF文档...")
loader = PyPDFLoader(pdf_path)
documents = loader.load()
print(f"✅ 加载成功!文档包含 {len(documents)} 页")
# 显示前几页的元数据
print(f"\n📋 原始文档元数据示例:")
for i in range(min(3, len(documents))): # 显示前3页
doc = documents[i]
print(f"\n第 {i+1} 页元数据:")
for key, value in doc.metadata.items():
print(f" {key}: {value}")
# 显示内容预览
content_preview = doc.page_content[:150].replace('\n', ' ')
print(f" 内容预览: {content_preview}...")
if len(documents) > 3:
print(f"\n... 还有 {len(documents)-3} 页未显示")
# 2. 使用RecursiveCharacterTextSplitter进行分块
print(f"\n" + "=" * 70)
print("开始文档分块处理...")
print("=" * 70)
# 创建分块器
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每个块的最大字符数
chunk_overlap=100, # 块之间的重叠字符数
separators=["\n\n", "\n", "。", "!", "?", ",", " ", ""],
length_function=len,
add_start_index=True # 添加起始索引元数据
)
print(f"\n🔪 正在使用RecursiveCharacterTextSplitter进行分块...")
chunks = text_splitter.split_documents(documents)
print(f"✅ 分块完成!生成 {len(chunks)} 个文本块")
# 3. 展示分块后的元数据继承情况
print(f"\n" + "=" * 70)
print("分块后元数据继承分析")
print("=" * 70)
# 显示一些示例块
print(f"\n📊 前5个文本块的详细信息:")
for i in range(min(5, len(chunks))):
chunk = chunks[i]
print(f"\n--- 块 #{i+1} ---")
# 内容预览
content = chunk.page_content
if len(content) > 200:
content_preview = content[:200] + "..."
else:
content_preview = content
print(f"内容 ({len(content)} 字符):")
print(f" {content_preview}")
# 元数据信息
print(f"元数据:")
for key, value in chunk.metadata.items():
print(f" {key}: {value}")
# 特别说明元数据继承
source = chunk.metadata.get('source', '未知')
page = chunk.metadata.get('page', '未知')
start_index = chunk.metadata.get('start_index', '未知')
print(f"✅ 继承自: {source} 的第 {page + 1 if isinstance(page, int) else page} 页")
if start_index != '未知':
print(f"📏 在原始文本中的起始位置: {start_index}")
# 4. 元数据分析报告
print(f"\n" + "=" * 70)
print("元数据分析报告")
print("=" * 70)
# 统计元数据字段
metadata_fields = {}
for chunk in chunks:
for key in chunk.metadata.keys():
metadata_fields[key] = metadata_fields.get(key, 0) + 1
print(f"\n📈 元数据字段统计 (共 {len(chunks)} 个块):")
for field, count in sorted(metadata_fields.items()):
percentage = (count / len(chunks)) * 100
print(f" {field}: {count} 个块 ({percentage:.1f}%)")
# 分析页码分布
page_numbers = {}
for chunk in chunks:
page = chunk.metadata.get('page')
if page is not None:
page_numbers[page] = page_numbers.get(page, 0) + 1
if page_numbers:
print(f"\n📄 页码分布 (从0开始计数):")
for page in sorted(page_numbers.keys()):
print(f" 第 {page + 1} 页: {page_numbers[page]} 个块")
# 检查是否有跨页块
print(f"\n🔄 分块重叠分析:")
print(f" 块大小 (chunk_size): {text_splitter._chunk_size} 字符")
print(f" 重叠大小 (chunk_overlap): {text_splitter._chunk_overlap} 字符")
print(f" 分块器通过重叠确保语义连续性")
# 5. 演示如何在实际应用中使用这些元数据
print(f"\n" + "=" * 70)
print("实际应用示例")
print("=" * 70)
print(f"\n💡 在RAG系统中,这些元数据将被用于:")
print(f" 1. 向量化存储: 将块的文本内容转换为向量,同时保存元数据")
print(f" 2. 检索增强: 查询时可以根据元数据过滤 (如特定页码、文档来源)")
print(f" 3. 答案溯源: 生成答案时引用来源文档和具体页码")
print(f" 4. 权限控制: 根据文档元数据控制访问权限")
# 模拟检索场景
print(f"\n🔍 模拟检索场景:")
print(f" 假设用户查询: '文档中提到的关键概念是什么?'")
print(f" 检索系统会:")
print(f" 1. 计算查询向量")
print(f" 2. 在向量库中查找相似向量")
print(f" 3. 返回最相关的文本块及其元数据")
# 示例检索结果
if len(chunks) > 0:
example_chunk = chunks[0]
print(f"\n 示例检索结果:")
print(f" 文本: {example_chunk.page_content[:100]}...")
print(f" 来源: {example_chunk.metadata.get('source')}")
print(f" 页码: {example_chunk.metadata.get('page', 0) + 1}")
print(f" 起始位置: {example_chunk.metadata.get('start_index', 0)}")
print(f"\n🎉 演示完成!")
except FileNotFoundError:
print(f"\n❌ 错误: 找不到文件 '{pdf_path}'")
print("请确保PDF文件在当前目录")
except Exception as e:
print(f"\n❌ 处理过程中发生错误: {e}")
print("请确保已安装所需库: pip install langchain langchain-community pypdf")
if __name__ == "__main__":
# 检查必要的库是否已安装
try:
import langchain_community
import pypdf
main()
except ImportError:
print("❌ 缺少必要的库")
print("请先安装所需库:")
print(" pip install langchain langchain-community pypdf")
print("\n或者使用以下命令安装所有依赖:")
print(" pip install langchain langchain-community pypdf chromadb")
结果:
bash
======================================================================
RAG系统分块与元数据继承演示
======================================================================
📥 正在加载PDF文档...
✅ 加载成功!文档包含 20 页
📋 原始文档元数据示例:
第 1 页元数据:
producer: pikepdf 8.15.1
creator: arXiv GenPDF (tex2pdf:)
creationdate:
author: Xiaohan Yu; Pu Jian; Chong Chen
doi: https://doi.org/10.48550/arXiv.2506.10380
license: http://arxiv.org/licenses/nonexclusive-distrib/1.0/
ptex.fullbanner: This is pdfTeX, Version 3.141592653-2.6-1.40.28 (TeX Live 2025) kpathsea version 6.4.1
title: TableRAG: A Retrieval Augmented Generation Framework for Heterogeneous Document Reasoning
trapped: /False
arxivid: https://arxiv.org/abs/2506.10380v2
source: 2506.10380v2.pdf
total_pages: 20
page: 0
page_label: 1
内容预览: TableRAG: A Retrieval Augmented Generation Framework for Heterogeneous Document Reasoning Xiaohan Yu∗, Pu Jian∗, Chong Chen/envel⌢pe Huawei Cloud BU, ...
第 2 页元数据:
producer: pikepdf 8.15.1
creator: arXiv GenPDF (tex2pdf:)
creationdate:
author: Xiaohan Yu; Pu Jian; Chong Chen
doi: https://doi.org/10.48550/arXiv.2506.10380
license: http://arxiv.org/licenses/nonexclusive-distrib/1.0/
ptex.fullbanner: This is pdfTeX, Version 3.141592653-2.6-1.40.28 (TeX Live 2025) kpathsea version 6.4.1
title: TableRAG: A Retrieval Augmented Generation Framework for Heterogeneous Document Reasoning
trapped: /False
arxivid: https://arxiv.org/abs/2506.10380v2
source: 2506.10380v2.pdf
total_pages: 20
page: 1
page_label: 2
内容预览: As illustrated in Figure 1, the RAG approach com- putes percentage over the top-N most relevant chunks rather than the full table, and thus results in...
第 3 页元数据:
producer: pikepdf 8.15.1
creator: arXiv GenPDF (tex2pdf:)
creationdate:
author: Xiaohan Yu; Pu Jian; Chong Chen
doi: https://doi.org/10.48550/arXiv.2506.10380
license: http://arxiv.org/licenses/nonexclusive-distrib/1.0/
ptex.fullbanner: This is pdfTeX, Version 3.141592653-2.6-1.40.28 (TeX Live 2025) kpathsea version 6.4.1
title: TableRAG: A Retrieval Augmented Generation Framework for Heterogeneous Document Reasoning
trapped: /False
arxivid: https://arxiv.org/abs/2506.10380v2
source: 2506.10380v2.pdf
total_pages: 20
page: 2
page_label: 3
内容预览: Document chunking Document-oriented Database Table Schema Extraction Relational Database Table Parsing Retrival Results Table Schema { "table_name...
... 还有 17 页未显示
======================================================================
开始文档分块处理...
======================================================================
🔪 正在使用RecursiveCharacterTextSplitter进行分块...
✅ 分块完成!生成 163 个文本块
======================================================================
分块后元数据继承分析
======================================================================
📊 前5个文本块的详细信息:
--- 块 #1 ---
内容 (455 字符):
TableRAG: A Retrieval Augmented Generation Framework for
Heterogeneous Document Reasoning
Xiaohan Yu∗, Pu Jian∗, Chong Chen/envel⌢pe
Huawei Cloud BU, Beijing
{yuxiaohan5, jinapu2, chenchong55}@huawei....
元数据:
producer: pikepdf 8.15.1
creator: arXiv GenPDF (tex2pdf:)
creationdate:
author: Xiaohan Yu; Pu Jian; Chong Chen
doi: https://doi.org/10.48550/arXiv.2506.10380
license: http://arxiv.org/licenses/nonexclusive-distrib/1.0/
ptex.fullbanner: This is pdfTeX, Version 3.141592653-2.6-1.40.28 (TeX Live 2025) kpathsea version 6.4.1
title: TableRAG: A Retrieval Augmented Generation Framework for Heterogeneous Document Reasoning
trapped: /False
arxivid: https://arxiv.org/abs/2506.10380v2
source: 2506.10380v2.pdf
total_pages: 20
page: 0
page_label: 1
start_index: 0
✅ 继承自: 2506.10380v2.pdf 的第 1 页
📏 在原始文本中的起始位置: 0
--- 块 #2 ---
内容 (460 字符):
when applied to heterogeneous documents,
comprising both textual and tabular compo-
nents, existing RAG approaches exhibit critical
limitations. The prevailing practice of flatten-
ing tables and chun...
元数据:
producer: pikepdf 8.15.1
creator: arXiv GenPDF (tex2pdf:)
creationdate:
author: Xiaohan Yu; Pu Jian; Chong Chen
doi: https://doi.org/10.48550/arXiv.2506.10380
license: http://arxiv.org/licenses/nonexclusive-distrib/1.0/
ptex.fullbanner: This is pdfTeX, Version 3.141592653-2.6-1.40.28 (TeX Live 2025) kpathsea version 6.4.1
title: TableRAG: A Retrieval Augmented Generation Framework for Heterogeneous Document Reasoning
trapped: /False
arxivid: https://arxiv.org/abs/2506.10380v2
source: 2506.10380v2.pdf
total_pages: 20
page: 0
page_label: 1
start_index: 372
✅ 继承自: 2506.10380v2.pdf 的第 1 页
📏 在原始文本中的起始位置: 372
--- 块 #3 ---
内容 (455 字符):
dress these challenges, we propose TableRAG,
an SQL-based framework that unifies textual
understanding and complex manipulations over
tabular data. TableRAG iteratively operates in
four steps: context...
元数据:
producer: pikepdf 8.15.1
creator: arXiv GenPDF (tex2pdf:)
creationdate:
author: Xiaohan Yu; Pu Jian; Chong Chen
doi: https://doi.org/10.48550/arXiv.2506.10380
license: http://arxiv.org/licenses/nonexclusive-distrib/1.0/
ptex.fullbanner: This is pdfTeX, Version 3.141592653-2.6-1.40.28 (TeX Live 2025) kpathsea version 6.4.1
title: TableRAG: A Retrieval Augmented Generation Framework for Heterogeneous Document Reasoning
trapped: /False
arxivid: https://arxiv.org/abs/2506.10380v2
source: 2506.10380v2.pdf
total_pages: 20
page: 0
page_label: 1
start_index: 744
✅ 继承自: 2506.10380v2.pdf 的第 1 页
📏 在原始文本中的起始位置: 744
--- 块 #4 ---
内容 (468 字符):
benchmark designed to evaluate the multi-hop
heterogeneous reasoning capabilities. Experi-
mental results demonstrate that TableRAG con-
sistently outperforms existing baselines on both
public dataset...
元数据:
producer: pikepdf 8.15.1
creator: arXiv GenPDF (tex2pdf:)
creationdate:
author: Xiaohan Yu; Pu Jian; Chong Chen
doi: https://doi.org/10.48550/arXiv.2506.10380
license: http://arxiv.org/licenses/nonexclusive-distrib/1.0/
ptex.fullbanner: This is pdfTeX, Version 3.141592653-2.6-1.40.28 (TeX Live 2025) kpathsea version 6.4.1
title: TableRAG: A Retrieval Augmented Generation Framework for Heterogeneous Document Reasoning
trapped: /False
arxivid: https://arxiv.org/abs/2506.10380v2
source: 2506.10380v2.pdf
total_pages: 20
page: 0
page_label: 1
start_index: 1109
✅ 继承自: 2506.10380v2.pdf 的第 1 页
📏 在原始文本中的起始位置: 1109
--- 块 #5 ---
内容 (479 字符):
ing over both unstructured text and structured tabu-
lar data, presents substantial challenges. Tables are
characterized by interdependent rows and columns,
while natural language texts are sequential...
元数据:
producer: pikepdf 8.15.1
creator: arXiv GenPDF (tex2pdf:)
creationdate:
author: Xiaohan Yu; Pu Jian; Chong Chen
doi: https://doi.org/10.48550/arXiv.2506.10380
license: http://arxiv.org/licenses/nonexclusive-distrib/1.0/
ptex.fullbanner: This is pdfTeX, Version 3.141592653-2.6-1.40.28 (TeX Live 2025) kpathsea version 6.4.1
title: TableRAG: A Retrieval Augmented Generation Framework for Heterogeneous Document Reasoning
trapped: /False
arxivid: https://arxiv.org/abs/2506.10380v2
source: 2506.10380v2.pdf
total_pages: 20
page: 0
page_label: 1
start_index: 1525
✅ 继承自: 2506.10380v2.pdf 的第 1 页
📏 在原始文本中的起始位置: 1525
======================================================================
元数据分析报告
======================================================================
📈 元数据字段统计 (共 163 个块):
arxivid: 163 个块 (100.0%)
author: 163 个块 (100.0%)
creationdate: 163 个块 (100.0%)
creator: 163 个块 (100.0%)
doi: 163 个块 (100.0%)
license: 163 个块 (100.0%)
page: 163 个块 (100.0%)
page_label: 163 个块 (100.0%)
producer: 163 个块 (100.0%)
ptex.fullbanner: 163 个块 (100.0%)
source: 163 个块 (100.0%)
start_index: 163 个块 (100.0%)
title: 163 个块 (100.0%)
total_pages: 163 个块 (100.0%)
trapped: 163 个块 (100.0%)
📄 页码分布 (从0开始计数):
第 1 页: 11 个块
第 2 页: 11 个块
第 3 页: 7 个块
第 4 页: 11 个块
第 5 页: 11 个块
第 6 页: 9 个块
第 7 页: 10 个块
第 8 页: 10 个块
第 9 页: 13 个块
第 10 页: 13 个块
第 11 页: 3 个块
第 12 页: 9 个块
第 13 页: 10 个块
第 14 页: 6 个块
第 15 页: 10 个块
第 16 页: 1 个块
第 17 页: 7 个块
第 18 页: 5 个块
第 19 页: 3 个块
第 20 页: 3 个块
🔄 分块重叠分析:
块大小 (chunk_size): 500 字符
重叠大小 (chunk_overlap): 100 字符
分块器通过重叠确保语义连续性
======================================================================
实际应用示例
======================================================================
💡 在RAG系统中,这些元数据将被用于:
1. 向量化存储: 将块的文本内容转换为向量,同时保存元数据
2. 检索增强: 查询时可以根据元数据过滤 (如特定页码、文档来源)
3. 答案溯源: 生成答案时引用来源文档和具体页码
4. 权限控制: 根据文档元数据控制访问权限
🔍 模拟检索场景:
假设用户查询: '文档中提到的关键概念是什么?'
检索系统会:
1. 计算查询向量
2. 在向量库中查找相似向量
3. 返回最相关的文本块及其元数据
示例检索结果:
文本: TableRAG: A Retrieval Augmented Generation Framework for
Heterogeneous Document Reasoning
Xiaohan Yu...
来源: 2506.10380v2.pdf
页码: 1
起始位置: 0
🎉 演示完成!
3. 结构化文档的特殊处理
对于PDF中的表格、Word中的标题层级、HTML的DOM结构:
- 使用专用解析器 :如
PyMuPDF、pdfplumber、BeautifulSoup,它们能提取更丰富的结构信息。 - 将结构作为元数据 :将"标题层级"(H1, H2, H3)作为元数据,分块时可以将小标题下的所有内容作为一个块,并记录其
parent_headings。 - 表格处理 :可以尝试将整个表格作为一个独立的块,并添加元数据
{"content_type": "table"}, 或将其转换为文本描述(如"下表显示了2023年Q1-Q4的销售额...")。
第四部分:存储与检索阶段的元数据应用
存储(向量数据库)
将文本块向量化时,必须将元数据与向量一并存储。主流向量数据库都支持此功能。
- ChromaDB :
metadata字段。 - Pinecone: 每个向量可以附带丰富的元数据。
- Weaviate : 使用自定义的
Class模式来定义属性(即元数据)。 - Qdrant : 支持
payload。
检索
这是元数据价值最大化的环节。通常采用**"向量相似度搜索 + 元数据过滤"**的混合检索模式。
代码实现:
python
"""
RAG系统:从PDF读取、分块到向量数据库存储的完整流程
演示元数据如何与向量一并存储
"""
import os
import uuid
from pathlib import Path
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import HuggingFaceEmbeddings
import chromadb
def main():
print("=" * 80)
print("RAG系统:PDF读取 → 分块 → 向量数据库存储完整流程")
print("=" * 80)
# 1. 查找PDF文件
pdf_path = '2506.10380v2.pdf'
print(f"\n📄 处理文件: {pdf_path}")
try:
# 2. 加载PDF文档
print("\n📥 正在加载PDF文档...")
loader = PyPDFLoader(pdf_path)
documents = loader.load()
print(f"✅ 加载成功!文档包含 {len(documents)} 页")
# 3. 分块处理
print("\n✂️ 正在分块文档...")
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=800,
chunk_overlap=100,
separators=["\n\n", "\n", "。", "!", "?", ",", " ", ""],
length_function=len,
add_start_index=True
)
chunks = text_splitter.split_documents(documents)
print(f"✅ 分块完成!生成 {len(chunks)} 个文本块")
# 显示示例块的元数据
print("\n📋 示例块的元数据:")
for i in range(min(3, len(chunks))):
chunk = chunks[i]
print(f"\n块 #{i+1}:")
print(f" 内容长度: {len(chunk.page_content)} 字符")
print(f" 元数据: {chunk.metadata}")
# 4. 初始化嵌入模型
print("\n🧠 正在初始化嵌入模型...")
# 使用轻量级的sentence-transformers模型
embeddings = HuggingFaceEmbeddings(
model_name="paraphrase-multilingual-MiniLM-L12-v2",
model_kwargs={'device': 'cpu'},
encode_kwargs={'normalize_embeddings': False}
)
# 测试嵌入模型
test_text = "这是一个测试句子"
test_vector = embeddings.embed_query(test_text)
print(f"✅ 嵌入模型初始化成功!向量维度: {len(test_vector)}")
# 5. 初始化Chroma向量数据库
print("\n💾 正在初始化向量数据库...")
# 创建持久化的chroma客户端
persist_directory = "./chroma_db"
chroma_client = chromadb.PersistentClient(path=persist_directory)
# 创建或获取集合
collection_name = "pdf_documents"
try:
collection = chroma_client.get_collection(name=collection_name)
print(f"✅ 连接到现有集合: {collection_name}")
print(f" 集合中现有文档数量: {collection.count()}")
except:
collection = chroma_client.create_collection(
name=collection_name,
metadata={"description": "存储PDF文档分块的向量数据库"}
)
print(f"✅ 创建新集合: {collection_name}")
# 6. 处理每个块:生成向量并存储到数据库
print("\n🚀 开始处理每个块并存储到向量数据库...")
processed_count = 0
skipped_count = 0
for i, chunk in enumerate(chunks):
try:
# 生成唯一的ID
chunk_id = str(uuid.uuid4())
# 提取文本内容
text_content = chunk.page_content
# 生成向量
vector = embeddings.embed_query(text_content)
# 准备元数据
metadata = {
**chunk.metadata,
"chunk_id": i,
"text_length": len(text_content),
"document_name": Path(pdf_path).name
}
# 添加页码的友好显示(从1开始)
if 'page' in metadata:
metadata['page_human'] = metadata['page'] + 1
# 存储到向量数据库
collection.add(
ids=[chunk_id],
embeddings=[vector],
metadatas=[metadata],
documents=[text_content]
)
processed_count += 1
if (i + 1) % 10 == 0:
print(f" 已处理 {i+1}/{len(chunks)} 个块...")
except Exception as e:
print(f" 警告: 处理块 {i+1} 时出错: {e}")
skipped_count += 1
continue
print(f"\n✅ 处理完成!")
print(f" 成功处理: {processed_count} 个块")
print(f" 跳过: {skipped_count} 个块")
# 7. 验证存储结果
print("\n🔍 验证向量数据库中的内容...")
total_count = collection.count()
print(f" 集合 '{collection_name}' 中总共有 {total_count} 个向量")
# 获取一些示例记录
if total_count > 0:
results = collection.peek(limit=min(3, total_count))
print("\n📊 向量数据库中的示例记录:")
for i, (id, metadata, document) in enumerate(zip(results['ids'], results['metadatas'], results['documents'])):
print(f"\n记录 #{i+1}:")
print(f" ID: {id}")
print(f" 元数据: {metadata}")
print(f" 内容预览: {document[:150]}...")
# 8. 演示检索功能
print("\n" + "=" * 80)
print("演示:元数据过滤检索")
print("=" * 80)
# 演示基本检索
test_queries = [
"文档的主要内容是什么?",
"总结关键点",
"作者是谁?"
]
print(f"\n📝 测试查询:")
for query in test_queries:
print(f" - {query}")
# 选择一个查询进行演示
demo_query = test_queries[0]
print(f"\n🔍 执行查询: '{demo_query}'")
try:
# 将查询转换为向量
query_vector = embeddings.embed_query(demo_query)
# 在向量数据库中搜索
search_results = collection.query(
query_embeddings=[query_vector],
n_results=3
)
print(f"✅ 找到 {len(search_results['ids'][0])} 个相关结果:")
for i, (id, distance, metadata, document) in enumerate(zip(
search_results['ids'][0],
search_results['distances'][0],
search_results['metadatas'][0],
search_results['documents'][0]
)):
print(f"\n结果 #{i+1}:")
print(f" ID: {id}")
print(f" 距离分数: {distance:.4f}")
print(f" 来源: {metadata.get('source', '未知')}")
print(f" 页码: {metadata.get('page_human', metadata.get('page', '未知'))}")
print(f" 内容: {document[:200]}...")
except Exception as e:
print(f"检索时出错: {e}")
# 9. 演示元数据过滤
print("\n" + "=" * 80)
print("演示:带元数据过滤的检索")
print("=" * 80)
# 获取文档的页码范围
if len(documents) > 0:
first_page = 0
last_page = len(documents) - 1
print(f"\n📄 文档页码范围: 第 {first_page + 1} 页 到 第 {last_page + 1} 页")
# 演示按页码过滤
print(f"\n🔍 演示:检索第1页的内容")
try:
# 进行元数据过滤查询
filter_results = collection.query(
query_texts=[demo_query],
n_results=3,
where={"page": 0} # 过滤条件:只要第1页(page从0开始)
)
print(f"✅ 在第1页找到 {len(filter_results['ids'][0])} 个相关结果:")
for i, (id, distance, metadata, document) in enumerate(zip(
filter_results['ids'][0],
filter_results['distances'][0],
filter_results['metadatas'][0],
filter_results['documents'][0]
)):
print(f"\n结果 #{i+1}:")
print(f" 页码: {metadata.get('page_human', metadata.get('page', '未知'))}")
print(f" 内容: {document[:200]}...")
except Exception as e:
print(f"过滤检索时出错: {e}")
# 10. 显示数据库文件大小
print(f"\n💾 数据库已自动保存到: {persist_directory}")
if os.path.exists(persist_directory):
total_size = sum(f.stat().st_size for f in Path(persist_directory).rglob('*') if f.is_file())
print(f"📊 数据库文件大小: {total_size / 1024 / 1024:.2f} MB")
print("\n" + "=" * 80)
print("🎉 流程完成!")
print("=" * 80)
print(f"\n📋 总结:")
print(f" 1. 加载了 {len(documents)} 页PDF文档")
print(f" 2. 生成了 {len(chunks)} 个文本块")
print(f" 3. 存储了 {processed_count} 个向量到数据库")
print(f" 4. 数据库位置: {persist_directory}")
print(f" 5. 集合名称: {collection_name}")
print(f"\n💡 你可以使用以下代码重新加载数据库:")
print(f"""
import chromadb
# 连接到持久化的数据库
chroma_client = chromadb.PersistentClient(path="{persist_directory}")
# 获取集合
collection = chroma_client.get_collection(name="{collection_name}")
print(f"集合中有 {{collection.count()}} 个向量")
# 查询示例
results = collection.query(
query_texts=["你的查询"],
n_results=5
)
""")
except FileNotFoundError:
print(f"\n❌ 错误: 找不到文件 '{pdf_path}'")
except ImportError as e:
print(f"\n❌ 缺少必要的库: {e}")
print("请运行: pip install langchain langchain-community pypdf chromadb sentence-transformers")
except Exception as e:
print(f"\n❌ 处理过程中发生错误: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
# 检查必要的库
required_libs = ['langchain', 'langchain_community', 'pypdf', 'chromadb', 'sentence_transformers']
missing_libs = []
for lib in required_libs:
try:
__import__(lib.replace('-', '_'))
except ImportError:
missing_libs.append(lib)
if missing_libs:
print("❌ 缺少必要的库:")
for lib in missing_libs:
print(f" - {lib}")
print(f"\n请运行: pip install {' '.join(missing_libs)}")
else:
main()
结果:
bash
================================================================================
RAG系统:PDF读取 → 分块 → 向量数据库存储完整流程
================================================================================
📄 处理文件: 2506.10380v2.pdf
📥 正在加载PDF文档...
✅ 加载成功!文档包含 20 页
✂️ 正在分块文档...
✅ 分块完成!生成 98 个文本块
📋 示例块的元数据:
块 #1:
内容长度: 788 字符
元数据: {'producer': 'pikepdf 8.15.1', 'creator': 'arXiv GenPDF (tex2pdf:)', 'creationdate': '', 'author': 'Xiaohan Yu; Pu Jian; Chong Chen', 'doi': 'https://doi.org/10.48550/arXiv.2506.10380', 'license': 'http://arxiv.org/licenses/nonexclusive-distrib/1.0/', 'ptex.fullbanner': 'This is pdfTeX, Version 3.141592653-2.6-1.40.28 (TeX Live 2025) kpathsea version 6.4.1', 'title': 'TableRAG: A Retrieval Augmented Generation Framework for Heterogeneous Document Reasoning', 'trapped': '/False', 'arxivid': 'https://arxiv.org/abs/2506.10380v2', 'source': '2506.10380v2.pdf', 'total_pages': 20, 'page': 0, 'page_label': '1', 'start_index': 0}
块 #2:
内容长度: 773 字符
元数据: {'producer': 'pikepdf 8.15.1', 'creator': 'arXiv GenPDF (tex2pdf:)', 'creationdate': '', 'author': 'Xiaohan Yu; Pu Jian; Chong Chen', 'doi': 'https://doi.org/10.48550/arXiv.2506.10380', 'license': 'http://arxiv.org/licenses/nonexclusive-distrib/1.0/', 'ptex.fullbanner': 'This is pdfTeX, Version 3.141592653-2.6-1.40.28 (TeX Live 2025) kpathsea version 6.4.1', 'title': 'TableRAG: A Retrieval Augmented Generation Framework for Heterogeneous Document Reasoning', 'trapped': '/False', 'arxivid': 'https://arxiv.org/abs/2506.10380v2', 'source': '2506.10380v2.pdf', 'total_pages': 20, 'page': 0, 'page_label': '1', 'start_index': 699}
块 #3:
内容长度: 798 字符
元数据: {'producer': 'pikepdf 8.15.1', 'creator': 'arXiv GenPDF (tex2pdf:)', 'creationdate': '', 'author': 'Xiaohan Yu; Pu Jian; Chong Chen', 'doi': 'https://doi.org/10.48550/arXiv.2506.10380', 'license': 'http://arxiv.org/licenses/nonexclusive-distrib/1.0/', 'ptex.fullbanner': 'This is pdfTeX, Version 3.141592653-2.6-1.40.28 (TeX Live 2025) kpathsea version 6.4.1', 'title': 'TableRAG: A Retrieval Augmented Generation Framework for Heterogeneous Document Reasoning', 'trapped': '/False', 'arxivid': 'https://arxiv.org/abs/2506.10380v2', 'source': '2506.10380v2.pdf', 'total_pages': 20, 'page': 0, 'page_label': '1', 'start_index': 1387}
🧠 正在初始化嵌入模型...
/Users/caotianchen.1/Desktop/pyproject/test2/pdf.py:57: LangChainDeprecationWarning: The class `HuggingFaceEmbeddings` was deprecated in LangChain 0.2.2 and will be removed in 1.0. An updated version of the class exists in the :class:`~langchain-huggingface package and should be used instead. To use it run `pip install -U :class:`~langchain-huggingface` and import as `from :class:`~langchain_huggingface import HuggingFaceEmbeddings``.
embeddings = HuggingFaceEmbeddings(
✅ 嵌入模型初始化成功!向量维度: 384
💾 正在初始化向量数据库...
✅ 创建新集合: pdf_documents
🚀 开始处理每个块并存储到向量数据库...
已处理 10/98 个块...
已处理 20/98 个块...
已处理 30/98 个块...
已处理 40/98 个块...
已处理 50/98 个块...
已处理 60/98 个块...
已处理 70/98 个块...
已处理 80/98 个块...
已处理 90/98 个块...
✅ 处理完成!
成功处理: 98 个块
跳过: 0 个块
🔍 验证向量数据库中的内容...
集合 'pdf_documents' 中总共有 98 个向量
📊 向量数据库中的示例记录:
记录 #1:
ID: 8806c97b-c448-40d6-a8aa-8cdbcaabec46
元数据: {'trapped': '/False', 'producer': 'pikepdf 8.15.1', 'start_index': 0, 'doi': 'https://doi.org/10.48550/arXiv.2506.10380', 'source': '2506.10380v2.pdf', 'page_label': '1', 'total_pages': 20, 'document_name': '2506.10380v2.pdf', 'page_human': 1, 'page': 0, 'title': 'TableRAG: A Retrieval Augmented Generation Framework for Heterogeneous Document Reasoning', 'creator': 'arXiv GenPDF (tex2pdf:)', 'text_length': 788, 'chunk_id': 0, 'license': 'http://arxiv.org/licenses/nonexclusive-distrib/1.0/', 'ptex.fullbanner': 'This is pdfTeX, Version 3.141592653-2.6-1.40.28 (TeX Live 2025) kpathsea version 6.4.1', 'arxivid': 'https://arxiv.org/abs/2506.10380v2', 'creationdate': '', 'author': 'Xiaohan Yu; Pu Jian; Chong Chen'}
内容预览: TableRAG: A Retrieval Augmented Generation Framework for
Heterogeneous Document Reasoning
Xiaohan Yu∗, Pu Jian∗, Chong Chen/envel⌢pe
Huawei Cloud BU, ...
记录 #2:
ID: d4c9869c-546d-415f-87c4-9ad48630ac64
元数据: {'license': 'http://arxiv.org/licenses/nonexclusive-distrib/1.0/', 'chunk_id': 1, 'text_length': 773, 'creationdate': '', 'document_name': '2506.10380v2.pdf', 'title': 'TableRAG: A Retrieval Augmented Generation Framework for Heterogeneous Document Reasoning', 'arxivid': 'https://arxiv.org/abs/2506.10380v2', 'trapped': '/False', 'page_label': '1', 'ptex.fullbanner': 'This is pdfTeX, Version 3.141592653-2.6-1.40.28 (TeX Live 2025) kpathsea version 6.4.1', 'producer': 'pikepdf 8.15.1', 'source': '2506.10380v2.pdf', 'doi': 'https://doi.org/10.48550/arXiv.2506.10380', 'page_human': 1, 'page': 0, 'creator': 'arXiv GenPDF (tex2pdf:)', 'total_pages': 20, 'author': 'Xiaohan Yu; Pu Jian; Chong Chen', 'start_index': 699}
内容预览: of LLMs in multi-hop, global queries. To ad-
dress these challenges, we propose TableRAG,
an SQL-based framework that unifies textual
understanding an...
记录 #3:
ID: 9efee4fc-ea98-4806-ac56-acd8cafda204
元数据: {'creator': 'arXiv GenPDF (tex2pdf:)', 'chunk_id': 2, 'license': 'http://arxiv.org/licenses/nonexclusive-distrib/1.0/', 'doi': 'https://doi.org/10.48550/arXiv.2506.10380', 'author': 'Xiaohan Yu; Pu Jian; Chong Chen', 'document_name': '2506.10380v2.pdf', 'ptex.fullbanner': 'This is pdfTeX, Version 3.141592653-2.6-1.40.28 (TeX Live 2025) kpathsea version 6.4.1', 'source': '2506.10380v2.pdf', 'title': 'TableRAG: A Retrieval Augmented Generation Framework for Heterogeneous Document Reasoning', 'page_human': 1, 'text_length': 798, 'start_index': 1387, 'arxivid': 'https://arxiv.org/abs/2506.10380v2', 'trapped': '/False', 'total_pages': 20, 'creationdate': '', 'page': 0, 'page_label': '1', 'producer': 'pikepdf 8.15.1'}
内容预览: ment question answering.
1 Introduction
Heterogeneous document-based question answer-
ing (Chen et al., 2020), which necessitates reason-
ing over bot...
================================================================================
演示:元数据过滤检索
================================================================================
📝 测试查询:
- 文档的主要内容是什么?
- 总结关键点
- 作者是谁?
🔍 执行查询: '文档的主要内容是什么?'
✅ 找到 3 个相关结果:
结果 #1:
ID: 32b64797-b5e7-417b-9733-29cfe9075f0b
距离分数: 14.5852
来源: 2506.10380v2.pdf
页码: 8
内容: ReAct across different domains.
knowledge base, and the most relevant document
chunks are subsequently incorporated into the gen-
eration process (Gao et al., 2023; Zhu et al., 2024;
Borgeaud et al., ...
结果 #2:
ID: 9efee4fc-ea98-4806-ac56-acd8cafda204
距离分数: 14.7981
来源: 2506.10380v2.pdf
页码: 1
内容: ment question answering.
1 Introduction
Heterogeneous document-based question answer-
ing (Chen et al., 2020), which necessitates reason-
ing over both unstructured text and structured tabu-
lar data,...
结果 #3:
ID: 202b69e3-2ed7-4a18-85bb-e9be6c3d98a4
距离分数: 16.1048
来源: 2506.10380v2.pdf
页码: 1
内容: comprehension of tables, such as direct answer
extraction (Pasupat and Liang, 2015a; Zhu et al.,
2021). When applied to extensive documents that
interleave textual and tabular elements, existing
RAG m...
================================================================================
演示:带元数据过滤的检索
================================================================================
📄 文档页码范围: 第 1 页 到 第 20 页
🔍 演示:检索第1页的内容
✅ 在第1页找到 3 个相关结果:
结果 #1:
页码: 1
内容: ment question answering.
1 Introduction
Heterogeneous document-based question answer-
ing (Chen et al., 2020), which necessitates reason-
ing over both unstructured text and structured tabu-
lar data,...
结果 #2:
页码: 1
内容: of LLMs in multi-hop, global queries. To ad-
dress these challenges, we propose TableRAG,
an SQL-based framework that unifies textual
understanding and complex manipulations over
tabular data. TableRA...
结果 #3:
页码: 1
内容: comprehension of tables, such as direct answer
extraction (Pasupat and Liang, 2015a; Zhu et al.,
2021). When applied to extensive documents that
interleave textual and tabular elements, existing
RAG m...
💾 数据库已自动保存到: ./chroma_db
📊 数据库文件大小: 1.43 MB
================================================================================
🎉 流程完成!
================================================================================
📋 总结:
1. 加载了 20 页PDF文档
2. 生成了 98 个文本块
3. 存储了 98 个向量到数据库
4. 数据库位置: ./chroma_db
5. 集合名称: pdf_documents
💡 你可以使用以下代码重新加载数据库:
import chromadb
# 连接到持久化的数据库
chroma_client = chromadb.PersistentClient(path="./chroma_db")
# 获取集合
collection = chroma_client.get_collection(name="pdf_documents")
print(f"集合中有 {collection.count()} 个向量")
# 查询示例
results = collection.query(
query_texts=["你的查询"],
n_results=5
)
最佳实践与建议
- 标准化与一致性:为所有文档定义统一的元数据Schema。
- 平衡粒度:元数据不是越多越好。根据业务需求(检索、过滤、溯源)决定必要的字段。
- 在提示词中利用元数据 :将检索到的块的元数据(特别是
source,page)插入给大模型的提示词中,要求它引用来源。
示例提示词:"请根据以下上下文回答问题。如果答案来自上下文,请注明出处。\n上下文:[文本块内容]\n来源:{source}, 第{page}页\n问题:{query}" - 验证与测试:务必检查分块后,尤其是跨页分块后,元数据(特别是页码)是否准确。构建一些需要精准溯源的测试用例。
- 考虑多索引:对于某些元数据(如作者、日期),如果经常用于等值过滤,可以考虑在向量数据库之外建立辅助索引(如传统数据库),进行高效的预过滤。
总结
RAG中的元数据管理是一个系统工程 ,贯穿文档加载、分块、存储和检索全流程。page_number****和 source是最核心的溯源元数据。成功的实现意味着:用户在得到AI生成的答案时,总能看到一个可靠的"参考文献",点击即可定位到原文的确切位置,从而构建起可信、可控的智能问答系统。