一、Document:一切数据的起点

1.1 Document 是什么?

在 LlamaIndex 中,Document 是所有数据处理流程的基石。无论原始数据来自 PDF、网页、数据库还是邮件,最终都会被封装为 Document 对象。

它不仅是文本容器,更是结构化信息的基本单元,包含以下核心属性:

属性

类型

说明

text

str

实际文本内容,供 LLM 理解

doc_id

UUID

唯一标识符,用于追踪文档生命周期

metadata

dict

自定义元数据(来源、作者、时间等)

hash

str

内容哈希值(SHA-256),用于检测变更

from llama_index.core import Document
 
raw_text = "项目名称:AI助手开发,预算:100万元,周期:6个月。"
document = Document(text=raw_text)
 
print("=== Document 基础信息 ===")
print(f"文档ID: {document.doc_id}")
print(f"内容预览: {document.text[:50]}...")
print(f"元数据: {document.metadata}")
print(f"哈希值: {document.hash}")

输出示例

文档ID: 7a3b3c7f-7d6d-4b2b-9e9e-7c8c5c5c5c5c

内容预览: 项目名称:AI助手开发,预算:100万元...

元数据: {}

哈希值: 7a3b3c7f7d6d4b2b9e9e7c8c5c5c5c5c

💡 关键点说明

  • doc_id 自动生成,即使内容相同也不同,利于去重与版本控制。

  • hash 基于内容生成,任何微小修改都会改变哈希,可用于增量同步判断。

  • 可手动指定 ID:Document(text=..., doc_id="custom_id")


1.2 元数据:提升智能的关键

虽然 text 是核心,但 metadata 才是实现精准检索与高级功能的“大脑”。

场景举例:

用户提问:“Q1 的营收是多少?” 

如果没有元数据,系统需搜索所有文档;若有 {"document_type": "财务报告", "fiscal_quarter": "Q1"},则可精准过滤。

示例:添加丰富元数据
project_report = """
项目季度报告 - Q1 2024
主要成就:
- 完成用户认证模块开发
- 用户数量增长至 10,000 人
"""
 
doc = Document(
    text=project_report,
    metadata={
        "source": "季度报告",
        "author": "张三",
        "department": "产品部",
        "date": "2024-03-31",
        "document_type": "内部报告",
        "confidence_level": "high"
    }
)
 
print("=== 带元数据的 Document ===")
print(f"内容长度: {len(doc.text)} 字符")
for k, v in doc.metadata.items():
    print(f"  {k}: {v}")

元数据的实际用途

用途

应用场景

过滤(Filtering)

仅查财务部文档 → department == 'finance'

排序(Sorting)

按日期优先返回最新报告

溯源(Provenance)

“根据张三提交的2024年Q1报告…”

权限控制

标记 confidential="internal" 控制访问

推荐元数据字段命名规范

字段名

示例

说明

source

技术规格书、会议纪要

来源描述

author

李四、研发团队

创建者

date

2024-03-31

ISO 8601 格式

document_type

报告、合同、邮件

分类标签

department

finance, engineering

英文小写,方便查询

version

v1.2

版本号

file_path

/docs/tech_spec_v1.pdf

原始路径

注意:元数据应轻量,避免存储大文本或重复信息。


1.3 从文件创建 Document

实际项目中,通常通过加载器批量导入文件。

LlamaIndex 提供多种 Reader,支持 .txt, .pdf, .md, .docx 等格式。

示例:使用 SimpleDirectoryReader 加载 Markdown 文件
import os
from llama_index.core import SimpleDirectoryReader
 
# 创建测试目录和文件
os.makedirs("documents", exist_ok=True)
files = {
    "technical_spec.md": "# 技术规格\n前端:React 18 + TS...",
    "project_plan.md": "# 项目计划\n开发阶段:2024-01-16..."
}
for fname, content in files.items():
    with open(f"documents/{fname}", "w", encoding="utf-8") as f:
        f.write(content)
 
# 加载文档
documents = SimpleDirectoryReader("documents").load_data()
 
print(f"共加载 {len(documents)} 个文档")
for i, doc in enumerate(documents):
    print(f"文档 {i+1}: {doc.metadata['file_name']} ({len(doc.text)} 字符)")

