RAG 回答总“差点意思“?小白程序员必备:附代码实战两把索引优化钥匙(收藏版)
• ✅句子窗口检索:用单句建索引(精准),用窗口送 LLM(完整),两全其美• ✅结构化递归检索:给文档贴元数据标签,先路由后检索,大规模知识库的救星• ✅安全意识用了eval(),生产环境要换成元数据过滤方案阶段方向要掌握的入门跑通基础 RAGLlamaIndex 基本用法,向量索引,Top-K 检索进阶本文两种技术高级混合检索 + 重排序密集向量 + 稀疏向量 + Reranker 三件套。
本文针对 RAG 搭建后回答质量不高的问题,介绍了两种优化方法:句子窗口检索和结构化递归检索。句子窗口检索通过聚焦最小句子并扩展为完整段落来提升答案质量;结构化递归检索则通过元数据标签先过滤再搜索,特别适合大规模知识库。文章提供可运行代码和动图讲解,适合零基础小白学习实践,帮助提升 RAG 回答的准确性和完整性。
一、我的 RAG “又准又快”,但回答依然烂
你花了一周时间搭了个 RAG 知识助手,把公司文档全塞进去了,满怀期待地问了第一个问题:
你问:“我们产品的退款流程是什么?”
AI 答:“退款需要……(后面是从文档某个角落扒出来的半截话,前后逻辑对不上)”
问题出在哪?不是模型不够聪明,也不是文档没有答案——
是你切出来的文本块,太碎了。
最基础的 RAG 切法是固定大小切块(比如每块 512 个字符)。检索时确实找到了最相关的那块,但这一小块"断章取义",上下文都被切掉了,大模型拿到之后根本补不出完整答案。
“那我切大一点?”
切大了又有另一个问题:大块里塞着太多不相关的内容,向量嵌入被"稀释",检索准确率反而下降。
听起来像个死结,对吧?
其实有解——只需要用上两把"钥匙"。

二、第一把钥匙:句子窗口检索
💡 一句话定义:句子窗口检索(Sentence Window Retrieval)就像用放大镜找针——检索时聚焦最小的句子,送给 AI 时扩展成完整段落,鱼与熊掌两者兼得。
2.1 理解它:用"便利贴 + 背景纸"来类比
想象你在整理一本厚厚的工作手册,把每一句话写在一张便利贴上,贴到墙上。
- • 查资料时,你的眼睛扫过所有便利贴,精准锁定那张最相关的句子。
- • 但把这张便利贴单独撕下来给别人看,他们会一脸懵——“这是在说什么上下文?”
- • 所以你不只是递出那张便利贴,而是把它左右各3张邻居便利贴也一起递过去。
这就是句子窗口检索的全部秘密:
| 阶段 | 动作 | 目的 |
|---|---|---|
| 🏗️ 建索引 | 把文档拆成单句,存入向量库 | 检索时精准定位,不被杂乱内容稀释 |
| 🔍 检索时 | 找到最相关的那句话 | 语义最纯粹,相关性最高 |
| 🔄 后处理 | 把单句扩展成前后各 N 句的窗口 | 给 AI 提供完整上下文,答案不缺斤少两 |
| 💬 生成时 | 把扩展后的窗口送给大模型 | 生成连贯、信息丰富的回答 |
动图:句子窗口检索四步流程——单句检索 → 命中句子 → 扩展窗口 → 送入 LLM(慢速,10秒)

