跳到主要内容

05. 上下文分块头部

为每个文档分块添加丰富的元数据头部信息,提高检索精度和上下文理解。

学习目标

  • 学习元数据的重要性
  • 掌握自动化头部生成
  • 实现层次化信息组织
  • 优化检索和生成效果

核心概念

分块头部的作用

# 原始分块
chunk = "Transformers use self-attention mechanisms..."

# 增强后的分块
enhanced_chunk = """
[Document: Attention Is All You Need]
[Section: 3.2 Attention Mechanisms]
[Keywords: transformer, self-attention, neural networks]
[Context: Deep learning architectures]

Transformers use self-attention mechanisms...
"""

头部信息类型

  1. 文档级元数据

    • 文档标题和作者
    • 发布时间和版本
    • 文档类型和来源
  2. 结构化信息

    • 章节和小节标题
    • 层次结构位置
    • 页码和段落编号
  3. 语义标签

    • 关键词和概念
    • 实体和关系
    • 主题和分类
开发中

详细的分块头部教程即将发布!

下一步

继续学习 文档增强 RAG,处理多文档场景。

上下文分块头部

在传统 RAG 中,我们直接对文本块进行嵌入和检索。上下文分块头部技术通过为每个文本块生成描述性的标题/头部,然后同时基于内容和标题进行检索,从而提高检索的准确性和相关性。

核心思想

传统 RAG 检索:

文本块: "人工智能是一个广泛的科学领域..."
嵌入: [仅基于文本内容的向量]
检索: 仅使用内容嵌入

带头部的 RAG 检索:

文本块: "人工智能是一个广泛的科学领域..."
头部: "人工智能基础概念介绍"
嵌入: [内容向量] + [头部向量]
检索: 结合内容和头部的平均相似度

技术优势

🎯 改善检索精度

  • 头部提供内容的高层次概括
  • 帮助捕获用户查询的意图

🔍 多层次匹配

  • 内容级别:详细信息匹配
  • 主题级别:概念和主题匹配

📊 增强语义理解

  • LLM 生成的头部包含更丰富的语义信息
  • 提供文本块的结构化描述

完整代码实现

import os
import numpy as np
import json
from openai import OpenAI
import fitz
from tqdm import tqdm
from llama_index.embeddings.huggingface import HuggingFaceEmbedding

def extract_text_from_pdf(pdf_path):
"""
从PDF文件中提取文本内容

Args:
pdf_path (str): PDF文件路径

Returns:
str: 提取的文本内容
"""
mypdf = fitz.open(pdf_path)
all_text = ""

for page_num in range(mypdf.page_count):
page = mypdf[page_num]
text = page.get_text("text")
all_text += text

return all_text

# 初始化OpenAI客户端
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.getenv("OPENROUTER_API_KEY")
)

def generate_chunk_header(chunk, model="meta-llama/Llama-3.2-3B-Instruct"):
"""
使用LLM为给定的文本块生成标题/头部

Args:
chunk (str): 要生成头部的文本块
model (str): 使用的模型名称

Returns:
str: 生成的头部/标题
"""
# 定义系统提示词
system_prompt = "为给定的文本生成简洁且信息丰富的标题。"

response = client.chat.completions.create(
model=model,
temperature=0,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": chunk}
]
)

return response.choices[0].message.content.strip()

def chunk_text_with_headers(text, n, overlap):
"""
将文本分块并为每个块生成头部

Args:
text (str): 要分块的完整文本
n (int): 块大小(字符数)
overlap (int): 重叠字符数

Returns:
List[dict]: 包含'header'和'text'键的字典列表
"""
chunks = []

# 按指定大小和重叠度分割文本
for i in range(0, len(text), n - overlap):
chunk = text[i:i + n]
header = generate_chunk_header(chunk) # 为块生成头部
chunks.append({"header": header, "text": chunk})

return chunks

def create_embeddings(text, model="BAAI/bge-base-en-v1.5"):
"""
为给定文本创建嵌入向量

Args:
text (str): 输入文本
model (str): 嵌入模型名称

Returns:
List[float]: 嵌入向量
"""
embedding_model = HuggingFaceEmbedding(model_name=model)

if isinstance(text, list):
response = embedding_model.get_text_embedding_batch(text)
else:
response = embedding_model.get_text_embedding(text)

return response

def cosine_similarity(vec1, vec2):
"""
计算两个向量的余弦相似度

Args:
vec1 (np.ndarray): 第一个向量
vec2 (np.ndarray): 第二个向量

Returns:
float: 余弦相似度值
"""
return np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2))

