LlamaIndex

LlamaIndex 深度学习笔记

0. 一句话定位

LlamaIndex 的核心价值:把企业内外部数据转换为 LLM 可高效检索、可组合查询、可追踪执行的知识索引层。

如果用传统后端类比:

  • 数据库 负责将结构化数据组织为可查询表、索引、执行计划。
  • LlamaIndex 负责将文档、网页、PDF、数据库记录、API 返回值等异构数据组织为 Documents / Nodes / Indices / Query Engines,让 LLM 能基于检索结果进行推理和回答。

它不是简单的"向量数据库封装器",而是一个面向 LLM 应用的数据中间层:

text 复制代码
原始数据源
  ↓ Data Connectors
Documents
  ↓ Node Parser / Text Splitter
Nodes
  ↓ Embedding / Index Struct / Vector Store
Indices
  ↓ Retriever / Query Engine / Response Synthesizer
最终答案 / 工具调用 / Agent 执行结果

1. LlamaIndex 与传统数据库索引的架构对比

1.1 传统数据库索引解决什么问题

传统数据库索引,例如 B+Tree、Hash Index、Bitmap Index,本质目标是:

  • 减少扫描范围
  • 优化谓词过滤
  • 提升精确查询性能
  • 为执行计划提供代价模型依据

典型 SQL:

sql 复制代码
SELECT * FROM user_order WHERE user_id = 10001 AND status = 'PAID';

数据库索引适合处理:

  • 字段值精确匹配;
  • 范围查询;
  • 排序;
  • Join;
  • 聚合。

它的前提是:数据结构化、Schema 明确、查询意图可形式化表达。

1.2 LlamaIndex 解决什么问题

LLM 应用的问题通常不是:

text 复制代码
user_id = 10001

而是:

text 复制代码
"总结一下过去三个月用户投诉退款失败的主要原因,并指出是否和支付网关升级有关。"

这个问题具备以下特征:

  • 查询意图是自然语言;
  • 数据源可能分散在工单、日志、Confluence、数据库、PDF、代码仓库;
  • 结果需要综合、归纳、推理;
  • 语义相关性比字段等值匹配更重要。

LlamaIndex 的核心思路是:

先把外部数据切成语义单元,再对语义单元建立索引,查询时先召回相关上下文,再由 LLM 做答案合成。


2. LlamaIndex 底层工作流

2.1 总体流程

text 复制代码
Data Source
  ↓
Data Connector / Reader
  ↓
Document
  ↓
Node Parser / Splitter
  ↓
Node
  ↓
Embedding Model
  ↓
Index / Vector Store / DocStore / IndexStore
  ↓
Retriever
  ↓
Query Engine
  ↓
Response Synthesizer
  ↓
Answer

2.2 Data Connectors:数据加载层

Data Connectors 负责从外部数据源加载原始内容,并转换为 LlamaIndex 的 Document

常见数据源:

  • 本地文件:Markdown、TXT、PDF、HTML;
  • 数据库:MySQL、PostgreSQL、MongoDB;
  • SaaS:Notion、Slack、Google Drive;
  • Web:网页、RSS、API;
  • 代码仓库:GitHub、GitLab。

极简示例:

python 复制代码
from llama_index.core import SimpleDirectoryReader

# 从本地目录加载文件,返回 Document 列表
documents = SimpleDirectoryReader(input_dir="./data").load_data()

print(type(documents[0]))
print(documents[0].text[:200])

工程理解:

  • Document 类似数据库中的一条原始记录;
  • 它通常粒度较大,可能是一篇文章、一个 PDF、一份接口文档;
  • 后续不会直接把完整 Document 塞给 LLM,而是切成更细的 Node

2.3 Documents 与 Nodes:解析与切分层

2.3.1 Document

Document 表示原始文档对象,包含:

  • 文本内容;
  • 元数据;
  • 文档 ID;
  • 数据来源信息。

示例:

python 复制代码
from llama_index.core import Document

document = Document(
    text="LlamaIndex 是一个面向 LLM 应用的数据框架。",
    metadata={"source": "manual", "category": "rag"},
)

print(document.text)
print(document.metadata)
2.3.2 Node

Node 是 LlamaIndex 更核心的检索单元。

它通常由 Document 切分而来,包含:

  • 当前文本片段;
  • 所属文档 ID;
  • 前后关系;
  • metadata;
  • embedding;
  • hash;
  • relationship。

示例:

python 复制代码
from llama_index.core import Document
from llama_index.core.text_splitter import SentenceSplitter

texts = [
    Document(text="LlamaIndex 支持文档加载、文本切分、索引构建、检索增强生成。" * 20)
]

splitter = SentenceSplitter(chunk_size=128, chunk_overlap=20)
nodes = splitter.get_nodes_from_documents(texts)

print(len(nodes))
print(nodes[0].text)

工程理解:

  • Document 是数据加载粒度;
  • Node 是检索、Embedding、索引构建的基本粒度;
  • Chunk 过大影响召回精度;
  • Chunk 过小影响上下文完整性和答案合成质量。
2.3.3 Node 关系与类型系统
2.3.3.1 Node 关系 (Relationships)

Node 通过 relationships 字段维护与其他节点的关系,这是 LlamaIndex 实现上下文追溯和文档结构化的核心机制。

关系类型说明:

关系类型 枚举值 含义 用途
SOURCE 1 指向原始文档 追溯 Node 来自哪个 Document
PREVIOUS 2 指向前一个文本块 获取上文,支持上下文扩展
NEXT 3 指向下一个文本块 获取下文,支持上下文扩展
PARENT 4 指向父节点 支持层级结构(如 TreeIndex)
CHILD 5 指向子节点 支持层级结构(如 TreeIndex)

关系结构示例:

json 复制代码
{
  "relationships": {
    "1": {
      "node_id": "ecd1e5a0-c0db-4cc3-80d3-e2c0a1ea0914",
      "node_type": "4",
      "metadata": {...},
      "hash": "ca91cab2...",
      "class_name": "RelatedNodeInfo"
    },
    "3": {
      "node_id": "66a93d09-08fd-4e97-a089-fab13d548c07",
      "node_type": "1",
      "metadata": {},
      "hash": "fc216585...",
      "class_name": "RelatedNodeInfo"
    }
  }
}

关系图解:
NEXT
NEXT
SOURCE
SOURCE
SOURCE
📄 Document (原始文档)

ID: ecd1e5a0-c0db-4cc3-80d3-e2c0a1ea0914

node_type: 4 (DOCUMENT)
✂️ 切分 (SentenceSplitter)
Node 0

node_type: 1 (TEXT)
Node 1

node_type: 1 (TEXT)
Node 2

node_type: 1 (TEXT)

工程价值:

  1. 追溯来源: 从任何 Node 都能找到它来自哪个原始文档
  2. 获取完整上下文: 通过 PREVIOUS/NEXT 关系获取前后文
  3. 文档级别操作: 可以找到同一文档的所有 Node
  4. 引用溯源: 在回答问题时,可以标注答案来自哪个文档
  5. 层级结构: 支持 TreeIndex 等层级索引结构

验证代码:

python 复制代码
import json
from llama_index.core import SimpleDirectoryReader
from llama_index.core.node_parser import SentenceSplitter

# 加载文档
documents = SimpleDirectoryReader(input_dir="./data").load_data()

print("原始 Document ID:", documents[0].doc_id)

# 切分为 Node
splitter = SentenceSplitter(chunk_size=128, chunk_overlap=20)
nodes = splitter.get_nodes_from_documents(documents)

print("第一个 Node ID:", nodes[0].node_id)

# 获取 SOURCE 关系
source_relationship = nodes[0].relationships.get("1")
if source_relationship:
    print("SOURCE 关系的 node_id:", source_relationship.node_id)
    print("SOURCE 关系的 node_type:", source_relationship.node_type)
    print("是否等于原始 Document ID:", source_relationship.node_id == documents[0].doc_id)
2.3.3.2 Node 类型 (node_type)

node_type 字段标识节点的类型,用于区分不同的对象类型。

核心类型枚举:

node_type 类型名称 说明 典型用途
1 TEXT 文本节点 最常见的 Node 类型,存储文本片段
2 IMAGE 图像节点 存储图像数据和元数据
3 INDEX 索引节点 指向另一个索引结构
4 DOCUMENT 文档对象 原始文档,是 Node 的来源

类型判断示例:

python 复制代码
from llama_index.core import Document
from llama_index.core.node_parser import SentenceSplitter

documents = [Document(text="LlamaIndex 支持多种节点类型。")]
splitter = SentenceSplitter(chunk_size=128)
nodes = splitter.get_nodes_from_documents(documents)

# 查看 Node 类型
print("Node 类型:", nodes[0].node_type)  # 输出: ObjectType.TEXT

# 查看 SOURCE 关系指向的类型
source_rel = nodes[0].relationships.get("1")
if source_rel:
    print("SOURCE 类型:", source_rel.node_type)  # 输出: ObjectType.DOCUMENT

工程理解:

  1. 类型安全 : 通过 node_type 可以在运行时判断节点类型,避免类型错误
  2. 多模态支持: 支持文本、图像等多种数据类型
  3. 关系验证: 在建立关系时可以验证类型是否匹配
  4. 索引组合: 支持索引嵌套和组合查询

完整 Node 结构示例:

json 复制代码
{
  "id_": "ea90bd8c-1a0a-4fd2-843f-1fc4ca85724a",
  "embedding": null,
  "metadata": {
    "file_path": "E:\\data\\document.txt",
    "file_name": "document.txt",
    "file_type": "text/plain",
    "file_size": 7915,
    "creation_date": "2026-04-25",
    "last_modified_date": "2026-04-25"
  },
  "excluded_embed_metadata_keys": ["file_name", "file_type", "file_size"],
  "excluded_llm_metadata_keys": ["file_name", "file_type", "file_size"],
  "relationships": {
    "1": {
      "node_id": "af49ea29-59dd-4979-ad96-b443bf83569a",
      "node_type": "4",
      "metadata": {...},
      "hash": "ca91cab2...",
      "class_name": "RelatedNodeInfo"
    },
    "3": {
      "node_id": "66a93d09-08fd-4e97-a089-fab13d548c07",
      "node_type": "1",
      "metadata": {},
      "hash": "fc2165851...",
      "class_name": "RelatedNodeInfo"
    }
  },
  "text": "智研咖啡馆合作伙伴信息\n\n一、咖啡豆供应商\n\n1.",
  "start_char_idx": 0,
  "end_char_idx": 25,
  "class_name": "TextNode"
}

字段说明:

  • id_: Node 的唯一标识符
  • embedding: 向量嵌入(生成后为浮点数组)
  • metadata: 元数据信息
  • relationships: 与其他节点的关系
  • text: 实际文本内容
  • start_char_idx / end_char_idx: 在原文档中的位置
  • class_name: 类名,通常为 "TextNode"

生产建议:

  1. 充分利用关系: 在需要上下文时,通过 PREVIOUS/NEXT 关系获取前后文
  2. 追溯来源: 在回答时通过 SOURCE 关系标注答案来源
  3. 类型检查 : 在处理多模态数据时,先检查 node_type 再处理
  4. 元数据设计 : 合理设计 metadata,支持业务过滤和权限控制

3. 安装与最小可运行环境

3.1 推荐安装

bash 复制代码
pip install llama-index llama-index-llms-openai llama-index-embeddings-openai

如使用本地向量库:

bash 复制代码
pip install llama-index-vector-stores-chroma chromadb

3.2 环境变量

bash 复制代码
export OPENAI_API_KEY="你的 API Key"

Windows PowerShell:

powershell 复制代码
$env:OPENAI_API_KEY="你的 API Key"

3.3 全局 Settings

LlamaIndex 新版本推荐通过 Settings 配置全局 LLM 与 Embedding。

python 复制代码
from llama_index.core import Settings
from llama_index.llms.openai import OpenAI
from llama_index.embeddings.openai import OpenAIEmbedding

Settings.llm = OpenAI(model="gpt-4o-mini", temperature=0)
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")

4. 核心索引结构解析

LlamaIndex 的索引不是单一结构,而是一组围绕不同查询模式设计的数据结构。

核心索引包括:

  • VectorStoreIndex:语义向量检索;
  • SummaryIndex:顺序扫描与摘要聚合;
  • TreeIndex:树状摘要与层级归纳;
  • KeywordTableIndex:关键词倒排;
  • KnowledgeGraphIndex / PropertyGraphIndex:实体关系图谱。

本文重点分析前三类。


5. VectorStoreIndex:向量索引

5.1 核心价值

VectorStoreIndex 是生产 RAG 中最常用的索引结构,用于语义相似度检索。

它适合回答:

  • "与问题最相关的文档片段是什么?"
  • "这段报错和哪个知识库条目最相似?"
  • "用户问题应该召回哪些上下文?"

5.2 底层结构

构建时:

text 复制代码
Node.text
  ↓ Embedding Model
Dense Vector
  ↓ Vector Store
向量索引结构(HNSW / IVF / Flat / DiskANN 等,取决于后端)

查询时:

text 复制代码
Query Text
  ↓ Query Embedding
Query Vector
  ↓ Top-K 相似度搜索
Relevant Nodes
  ↓ Response Synthesizer
Answer

5.3 与数据库索引类比

维度 数据库 B+Tree VectorStoreIndex
查询方式 精确匹配 / 范围查询 语义相似度查询
Key 字段值 Embedding 向量
距离度量 比较大小 Cosine / Dot Product / L2
返回结果 符合谓词的行 Top-K 相似节点
适合场景 订单查询、用户查询 FAQ、知识库、文档问答

5.4 可运行代码:最小 RAG

python 复制代码
from llama_index.core import Document, Settings, VectorStoreIndex
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.llms.openai import OpenAI

Settings.llm = OpenAI(model="gpt-4o-mini", temperature=0)
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")

documents = [
    Document(text="LlamaIndex 的 VectorStoreIndex 适合语义检索和 RAG。"),
    Document(text="SummaryIndex 适合需要遍历全文并做整体摘要的任务。"),
    Document(text="TreeIndex 通过树状摘要结构支持层级归纳。"),
]

index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine(similarity_top_k=2)

response = query_engine.query("哪种索引适合 RAG 语义检索?")
print(response)

5.5 适用业务场景

适合:

  • 企业知识库问答;
  • 技术文档检索;
  • FAQ 匹配;
  • 工单相似问题推荐;
  • 代码语义搜索;
  • RAG 问答主链路。

不适合:

  • 精确数值计算;
  • 全表聚合统计;
  • 必须 100% 精确匹配的审计查询;
  • 复杂 Join 查询。

6. SummaryIndex:摘要 / 顺序扫描索引

6.1 核心价值

SummaryIndex 更接近"全表扫描 + 汇总归纳",适合对所有节点进行遍历后综合回答。

它不是为了 Top-K 精准召回,而是为了:

  • 对完整文档做摘要;
  • 汇总所有输入节点;
  • 在数据量不大时保证覆盖率;
  • 处理"全局性问题"。

6.2 底层结构

SummaryIndex 的底层结构可以理解为一个顺序列表:

text 复制代码
IndexStruct
  ├── Node 1
  ├── Node 2
  ├── Node 3
  └── Node N

查询时通常会遍历多个或全部节点,再交给 LLM 合成答案。

6.3 类比数据库

类似:

sql 复制代码
SELECT SUM(amount) FROM orders;

如果没有合适索引,数据库需要扫描大量行。

SummaryIndex 也类似:牺牲检索速度,换取覆盖完整性。

6.4 可运行代码:全文摘要

python 复制代码
from llama_index.core import Document, Settings, SummaryIndex
from llama_index.llms.openai import OpenAI

Settings.llm = OpenAI(model="gpt-4o-mini", temperature=0)

documents = [
    Document(text="第一季度系统主要问题是登录超时,原因包括缓存击穿和数据库连接池耗尽。"),
    Document(text="第二季度系统主要问题是支付失败,原因包括支付网关升级和签名算法不兼容。"),
    Document(text="第三季度系统主要问题是消息积压,原因包括消费者扩容不足和重试策略不合理。"),
]

index = SummaryIndex.from_documents(documents)
query_engine = index.as_query_engine(response_mode="tree_summarize")

response = query_engine.query("总结三个季度系统问题的共同规律。")
print(response)

6.5 适用业务场景

适合:

  • 文档摘要;
  • 会议纪要总结;
  • 多段材料归纳;
  • 小规模数据全集分析;
  • "请总结全部内容"类问题。

不适合:

  • 大规模知识库实时问答;
  • 低延迟 Top-K 检索;
  • 高并发 RAG 主链路。

7. TreeIndex:树状层级索引

7.1 核心价值

TreeIndex 通过层级摘要组织节点,适合层级归纳、从粗到细的查询与长文档总结。

它的思想类似:

text 复制代码
Leaf Nodes 原文片段
  ↓ summarize
Parent Summary Nodes
  ↓ summarize
Root Summary Node

7.2 底层结构

text 复制代码
                 Root Summary
                /            \
       Summary A              Summary B
       /      \               /      \
   Node 1   Node 2        Node 3   Node 4

构建 TreeIndex 时,LlamaIndex 会对底层节点逐层摘要,形成树状结构。

7.3 查询方式

TreeIndex 查询通常有两种思路:

  • 从根节点向下选择相关分支
  • 从叶子节点向上合成摘要

它适合处理:

  • 长文档全局理解;
  • 多章节报告摘要;
  • 层级结构明显的知识库;
  • 需要先粗筛再细读的问题。

7.4 可运行代码:树状摘要

python 复制代码
from llama_index.core import Document, Settings, TreeIndex
from llama_index.llms.openai import OpenAI

Settings.llm = OpenAI(model="gpt-4o-mini", temperature=0)

documents = [
    Document(text="第一章:RAG 的核心是检索增强生成,关键在于召回质量。"),
    Document(text="第二章:向量数据库通过 embedding 相似度完成语义检索。"),
    Document(text="第三章:查询引擎负责把检索结果合成为最终答案。"),
    Document(text="第四章:生产系统需要关注延迟、成本、权限和可观测性。"),
]

index = TreeIndex.from_documents(documents)
query_engine = index.as_query_engine()

response = query_engine.query("这份材料主要讲了什么?")
print(response)
7.4.1 TreeIndex 性能与成本警告 ⚠️

重要: TreeIndex 构建极其耗时,不推荐用于大文档或生产环境。

性能问题分析

TreeIndex 的构建过程需要为每个 Node 调用 LLM 生成摘要:

text 复制代码
原始文档 (10,000 字)
  ↓ 切分 (chunk_size=512)
约 20 个 Node
  ↓ 第一层: 为每个 Node 调用 LLM 生成摘要 (20 次 LLM 调用)
约 5 个摘要 Node
  ↓ 第二层: 为每个摘要 Node 再调用 LLM (5 次 LLM 调用)
1 个根摘要 Node
  ↓ 总计: 25+ 次 LLM 调用

实际案例:

python 复制代码
# 6 个文档,每个约 5000 字
documents = SimpleDirectoryReader(input_dir="./data").load_data()

# 假设每个文档切分成 50 个 Node
# 总共 300 个 Node
# 需要调用 LLM 300+ 次!

index = TreeIndex.from_documents(documents)
# ⚠️ 这一行可能需要 10-30 分钟!
# ⚠️ 成本: 300 次 LLM 调用,约 $0.5-$2 (取决于模型)

时间成本估算:

文档数量 总字数 Node 数量 LLM 调用次数 预计耗时 预计成本 (GPT-4o-mini)
1 个 1,000 字 5 个 5-10 次 30 秒 $0.01
1 个 5,000 字 25 个 25-50 次 2-5 分钟 $0.05
6 个 30,000 字 300 个 300-500 次 10-30 分钟 0.5-2
50 个 250,000 字 2,500 个 2,500-4,000 次 2-5 小时 5-20

工程建议:

  1. 仅用于小规模文档 (< 5,000 字)
  2. 不要用于实时查询场景
  3. 构建后持久化索引,避免重复构建
  4. 优先考虑 VectorStoreIndex 或 SummaryIndex
减少构建时间的方法

方法1: 限制文档长度

python 复制代码
# 只取文档前 1000 字符
original_docs = SimpleDirectoryReader(input_dir="./data").load_data()
documents = [
    Document(text=doc.text[:1000], metadata=doc.metadata)
    for doc in original_docs
]

index = TreeIndex.from_documents(documents)

方法2: 只加载部分文件

python 复制代码
# 只加载一个文件进行测试
documents = SimpleDirectoryReader(
    input_files=["./data/sample.txt"]
).load_data()

index = TreeIndex.from_documents(documents)

方法3: 持久化索引,避免重复构建

python 复制代码
from llama_index.core import StorageContext, load_index_from_storage

# 第一次构建
index = TreeIndex.from_documents(documents)
index.storage_context.persist(persist_dir="./tree_index_storage")

# 后续直接加载
storage_context = StorageContext.from_defaults(persist_dir="./tree_index_storage")
index = load_index_from_storage(storage_context)
7.4.2 TreeIndex 的两种查询模式

TreeIndex 支持两种查询模式,触发条件和所需配置不同。

模式1: 树状遍历模式 (默认,推荐)

触发条件: 不传 similarity_top_k 参数

工作原理:

text 复制代码
Query
  ↓
从根节点开始
  ↓
根据 LLM 判断选择相关分支
  ↓
递归向下遍历
  ↓
返回叶子节点

代码示例:

python 复制代码
from llama_index.core import TreeIndex, Settings
from llama_index.llms.openai import OpenAI

Settings.llm = OpenAI(model="gpt-4o-mini", temperature=0)
# 不需要配置 embed_model

index = TreeIndex.from_documents(documents)

# 方式1: 使用 query_engine (推荐)
query_engine = index.as_query_engine()  # 不传 similarity_top_k
response = query_engine.query("这份材料主要讲了什么?")

# 方式2: 使用 retriever
retriever = index.as_retriever()  # 不传 similarity_top_k
nodes = retriever.retrieve("这份材料主要讲了什么?")

所需配置:

  • ✅ 必须: Settings.llm (用于判断分支选择)
  • ❌ 不需要: Settings.embed_model

优点:

  • 不需要向量化
  • 遵循 TreeIndex 的设计理念
  • 适合层级归纳和摘要
模式2: 向量相似度模式 (不推荐)

触发条件: 传入 similarity_top_k 参数

工作原理:

text 复制代码
Query
  ↓ Embedding
Query Vector
  ↓ 向量相似度计算
Top-K 相似节点
  ↓
返回结果

代码示例:

python 复制代码
from llama_index.core import TreeIndex, Settings
from llama_index.llms.openai import OpenAI
from llama_index.embeddings.openai import OpenAIEmbedding

Settings.llm = OpenAI(model="gpt-4o-mini", temperature=0)
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")  # 必须配置!

index = TreeIndex.from_documents(documents)

# 使用向量相似度检索
query_engine = index.as_query_engine(similarity_top_k=3)  # 需要 embed_model
response = query_engine.query("这份材料主要讲了什么?")

retriever = index.as_retriever(similarity_top_k=3)  # 需要 embed_model
nodes = retriever.retrieve("这份材料主要讲了什么?")

所需配置:

  • ✅ 必须: Settings.llm
  • ✅ 必须: Settings.embed_model (用于向量化)

缺点:

  • 违背 TreeIndex 的设计理念
  • 需要额外配置 embedding 模型
  • 不如直接使用 VectorStoreIndex
两种模式对比
维度 树状遍历模式 向量相似度模式
触发条件 不传 similarity_top_k 传入 similarity_top_k
需要 LLM ✅ 是 ✅ 是
需要 Embedding ❌ 否 ✅ 是
查询方式 层级遍历 向量相似度
符合设计理念 ✅ 是 ❌ 否
推荐使用 ✅ 推荐 ❌ 不推荐

工程建议:

  1. TreeIndex 应该使用树状遍历模式 (不传 similarity_top_k)
  2. 如果需要向量检索,直接使用 VectorStoreIndex
  3. 不要混用 TreeIndex 和向量相似度检索
常见错误

错误1: 使用 TreeIndex 做向量检索

python 复制代码
# ❌ 错误: TreeIndex 不适合向量检索
index = TreeIndex.from_documents(documents)
query_engine = index.as_query_engine(similarity_top_k=3)  # 不推荐!

正确做法:

python 复制代码
# ✅ 正确: 向量检索用 VectorStoreIndex
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine(similarity_top_k=3)

错误2: 忘记配置 embed_model

python 复制代码
# ❌ 错误: 使用 similarity_top_k 但没有配置 embed_model
Settings.llm = OpenAI(model="gpt-4o-mini")
# Settings.embed_model = ...  # 忘记配置!

index = TreeIndex.from_documents(documents)
query_engine = index.as_query_engine(similarity_top_k=3)  # 会报错!

正确做法:

python 复制代码
# ✅ 正确: 配置 embed_model
Settings.llm = OpenAI(model="gpt-4o-mini")
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")

index = TreeIndex.from_documents(documents)
query_engine = index.as_query_engine(similarity_top_k=3)

错误3: Document.text 是只读属性

python 复制代码
# ❌ 错误: 不能直接修改 Document.text
documents = SimpleDirectoryReader(input_dir="./data").load_data()
documents[0].text = documents[0].text[:1000]  # AttributeError!

正确做法:

python 复制代码
# ✅ 正确: 创建新的 Document 对象
original_docs = SimpleDirectoryReader(input_dir="./data").load_data()
documents = [
    Document(text=doc.text[:1000], metadata=doc.metadata)
    for doc in original_docs
]

7.5 适用业务场景

适合:

  • 长文档总结;
  • 章节型报告;
  • 法务合同归纳;
  • 技术白皮书摘要;
  • 研究论文综述。

不适合:

  • 实时低延迟问答;
  • 需要精确引用某个片段的场景;
  • 超大规模动态知识库频繁更新。

8. 三类核心索引对比

索引 底层结构 查询模式 优点 缺点 典型场景
VectorStoreIndex 向量空间 Top-K 语义相似度 快、适合大规模 RAG 可能漏召回 知识库问答
SummaryIndex 节点顺序列表 全量扫描汇总 覆盖完整 慢、成本高 全文摘要
TreeIndex 摘要树 层级归纳 适合长文档 构建成本高 报告总结

工程建议:

  • 默认 RAG 主链路优先使用 VectorStoreIndex。
  • 需要全文覆盖时使用 SummaryIndex。
  • 长文档、多章节归纳使用 TreeIndex。
  • 复杂业务可以组合多个索引,而不是强行用一种索引解决所有问题。

9. Retriever:检索器抽象

9.1 Retriever 的位置

Retriever 是 Index 和 Query Engine 之间的桥梁。

text 复制代码
Index
  ↓ as_retriever()
Retriever
  ↓ retrieve(query)
Nodes

它只负责召回,不负责最终回答。

9.2 可运行代码:只检索不回答

python 复制代码
from llama_index.core import Document, Settings, VectorStoreIndex
from llama_index.embeddings.openai import OpenAIEmbedding

Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")

documents = [
    Document(text="Redis 适合缓存热点数据。"),
    Document(text="Kafka 适合构建高吞吐消息系统。"),
    Document(text="MySQL 适合事务型关系数据存储。"),
]

index = VectorStoreIndex.from_documents(documents)
retriever = index.as_retriever(similarity_top_k=2)

nodes = retriever.retrieve("如何处理高吞吐异步消息?")

for node in nodes:
    print("score=", node.score)
    print(node.text)

9.3 工程意义

拆出 Retriever 后可以实现:

  • 自定义排序;
  • 多路召回;
  • 混合检索;
  • 权限过滤;
  • 召回结果调试;
  • 召回结果缓存。

10. Query Engine:查询引擎

10.1 Query Engine 的职责

QueryEngine 负责完整查询链路:

text 复制代码
用户问题
  ↓
Retriever 召回 Nodes
  ↓
Response Synthesizer 合成答案
  ↓
返回 Response

最小示例:

python 复制代码
from llama_index.core import Document, Settings, VectorStoreIndex
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.llms.openai import OpenAI

Settings.llm = OpenAI(model="gpt-4o-mini", temperature=0)
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")

documents = [Document(text="Spring Boot 默认使用内嵌 Tomcat 作为 Web 容器。")]
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()

response = query_engine.query("Spring Boot 默认 Web 容器是什么?")
print(response)

10.2 Response Synthesizer