输出:

共加载 2 个文档

文档 1: technical_spec.md (XX 字符)

文档 2: project_plan.md (XX 字符)

关键特性

  • 自动填充 file_namefile_path 到 metadata。

  • 支持扩展自定义解析逻辑(继承 BaseReader)。

  • 大规模数据建议分批加载,防止内存溢出(OOM)。


二、Index:数据的大脑 —— 让机器高效“记住”

如果说 Document 是原材料,那 Index 就是加工厂,负责将非结构化文本转化为可快速检索的知识结构。

2.1 索引构建三步曲

步骤

功能

工具/方法

分块(Chunking)

拆分长文档为短片段(Node)

SimpleNodeParser

向量化(Embedding)

将文本转为语义向量

BAAI/bge, OpenAI embeddings

索引构建(Indexing)

存储向量并支持 ANN 搜索

FAISS, Chroma, Pinecone

示例:手动 vs 自动创建索引
from llama_index.core import VectorStoreIndex
from llama_index.core.node_parser import SimpleNodeParser
 
def create_index_with_control(documents):
    # 方法1:自动创建(推荐快速原型)
    auto_index = VectorStoreIndex.from_documents(documents)
    
    # 方法2:手动控制分块过程(适合调优)
    parser = SimpleNodeParser.from_defaults(chunk_size=512, chunk_overlap=50)
    nodes = parser.get_nodes_from_documents(documents)
    
    manual_index = VectorStoreIndex(nodes)
    
    print(f"分块后生成 {len(nodes)} 个 Node")
    for i, node in enumerate(nodes[:2]):
        print(f"Node {i+1} ({len(node.text)} 字符): {node.text[:80]}...")
    
    return auto_index, manual_index, nodes

提示:手动控制让你可以调整 chunk_size、重叠策略等,直接影响检索效果。


2.2 主流索引类型对比

类型

适用场景

优点

缺点

VectorStoreIndex

语义搜索、问答系统

快速模糊匹配,支持相似度检索

难以跨段落推理

SummaryIndex

总结类任务

整体概括能力强

忽略细节

TreeIndex

复杂推理、多跳问答

支持递归聚合信息

构建慢,内存高

示例:创建三种索引
from llama_index.core import SummaryIndex, TreeIndex
 
indexes = {
    "vector": VectorStoreIndex.from_documents(documents),
    "summary": SummaryIndex.from_documents(documents),
    "tree": TreeIndex.from_documents(documents)
}
 
print("向量索引:适合大多数问答")
print("摘要索引:适合总结‘请概述这份文档’")
print("树状索引:适合‘比较两份报告差异’")

实战建议:采用混合索引策略,例如主索引用 VectorStoreIndex,辅以 SummaryIndex 提供摘要视图。


三、QueryEngine:通往智能问答的桥梁

QueryEngine 是用户与知识库之间的接口,接收自然语言问题,返回结构化答案。

3.1 基础使用:一行启动问答服务

query_engine = indexes["vector"].as_query_engine()
 
response = query_engine.query("项目使用什么前端框架?")
print(response)
# 输出:项目使用 React 18 + TypeScript 作为前端框架。

看似简单的一行,背后隐藏复杂流程。


3.2 内部机制揭秘:Retriever + Synthesizer

QueryEngine 本质上由两个核心组件构成:

组件

职责

Retriever

从 Index 中找出最相关的 Node(Top-k)

ResponseSynthesizer

将 Node 内容整合,交给 LLM 生成自然语言回答

手动组装引擎:理解全流程
from llama_index.core.retrievers import VectorIndexRetriever
from llama_index.core import get_response_synthesizer
from llama_index.core.query_engine import RetrieverQueryEngine
 
# 1. 创建检索器
retriever = VectorIndexRetriever(index=indexes["vector"], similarity_top_k=3)
 
# 2. 创建合成器
synthesizer = get_response_synthesizer()
 
# 3. 组装查询引擎
custom_engine = RetrieverQueryEngine(retriever=retriever, response_synthesizer=synthesizer)
 