def semantic_search(query, chunks, k=5):
"""
基于查询搜索最相关的文本块

Args:
query (str): 用户查询
chunks (List[dict]): 包含头部和嵌入的文本块列表
k (int): 返回的结果数

Returns:
List[dict]: 最相关的k个文本块
"""
# 为查询创建嵌入
query_embedding = create_embeddings(query)

similarities = []

# 计算每个块的相似度
for chunk in chunks:
# 计算查询与文本内容的相似度
sim_text = cosine_similarity(
np.array(query_embedding),
np.array(chunk["embedding"])
)
# 计算查询与头部的相似度
sim_header = cosine_similarity(
np.array(query_embedding),
np.array(chunk["header_embedding"])
)
# 计算平均相似度
avg_similarity = (sim_text + sim_header) / 2
similarities.append((chunk, avg_similarity))

# 按相似度降序排序
similarities.sort(key=lambda x: x[1], reverse=True)

# 返回前k个最相关的块
return [x[0] for x in similarities[:k]]

def generate_response(system_prompt, user_message, model="meta-llama/Llama-3.2-3B-Instruct"):
"""
生成AI回答

Args:
system_prompt (str): 系统提示词
user_message (str): 用户消息
model (str): 使用的模型

Returns:
str: AI生成的回答
"""
response = client.chat.completions.create(
model=model,
temperature=0,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_message}
]
)

return response.choices[0].message.content

实际应用示例

# 1. 文档处理
pdf_path = "data/AI_Information.pdf"
extracted_text = extract_text_from_pdf(pdf_path)

# 2. 分块并生成头部
print("正在分块并生成头部...")
text_chunks = chunk_text_with_headers(extracted_text, 1000, 200)

# 3. 显示示例
print("示例块:")
print("头部:", text_chunks[0]['header'])
print("内容:", text_chunks[0]['text'][:200] + "...")

# 4. 为每个块生成嵌入
embeddings = []

print("正在生成嵌入...")
for chunk in tqdm(text_chunks, desc="处理块"):
# 为文本内容创建嵌入
text_embedding = create_embeddings(chunk["text"])
# 为头部创建嵌入
header_embedding = create_embeddings(chunk["header"])

# 保存所有信息
embeddings.append({
"header": chunk["header"],
"text": chunk["text"],
"embedding": text_embedding,
"header_embedding": header_embedding
})

# 5. 加载测试查询
with open('data/val.json') as f:
data = json.load(f)

query = data[0]['question']
print(f"查询: {query}")

# 6. 执行语义搜索
top_chunks = semantic_search(query, embeddings, k=2)

# 7. 显示结果
print("搜索结果:")
for i, chunk in enumerate(top_chunks):
print(f"\n结果 {i + 1}:")
print(f"头部: {chunk['header']}")
print(f"内容: {chunk['text'][:300]}...")
print("-" * 50)

# 8. 生成最终回答
system_prompt = "你是一个AI助手,严格基于给定的上下文回答问题。"

# 组合上下文
context = "\n\n".join([
f"头部: {chunk['header']}\n内容: {chunk['text']}"
for chunk in top_chunks
])

user_message = f"上下文:\n{context}\n\n问题: {query}"
response = generate_response(system_prompt, user_message)
print(f"\nAI回答: {response}")

核心技术解析

1. 头部生成策略

def generate_chunk_header(chunk, model="meta-llama/Llama-3.2-3B-Instruct"):
"""
头部生成的核心逻辑
- 使用LLM提炼文本的关键信息
- 生成简洁但信息丰富的标题
- 保持与原文内容的一致性
"""
system_prompt = "为给定的文本生成简洁且信息丰富的标题。"
# 低温度确保生成稳定的、相关的标题
response = client.chat.completions.create(
model=model,
temperature=0, # 确保输出的一致性
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": chunk}
]
)
return response.choices[0].message.content.strip()

2. 双重嵌入策略

# 为每个块创建两个嵌入向量
text_embedding = create_embeddings(chunk["text"]) # 内容嵌入
header_embedding = create_embeddings(chunk["header"]) # 头部嵌入

# 在检索时计算平均相似度
sim_text = cosine_similarity(query_embedding, text_embedding)
sim_header = cosine_similarity(query_embedding, header_embedding)
avg_similarity = (sim_text + sim_header) / 2 # 平均分配权重

3. 相似度融合方法

除了简单平均,还可以使用其他融合策略:

# 方法1: 简单平均(推荐)
avg_similarity = (sim_text + sim_header) / 2

# 方法2: 加权平均
weight_text = 0.7
weight_header = 0.3
weighted_similarity = weight_text * sim_text + weight_header * sim_header

# 方法3: 最大值
max_similarity = max(sim_text, sim_header)