ResponseSynthesizer 决定如何把多个 Node 合成为答案。

常见模式:

  • compact:压缩上下文后回答;
  • refine:逐段迭代修正答案;
  • tree_summarize:树状摘要;
  • simple_summarize:简单总结。

示例:

python 复制代码
from llama_index.core import Document, Settings, VectorStoreIndex
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.llms.openai import OpenAI

Settings.llm = OpenAI(model="gpt-4o-mini", temperature=0)
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")

documents = [
    Document(text="A 服务负责订单创建。"),
    Document(text="B 服务负责库存扣减。"),
    Document(text="C 服务负责支付确认。"),
]

index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine(response_mode="refine", similarity_top_k=3)

response = query_engine.query("订单链路包含哪些服务?")
print(response)

10.3 Response Synthesizer 深度解析

10.3.1 response_mode 的本质

核心问题 : response_mode 优化的是什么?

答案 : ✅ 优化的是 AI 回答的生成过程,而不是检索结果!

工作流程:

text 复制代码
检索阶段 (Retriever)
  ↓
召回 Top-K Nodes (例如 3 个)
  ↓
Response Synthesizer (response_mode 在这里起作用!)
  ↓
如何把这 3 个 Nodes 合成为最终答案?
  ↓
最终回答

实现方式 : ✅ 通过额外调用 LLM 进行优化,不是内置算法!

10.3.2 四种 response_mode 的底层机制
模式 1: compact (压缩上下文)

工作原理:

text 复制代码
Node 1: 文本 A (500 tokens)
Node 2: 文本 B (600 tokens)  
Node 3: 文本 C (400 tokens)

↓ 合并压缩

Context: 文本 A + 文本 B + 文本 C (1500 tokens)

↓ 一次 LLM 调用

Prompt:
"""
基于以下上下文回答问题:
{Context}

问题: {Query}
"""

↓ LLM 生成

答案

特点:

  • LLM 调用次数: 1 次
  • Token 消耗: 最低
  • 延迟: 最低
  • 质量: 中等
  • 适用场景: 实时问答、高并发、成本敏感

模式 2: refine (逐段迭代修正)

工作原理:

text 复制代码
第 1 轮 LLM 调用:
Prompt:
"""
基于以下上下文回答问题:
Node 1: 文本 A

问题: {Query}
"""
↓
初步答案 Answer_1

第 2 轮 LLM 调用:
Prompt:
"""
原始问题: {Query}
已有答案: {Answer_1}
新上下文: Node 2: 文本 B

请根据新上下文优化答案,如果新上下文无关则保持原答案。
"""
↓
优化答案 Answer_2

第 3 轮 LLM 调用:
Prompt:
"""
原始问题: {Query}
已有答案: {Answer_2}
新上下文: Node 3: 文本 C

请根据新上下文优化答案,如果新上下文无关则保持原答案。
"""
↓
最终答案 Answer_3

特点:

  • LLM 调用次数: N 次 (N = 召回的 Nodes 数量)
  • Token 消耗: 最高
  • 延迟: 最高
  • 质量: 最高
  • 适用场景: 对答案质量要求极高、成本不敏感

优势:

  • ✅ 逐步精炼,质量高
  • ✅ 可以处理更多上下文(不受单次窗口限制)
  • ✅ 每个 Node 都能充分影响答案

劣势:

  • ❌ 慢,成本高
  • ❌ 需要多次 LLM 调用
  • ❌ 串行执行,无法并行

模式 3: tree_summarize (树状摘要)

工作原理:

text 复制代码
Node 1 + Node 2 → LLM 调用 1 → 摘要 A
Node 3 + Node 4 → LLM 调用 2 → 摘要 B

摘要 A + 摘要 B → LLM 调用 3 → 最终答案

特点:

  • LLM 调用次数: log(N) 次 (树的层数)
  • Token 消耗: 中等
  • 延迟: 中等
  • 质量:
  • 适用场景: 召回 Nodes 非常多(10+ 个)、需要处理超长上下文

优势:

  • ✅ 可以处理大量 Nodes
  • ✅ 并行化潜力(同层可并行)
  • ✅ 避免单次上下文过长

劣势:

  • ❌ 复杂度高
  • ❌ 可能丢失细节
  • ❌ 中间摘要可能引入偏差

模式 4: simple_summarize (简单总结)

工作原理:

text 复制代码
Node 1 + Node 2 + Node 3 → 一次 LLM 调用 → 答案

特点:

  • LLM 调用次数: 1 次
  • Token 消耗:
  • 延迟:
  • 质量: 中等
  • 适用场景: 类似 compact,但 Prompt 更侧重总结

10.3.3 实际例子对比

假设召回了 3 个 Nodes:

  • Node 1: "A 服务负责订单创建。"
  • Node 2: "B 服务负责库存扣减。"
  • Node 3: "C 服务负责支付确认。"

问题: "订单链路包含哪些服务?"

使用 compact 模式:
text 复制代码
LLM 调用 1 次:

Prompt:
"""
基于以下上下文回答问题:
- A 服务负责订单创建。
- B 服务负责库存扣减。
- C 服务负责支付确认。

问题: 订单链路包含哪些服务?
"""

答案: "订单链路包含 A、B、C 三个服务,分别负责订单创建、库存扣减和支付确认。"
使用 refine 模式:
text 复制代码
LLM 调用 1:
Prompt: "基于 'A 服务负责订单创建' 回答问题"
答案 1: "订单链路包含 A 服务,负责订单创建。"

LLM 调用 2:
Prompt: "已有答案: '订单链路包含 A 服务...'
        新上下文: 'B 服务负责库存扣减'
        请优化答案"
答案 2: "订单链路包含 A 服务(订单创建)和 B 服务(库存扣减)。"

LLM 调用 3:
Prompt: "已有答案: '订单链路包含 A、B 服务...'
        新上下文: 'C 服务负责支付确认'
        请优化答案"
答案 3: "订单链路包含 A 服务(订单创建)、B 服务(库存扣减)、C 服务(支付确认)。"

对比:

  • compact: 1 次调用,快速但可能遗漏细节
  • refine: 3 次调用,逐步完善,质量更高

10.3.4 成本与性能对比

假设每次 LLM 调用消耗 1000 tokens:

response_mode LLM 调用次数 Token 消耗 延迟 质量 推荐场景
compact 1 次 ~1000 最低 中等 实时问答、高并发
simple_summarize 1 次 ~1000 最低 中等 快速总结
refine N 次 ~N×1000 最高 高质量要求
tree_summarize log(N) 次 ~log(N)×1000 中等 大量 Nodes

实际成本示例 (召回 10 个 Nodes):

  • compact: 1 次调用
  • refine: 10 次调用
  • tree_summarize: 4 次调用

10.3.5 工程选型建议
什么时候用 compact?
  • ✅ 实时问答场景
  • ✅ 高并发场景
  • ✅ 成本敏感
  • ✅ 召回的 Nodes 较少(2-3 个)
  • ✅ 对答案质量要求不极致
什么时候用 refine?
  • ✅ 对答案质量要求极高
  • ✅ 召回的 Nodes 较多(5-10 个)
  • ✅ 成本不敏感
  • ✅ 延迟不敏感
  • ✅ 需要充分利用每个 Node 的信息
什么时候用 tree_summarize?
  • ✅ 召回的 Nodes 非常多(10+ 个)
  • ✅ 需要处理超长上下文
  • ✅ 可以接受中等成本
  • ✅ 需要全局理解
什么时候用 simple_summarize?
  • ✅ 需要快速总结
  • ✅ 对格式要求不高
  • ✅ 成本敏感

11. 高级检索策略一:Router Query Engine

11.1 核心思想

Router Query Engine 根据用户问题自动选择最合适的 Query Engine。

典型场景:

  • 有些问题适合向量检索;
  • 有些问题适合全文摘要;
  • 有些问题适合 SQL 查询;
  • 有些问题适合工具调用。

Router 类似后端中的策略路由:

text 复制代码
Query
  ↓ LLM Selector
选择 QueryEngineTool
  ↓
目标 Query Engine
  ↓
Answer

11.2 业务类比

类似 Spring 中根据请求类型路由到不同 Handler:

text 复制代码
/order/detail     → OrderDetailHandler
/order/statistics → OrderStatisticsHandler
/document/search  → DocumentSearchHandler

LlamaIndex Router 是语义级路由:

text 复制代码
"总结全文" → SummaryIndex Query Engine
"查找某个概念" → VectorStoreIndex Query Engine

11.3 可运行代码:向量检索 vs 全文总结自动路由

python 复制代码
from llama_index.core import Document, Settings, SummaryIndex, VectorStoreIndex
from llama_index.core.query_engine import RouterQueryEngine
from llama_index.core.selectors import LLMSingleSelector
from llama_index.core.tools import QueryEngineTool
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.llms.openai import OpenAI

Settings.llm = OpenAI(model="gpt-4o-mini", temperature=0)
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")

documents = [
    Document(text="VectorStoreIndex 适合基于 embedding 的语义检索。"),
    Document(text="SummaryIndex 适合对所有节点进行遍历和整体摘要。"),
    Document(text="TreeIndex 适合长文档的层级摘要和归纳。"),
]

vector_index = VectorStoreIndex.from_documents(documents)
summary_index = SummaryIndex.from_documents(documents)

vector_engine = vector_index.as_query_engine(similarity_top_k=2)
summary_engine = summary_index.as_query_engine(response_mode="tree_summarize")

