Node Parsers 节点解析器
File-Based Node Parsers 面向文件类型与结构(如 Markdown、JSON、PDF 等),根据文件的语义和格式选择专门的解析器,将文件解析为带元数据的 Node,保留章节层级、标题、表格等结构信息。
MarkdownNodeParser
MarkdownNodeParser 专门用于处理 Markdown 文件 ,能够识别 Markdown 的层级结构(标题、列表、代码块等)并据此切分。
# 文本类型是markdown的
from llama_index.core.node_parser import MarkdownNodeParser
from llama_index.core.readers import SimpleDirectoryReader
markdown_docs = SimpleDirectoryReader(input_files=["扩展调参.md"]).load_data()
# markdown_docs = [Document(text="# 主标题\n\n这是第一段。\n\n## 子标题\n\n这是第二段。")]
# 创建Markdown解析器
parser = MarkdownNodeParser()
# 从Markdown文件创建节点
nodes = parser.get_nodes_from_documents(markdown_docs)
# 显示切分结果
for i, node in enumerate(nodes):
print(f"节点 {i+1} (字符数: {len(node.text)}):")
print("-" * 30)
print(node.text)
print("\n" + "="*50 + "\n")
JSONNodeParser
JSONNodeParser 用于处理 JSON 文件,能够根据 JSON 结构进行切分,保持数据的层次关系。
from llama_index.core import Document
from llama_index.core.node_parser import JSONNodeParser
json = """
{"criteria":{"mainStatus":"0","waybillNo":"","transportPlanNo":"","mainStatusList":[],"coStatusList":[],"createTime":[1774195200000,1774454399999],"waybillName":"","vehicleIdList":[],"driverIdList":[],"driverPhone":"","customAreaIdList":[],"orderNo":"","dispatchTime":[],"assignCarTime":[],"sendCarTime":[],"lineCode":"","transportModeId":"","carrierCorpIdList":[],"waybillOrgIdList":[],"additionalRequirements":[],"customerIdList":[],"goodsTypeIdList":[],"terminalIdList":[],"actualDoneTime":[],"fixLineIdList":[],"deliveryIdList":[],"delegateStatus":"","vehicleOrigin":[],"dispatchMode":[],"loadModelId":"","subjectIdList":[],"extendMap":{},"current":1,"size":20,"orderTypes":[70],"mainStatuses":[]},"size":20,"current":1}
"""
# 创建JSON解析器
parser = JSONNodeParser()
json_docs = [Document(text=json)]
# 从JSON文件创建节点
nodes = parser.get_nodes_from_documents(json_docs)
# 显示切分结果
for i, node in enumerate(nodes):
print(f"节点 {i+1} (字符数: {len(node.text)}):")
print("-" * 30)
print(node.text)
print("\n" + "="*50 + "\n")
SemanticSplitterNodeParser
SemanticSplitterNodeParser 通过嵌入模型计算文本块间的语义相似度,实现自适应断点识别,核心解决固定分块的语义割裂问题。其检索准确率较固定分块提升约 20%,适合对上下文连贯性要求高的场景(如学术论文、长文档理解)。
实现原理
- 句子分割:将文档拆分为独立句子单元
- 嵌入计算:通过嵌入模型(如 OpenAIEmbedding、BAAI/bge-m3)生成句子向量
- 相似度判断:计算相邻句子向量的余弦相似度
- 断点识别:当相似度低于设定阈值(如
breakpoint_percentile_threshold=90)时执行切分 - 块生成:合并语义相近的句子为完整分块
工作流程简述:先将文本拆分成句子,通过滑动窗口计算句子群的综合语义,在语义发生显著变化的地方进行分割。分割点是动态的、由语义决定的,buffer_size 参数确保在判断是否分割时已考虑了当前句子周围一定范围内的语义上下文。
代码实现
pip install llama-index
需要配置 OpenAI Embedding 模型(或其他嵌入模型)。
import re
from typing import List
from llama_index.core import Document
from llama_index.core.settings import Settings
from llama_index.core.node_parser import SemanticSplitterNodeParser
from llama_index.embeddings.openai import OpenAIEmbedding
# 设置全局 Embedding 模型(请自行配置 API Key)
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")
# 中文拆句器(更鲁棒)
def split_chinese_sentences(text: str) -> List[str]:
"""
将中文文本按常见句末标点拆分,保留标点并去除多余空白。
"""
if not text:
return []
text = text.replace("……", "…")
# 在常见句末标点后断句(保留标点)
pieces = re.split(r'(?<=[。!?…\?\!\.])\s*', text)
sentences = [p.strip() for p in pieces if p and p.strip()]
return sentences
# 示例中文文本
long_chinese_text = (
"本季度公司财务表现良好,营收增长15%,净利润同比提升10%。"
"在产品方面,我们完成了新一代搜索引擎的内测,搜索精度和召回率都有明显提升。"
"同时,基础设施团队迁移到新的集群架构,缩短了部署时间并降低了成本。"
"关于市场推广,最近在北京与上海分别举办了两场线下用户交流会,"
"收集到了大量用户反馈,尤其是对移动端体验的改进建议。"
"另一方面,我们正在探索与第三方数据提供商的合作,"
"以期在广告定向和推荐系统上获得更准确的信号。"
"此外,法律合规团队提醒需关注新的隐私合规要求 ,"
"包括数据最小化和用户可解释性方面的合规文档准备。"
"最后,团队在招聘方面也有所动作,已开放多个后端与算法岗位。"
)
doc = Document(text=long_chinese_text, metadata={"doc_id": "示例文档1"})
# 创建语义切分器
splitter = SemanticSplitterNodeParser(
buffer_size=2, # 以多少句子为一组做相似度计算
breakpoint_percentile_threshold=80, # 相似度断点阈值,低于该阈值的相似度会被视为断点
embed_model=Settings.embed_model, # 用于计算嵌入的模型
sentence_splitter=split_chinese_sentences, # 自定义中文拆句器
include_metadata=True, # 是否包含 metadata
include_prev_next_rel=True, # 是否包含上一句与下一句的关系
)
nodes = splitter.get_nodes_from_documents([doc])
for idx, node in enumerate(nodes):
print(f"--- chunk {idx} ---")
print("text:", node.text)
print("metadata keys:", list(node.metadata.keys()))
print()
- buffer_size:
1对局部句子差异敏感;2~3以更宽窗口判断相似度,得到更长但更连贯的 chunk - breakpoint_percentile_threshold:算法基于数据分布计算阈值,降低阈值产生更多小块,提高阈值合并更多句子
- 中文拆句器要可靠:对复杂文本(引号、括号、列表)需要更细致的预处理
- 超长 chunk 的安全裁剪:极端结构化文本中可能产生超长 chunk,可在
SemanticSplitterNodeParser之后接一个SentenceSplitter作后备分割
SentenceWindowNodeParser
SentenceWindowNodeParser 的核心在于检索单元和上下文窗口的分离:
- 精细索引:将文档拆分成单个句子作为基础节点,细粒度拆分有助于向量模型更好地表征句子语义,提升检索精度
- 窗口上下文:每个句子节点在元数据中存储周围句子构成的窗口文本。检索时先找到最相关句子,再将其替换为上下文窗口传递给 LLM
这种方法有效缓解了 RAG 系统中"检索精度"与"生成答案所需上下文完整性"之间的矛盾。
特性总结:
| 特性维度 | 具体说明 |
|---|---|
| 核心原理 | 按句子拆分并建立索引,检索返回匹配句子及周围句子(滑动窗口) |
| 主要优势 | 检索与上下文解耦:检索用小粒度句子提升精度,提供给 LLM 的是更完整的窗口文本 |
| 关键参数 | window_size 控制窗口大小;window_metadata_key 存储窗口文本的元数据键名 |
| 典型场景 | 技术文档、学术论文、法律合同等结构清晰、句子间关联紧密的文档 |
英文实践
from llama_index.core import Document
from llama_index.core.node_parser import SentenceWindowNodeParser
# ============================================
# SentenceWindowNodeParser: 句子窗口节点解析器
# ============================================
# 核心功能:将文档切分为单个句子,每个句子节点保留其周围句子的窗口上下文
# 适用场景:需要句子级别检索 + 上下文增强的 RAG 应用
# 测试文本(英文便于观察句子边界)
text = "I love programming. Python is my most favorite language. I love LLMs. I love LlamaIndex."
# 初始化句子窗口解析器
node_parser = SentenceWindowNodeParser.from_defaults(
window_size=1, # 窗口大小:前后各捕获多少个句子
window_metadata_key="window", # 元数据键:存储周围句子窗口的文本
original_text_metadata_key="original_sentence", # 元数据键:存储原始句子
include_metadata=True, # 是否在节点中包含元数据
# sentence_splitter=custom_splitter # 可选:传入自定义的分割器
)
# 将文档切分为节点
nodes = node_parser.get_nodes_from_documents([Document(text=text)])
# ============================================
# 示例1:打印所有节点的文本
# ============================================
print("所有节点文本:")
for i, node in enumerate(nodes):
print(f"Node {i}: {node.text}")
# ============================================
# 示例2:查看节点元数据(窗口上下文)
# ============================================
print("\n第二个节点周围的窗口文本:")
print(nodes[1].metadata["window"])
print("原始句子:", nodes[1].metadata["original_sentence"])
print("=" * 80)
# ============================================
# 示例3:查看节点关 系
# ============================================
print("\n第二个节点的关系信息:")
print(nodes[1].relationships)
# SentenceWindowNodeParser切分后的relationShip结构
{
< NodeRelationship.SOURCE: '1' > :
RelatedNodeInfo(
node_id = 'f445ded2-368b-4e2a-9769-70049028bdda',
node_type = <ObjectType.DOCUMENT: '4' > ,
metadata = {},
hash = '23f0fdc1f3f7d315c4602ce57a4fd967dde771b7105a3a62a1d602e8a8fabba8'),
<NodeRelationship.PREVIOUS: '2' > :
RelatedNodeInfo(
node_id = '4049b298-bd47-48f9-814e-7239aab8f02d',
node_type = <ObjectType.TEXT: '1' > ,
metadata = {
'window': 'I love programming. Python is my most favorite language. ',
'original_sentence': 'I love programming. '
},
hash = '2177457ce21b218a610394927ed308ab4ef27723b2d7d3cd2d1e3d1365cacdb7'),
<NodeRelationship.NEXT: '3' > :
RelatedNodeInfo(
node_id = '6dab22c2-3e5b-4391-b511-e0425cadbb1e',
node_type = <ObjectType.TEXT: '1' > ,
metadata = {
'window': 'Python is my most favorite language. I love LLMs. I love LlamaIndex.',
'original_sentence': 'I love LLMs. '
},
hash = '750a235a276c28bca95e4305d511e7e76d8b3e52b67eafc688a8c1957fe5d02e')
}
中文实践
"""
SentenceWindowNodeParser 示例:中文句子窗口切分与上下文检索
本文件展示如何使用 SentenceWindowNodeParser 对中文文本进行句子级别切分,
并在检索时通过窗口机制恢复上下文,提高检索质量。
"""
import re
from typing import List
from llama_index.core import Document
from llama_index.core.node_parser import SentenceWindowNodeParser
from llama_index.core.postprocessor import MetadataReplacementPostProcessor
# ==================== 1. 中文句子切分器 ====================
def split_chinese_sentences(text: str) -> List[str]:
"""
将中文文本按常见句末标点拆分为独立句子。
Args:
text: 待处理的中文文本
Returns:
句子列表(已去除空串和首尾空白)
"""
# 使用正向后顾断言 (?<=) 保留标点 符号,确保拆分后句子仍包含标点
pieces = re.split(r'(?<=[。!?\?!.])', text)
# 过滤空字符串并去除首尾空白
sentences = [p.strip() for p in pieces if p and p.strip()]
return sentences
# ==================== 2. 初始化 SentenceWindowNodeParser ====================
# SentenceWindowNodeParser 会将文档拆分为单个句子,并在每个句子的 metadata 中
# 存储其相邻句子(窗口),便于后续检索时恢复上下文
node_parser = SentenceWindowNodeParser(
sentence_splitter=split_chinese_sentences, # 自定义中文句子切分器
window_size=1, # 窗口大小:左右各1句构成上下文窗口
window_metadata_key="window", # 窗口内容存储在 metadata 的 key
original_text_metadata_key="original_text", # 原始完整文本的存储 key(可选)
include_metadata=True, # 是否在节点中保留 metadata
include_prev_next_rel=True, # 是否建立与相邻句子的关系(用于图遍历)
)
# ==================== 3. 示例文本处理 ====================
text = "本报告中的信息均来源于我们认为可靠的已公开资料,本公司对这些信息的真实性、准确性及完整性不作任何保证。 本报告中的信息、意见等均仅供客户参考,该等信息、意见并未考虑到获取本报告人员的具体投资目的、财务状况以 及特定需求,在任何时候均不构成对任何人的个人推荐。客户应当对本报 告中的信息和意见进行独立评估,并应同时 思量各自的投资目的、财务状况以及特定需求,必要时就法律、商业、财务、税收等方面咨询专家的意见。客户应自 主作出投资决策并自行承担投资风险。本公司特别提示,本公司不会与任何客户以任何形式分享证券投资收益或分担 证券投资损失,任何形式的分享证券投资收益或者分担证券投资损失的书面或口头承诺均为无效。市场有风险,投资 须谨慎。对依据或者使用本报告所造成的一切后果,本公司和关联人员均不承担任何法律责任。"
doc = Document(text=text, metadata={"doc_id": "示例文档1"})
nodes = node_parser.get_nodes_from_documents([doc])
# 打印前2个节点,验证切分效果和 metadata 内容
for i, node in enumerate(nodes[:2]):
print("---- NODE", i, "----")
print("node.text:", repr(node.text)) # 单个句子
print("=" * 80)
print("window metadata:", node.metadata.get("window")) # 上下文窗口(包含相邻句子)
print("original_text:", node.metadata.get("original_text")) # 原始完整文本
print()
# ==================== 4. 检索时上下文恢复 ====================
# 在实际检索场景中,可通过 MetadataReplacementPostProcessor 将被检索到的单句
# 替换为包含上下文的窗口文本,从而提升 LLM 理解能力
postproc = MetadataReplacementPostProcessor(target_metadata_key="window")
# 使用方式:
# query_engine = index.as_query_engine(node_postprocessors=[postproc])
# 检索时,postproc 会将 node.text 替换为 metadata['window'],使 LLM 看到带上下文的文本
- 长文档需先将所有页面文本合并为一个文档,确保句子窗口能跨页面边界正确划分
- window_size 建议从 3 开始调整,太小上下文不足,太大 token 成本上升
- 适用于结构清晰、句子间关联紧密的文档(技术文档、学术论文、法律合同)
HierarchicalNodeParser
HierarchicalNodeParser 结合文档结构(标题、章节、段落)和语义边界进行多层次切分,核心优势在于保留文档原生逻辑层级,支持父节点与子节点的嵌套组织。