# 测试并观察中间结果
question = "技术架构和性能要求是什么?"
nodes = retriever.retrieve(question)
 
print(f"\n🔍 检索到 {len(nodes)} 个相关节点:")
for i, node in enumerate(nodes):
    src = node.metadata.get("file_name", "未知")
    print(f"  [{i+1}] 来自 {src}, 相似度: {node.score:.4f}")
    print(f"      {node.text[:100]}...")
 
response = custom_engine.query(question)
print(f"\n🎯 最终答案:\n{response}")

输出示例:

检索到 2 个相关节点:

[1] 来自 technical_spec.md, 相似度: 0.8542

     # 技术规格文档 系统架构:前端:React 18 + TypeScript...

[2] 来自 technical_spec.md, 相似度: 0.8123

     性能要求:< 200ms,并发用户:10,000...

最终答案: 项目前端使用 React 18 + TypeScript,后端使用 Python FastAPI...

完整流程解析

  1. 问题编码:将问题用 embedding 模型转为向量;

  2. 近邻搜索:在向量空间中查找 Top-k 最相似的 Node;

  3. (可选)重排序:使用 Cross-Encoder 提升排序精度;

  4. 上下文拼接:将 top-k Node 文本传给 LLM;

  5. 生成答案:LLM 结合上下文生成流畅回答;

  6. 溯源输出:高级引擎支持返回引用来源。


四、三巨头协作实战:端到端演示

def end_to_end_demo():
    print("🚀 三巨头协作实战:Document → Index → QueryEngine")
 
    # Step 1: 构建 Document
    plan = """
公司战略规划 2024
市场定位:面向中小企业的AI解决方案提供商
财务目标:年度营收5000万元,毛利率60%
团队规划:技术团队扩张至50人
"""
    doc = Document(
        text=plan,
        metadata={"type": "strategy", "year": "2024", "dept": "executive"}
    )
    print("Document 创建完成")
 
    # Step 2: 构建 Index
    index = VectorStoreIndex.from_documents([doc])
    print("向量索引构建完成")
 
    # Step 3: 创建 QueryEngine 并提问
    engine = index.as_query_engine()
    questions = [
        "市场定位是什么?",
        "年度营收目标是多少?",
        "技术团队如何扩张?"
    ]
    
    print("\n智能问答结果:")
    for q in questions:
        ans = engine.query(q)
        print(f"Q: {q}")
        print(f"A: {ans}\n")
 
end_to_end_demo()

输出

Q: 市场定位是什么?

A: 面向中小企业的AI解决方案提供商

Q: 年度营收目标是多少?

A: 年度营收目标是5000万元

Q: 技术团队如何扩张?

A: 技术团队将扩张至50人

设计理念体现

  • 分层抽象:各组件职责明确

  • 松耦合:可独立替换任一环节

  • 易扩展:可在任意层级插入自定义逻辑


五、最佳实践与常见问题

5.1 元数据设计原则

原则

说明

一致性

统一字段命名,避免 dept / department 混用

相关性

只保留对检索有用的字段(如 date, type

标准化

使用 ISO 时间、英文小写部门名

可扩展性

预留 tags, category 字段应对未来需求

good_metadata = {
    "source": "Q1财报",
    "author": "CFO办公室",
    "date": "2024-03-31",
    "document_type": "financial_report",
    "fiscal_quarter": "Q1",
    "department": "finance",
    "version": "1.0",
    "tags": ["revenue", "profit"]
}

5.2 调试与优化技巧

当查询效果不佳时,请检查以下方面:

问题

可能原因

解决方案

答案不准

分块不合理

调整 chunk_size 或增加 overlap

缺少上下文

元数据未利用

添加 section, chapter 等字段

性能慢

返回太多 Node

减少 similarity_top_k(默认 2)

内存高

数据一次性加载

改用 PersistentIndex 或分批处理

调试建议代码:
def inspect_nodes(nodes):
    print("🔍 检查分块质量:")
    for i, n in enumerate(nodes[:2]):
        print(f"Node {i+1}: {len(n.text)} 字符")
        print(f"Preview: {n.text[:100]}...")
        print(f"Metadata: {n.metadata}\n")

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