query_engine = RouterQueryEngine(
    selector=LLMSingleSelector.from_defaults(),
    query_engine_tools=[
        QueryEngineTool.from_defaults(
            query_engine=vector_engine,
            description="适合回答具体概念、局部事实、精准语义检索问题。",
        ),
        QueryEngineTool.from_defaults(
            query_engine=summary_engine,
            description="适合回答全文总结、整体归纳、全局分析问题。",
        ),
    ],
)

response = query_engine.query("请整体总结这些索引结构的区别。")
print(response)

11.4 适用场景

适合:

  • 多知识库问答;
  • 多索引组合;
  • 文档问答 + SQL 查询混合;
  • 根据问题类型自动选择工具;
  • Agent 前置工具路由。

风险:

  • 路由依赖 LLM 判断,可能选错;
  • 工具描述质量直接影响路由准确率;
  • 多工具场景下调试复杂度上升。

调优建议:

  • 工具描述要互斥、清晰、短;
  • 对高频问题建立规则路由兜底;
  • 记录 selector 的选择结果;
  • 将 Router 决策过程纳入日志和可观测性。

12. 高级检索策略二:Sub-Question Query Engine

12.1 核心思想

Sub-Question Query Engine 会把复杂问题拆成多个子问题,分别查询不同工具,再综合答案。

与 RouterQueryEngine 的关系

SubQuestionQueryEngine = 问题拆分 + 路由查询 + 答案综合

维度 RouterQueryEngine SubQuestionQueryEngine
问题处理 单个问题 → 路由到一个工具 复杂问题 → 拆分成多个子问题
工具调用 选择一个最合适的工具 可能调用多个工具
执行方式 单次路由决策 多次查询 + 结果综合
适用场景 问题明确、单一数据源 问题复杂、需要多源对比

技术实现关系:

python 复制代码
# SubQuestionQueryEngine 内部流程(伪代码)
class SubQuestionQueryEngine:
    def query(self, complex_question):
        # 步骤1: 拆分问题
        sub_questions = question_generator.generate(complex_question)
        
        # 步骤2: 每个子问题进行路由(类似 Router)
        answers = []
        for sub_q in sub_questions:
            tool = selector.select(sub_q, tools)  # 路由逻辑
            answer = tool.query(sub_q)
            answers.append(answer)
        
        # 步骤3: 综合答案
        return synthesizer.synthesize(answers)
适合问题示例

Router 适合:

text 复制代码
"VectorStoreIndex 的底层结构是什么?"
→ 直接路由到 vector_index_tool

SubQuestion 适合:

text 复制代码
"比较 VectorStoreIndex 和 SummaryIndex 的底层结构、查询性能和适用场景。"

它会拆成:

text 复制代码
1. VectorStoreIndex 的底层结构是什么? → vector_index_tool
2. SummaryIndex 的底层结构是什么? → summary_index_tool
3. 二者性能差异是什么? → 可能两个工具都调用
4. 二者分别适合什么场景? → 综合两个工具的结果

12.2 执行流程

text 复制代码
Complex Query
  ↓ Question Generator
Sub Questions
  ↓ QueryEngineTool 并行或串行查询
Intermediate Answers
  ↓ Response Synthesizer
Final Answer

12.3 可运行代码:复杂问题拆解

python 复制代码
from llama_index.core import Document, Settings, VectorStoreIndex
from llama_index.core.query_engine import SubQuestionQueryEngine
from llama_index.core.tools import QueryEngineTool, ToolMetadata
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.llms.openai import OpenAI

Settings.llm = OpenAI(model="gpt-4o-mini", temperature=0)
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")

vector_docs = [
    Document(text="VectorStoreIndex 使用 embedding 向量表示 Node,并通过相似度 Top-K 检索相关文本。"),
    Document(text="VectorStoreIndex 适合知识库问答、FAQ、语义搜索和 RAG 主链路。"),
]

summary_docs = [
    Document(text="SummaryIndex 将节点组织为列表,查询时适合遍历全部节点并进行整体摘要。"),
    Document(text="SummaryIndex 适合全文总结、会议纪要、全局归纳,但大规模场景成本较高。"),
]

vector_engine = VectorStoreIndex.from_documents(vector_docs).as_query_engine()
summary_engine = VectorStoreIndex.from_documents(summary_docs).as_query_engine()

tools = [
    QueryEngineTool(
        query_engine=vector_engine,
        metadata=ToolMetadata(
            name="vector_index_tool",
            description="回答 VectorStoreIndex 的结构、性能和适用场景。",
        ),
    ),
    QueryEngineTool(
        query_engine=summary_engine,
        metadata=ToolMetadata(
            name="summary_index_tool",
            description="回答 SummaryIndex 的结构、性能和适用场景。",
        ),
    ),
]

query_engine = SubQuestionQueryEngine.from_defaults(query_engine_tools=tools)

response = query_engine.query("比较 VectorStoreIndex 和 SummaryIndex 的底层结构、性能和业务适用场景。")
print(response)

12.4 适用场景

适合:

  • 多源复杂问答;
  • 对比分析;
  • 财报多维分析;
  • 多文档交叉验证;
  • 技术方案选型。

不适合:

  • 简单 FAQ;
  • 极低延迟场景;
  • 子问题拆解错误成本高的场景。

调优建议:

  • 限制子问题数量;
  • 明确工具边界;
  • 缓存子问题结果;
  • 对关键问题增加人工规则拆解;
  • 为每个子问题记录 trace。

13. 高级检索策略三:Metadata Filter

13.1 为什么需要 Metadata Filter

纯向量检索只看语义相似度,可能召回权限不符、时间不符、业务域不符的内容。

Metadata Filter 的作用类似于 SQL 中的 WHERE 子句,在向量检索前先进行结构化筛选。

例如:

text 复制代码
查询:"退款失败原因"
约束:只查 2025 年支付系统文档

这不是 embedding 能稳定解决的问题,应该使用 metadata filter。

工作原理示例
python 复制代码
# 1. 构建带 metadata 的文档
documents = [
    Document(text="支付网关升级导致签名验证失败。", metadata={"system": "payment", "year": "2025"}),
    Document(text="登录系统缓存击穿导致接口超时。", metadata={"system": "auth", "year": "2025"}),
    Document(text="库存系统锁竞争导致扣减失败。", metadata={"system": "stock", "year": "2024"}),
]

# 2. 定义过滤条件(类似 SQL WHERE system = 'payment')
filters = MetadataFilters(
    filters=[ExactMatchFilter(key="system", value="payment")]
)

# 3. 查询时应用过滤器
# 执行流程: 先过滤 metadata → 再进行向量检索 → 返回结果
# 相当于: SELECT * FROM docs WHERE system = 'payment' AND vector_similarity(query, text) > threshold
query_engine = index.as_query_engine(filters=filters, similarity_top_k=2)

执行顺序:

text 复制代码
原始文档集合
  ↓ Metadata Filter (WHERE system = 'payment')
符合条件的文档子集
  ↓ Vector Similarity Search (语义检索)
Top-K 最相关结果

13.2 可运行代码:按 metadata 过滤

python 复制代码
from llama_index.core import Document, Settings, VectorStoreIndex
from llama_index.core.vector_stores import ExactMatchFilter, MetadataFilters
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.llms.openai import OpenAI

Settings.llm = OpenAI(model="gpt-4o-mini", temperature=0)
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")

documents = [
    Document(text="支付网关升级导致签名验证失败。", metadata={"system": "payment", "year": "2025"}),
    Document(text="登录系统缓存击穿导致接口超时。", metadata={"system": "auth", "year": "2025"}),
    Document(text="库存系统锁竞争导致扣减失败。", metadata={"system": "stock", "year": "2024"}),
]

index = VectorStoreIndex.from_documents(documents)
filters = MetadataFilters(
    filters=[ExactMatchFilter(key="system", value="payment")]
)

query_engine = index.as_query_engine(filters=filters, similarity_top_k=2)
response = query_engine.query("失败原因是什么?")
print(response)

13.3 工程建议

元数据设计应该像数据库字段设计一样认真。

推荐 metadata:

  • tenant_id:租户隔离;
  • user_id:用户隔离;
  • department:部门;
  • system:业务系统;
  • source:来源;
  • created_at:创建时间;
  • doc_type:文档类型;
  • permission_level:权限级别。

14. 高级检索策略四:持久化与向量数据库

14.1 本地持久化

默认情况下,索引可能在内存中。生产环境必须考虑持久化。

python 复制代码
from llama_index.core import Document, Settings, StorageContext, VectorStoreIndex, load_index_from_storage
from llama_index.embeddings.openai import OpenAIEmbedding

Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")

documents = [Document(text="LlamaIndex 支持索引持久化。")]
index = VectorStoreIndex.from_documents(documents)

index.storage_context.persist(persist_dir="./storage")

storage_context = StorageContext.from_defaults(persist_dir="./storage")
loaded_index = load_index_from_storage(storage_context)

query_engine = loaded_index.as_query_engine()
print(query_engine.query("LlamaIndex 支持什么?"))

14.2 Chroma 向量库存储

python 复制代码
import chromadb
from llama_index.core import Document, Settings, StorageContext, VectorStoreIndex
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.vector_stores.chroma import ChromaVectorStore

Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")

# 1. Chroma 层: 创建数据库连接和集合
chroma_client = chromadb.PersistentClient(path="./chroma_db")  # 类似 MySQL 连接
chroma_collection = chroma_client.get_or_create_collection("llamaindex_demo")  # 类似创建表

# 2. 适配器层: 将 Chroma 适配到 LlamaIndex
vector_store = ChromaVectorStore(chroma_collection=chroma_collection)  # 类似 JDBC 驱动

