RAG 实战进阶:彻底解决 PDF 复杂表格解析难题 (LlamaParse + 递归检索)
LangChain就像积木,适合搭建各种花哨的 Agent 流程。LlamaIndex就像显微镜,适合对数据进行深度处理和精准检索。用 LlamaIndex 处理数据 (RAG),用 LangChain/AutoGen 编排逻辑 (Agent)。这就是传说中的架构!下期预告搞定了数据,但一个 AI 还是干不完活怎么办?下周我们将进入Multi-Agent (多智能体)的世界,教你用AutoGen组

关键词:LlamaIndex, RAG, PDF 解析, 复杂表格, 数据治理
大家好,我是飞哥!👋
在之前的课程里,我们用 LangChain 轻松搭建了一个“个人知识库”。
但是,当你拿着这个 Demo 去找老板汇报时,可能会遇到这样一个尴尬的场景:
老板:“飞啊,这有个几十页的《2023春季招聘岗位表》PDF,里面全是密密麻麻的表格。你帮我查查,有哪些岗位是招‘会计’的?而且薪资要在 5000 以上?”
AI:“根据文档…(胡言乱语)…抱歉,我看不懂表格结构。”
为什么? 🤯
因为传统的 PDF 解析器(如 PyPDF)遇到复杂表格时,会把结构打散成一堆乱码。AI 看不懂乱码,自然就幻觉了。
这就是“玩具级 RAG”和“企业级 RAG”的分水岭。
今天,我们要请出 AI 数据领域的特种兵 —— LlamaIndex。
1. 为什么你需要 LlamaIndex?(Why)
如果把 LangChain 比作一个“全能指挥官”(擅长调度 Agent、调用工具、不仅限于 RAG);
那么 LlamaIndex 就是一个“顶级图书管理员”(专注于数据索引、数据检索和数据清洗)。
在企业级实战中,数据质量决定了 AI 的上限。
LlamaIndex 的核心理念是:Data-Centric AI (以数据为中心的 AI)。
它解决了三大痛点:
- 烂数据解析:完美处理 PDF 表格、PPT、Excel。
- 上下文丢失:通过“递归检索”解决切片太小看不懂的问题。
- 混合查询:既能查非结构化文本,又能查结构化 SQL。
2. 核心黑科技一:LlamaParse (表格杀手) 🔪
这是 LlamaIndex 官方推出的解析神器。它不是简单的提取文本,而是利用大模型视觉能力,将 PDF 重构为 Markdown 格式。
❌ 传统方式 (PyPDF)
一个表格会被解析成:单位名称 岗位名称 薪资 石家庄市安居建设管理集团有限公司 会计岗 3500-6000 2人 本科
(完全丢失了行和列的关系,AI 根本分不清哪个工资对应哪个岗位)
✅ LlamaIndex 方式 (LlamaParse)
它会解析成标准的 Markdown 表格(这正是 test_parsed.md 里的样子):
| 单位名称 | 岗位名称 | 需求人数 | 薪资待遇 |
| :--- | :--- | :--- | :--- |
| 石家庄市安居建设管理集团有限公司 | 会计岗 | 2 | 3500-6000 |
| 石家庄市安居建设管理集团有限公司 | 结构设计师 | 1 | 3500-6000 |
(AI 一眼就能看出 会计岗 的薪资是 3500-6000!)
💻 代码实战
# 安装依赖
pip install llama-parse nest_asyncio python-dotenv
import os
import nest_asyncio
from llama_parse import LlamaParse
from dotenv import load_dotenv
# 解决异步循环冲突问题
nest_asyncio.apply()
# 加载环境变量
load_dotenv()
# 1. 初始化解析器 (指定输出 markdown)
parser = LlamaParse(
result_type="markdown",
verbose=True,
language="ch_sim", # 针对简体中文优化
system_prompt="""
你是一个专业的文档解析助手。你的任务是将招聘文档PDF转换为精准的Markdown格式。
【核心要求】
1. 必须保留文档中的所有表格结构。
2. 对于表格单元格内的多行文本,严格禁止使用换行符(\n),请务必使用 '<br>' 标签代替。
"""
)
# 2. 解析复杂 PDF
# 这里我们使用 "test.pdf" (实际是石家庄国企招聘表)
# 确保文件在同级目录下
pdf_path = "test.pdf"
if os.path.exists(pdf_path):
documents = parser.load_data(pdf_path)
# 3. 保存结果
output_file = "test_parsed.md"
with open(output_file, "w", encoding="utf-8") as f:
# 飞哥提示:一定要遍历所有文档页,否则只保存第一页!
for doc in documents:
f.write(doc.text + "\n\n")
print(f"✅ 解析成功!共解析 {len(documents)} 页,结果已保存至: {output_file}")
print("\n🔍 内容预览 (前 500 字符):\n")
print(documents[0].text[:500])
else:
print(f"❌ 未找到文件: {pdf_path}")
3. 核心黑科技二:递归检索 (Recursive Retrieval) 🔍
这也是一个经典痛点:
- 切片太小:检索精准,但 AI 看了只有一句话(例如“3500-6000”),不知道是哪个公司的哪个岗位,回答胡言乱语。
- 切片太大:包含了太多无关文字,噪音太大,导致检索不到关键信息。
LlamaIndex 的递归检索 (Recursive Retrieval) 完美解决了这个问题。用一个通俗的比喻就是:
“检索时看缩略图,回答时看高清原图。”
🧩 原理图解
我们不直接检索复杂的岗位表格原文,而是先给表格生成一个“摘要”。
[ 用户的提问 ] --> 匹配 --> [ 岗位表摘要 (缩略图) ]
|
(自动跳转)
|
v
[ 岗位表原文 (高清大图) ] --> 喂给 AI
- IndexNode (缩略图):包含表格的总结(如“这是安居集团的招聘岗位列表,包含会计、工程师等岗位”)。体积小,语义清晰,极易被检索命中。
- BaseNode (原图):包含完整的 Markdown 岗位表格。体积大,细节全,专门用来给 AI 生成答案。
💻 代码实战
from llama_index.core.node_parser import MarkdownElementNodeParser
from llama_index.core import VectorStoreIndex
# 1. 初始化解析器 (专治 Markdown 复杂结构)
# 它会自动把文档拆成“摘要节点”和“原始节点”
node_parser = MarkdownElementNodeParser(llm=llm, num_workers=8)
# 2. 解析文档,获取节点
# nodes 列表中混合了“摘要节点”和“原始节点”
nodes = node_parser.get_nodes_from_documents(documents)
# 3. 构建递归索引
# VectorStoreIndex 会自动识别节点关系,建立“摘要->原文”的映射
recursive_index = VectorStoreIndex(nodes)
# 4. 创建查询引擎
# 当检索命中“摘要”时,它会自动提取对应的“原文”给 LLM
query_engine = recursive_index.as_query_engine(similarity_top_k=5)
3.5. 进阶彩蛋:Router 路由模式 (RouterQueryEngine) 🚦
企业里不仅仅只有一份文档。如果有财务报表、法律合同、技术文档,混合在一起查容易乱套。
Router (路由) 就像一个交通指挥官。它会先分析用户的问题,然后决定去查哪一个知识库。
- 用户问:“会计岗招几个人?” -> 路由到 [招聘岗位表]
- 用户问:“面试流程是什么?” -> 路由到 [公司制度文档]
这才是真正的“智能”知识库!(在后续 Multi-Agent 课程中我们会深入讲解)
4. 实战项目:企业级“智能招聘助手” 👨💼
我们要利用上面两个技术,做一个能精准筛选岗位的求职/HR助手。
🛠️ 核心逻辑
- 数据层:用
LlamaParse解析招聘简章 PDF (test.pdf),提取出所有岗位、薪资和要求表格。 - 索引层:构建
VectorStoreIndex,并开启递归检索。 - 应用层:用户问“有哪些岗位招会计?”,系统检索到对应的表格行,并精准回答。
🚀 核心代码片段
from llama_index.core import VectorStoreIndex, Settings, SimpleDirectoryReader
from llama_index.llms.openai_like import OpenAILike
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from dotenv import load_dotenv
import nest_asyncio
import os
# 解决异步循环冲突问题
nest_asyncio.apply()
load_dotenv()
# 1. 设定模型 (使用 DeepSeek 兼容接口)
# 使用 OpenAILike 避免模型名称校验问题
Settings.llm = OpenAILike(
model="deepseek-chat",
api_key=os.getenv("DEEPSEEK_API_KEY"),
api_base="https://api.deepseek.com",
is_chat_model=True
)
# 2. 设定 Embedding 模型 (使用本地免费模型,无需 OpenAI Key)
# 这一步非常重要!否则 LlamaIndex 默认会找 OpenAI Key
Settings.embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-small-zh-v1.5")
# 3. 读取数据 (这里读取 LlamaParse 解析后的 Markdown)
reader = SimpleDirectoryReader(input_files=["test_parsed.md"])
docs = reader.load_data()
# 4. 构建索引 (应用黑科技:递归检索)
# 引入 MarkdownElementNodeParser 把表格拆解为“摘要”和“原文”
from llama_index.core.node_parser import MarkdownElementNodeParser
node_parser = MarkdownElementNodeParser(llm=Settings.llm, num_workers=8)
nodes = node_parser.get_nodes_from_documents(docs)
# 构建递归索引
index = VectorStoreIndex(nodes)
# 5. 创建聊天引擎并提问
chat_engine = index.as_chat_engine(chat_mode="context", similarity_top_k=5)
response = chat_engine.chat("帮我找找安居集团有哪些岗位招会计?薪资是多少?")
print(response)
5. 飞哥总结 📝
- LangChain 就像积木,适合搭建各种花哨的 Agent 流程。
- LlamaIndex 就像显微镜,适合对数据进行深度处理和精准检索。
在未来的 AI 智能体开发中,我的建议是:
用 LlamaIndex 处理数据 (RAG),用 LangChain/AutoGen 编排逻辑 (Agent)。
这就是传说中的 “Llama-Chain” 架构!
下期预告:
搞定了数据,但一个 AI 还是干不完活怎么办?
下周我们将进入 Multi-Agent (多智能体) 的世界,教你用 AutoGen 组建一个“AI 专家团队”!
创作不易,记得👇关注飞哥👇 ,点赞、收藏哦~~,下篇见👋,项目源码看评论区👇
更多推荐


所有评论(0)