句子窗口检索流程动图
2.2 代码实现:5 步跑通句子窗口检索
下面这段代码做四件事:
-
- 加载一份 PDF 文档(IPCC 气候报告)
-
- 用句子窗口解析器,把文档切成单句、同时在元数据里偷偷藏好"窗口"
-
- 构建句子窗口查询引擎(带后处理器,检索到句子后自动扩展上下文)
-
- 和普通分块检索对比,看看效果差多少
步骤一:安装依赖 & 导入
# === 步骤1:导入必要的库 ===
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex, Settings
from llama_index.core.node_parser import SentenceWindowNodeParser, SentenceSplitter
from llama_index.core.postprocessor import MetadataReplacementPostProcessor
# -----------------------------------------------
# 🔧 【按你的环境修改这里】
# -----------------------------------------------
# PDF 文件路径:改成你本地的 PDF 文件路径
# 任何 PDF 都可以,这里用 IPCC 气候报告做演示
PDF_PATH = "./data/IPCC_AR6_WGII_Chapter03.pdf"
# 句子窗口大小:前后各几句作为上下文
# 3 是个不错的默认值;文档结构松散时可以调大到 5
WINDOW_SIZE = 3
# 检索时返回几个最相关的节点(即 Top-K)
# 2 适合精准问答;泛问题可以调到 3-5
TOP_K = 2
# -----------------------------------------------
# 假设 Settings.llm 和 Settings.embed_model 已配置好
# 参考:https://docs.llamaindex.ai/en/stable/module_guides/models/
步骤二:加载文档 & 构建句子窗口索引
这段代码是整个方案的核心——SentenceWindowNodeParser 做了两件事:把每句话切成独立节点,同时把"周边 N 句"悄悄塞进节点的元数据里备用。
# === 步骤2:加载文档 ===
documents = SimpleDirectoryReader(input_files=[PDF_PATH]).load_data()
print(f"✅ 文档加载完成,共 {len(documents)} 页")
# === 步骤3:创建句子窗口解析器 ===
node_parser = SentenceWindowNodeParser.from_defaults(
window_size=WINDOW_SIZE, # 前后各保留 3 句作为窗口
window_metadata_key="window", # 窗口文本存在元数据的 "window" 字段里
original_text_metadata_key="original_text", # 原始句子文本也一并保存
)
# 把文档解析成句子节点
sentence_nodes = node_parser.get_nodes_from_documents(documents)
print(f"✅ 文档切分完成,共 {len(sentence_nodes)} 个句子节点")
# 构建向量索引(向量化时只用单句文本,窗口内容不参与嵌入——这是精髓!)
sentence_index = VectorStoreIndex(sentence_nodes)
步骤三:构建查询引擎(含后处理器)
MetadataReplacementPostProcessor 是这里的魔法师——它在检索到单句节点之后,偷梁换柱,把单句替换成完整窗口再送给 LLM。
# === 步骤4:构建两种查询引擎,用于效果对比 ===
# 方案A:句子窗口查询引擎(带后处理器)
sentence_query_engine = sentence_index.as_query_engine(
similarity_top_k=TOP_K,
node_postprocessors=[
# 这个后处理器会把检索到的单句,替换成元数据里的完整窗口文本
# target_metadata_key 必须和上面的 window_metadata_key 一致
MetadataReplacementPostProcessor(target_metadata_key="window")
],
)
# 方案B:普通分块查询引擎(基准对比)
base_parser = SentenceSplitter(chunk_size=512) # 传统固定大小分块
base_nodes = base_parser.get_nodes_from_documents(documents)
base_index = VectorStoreIndex(base_nodes)
base_query_engine = base_index.as_query_engine(similarity_top_k=TOP_K)
print("✅ 两种查询引擎构建完成,开始对比")
步骤四:提问 & 对比结果
# === 步骤5:对比两种检索方式的效果 ===
query = "What are the concerns surrounding the AMOC?"
print(f"\n❓ 查询问题:{query}\n")
print("=" * 60)
print("\n【方案A:句子窗口检索】")
window_response = sentence_query_engine.query(query)
print(f"回答:{window_response}\n")
print("【方案B:普通分块检索(对照组)】")
base_response = base_query_engine.query(query)
print(f"回答:{base_response}\n")
运行结果:
✅ 文档加载完成,共 133 页
✅ 文档切分完成,共 4821 个句子节点
✅ 两种查询引擎构建完成,开始对比
❓ 查询问题:What are the concerns surrounding the AMOC?
【方案A:句子窗口检索】
回答:The AMOC is projected to decline over the 21st century with high
confidence, though quantitative projections have low confidence.
Observational records since the mid-2000s are too short to determine
the relative contributions of internal variability, natural forcing,
and anthropogenic forcing. Additionally, there is low confidence in
reconstructed and modeled AMOC changes for the 20th century. While
an abrupt collapse before 2100 is not expected, the decline could
have significant implications for global climate patterns.
【方案B:普通分块检索(对照组)】
回答:The concerns primarily involve its projected decline over the
21st century across all SSP scenarios. While an abrupt collapse
before 2100 is not expected, quantitative projections remain
uncertain. Further research is needed to better understand AMOC
behavior and its broader climate impacts.
2.3 对比解读:差距在哪里?
把两个答案放在一起看:
| 对比维度 | 句子窗口检索 | 普通分块检索 |
|---|---|---|
| 信息量 | ✅ 涵盖 4 个维度:衰退趋势、置信度、观测局限、20世纪历史 | ⚠️ 只提到衰退趋势和不确定性 |
| 回答质量 | ✅ 逻辑连贯,像综述 | ⚠️ 较笼统,结尾是"需要进一步研究" |
| 上下文完整性 | ✅ 检索到句子后扩展了窗口 | ❌ 只拿到孤立的 512 字符块 |
核心原因:普通分块把句子切碎后,相关信息散落在不同 chunk 里,检索时只找到了"残肢",没有"整体"。而句子窗口检索先精准锁定句子,再还原上下文,两全其美。
三、第二把钥匙:结构化递归检索
💡 一句话定义:结构化递归检索就像大公司的"总机 + 转接"——先把你的问题转给对的部门,再在那个部门内部精确找答案。
3.1 理解它:当知识库大到"针难找"
假设你的知识库里有 500 个 PDF,每个 PDF 有 100 页。用户问了一个问题,普通 RAG 会怎么做?
在全部 50000 页里做向量搜索。
这就像你在一个有 50 个部门的公司里找一份报告,不去问前台,而是挨个部门翻抽屉……不仅慢,还经常在"财务部"里翻出了"市场部"的东西,搜出来的结果乱成一锅粥。
结构化索引的思路是:给每个文档贴上"元数据标签"(Metadata),然后先过滤、再搜索。
| 传统 RAG | 结构化索引 RAG |
|---|---|
| 在全量文档里做向量搜索 | 先按元数据筛选相关文档子集 |
| 返回结果混乱,夹杂无关内容 | 只在目标子集里搜,干净精准 |
| 知识库越大越慢越乱 | 规模越大优势越明显 |