# 3. LlamaIndex 层: 管理存储上下文
storage_context = StorageContext.from_defaults(vector_store=vector_store)  # 类似 Spring DataSource

documents = [
    Document(text="Chroma 可以作为 LlamaIndex 的向量存储后端。"),
    Document(text="生产环境中向量库需要关注索引构建、召回速度和持久化。"),
]

index = VectorStoreIndex.from_documents(
    documents,
    storage_context=storage_context,
)

query_engine = index.as_query_engine(similarity_top_k=1)
response = query_engine.query("Chroma 在这里的作用是什么?")
print(response)

补充:

组件 层级 职责 类比
chroma_client Chroma 层 数据库连接,持久化 MySQL 连接
chroma_collection Chroma 层 存储向量,提供检索 MySQL 表
ChromaVectorStore 适配器层 适配 Chroma 到 LlamaIndex JDBC 驱动
StorageContext LlamaIndex 层 管理所有存储组件 Spring DataSource
LlamaIndex 组件 MySQL/MyBatis 对应物 职责
Chroma 数据库 MySQL 数据库 实际存储数据
chroma_collection MySQL 表 (table) 存储向量数据
VectorStoreIndex MySQL 索引 (B+Tree/IVF) 加速查询的数据结构
ChromaVectorStore JDBC Driver 数据库驱动
StorageContext DataSource 数据源管理
QueryEngine MyBatis Mapper 提供查询接口
Retriever MyBatis Executor 执行检索逻辑

14.3 生产选型建议

向量库 优点 缺点 适合场景
Chroma 简单、本地友好 大规模能力有限 PoC、小型项目
Milvus 高性能、分布式 运维复杂 大规模向量检索
Qdrant Rust 实现、过滤能力强 生态相对小 中大型 RAG
Weaviate GraphQL、模块化 部署复杂 知识平台
Elasticsearch 混合检索强 向量能力依版本而定 传统搜索融合
pgvector 直接复用 PostgreSQL 超大规模性能受限 中小数据、低运维

15. Chunk 设计:RAG 性能的第一关键参数

15.1 Chunk Size 的影响

chunk_size 决定 Node 文本大小。

Chunk 大小 优点 缺点
小 Chunk 召回精准、噪声少 语义上下文不足
大 Chunk 上下文完整 召回不精准、成本高
中等 Chunk 平衡精度和上下文 需要按业务调参

15.2 推荐起点

经验值:

  • FAQ:128 ~ 256 tokens
  • 技术文档:512 ~ 1024 tokens
  • 法务合同:512 ~ 1500 tokens
  • 代码文档:按函数 / 类 / Markdown 标题切分;
  • 表格数据:按行组或业务实体切分。

15.3 常见文本切割方式

15.3.1 按字符长度切分 (TokenTextSplitter)

适用场景: 通用文本、不关心语义边界、追求均匀分布

python 复制代码
from llama_index.core import Document
from llama_index.core.text_splitter import TokenTextSplitter

text = """LlamaIndex 是一个数据框架。它支持数据加载、文本切分、索引构建和查询引擎。合理的 chunk_size 会显著影响 RAG 召回质量。向量检索依赖语义相似度。"""

documents = [Document(text=text)]

splitter = TokenTextSplitter(
    chunk_size=50,      # 按 token 数量切分
    chunk_overlap=10,   # 重叠 10 个 token
)

nodes = splitter.get_nodes_from_documents(documents)

print("=== TokenTextSplitter 切分结果 ===")
for i, node in enumerate(nodes):
    print(f"\nNode {i} ({len(node.text)} 字符):")
    print(node.text)
    print("-" * 50)

# 输出示例:
# Node 0: "LlamaIndex 是一个数据框架。它支持数据加载、文本切分、索引构建和查询引擎。合理的 chunk_size 会显著影响"
# Node 1: "chunk_size 会显著影响 RAG 召回质量。向量检索依赖语义相似度。"

切分过程:

text 复制代码
原始文本 (120 字符)
  ↓ 按 token 数量切分 (chunk_size=50)
Node 0: [0:50 tokens] + overlap
Node 1: [40:90 tokens] (包含 10 token 重叠)
Node 2: [80:120 tokens]
15.3.2 按句子切分 (SentenceSplitter)

适用场景: 保持语义完整性、FAQ、技术文档、对话记录

python 复制代码
from llama_index.core import Document
from llama_index.core.text_splitter import SentenceSplitter

text = """LlamaIndex 是一个数据框架。它支持数据加载、文本切分、索引构建和查询引擎。合理的 chunk_size 会显著影响 RAG 召回质量。向量检索依赖语义相似度。Embedding 模型决定检索效果。"""

documents = [Document(text=text)]

splitter = SentenceSplitter(
    chunk_size=80,      # 最大字符数
    chunk_overlap=20,   # 重叠字符数
    separator=" ",      # 句子分隔符
)

nodes = splitter.get_nodes_from_documents(documents)

print("=== SentenceSplitter 切分结果 ===")
for i, node in enumerate(nodes):
    print(f"\nNode {i} ({len(node.text)} 字符):")
    print(node.text)
    print("-" * 50)

# 输出示例:
# Node 0: "LlamaIndex 是一个数据框架。它支持数据加载、文本切分、索引构建和查询引擎。"
# Node 1: "合理的 chunk_size 会显著影响 RAG 召回质量。向量检索依赖语义相似度。"
# Node 2: "Embedding 模型决定检索效果。"

切分过程:

text 复制代码
原始文本
  ↓ 按句子边界切分 (保持句子完整)
Node 0: 句子1 + 句子2 (不超过 80 字符)
Node 1: 句子2(重叠) + 句子3 + 句子4
Node 2: 句子4(重叠) + 句子5
15.3.3 按段落切分 (SentenceSplitter with paragraph)

适用场景: 长文档、文章、报告、保持段落完整性

python 复制代码
from llama_index.core import Document
from llama_index.core.text_splitter import SentenceSplitter

text = """第一段:LlamaIndex 是一个数据框架。它支持数据加载、文本切分、索引构建和查询引擎。

第二段:合理的 chunk_size 会显著影响 RAG 召回质量。向量检索依赖语义相似度。

第三段:Embedding 模型决定检索效果。生产环境需要考虑成本和性能。"""

documents = [Document(text=text)]

splitter = SentenceSplitter(
    chunk_size=200,
    chunk_overlap=50,
    paragraph_separator="\n\n",  # 按段落分隔
)

nodes = splitter.get_nodes_from_documents(documents)

print("=== 按段落切分结果 ===")
for i, node in enumerate(nodes):
    print(f"\nNode {i}:")
    print(node.text)
    print("-" * 50)

# 输出示例:
# Node 0: "第一段:LlamaIndex 是一个数据框架。它支持数据加载、文本切分、索引构建和查询引擎。"
# Node 1: "第二段:合理的 chunk_size 会显著影响 RAG 召回质量。向量检索依赖语义相似度。"
# Node 2: "第三段:Embedding 模型决定检索效果。生产环境需要考虑成本和性能。"
15.3.4 按语义切分 (SemanticSplitterNodeParser)

适用场景: 高质量切分、主题变化明显的文档、需要精准召回

python 复制代码
from llama_index.core import Document, Settings
from llama_index.core.node_parser import SemanticSplitterNodeParser
from llama_index.embeddings.openai import OpenAIEmbedding

Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")

text = """LlamaIndex 是一个数据框架。它支持数据加载、文本切分、索引构建和查询引擎。
支付系统升级导致签名验证失败。需要检查网关配置和证书有效期。
Embedding 模型决定检索效果。生产环境需要考虑成本和性能。"""

documents = [Document(text=text)]

splitter = SemanticSplitterNodeParser(
    buffer_size=1,              # 缓冲区大小
    breakpoint_percentile_threshold=95,  # 语义变化阈值
    embed_model=Settings.embed_model,
)

nodes = splitter.get_nodes_from_documents(documents)

print("=== SemanticSplitter 切分结果 ===")
for i, node in enumerate(nodes):
    print(f"\nNode {i}:")
    print(node.text)
    print("-" * 50)

# 输出示例:
# Node 0: "LlamaIndex 是一个数据框架。它支持数据加载、文本切分、索引构建和查询引擎。"
# Node 1: "支付系统升级导致签名验证失败。需要检查网关配置和证书有效期。"
# Node 2: "Embedding 模型决定检索效果。生产环境需要考虑成本和性能。"

切分过程:

text 复制代码
原始文本
  ↓ 计算相邻句子的 embedding 相似度
句子1 ←→ 句子2: 相似度 0.92 (同主题)
句子2 ←→ 句子3: 相似度 0.45 (主题变化) ← 切分点
句子3 ←→ 句子4: 相似度 0.88 (同主题)
  ↓ 按语义边界切分
Node 0: 句子1 + 句子2
Node 1: 句子3 + 句子4
15.3.5 按代码结构切分 (CodeSplitter)

适用场景: 代码文档、API 文档、技术教程

python 复制代码
from llama_index.core import Document
from llama_index.core.node_parser import CodeSplitter

code_text = """
def calculate_total(items):
    '''计算订单总价'''
    total = sum(item.price for item in items)
    return total

class OrderService:
    '''订单服务类'''
    def create_order(self, user_id, items):
        total = calculate_total(items)
        return Order(user_id=user_id, total=total)
"""

documents = [Document(text=code_text)]

splitter = CodeSplitter(
    language="python",
    chunk_lines=10,      # 每个 chunk 最多 10 行
    chunk_lines_overlap=2,  # 重叠 2 行
    max_chars=500,
)

nodes = splitter.get_nodes_from_documents(documents)