适用场景选择:
- 结构文档(如
.md、.pdf、.docx):默认使用 HierarchicalParser,优先保留文档结构 - 非结构文档(如
.txt、.csv):默认使用 SentenceSplitter,平衡效率与基础语义完整性
from llama_index.core import Document
from llama_index.core.node_parser.relational.hierarchical import HierarchicalNodeParser
# 构造示例中文文档
text = (
"第一章:公司发展背景。公司成立于2005年,最初是一家小型软件外包公司。"
"随着云计算与大数据的兴起,公司在2010年转型为云服务提供商,"
"并在2015年完成了A轮融资,融资金额达数千万美元。"
"第二章:产品与服务。公司主要提供数据分析平台、实时流处理系统和人工智能模型服务。"
"其中,数据分析平台支持海量日志处理;流处理系统可实现毫秒级 别延迟。"
"第三章:市场与竞争。国内外竞争者众多,我们面临来自大型互联网公司的压力,"
"但我们的优势在于垂直行业深耕与定制化服务。"
"第四章:未来展望。我们计划在2026年前进入国际市场,并开展亚太地区的业务。"
)
# 创建 HierarchicalNodeParser
node_parser = HierarchicalNodeParser.from_defaults(
chunk_sizes=[300, 120], # 父级 300 字符、子级 120 字符
chunk_overlap=30, # 重叠区域大小
include_metadata=True,
include_prev_next_rel=True,
)
nodes = node_parser.get_nodes_from_documents(
[Document(text=text, metadata={"doc_id": "示例文档"})]
)
for idx, node in enumerate(nodes):
print(f"--- node {idx} ---")
print("text:", node.text[:80], "...")
print("metadata:", {k: node.metadata.get(k)
for k in ["chunk_size", "chunk_level", "doc_id"] if k in node.metadata})
print()
混合切分策略
结合多种切分策略,发挥各自优势,可根据实际场景灵活组合。
Unstructured chunk_by_title
核心思想:利用 Title 元素作为分段标志,将 Title 与其后的内容组合成语义完整的 chunk。
优势:
- 保留文档结构边界
- 自动合并小段落
- 保留元数据层级信息
- 避免跨章节混合
"""PDF文档解析与切分示例 - 使用unstructured库"""
import json
from llama_index.core.schema import Document, TextNode
from unstructured.chunking.title import chunk_by_title
from unstructured.partition.pdf import partition_pdf
def process_chunk_metadata(chunk):
"""处理chunk的metadata,提取有用信息并清理无用字段"""
metadata = chunk.metadata.to_dict()
# 增强metadata: 添加元 素类型信息
metadata['element_type'] = type(chunk).__name__
# 如果有原始元素,提取更多信息
if hasattr(chunk.metadata, 'orig_elements') and chunk.metadata.orig_elements:
metadata['orig_element_types'] = [type(e).__name__ for e in chunk.metadata.orig_elements]
metadata['contains_title'] = any(type(e).__name__ == 'Title' for e in chunk.metadata.orig_elements)
# 移除不适合存储的字段
metadata.pop('languages', None)
metadata.pop('orig_elements', None)
return metadata
def pdf_to_documents(pdf_path, max_chars=1800, combine_under=150):
"""解析PDF并转换为LlamaIndex Documents"""
# 解析PDF
elements = partition_pdf(
filename=pdf_path,
strategy="hi_res",
extract_images_in_pdf=False,
)
# 按标题切分
chunked_elements = chunk_by_title(
elements,
max_characters=max_chars,
combine_text_under_n_chars=combine_under,
)
print(f"✓ 解析出 {len(elements)} 个元素")
print(f"✓ 切分成 {len(chunked_elements)} 个chunks")
# 转换为Documents
documents = []
for chunk in chunked_elements:
metadata = process_chunk_metadata(chunk)
doc = Document(text=chunk.text, metadata=metadata)
documents.append(doc)
return documents
def pdf_to_nodes(pdf_path, max_chars=1800, combine_under=150):
"""解析PDF并转换为TextNodes"""
elements = partition_pdf(
filename=pdf_path,
strategy="hi_res",
extract_images_in_pdf=False,
)
chunked_elements = chunk_by_title(
elements,
max_characters=max_chars,
combine_text_under_n_chars=combine_under,
)
nodes = []
for i, chunk in enumerate(chunked_elements):
metadata = process_chunk_metadata(chunk)
node = TextNode(text=chunk.text, metadata=metadata, id_=f"chunk_{i}")
nodes.append(node)
return nodes
if __name__ == "__main__":
pdf_path = "甬兴证券-AI行业点评报告:海外科技巨头持续发力AI,龙头公司中报业绩亮眼.pdf"
# 转换为Documents
documents = pdf_to_documents(pdf_path)
print("\n" + "=" * 80)
print("转换为LlamaIndex Documents")
print("=" * 80)
for i, doc in enumerate(documents[:3]):
print(f"\n--- Document {i+1} ---")
print(f"文本长度: {len(doc.text)} 字符")
print(f"文本预览: {doc.text[:80]}...")
print(f"Metadata: {json.dumps(doc.metadata, ensure_ascii=False, indent=2)}")
# 转换为Nodes
nodes = pdf_to_nodes(pdf_path)
print(f"\n✓ 创建 {len(nodes)} 个TextNodes")
print("=" * 80)
print(nodes[0].text)
print("=" * 80)
print(nodes[0].metadata)
HierarchicalNodeParser + SemanticSplitter
核心思想:创建多层级的 chunk 结构,对过长文本再使用 SemanticSplitter 进行语义二次切分。
优势:
- 提供多粒度检索
- 自动保留父子关系
- 适合长文档
from llama_index.core.node_parser import (
SentenceSplitter,
SemanticSplitterNodeParser,
HierarchicalNodeParser,
)
from llama_index.core.settings import Settings
# 创建层次化解析器
hierarchical_parser = HierarchicalNodeParser.from_defaults(
chunk_sizes=[512, 256, 128] # 父段落、子段落、孙段落
)
# 创建语义分割器
semantic_splitter = SemanticSplitterNodeParser(
buffer_size=1,
breakpoint_percentile_threshold=90,
embed_model=Settings.embed_model,
)
# 混合策略:先层次化解析,再对过大的块进行语义分割
def hybrid_chunking(documents):
# 第一阶段:层次化解析
nodes = hierarchical_parser.get_nodes_from_documents(documents)
# 第二阶段:对过大的节点进行语义分割
final_nodes = []
for node in nodes:
if len(node.text) > 500:
sub_nodes = semantic_splitter.get_nodes_from_documents([node])
final_nodes.extend(sub_nodes)
else:
final_nodes.append(node)
return final_nodes
# 使用:nodes = hybrid_chunking(documents)
Metadata 增强的自定义切分
核心思想:将 Title 信息注入到后续 chunks 的 metadata 中,实现精确的 metadata 传播和标题信息保留。
import json
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core import Document
from typing import List
from unstructured.partition.md import partition_md
from unstructured.partition.pdf import partition_pdf
# 解析PDF
elements = partition_md(
filename=r'/Users/wangkaiqi/Documents/my_project/ai-agent/langchain/RAG/文档切分/扩展调参.md',
strategy="hi_res",
extract_images_in_pdf=False,
)
# ============= 步骤1: 预处理unstructured elements =============
def create_documents_with_title_context(elements) -> List[Document]:
"""
将unstructured elements转换为Documents,并将Title信息注入metadata
"""
documents = []
current_title_hierarchy = {
"h1": "",
"h2": "",
"h3": "",
}
accumulated_text = []
accumulated_metadata = {}
for elem in elements:
elem_type = type(elem).__name__
elem_metadata = elem.metadata.to_dict()
# 如果是Title,更新层级信息
if elem_type == "Title":
# 如果有累积的文本,先创建Document
if accumulated_text:
doc = Document(
text="\n\n".join(accumulated_text),
metadata=accumulated_metadata.copy()
)
documents.append(doc)
accumulated_text = []
# 简单的层级判断(可根据实际情况改进)
title_text = elem.text
if len(title_text) < 20: # 短标题可能是低层级
current_title_hierarchy["h3"] = title_text
elif len(title_text) < 40:
current_title_hierarchy["h2"] = title_text
current_title_hierarchy["h3"] = ""
else:
current_title_hierarchy["h1"] = title_text
current_title_hierarchy["h2"] = ""
current_title_hierarchy["h3"] = ""
# 初始化新section的metadata
accumulated_metadata = {
**elem_metadata,
"section_title": title_text,
"title_h1": current_title_hierarchy["h1"],
"title_h2": current_title_hierarchy["h2"],
"title_h3": current_title_hierarchy["h3"],
}
# Title本身也加入文本
accumulated_text.append(f"# {title_text}")
else:
# 非Title元素,累积到当前section
if not accumulated_metadata:
# 如果还没有metadata(文档开头没有Title的情况)
accumulated_metadata = {
**elem_metadata,
"section_title": "前言",
}
accumulated_text.append(elem.text)
# 更新metadata(保留最新的page_number等信息)
accumulated_metadata.update({
k: v for k, v in elem_metadata.items()
if k in ['page_number', 'filename']
})
# 处理最后累积的文本
if accumulated_text:
doc = Document(
text="\n\n".join(accumulated_text),
metadata=accumulated_metadata
)
documents.append(doc)
return documents
# ============= 步骤2: 应用自定义处理 =============
enriched_documents = create_documents_with_title_context(elements)
print("=" * 80)
print("Metadata增强的Documents")
print("=" * 80)
for i, doc in enumerate(enriched_documents[:3]):
print(f"\n--- Document {i+1} ---")
print(f"文本长度: {len(doc.text)} 字符")
print(f"文本预览:\n{doc.text[:8000]}...")
print(f"\nMetadata:")
for key, value in doc.metadata.items():
print(f" {key}: {value}")
# ============= 步骤3: 使用SentenceSplitter进一步切分 =============
"""
如果Documents还是太大,可以进一步切分
关键: metadata会自动继承到所有child nodes
"""
node_parser = SentenceSplitter(
chunk_size=512,
chunk_overlap=50,
separator=" ",
)
nodes = node_parser.get_nodes_from_documents(enriched_documents)
print("\n" + "=" * 80)
print("进一步切分后的Nodes (metadata已继承)")
print("=" * 80)
print(f"总节点数: {len(nodes)}")
for i, node in enumerate(nodes[:3]):
print(f"\n--- Node {i+1} ---")
print(f"Node ID: {node.node_id}")
print(f"文本预览: {node.text[:80]}...")
print(f"继承的Metadata: {json.dumps(node.metadata, ensure_ascii=False, indent=2)}")
# ============= 步骤4: 使用Metadata Extractor进一步增强 =============
"""
可选: 使用LLM自动提取更多metadata
"""
# 导入metadata提取器
from llama_index.core.extractors import (
TitleExtractor,
SummaryExtractor,
KeywordExtractor,
QuestionsAnsweredExtractor,
)
# 导入IngestionPipeline管道
from llama_index.core.ingestion import IngestionPipeline
# 定义metadata提取器
extractors = [
TitleExtractor(nodes=5), # 从前5个节点提取标题
KeywordExtractor(keywords=10), # 提取10个关键词
SummaryExtractor(summaries=["self"]), # 生成摘要
QuestionsAnsweredExtractor(questions=3), # 生成3个问题
]
# 构建处理管道
pipeline = IngestionPipeline(
transformations=[
node_parser, # 先切分
*extractors, # 再提取metadata
]
)
# 执行
enhanced_nodes = pipeline.run(documents=enriched_documents)
# 查看增强后的metadata
for node in enhanced_nodes[:2]:
print(f"\\nNode: {node.text[:50]}...")
print(f"Metadata: {node.metadata}")