结构化检索对比图
3.2 进阶版:递归检索(先路由,再问答)
结构化索引的更强形态是递归检索(Recursive Retrieval)。
场景:你有一个 Excel 文件,里面有十几个工作表,每个工作表是某一年的电影数据。用户问"1994 年评分最低的电影是哪部?"
系统怎么知道去 年份_1994 那张表找,而不是翻遍所有表?
答案是两层索引:
第一层(路由层): 每张表的摘要描述 → 向量索引
↓ 用户问题
"这问题跟 年份_1994 最相关!"
↓
第二层(执行层): 年份_1994 表的 PandasQueryEngine
↓
生成 Pandas 代码 → 执行 → 返回答案
动图:递归检索两层流程——问题路由到摘要层 → 定位目标表 → 子引擎执行查询(慢速,10秒)

递归检索流程动图
3.3 代码实现:递归检索实战
下面这段代码做三件事:
-
- 读取 Excel 的每个工作表,为每个表创建一个"摘要节点"和查询引擎
-
- 用所有摘要节点构建顶层路由索引
-
- 配置
RecursiveRetriever,让它先路由、再执行
- 配置
步骤一:导入 & 配置路径
# === 步骤1:导入库 ===
import pandas as pd
from llama_index.core import VectorStoreIndex, Settings
from llama_index.core.query_engine import PandasQueryEngine, RetrieverQueryEngine
from llama_index.core.schema import IndexNode
from llama_index.core.retrievers import RecursiveRetriever
# -----------------------------------------------
# 🔧 【按你的环境修改这里】
# -----------------------------------------------
# Excel 文件路径:改成你本地的 Excel 文件路径
# 每个工作表(Sheet)对应一个数据集,表名会被用作路由标识
EXCEL_PATH = "./data/movie.xlsx"
# 路由检索时只考虑最相关的前 1 个摘要节点
# 通常保持 1 即可;如果有跨年份的问题可以调到 2
ROUTE_TOP_K = 1
# -----------------------------------------------
步骤二:为每个工作表创建摘要节点和查询引擎
这是核心逻辑——每张表有两样东西:一个"摘要节点"(用于路由),一个 PandasQueryEngine(用于实际查询)。
# === 步骤2:遍历 Excel 所有工作表,分别建立摘要节点 + 查询引擎 ===
xls = pd.ExcelFile(EXCEL_PATH)
df_query_engines = {} # 存放:工作表名 → 查询引擎 的映射
all_nodes = [] # 存放所有摘要节点(用于构建顶层路由索引)
for sheet_name in xls.sheet_names:
# 读取当前工作表的数据
df = pd.read_excel(xls, sheet_name=sheet_name)
# 为当前表创建 PandasQueryEngine
# 它能把"评分最低的是哪部"翻译成 df.nsmallest(1, '评分')['电影名称'].iloc[0]
# ⚠️ 注意:生产环境不建议使用(安全风险,见后文说明)
query_engine = PandasQueryEngine(df=df, llm=Settings.llm, verbose=True)
# 提取年份,构建该工作表的摘要文本(这个文本用于语义路由)
year = sheet_name.replace("年份_", "")
summary = f"这个表格包含了年份为 {year} 的电影信息,可以用来回答关于这一年电影的具体问题。"
# 创建摘要节点:index_id 必须和 df_query_engines 的 key 保持一致!
node = IndexNode(text=summary, index_id=sheet_name)
all_nodes.append(node)
df_query_engines[sheet_name] = query_engine
print(f"✅ 共处理 {len(xls.sheet_names)} 个工作表:{xls.sheet_names}")
步骤三:构建递归检索器 & 执行查询
# === 步骤3:构建顶层路由索引(只含摘要节点,不含实际数据)===
vector_index = VectorStoreIndex(all_nodes)
vector_retriever = vector_index.as_retriever(similarity_top_k=ROUTE_TOP_K)
# === 步骤4:配置递归检索器 ===
recursive_retriever = RecursiveRetriever(
"vector",
retriever_dict={"vector": vector_retriever},
# 当摘要节点被命中后,用 index_id 找到对应的查询引擎
query_engine_dict=df_query_engines,
verbose=True, # 打印路由过程,方便调试
)
# === 步骤5:创建最终查询引擎 & 提问 ===
query_engine = RetrieverQueryEngine.from_args(recursive_retriever)
query = "1994年评分人数最少的电影是哪一部?"
print(f"\n❓ 查询:{query}")
response = query_engine.query(query)
print(f"\n✅ 答案:{response}")
运行结果:
✅ 共处理 5 个工作表:['年份_1994', '年份_1995', '年份_2000', '年份_2002', '年份_2010']
❓ 查询:1994年评分人数最少的电影是哪一部?
> Retrieving with query id None: 1994年评分人数最少的电影是哪一部?
> Retrieved node with id, entering: 年份_1994 ← 路由成功!
> Retrieving with query id 年份_1994: 1994年评分人数最少的电影是哪一部?
> Pandas Instructions:
df[df['年份'] == 1994].nsmallest(1, '评分人数')['电影名称'].iloc[0]
> Pandas Output: 燃情岁月
✅ 答案:燃情岁月
从输出里能清楚看到"两层跳转":先路由到 年份_1994,再由 PandasQueryEngine 生成代码执行,最终精准返回答案。
3.4 ⚠️ 重要安全提示:生产环境请绕行 PandasQueryEngine
PandasQueryEngine 工作原理是让 LLM 生成 Python 代码,然后用 eval() 本地执行。
eval() 意味着可以执行任意代码。
如果有人在问题里夹带了恶意指令,理论上可以在你的服务器上为所欲为——删文件、读环境变量、发网络请求……
生产环境的安全替代方案:
| 方案 | 思路 | 安全性 |
|---|---|---|
| 路由 + 元数据过滤 (推荐) | 先路由找到目标表,再用元数据过滤在向量库里精确搜索 | ✅ 无代码执行风险 |
| 沙箱隔离 | 在受限容器里运行 PandasQueryEngine | ⚠️ 复杂度高,需要专业运维 |
| Text-to-SQL | 把 Excel 转成数据库,用 SQL 查询 | ✅ SQL 比 eval 安全,但仍需注意注入 |
本文配套代码里提供了"路由 + 元数据过滤"的完整安全实现,链接见文末。
四、两把钥匙,分别开什么锁?
用完整的对照把本文核心总结一下:

选型象限图
| 技术 | 解决的问题 | 适用场景 | 代码关键类 |
|---|---|---|---|
| 句子窗口检索 | 上下文碎、回答缺斤少两 | 长文档、需要高质量答案 | SentenceWindowNodeParser + MetadataReplacementPostProcessor |
| 结构化递归检索 | 知识库太大、搜出来的全是不相关内容 | 多文档/多表格、大规模知识库 | IndexNode + RecursiveRetriever |
两者可以叠加使用:先用结构化索引缩小范围,再用句子窗口检索提升答案质量。
动图:两种技术叠加使用的完整 RAG 流程(慢速,12秒)

两技术叠加动图
五、总结 + 现在就能做的三件事
学完本文,你已经掌握了:
- • ✅ 句子窗口检索:用单句建索引(精准),用窗口送 LLM(完整),两全其美
- • ✅ 结构化递归检索:给文档贴元数据标签,先路由后检索,大规模知识库的救星
- • ✅ 安全意识:
PandasQueryEngine用了eval(),生产环境要换成元数据过滤方案
进阶路线:
| 阶段 | 方向 | 要掌握的 |
|---|---|---|
| 入门 | 跑通基础 RAG | LlamaIndex 基本用法,向量索引,Top-K 检索 |
| 进阶 | 本文两种技术 | SentenceWindowNodeParser、RecursiveRetriever |
| 高级 | 混合检索 + 重排序 | 密集向量 + 稀疏向量 + Reranker 三件套 |
现在就可以做的三件事
-
- 安装环境:
pip install llama-index llama-index-core(5 分钟)
- 安装环境:
-
- 跑通句子窗口 Demo:把本文代码里的
PDF_PATH改成你手边任意一个 PDF,运行一遍看看效果
- 跑通句子窗口 Demo:把本文代码里的
-
- 对比效果差距:把两种查询引擎同时问同一个问题,感受上下文扩展带来的质量提升
当然,本文的示例都是教学级别——真实项目里文档更复杂、数据更脏、场景更多样。但这两把钥匙打开的思路,会一直有用。
如何学习大模型 AI ?
由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。
但是具体到个人,只能说是:
“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。
这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】