print("=== CodeSplitter 切分结果 ===")
for i, node in enumerate(nodes):
    print(f"\nNode {i}:")
    print(node.text)
    print("-" * 50)

# 输出示例:
# Node 0: "def calculate_total(items):\n    '''计算订单总价'''\n    total = sum(item.price for item in items)\n    return total"
# Node 1: "class OrderService:\n    '''订单服务类'''\n    def create_order(self, user_id, items):\n        total = calculate_total(items)\n        return Order(user_id=user_id, total=total)"
15.3.6 父子文档切分 (Parent-Child Document Splitting)

适用场景: 需要兼顾召回精度和上下文完整性的生产环境

核心思想: 用小 chunk 做检索(精准),用大 chunk 做生成(完整上下文)

python 复制代码
from llama_index.core import Document, Settings, VectorStoreIndex
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.retrievers import RecursiveRetriever
from llama_index.core.query_engine import RetrieverQueryEngine
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.llms.openai import OpenAI

Settings.llm = OpenAI(model="gpt-4o-mini", temperature=0)
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")

text = """LlamaIndex 是一个数据框架。它支持数据加载、文本切分、索引构建和查询引擎。
合理的 chunk_size 会显著影响 RAG 召回质量。向量检索依赖语义相似度。
Embedding 模型决定检索效果。生产环境需要考虑成本和性能。
支付系统升级导致签名验证失败。需要检查网关配置和证书有效期。"""

documents = [Document(text=text)]

# 1. 创建父文档 (大 chunk)
parent_splitter = SentenceSplitter(chunk_size=200, chunk_overlap=50)
parent_nodes = parent_splitter.get_nodes_from_documents(documents)

# 2. 为每个父文档创建子文档 (小 chunk)
child_nodes = []
for parent_node in parent_nodes:
    child_splitter = SentenceSplitter(chunk_size=80, chunk_overlap=20)
    child_docs = [Document(text=parent_node.text, metadata=parent_node.metadata)]
    children = child_splitter.get_nodes_from_documents(child_docs)
    
    # 建立父子关系
    for child in children:
        child.relationships["parent"] = parent_node.node_id
    
    child_nodes.extend(children)

# 3. 构建索引 (只对子文档建索引)
child_index = VectorStoreIndex(child_nodes)

# 4. 创建递归检索器
retriever = RecursiveRetriever(
    "vector",
    retriever_dict={"vector": child_index.as_retriever(similarity_top_k=2)},
    node_dict={parent.node_id: parent for parent in parent_nodes},
)

# 5. 查询 (检索子文档,返回父文档)
query_engine = RetrieverQueryEngine.from_args(retriever)
response = query_engine.query("支付系统失败的原因是什么?")
print(response)

print("\n=== 父子文档结构 ===")
print(f"父文档数量: {len(parent_nodes)}")
print(f"子文档数量: {len(child_nodes)}")
for i, parent in enumerate(parent_nodes):
    print(f"\n父文档 {i} ({len(parent.text)} 字符):")
    print(parent.text[:100] + "...")

工作流程:

text 复制代码
原始文档 (400 字符)
  ↓ 父切分器 (chunk_size=200)
父文档 A (200 字符) | 父文档 B (200 字符)
  ↓ 子切分器 (chunk_size=80)
子 A1 (80) | 子 A2 (80) | 子 A3 (40) | 子 B1 (80) | 子 B2 (80) | 子 B3 (40)
  ↓ 检索阶段
用户查询 → 检索子文档 (精准匹配) → 找到子 B1
  ↓ 生成阶段
返回父文档 B (完整上下文 200 字符) → LLM 生成答案

优势:

  • ✅ 召回精准 (小 chunk 语义聚焦)
  • ✅ 上下文完整 (大 chunk 提供完整信息)
  • ✅ 兼顾精度和质量
15.3.7 层级切分 (Hierarchical Node Parser)

适用场景: 结构化文档 (Markdown、HTML、技术文档)

核心思想: 按文档结构层级切分,保持层级关系

python 复制代码
from llama_index.core import Document
from llama_index.core.node_parser import HierarchicalNodeParser, SentenceSplitter

text = """# LlamaIndex 教程

## 1. 安装指南

### 1.1 环境要求
需要 Python 3.8+ 版本。建议使用虚拟环境。

### 1.2 安装步骤
使用 pip install llama-index 命令安装。

## 2. 快速开始

### 2.1 加载数据
使用 SimpleDirectoryReader 加载本地文件。

### 2.2 构建索引
使用 VectorStoreIndex 构建向量索引。"""

documents = [Document(text=text)]

# 定义三层切分器
node_parser = HierarchicalNodeParser.from_defaults(
    chunk_sizes=[2048, 512, 128],  # 三层: 章节 → 小节 → 段落
)

nodes = node_parser.get_nodes_from_documents(documents)

print("=== 层级切分结果 ===")
for i, node in enumerate(nodes):
    print(f"\nNode {i} (层级 {node.metadata.get('level', 'unknown')}):")
    print(node.text[:100] + "..." if len(node.text) > 100 else node.text)
    print(f"父节点: {node.relationships.get('parent', 'None')}")
    print("-" * 50)

层级结构:

text 复制代码
Level 0 (2048 tokens): 整个文档
  ├─ Level 1 (512 tokens): ## 1. 安装指南
  │   ├─ Level 2 (128 tokens): ### 1.1 环境要求
  │   └─ Level 2 (128 tokens): ### 1.2 安装步骤
  └─ Level 1 (512 tokens): ## 2. 快速开始
      ├─ Level 2 (128 tokens): ### 2.1 加载数据
      └─ Level 2 (128 tokens): ### 2.2 构建索引

优势:

  • ✅ 保持文档结构
  • ✅ 支持多粒度检索
  • ✅ 适合结构化文档
15.3.8 Markdown 标题切分 (MarkdownNodeParser)

适用场景: Markdown 文档、技术博客、Wiki

python 复制代码
from llama_index.core import Document
from llama_index.core.node_parser import MarkdownNodeParser

markdown_text = """# LlamaIndex 核心概念

## Document
Document 是原始文档对象,包含文本和元数据。

## Node
Node 是检索的基本单元,由 Document 切分而来。

## Index
Index 是对 Node 的组织结构,支持高效检索。

## Query Engine
Query Engine 负责查询执行和答案合成。"""

documents = [Document(text=markdown_text)]

parser = MarkdownNodeParser()
nodes = parser.get_nodes_from_documents(documents)

print("=== Markdown 切分结果 ===")
for i, node in enumerate(nodes):
    print(f"\nNode {i}:")
    print(f"标题: {node.metadata.get('header', 'N/A')}")
    print(node.text)
    print("-" * 50)

# 输出示例:
# Node 0: 标题: # LlamaIndex 核心概念
# Node 1: 标题: ## Document, 内容: Document 是原始文档对象...
# Node 2: 标题: ## Node, 内容: Node 是检索的基本单元...

切分规则:

text 复制代码
按 Markdown 标题层级切分:
# 一级标题 → 独立 Node
## 二级标题 → 独立 Node (包含下面的内容直到下一个标题)
### 三级标题 → 独立 Node
15.3.9 JSON 结构化切分 (JSONNodeParser)

适用场景: API 文档、配置文件、结构化数据

python 复制代码
from llama_index.core import Document
from llama_index.core.node_parser import JSONNodeParser
import json

json_data = {
    "user": {
        "id": 10001,
        "name": "张三",
        "orders": [
            {"order_id": "O001", "amount": 299.0, "status": "paid"},
            {"order_id": "O002", "amount": 599.0, "status": "pending"}
        ]
    },
    "payment": {
        "gateway": "alipay",
        "config": {"timeout": 30, "retry": 3}
    }
}

documents = [Document(text=json.dumps(json_data, ensure_ascii=False))]

parser = JSONNodeParser()
nodes = parser.get_nodes_from_documents(documents)

print("=== JSON 切分结果 ===")
for i, node in enumerate(nodes):
    print(f"\nNode {i}:")
    print(f"路径: {node.metadata.get('json_path', 'N/A')}")
    print(node.text)
    print("-" * 50)

# 输出示例:
# Node 0: 路径: user, 内容: {"id": 10001, "name": "张三", ...}
# Node 1: 路径: user.orders[0], 内容: {"order_id": "O001", ...}
# Node 2: 路径: payment, 内容: {"gateway": "alipay", ...}
15.3.10 窗口切分 (SentenceWindowNodeParser)

适用场景: 需要动态扩展上下文的场景

核心思想: 检索时用小窗口,生成时动态扩展前后文

python 复制代码
from llama_index.core import Document, Settings, VectorStoreIndex
from llama_index.core.node_parser import SentenceWindowNodeParser
from llama_index.core.postprocessor import MetadataReplacementPostProcessor
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.llms.openai import OpenAI

Settings.llm = OpenAI(model="gpt-4o-mini", temperature=0)
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")

text = """句子1:LlamaIndex 是一个数据框架。句子2:它支持数据加载和文本切分。句子3:索引构建是核心功能。句子4:查询引擎负责检索。句子5:RAG 需要合理的 chunk 设计。"""

documents = [Document(text=text)]

# 窗口切分: 每个句子为一个 Node,但保存前后各 2 个句子的上下文
parser = SentenceWindowNodeParser.from_defaults(
    window_size=2,  # 前后各保留 2 个句子
    window_metadata_key="window",
    original_text_metadata_key="original_sentence",
)

nodes = parser.get_nodes_from_documents(documents)