# 方法4: 动态权重(根据查询类型调整)
if is_specific_query(query):
# 具体查询更依赖内容
avg_similarity = 0.8 * sim_text + 0.2 * sim_header
else:
# 概念性查询更依赖头部
avg_similarity = 0.4 * sim_text + 0.6 * sim_header

效果对比分析

传统 RAG vs 头部增强 RAG

查询: "什么是机器学习的监督学习?"

传统 RAG:

检索: 仅基于文本内容
结果: 可能匹配到包含"监督学习"的任何文本段落,
但不一定是专门介绍监督学习概念的段落

头部增强 RAG:

检索: 基于内容 + 头部
头部匹配: "监督学习基础概念" (高度相关)
内容匹配: 详细的监督学习解释
结果: 更精准地定位到专门讲解监督学习的段落

性能提升指标

  1. 检索准确度提升: 10-15%
  2. 回答相关性提升: 15-20%
  3. 计算成本增加: 50-70% (需要生成头部和额外嵌入)

头部生成优化策略

1. 提示词优化

# 基础版提示词
system_prompt = "为给定的文本生成简洁且信息丰富的标题。"

# 优化版提示词
system_prompt = """
你是一个专业的文档整理专家。为给定的文本段落生成一个准确、简洁的标题。
要求:
1. 标题应准确反映文本的主要内容
2. 使用3-8个词
3. 包含关键概念和术语
4. 避免过于宽泛或过于具体
"""

# 领域特定提示词
system_prompt = """
为这段关于人工智能的文本生成标题。
重点关注:技术概念、算法名称、应用领域。
格式:[主题] - [具体内容]
"""

2. 批量生成优化

def generate_headers_batch(chunks, batch_size=5):
"""
批量生成头部以提高效率
"""
headers = []

for i in range(0, len(chunks), batch_size):
batch = chunks[i:i + batch_size]

# 创建批量提示
batch_prompt = "为以下文本段落分别生成标题:\n\n"
for j, chunk in enumerate(batch):
batch_prompt += f"段落{j+1}:\n{chunk}\n\n"

batch_prompt += "请按顺序返回标题,每行一个:"

response = client.chat.completions.create(
model="meta-llama/Llama-3.2-3B-Instruct",
temperature=0,
messages=[
{"role": "system", "content": "你是文档标题生成专家。"},
{"role": "user", "content": batch_prompt}
]
)

# 解析批量结果
batch_headers = response.choices[0].message.content.strip().split('\n')
headers.extend(batch_headers[:len(batch)])

return headers

高级特性

1. 层次化头部

def generate_hierarchical_headers(chunk):
"""
生成多层次头部:主题 -> 子主题 -> 具体内容
"""
system_prompt = """
为文本生成三级标题:
1. 主题(如:机器学习)
2. 子主题(如:监督学习)
3. 具体内容(如:线性回归算法)

格式:主题 > 子主题 > 具体内容
"""
# 实现层次化标题生成逻辑
pass

2. 动态头部权重

def adaptive_similarity(query, chunk, query_type="general"):
"""
根据查询类型动态调整头部和内容的权重
"""
sim_text = cosine_similarity(query_embedding, chunk["embedding"])
sim_header = cosine_similarity(query_embedding, chunk["header_embedding"])

if query_type == "conceptual":
# 概念性查询更依赖头部
return 0.3 * sim_text + 0.7 * sim_header
elif query_type == "specific":
# 具体查询更依赖内容
return 0.8 * sim_text + 0.2 * sim_header
else:
# 通用查询平均权重
return 0.5 * sim_text + 0.5 * sim_header

最佳实践

✅ 推荐做法

  1. 头部质量控制: 定期检查生成的头部是否准确反映内容
  2. 权重调优: 根据具体应用场景调整内容和头部的权重
  3. 缓存机制: 对生成的头部进行缓存,避免重复生成
  4. 批量处理: 使用批量 API 减少请求次数和成本

❌ 避免问题

  1. 过长头部: 头部应简洁,避免成为第二个内容块
  2. 不准确头部: 确保头部真实反映文本内容
  3. 忽略成本: 头部生成会增加 API 调用成本
  4. 权重失衡: 避免过度依赖头部而忽略实际内容

扩展应用

头部增强技术可以扩展到:

  1. 多模态头部: 为图像、表格等生成描述性头部
  2. 个性化头部: 根据用户背景定制头部风格
  3. 实时头部: 基于用户反馈动态优化头部
  4. 语言适配: 为不同语言文档生成本地化头部

上下文分块头部是一个强大的 RAG 增强技术,通过为文本块生成语义丰富的头部,显著提升了检索的精确度和相关性。