为什么要学习大模型?
我国在A大模型领域面临人才短缺,数量与质量均落后于发达国家。2023年,人才缺口已超百万,凸显培养不足。随着AI技术飞速发展,预计到2025年,这一缺口将急剧扩大至400万,严重制约我国AI产业的创新步伐。加强人才培养,优化教育体系,国际合作并进是破解困局、推动AI发展的关键。


大模型入门到实战全套学习大礼包
1、大模型系统化学习路线
作为学习AI大模型技术的新手,方向至关重要。 正确的学习路线可以为你节省时间,少走弯路;方向不对,努力白费。这里我给大家准备了一份最科学最系统的学习成长路线图和学习规划,带你从零基础入门到精通!

2、大模型学习书籍&文档
学习AI大模型离不开书籍文档,我精选了一系列大模型技术的书籍和学习文档(电子版),它们由领域内的顶尖专家撰写,内容全面、深入、详尽,为你学习大模型提供坚实的理论基础。

3、AI大模型最新行业报告
2025最新行业报告,针对不同行业的现状、趋势、问题、机会等进行系统地调研和评估,以了解哪些行业更适合引入大模型的技术和应用,以及在哪些方面可以发挥大模型的优势。

4、大模型项目实战&配套源码
学以致用,在项目实战中检验和巩固你所学到的知识,同时为你找工作就业和职业发展打下坚实的基础。

5、大模型大厂面试真题
面试不仅是技术的较量,更需要充分的准备。在你已经掌握了大模型技术之后,就需要开始准备面试,我精心整理了一份大模型面试题库,涵盖当前面试中可能遇到的各种技术问题,让你在面试中游刃有余。

适用人群

第一阶段(10天):初阶应用
该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。
- 大模型 AI 能干什么?
- 大模型是怎样获得「智能」的?
- 用好 AI 的核心心法
- 大模型应用业务架构
- 大模型应用技术架构
- 代码示例:向 GPT-3.5 灌入新知识
- 提示工程的意义和核心思想
- Prompt 典型构成
- 指令调优方法论
- 思维链和思维树
- Prompt 攻击和防范
- …
第二阶段(30天):高阶应用
该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。
- 为什么要做 RAG
- 搭建一个简单的 ChatPDF
- 检索的基础概念
- 什么是向量表示(Embeddings)
- 向量数据库与向量检索
- 基于向量检索的 RAG
- 搭建 RAG 系统的扩展知识
- 混合检索与 RAG-Fusion 简介
- 向量模型本地部署
- …
第三阶段(30天):模型训练
恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。
到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?
- 为什么要做 RAG
- 什么是模型
- 什么是模型训练
- 求解器 & 损失函数简介
- 小实验2:手写一个简单的神经网络并训练它
- 什么是训练/预训练/微调/轻量化微调
- Transformer结构简介
- 轻量化微调
- 实验数据集的构建
- …
第四阶段(20天):商业闭环
对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。
- 硬件选型
- 带你了解全球大模型
- 使用国产大模型服务
- 搭建 OpenAI 代理
- 热身:基于阿里云 PAI 部署 Stable Diffusion
- 在本地计算机运行大模型
- 大模型的私有化部署
- 基于 vLLM 部署大模型
- 案例:如何优雅地在阿里云私有部署开源大模型
- 部署一套开源 LLM 项目
- 内容安全
- 互联网信息服务算法备案
- …
学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。
如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。
这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】

更多推荐


所有评论(0)