print("=== 窗口切分结果 ===")
for i, node in enumerate(nodes):
    print(f"\nNode {i}:")
    print(f"原始句子: {node.metadata.get('original_sentence', 'N/A')}")
    print(f"窗口内容: {node.metadata.get('window', 'N/A')}")
    print("-" * 50)

# 构建索引并查询
index = VectorStoreIndex(nodes)

# 使用后处理器替换为窗口内容
postprocessor = MetadataReplacementPostProcessor(target_metadata_key="window")
query_engine = index.as_query_engine(node_postprocessors=[postprocessor])

response = query_engine.query("索引构建的作用是什么?")
print(f"\n查询结果: {response}")

工作流程:

text 复制代码
原始文本: 句子1 句子2 句子3 句子4 句子5
  ↓ 窗口切分 (window_size=2)
Node 0: 原始=句子1, 窗口=[句子1, 句子2, 句子3]
Node 1: 原始=句子2, 窗口=[句子1, 句子2, 句子3, 句子4]
Node 2: 原始=句子3, 窗口=[句子1, 句子2, 句子3, 句子4, 句子5]
Node 3: 原始=句子4, 窗口=[句子2, 句子3, 句子4, 句子5]
Node 4: 原始=句子5, 窗口=[句子3, 句子4, 句子5]
  ↓ 检索阶段
用户查询 → 检索原始句子 (精准)
  ↓ 生成阶段
返回窗口内容 (包含前后文) → LLM 生成答案

优势:

  • ✅ 检索精准 (单句匹配)
  • ✅ 上下文动态扩展
  • ✅ 灵活控制窗口大小

15.4 切分方式对比与选型

切分方式 优点 缺点 适用场景
TokenTextSplitter 简单快速、均匀分布 可能切断句子、语义不完整 通用文本、不关心边界
SentenceSplitter 保持句子完整、语义连贯 可能产生过大/过小 chunk FAQ、技术文档、对话
段落切分 保持段落完整、上下文丰富 chunk 大小不均匀 长文档、文章、报告
SemanticSplitter 按主题切分、召回精准 需要 embedding、速度慢 高质量切分、主题明确
CodeSplitter 保持代码结构、函数完整 仅适用代码 代码文档、API 文档
父子文档切分 兼顾精度和上下文 实现复杂、存储翻倍 生产环境、高质量 RAG
层级切分 保持文档结构、多粒度检索 需要结构化文档 Markdown、HTML、Wiki
Markdown 切分 按标题切分、结构清晰 仅适用 Markdown 技术博客、文档、笔记
JSON 切分 保持数据结构、路径追溯 仅适用 JSON API 文档、配置文件
窗口切分 动态扩展上下文、灵活 存储开销大 需要前后文的场景

选型建议:

  1. FAQ/客服对话 → SentenceSplitter (chunk_size=128-256)
  2. 技术文档/Wiki → Markdown 切分 或 层级切分
  3. 长文章/报告 → 段落切分 (chunk_size=1024-2048)
  4. 代码文档 → CodeSplitter (按函数/类切分)
  5. 高精度场景 → SemanticSplitter 或 父子文档切分
  6. 通用场景 → TokenTextSplitter (快速简单)
  7. 结构化数据 → JSON 切分
  8. 需要前后文 → 窗口切分
  9. 生产环境 → 父子文档切分 (最佳实践)

工程实践:

python 复制代码
# 根据文档类型选择切分器
def get_splitter(doc_type: str):
    if doc_type == "faq":
        return SentenceSplitter(chunk_size=256, chunk_overlap=50)
    elif doc_type == "article":
        return SentenceSplitter(chunk_size=1024, chunk_overlap=200, paragraph_separator="\n\n")
    elif doc_type == "code":
        return CodeSplitter(language="python", chunk_lines=20)
    elif doc_type == "markdown":
        return MarkdownNodeParser()
    elif doc_type == "json":
        return JSONNodeParser()
    elif doc_type == "high_quality":
        return SemanticSplitterNodeParser(breakpoint_percentile_threshold=95)
    elif doc_type == "production":
        # 生产环境推荐: 父子文档切分
        return {
            "parent": SentenceSplitter(chunk_size=1024, chunk_overlap=200),
            "child": SentenceSplitter(chunk_size=256, chunk_overlap=50)
        }
    elif doc_type == "context_aware":
        return SentenceWindowNodeParser(window_size=3)
    else:
        return TokenTextSplitter(chunk_size=512, chunk_overlap=100)

其他常见切分方式:

还有一些特定场景的切分方式:

  1. HTML 切分 (HTMLNodeParser) - 按 HTML 标签结构切分
  2. 表格切分 (TableNodeParser) - 专门处理表格数据
  3. PDF 切分 (按页/按段落) - 保持 PDF 原始结构
  4. 多模态切分 - 同时处理文本、图片、表格

核心原则:

  • 精度优先 → 小 chunk + 语义切分
  • 上下文优先 → 大 chunk + 段落切分
  • 兼顾两者 → 父子文档切分 (生产推荐)
  • 结构化文档 → 按结构切分 (Markdown/JSON/HTML)
  • 成本敏感 → TokenTextSplitter (最快最便宜)

15.5 调优方法

不要凭感觉调 Chunk,应该建立评测集。

评测集至少包含:

  • 问题;
  • 标准答案;
  • 标准引用文档;
  • 期望召回 Node;
  • 是否要求精确事实。

核心指标:

  • Recall@K;
  • MRR;
  • Answer Faithfulness;
  • Context Precision;
  • Latency;
  • Token Cost。

16. Embedding 成本与性能优化

16.1 成本来源

Embedding 成本主要来自:

  • 初次构建索引时对所有 Node 生成向量;
  • 新增文档的增量 embedding;
  • 查询时 query embedding;
  • 多路召回时重复 embedding。

16.2 优化策略

方案一:Embedding 缓存

优点:

  • 避免重复计算;
  • 降低成本;
  • 提升构建速度。

缺点:

  • 需要缓存一致性策略;
  • 模型升级后缓存需要失效。
方案二:增量索引

优点:

  • 避免全量重建;
  • 适合频繁更新的知识库。

缺点:

  • 需要文档版本、hash、删除同步机制。
方案三:本地 Embedding 模型

优点:

  • 成本可控;
  • 数据不出内网;
  • 可离线部署。

缺点:

  • 效果可能低于商业模型;
  • GPU / 推理服务运维成本高。
方案四:分层召回
text 复制代码
关键词 / BM25 粗召回
  ↓
向量召回
  ↓
Reranker 重排
  ↓
LLM 合成

优点:

  • 提升召回率;
  • 降低单一路径风险。

缺点:

  • 链路复杂;
  • 调参成本增加。

17. 检索召回率低的常见原因

17.1 原因一:Chunk 切分破坏语义

表现:

  • 答案在原文里,但召回不到;
  • 召回片段缺少上下文;
  • 一个完整概念被切到多个 Node。

解决:

  • 增大 chunk_overlap
  • 按 Markdown 标题切分;
  • 按业务实体切分;
  • 对表格、代码、日志使用专用 parser。

17.2 原因二:Embedding 模型不匹配

表现:

  • 中文召回差;
  • 专业术语召回差;
  • 缩写、代码、错误码召回差。

解决:

  • 选择支持中文和技术语料的 embedding;
  • 使用领域微调 embedding;
  • 增加关键词检索兜底;
  • 对术语维护 synonym map。

17.3 原因三:Top-K 设置不合理

Top-K 太小:漏召回。

Top-K 太大:噪声多、成本高。

推荐:

  • FAQ:top_k = 2 ~ 5
  • 技术文档:top_k = 5 ~ 10
  • 多文档分析:top_k = 10 ~ 20
  • 配合 reranker 时可先召回 20 ~ 50 再重排。

17.4 原因四:缺少 Metadata Filter

解决:

  • 把业务约束结构化;
  • 先 filter 再 vector search;
  • 权限、租户、时间范围必须走 metadata,不要交给 LLM 猜。

18. Reranker:提升最终上下文质量

18.1 为什么需要 Reranker

向量检索负责召回,Reranker 负责精排。

text 复制代码
Vector Search Top 50
  ↓
Reranker Top 5
  ↓
LLM Answer

优势:

  • 减少噪声上下文;
  • 提升事实准确性;
  • 改善长尾问题表现。

18.2 工程建议

  • 高价值问答链路建议加 reranker;
  • 低延迟链路谨慎使用;
  • 可对 rerank 结果做缓存;
  • reranker 输入不宜过长。
相关推荐
m0_734949792 小时前
golang如何使用expvar暴露运行时指标_golang expvar运行时指标暴露步骤
jvm·数据库·python
qq_413847402 小时前
开发者工具怎么看HTML_Elements面板使用指南【操作】
jvm·数据库·python
zh路西法2 小时前
【ROS2多激光雷达融合】基于ROS2的双2D激光雷达点云融合与遮挡剔除方案
c++·python·机器人
qq_372906932 小时前
mysql如何设置密码过期策略_mysql default_password_lifetime
jvm·数据库·python
七颗糖很甜2 小时前
开源雷达NEXRAD Level 3 数据完整获取与 Python 处理教程
大数据·python·算法
SuAluvfy2 小时前
PyTorch 基础:数据操作与数据预处理
人工智能·pytorch·python
刘大猫.2 小时前
谷歌或将推出无屏健身手环 Fitbit Air,主打 AI 数字化健康教练
人工智能·ai·大模型·谷歌·算力·无屏健身手环·fitbit air
ydmy2 小时前
Embedding层(个人理解)
python·深度学习·embedding
维元码簿2 小时前
Claude Code 深度拆解:工具系统——30+ 内置工具地图与 MCP / Skills 协作
ai·agent·claude code·ai coding