阶段 3:RAG 基础与 PDF 知识库问答项目学习笔记
阶段 3:RAG 基础与 PDF 知识库问答项目学习笔记
学习目标:理解 RAG 的完整链路,掌握文件检索、向量化、文档切块、向量数据库、来源引用等核心概念,并能完成一个可演示、可写进简历、可在面试中讲清楚的 PDF RAG 项目。
1. 阶段 3 学习目标
阶段 3 的重点是从“会调用大模型”升级到“能构建一个带外部知识库的问答系统”。
你需要掌握的不只是 LangChain 或 Chroma 的调用方法,更重要的是理解:
- RAG 为什么存在;
- RAG 解决了大模型的什么问题;
- 文档为什么要切块;
- embedding 为什么可以用于语义搜索;
- vector store 存储了什么;
- top-k 怎么影响召回效果;
- metadata 为什么对来源追踪很重要;
- 一个 PDF RAG 项目怎么从 0 到 1 落地;
- 面试时如何解释你的设计取舍。
本阶段最终要完成的项目:
基于 RAG 的论文 / PDF 知识库问答系统。
最低功能要求:
- 支持多个 PDF 文档;
- 自动解析 PDF 内容;
- 自动切块;
- 自动生成 embedding;
- 存入向量数据库;
- 用户提问时先检索相关片段;
- 大模型基于检索结果生成答案;
- 返回答案时附带来源文件、页码和原文片段。
2. RAG 是什么
2.1 RAG 的定义
RAG 全称是:
Retrieval-Augmented Generation
检索增强生成
可以拆成两个部分:
| 部分 | 含义 |
|---|---|
| Retrieval | 检索,从外部知识库中找到和问题相关的内容 |
| Augmented Generation | 增强生成,把检索到的内容作为上下文交给大模型生成答案 |
普通大模型问答流程是:
用户问题 → 大模型 → 答案
RAG 问答流程是:
用户问题 → 检索知识库 → 得到相关文档片段 → 拼接上下文 → 大模型 → 答案 + 来源
RAG 的核心思想是:
让大模型在回答前先查资料,再基于资料回答。
2.2 RAG 和普通 Prompt 的区别
普通 Prompt:
请解释这篇论文的核心方法。
如果你没有把论文内容给模型,模型只能依赖它已有的知识和推测。
RAG Prompt:
请根据下面检索到的论文片段回答问题。
如果资料中没有答案,请回答无法确定。
同时把检索到的内容提供给模型。
区别在于:
| 对比项 | 普通问答 | RAG 问答 |
|---|---|---|
| 知识来源 | 模型参数中的知识 | 外部知识库 + 模型能力 |
| 是否可更新 | 较难更新 | 可以更新文档库 |
| 是否能查私有文档 | 不能 | 可以 |
| 是否能返回来源 | 通常不能 | 可以 |
| 幻觉风险 | 较高 | 相对较低 |
| 适合场景 | 通用问答 | 企业知识库、论文问答、合同问答、客服知识库 |
3. RAG 为什么能减少幻觉
3.1 大模型幻觉是什么
大模型幻觉指的是:
模型生成了看起来合理,但实际上不真实、不准确或没有依据的内容。
例如你问:
这篇 PDF 第三章提出了什么算法?
如果模型没有看到 PDF,它可能会根据论文标题或常见论文结构编造一个答案。
这不是模型“故意撒谎”,而是因为大模型本质上是在根据上下文预测下一个 token,它并不会天然知道自己是否掌握事实依据。
3.2 幻觉产生的常见原因
常见原因包括:
-
没有足够上下文
用户问题依赖外部资料,但模型没有看到资料。 -
模型参数知识过时
大模型训练数据有时间边界,可能不知道最新内容。 -
问题过于具体
比如某个内部文档、某篇论文、某个公司制度。 -
模型过度泛化
模型会根据相似场景生成“看起来像”的答案。 -
Prompt 限制不够
没有要求模型“只根据资料回答”,模型就可能补充无依据内容。
3.3 RAG 减少幻觉的方式
RAG 通过三个机制降低幻觉:
1. 外部知识注入
先从知识库中检索真实资料,再让模型基于资料回答。
问题 → 找资料 → 基于资料回答
2. 限制模型回答范围
通过 prompt 约束模型:
请只根据参考资料回答。
如果参考资料中没有答案,请回答“根据当前资料无法确定”。
3. 返回来源,方便验证
回答中附带:
来源:paper1.pdf,第 5 页
用户可以检查答案是否真的来自原文。
3.4 RAG 不能完全消除幻觉
需要注意:
RAG 能降低幻觉,但不能完全消除幻觉。
原因是 RAG 仍然可能在这些地方出错:
| 环节 | 可能问题 |
|---|---|
| PDF 解析 | 表格、公式、图片解析错误 |
| 文档切块 | 关键信息被切断 |
| embedding | 语义表示不准确 |
| 检索 | 找到的 chunk 不相关 |
| top-k | 召回过少或过多 |
| prompt | 没有限制模型只能基于资料回答 |
| 生成 | 模型过度总结、补充不存在的信息 |
所以 RAG 系统的质量取决于整条链路,而不是只看大模型本身。
4. RAG 的完整工作流程
RAG 通常分为两个阶段:
- 离线入库阶段;
- 在线问答阶段。
4.1 离线入库阶段
离线入库就是提前把 PDF、Word、网页等文档处理好,存入知识库。
流程:
对应步骤说明:
| 步骤 | 作用 |
|---|---|
| 文档解析 | 从 PDF 中提取文本 |
| 文档清洗 | 去除空行、页眉页脚、乱码 |
| 文档切块 | 把长文本切成多个 chunk |
| 向量化 | 把每个 chunk 转成 embedding |
| 入库 | 把 chunk、embedding、metadata 存入向量数据库 |
4.2 在线问答阶段
在线问答就是用户输入问题后,系统实时检索并生成答案。
流程:
简化理解:
用户问问题
系统先从知识库查资料
把查到的资料给大模型
大模型基于资料回答
最后返回答案和引用来源
5. RAG 的核心模块
一个完整 RAG 系统可以拆成这些核心模块:
| 模块 | 作用 |
|---|---|
| Loader | 加载 PDF、网页、Markdown、Word 等文档 |
| Parser | 解析文档内容 |
| Cleaner | 清洗文本 |
| Splitter | 文档切块 |
| Embedding Model | 生成文本向量 |
| Vector Store | 存储和检索向量 |
| Retriever | 根据问题召回相关 chunk |
| Prompt Builder | 构造上下文和提示词 |
| LLM | 基于上下文生成答案 |
| Source Tracker | 返回来源文件、页码和片段 |
| API Layer | 提供上传、检索、问答接口 |
面试时可以这样总结:
RAG 系统本质上是一个“检索系统 + 生成系统”的组合。检索系统负责找到相关知识,生成系统负责把知识组织成自然语言答案。
6. PDF 文档处理流程
PDF RAG 项目的第一步是处理 PDF。
6.1 PDF 解析的难点
PDF 和普通文本文件不一样,它不是天然结构化的。常见问题:
- 文字顺序可能错乱;
- 页眉页脚会干扰内容;
- 表格解析困难;
- 公式可能无法提取;
- 图片中的文字需要 OCR;
- 双栏论文可能解析顺序错误;
- 页码、脚注、参考文献会影响检索。
所以 PDF RAG 的质量很大程度取决于 PDF 解析质量。
6.2 PDF 解析后需要保留页码
每一页解析出来后,应该保留 metadata:
{
"source": "paper1.pdf",
"page": 3
}
因为后面需要引用来源:
来源:paper1.pdf,第 3 页
如果解析阶段没有保存页码,后面很难补救。
6.3 文档清洗
PDF 解析后通常需要清洗:
- 删除多余空行;
- 删除重复页眉页脚;
- 合并断行;
- 去掉无意义字符;
- 保留标题和段落结构;
- 对参考文献部分可以选择保留或降低权重。
示例:
原始解析结果:
Retrieval-Augmented
Generation is a method
Page 3
for improving language models.
清洗后:
Retrieval-Augmented Generation is a method for improving language models.
7. Chunk 文档切块策略
7.1 什么是 chunk
chunk 是文档被切分后的一个小文本片段。
例如一篇论文有 30 页,系统不会把整篇论文一次性放入 prompt,而是拆成多个 chunk:
chunk 1:摘要部分
chunk 2:引言部分
chunk 3:方法部分第一段
chunk 4:方法部分第二段
...
每个 chunk 会单独向量化,并作为检索的基本单位。
7.2 为什么必须切块
原因主要有四个:
1. 模型上下文有限
长文档可能有几万字,无法每次全部塞给模型。
2. 检索需要细粒度
如果整篇论文作为一个 chunk,用户问一个具体问题时,召回结果太粗。
3. 降低成本
只把相关 chunk 放入 prompt,可以减少 token 成本。
4. 提高答案准确性
相关片段越聚焦,模型越不容易被无关内容干扰。
7.3 chunk size 太大有什么问题
chunk 太大时:
- 一个 chunk 中包含多个主题;
- 检索结果不够精准;
- prompt token 消耗增加;
- 模型容易被无关内容干扰;
- 来源片段不够精确。
例如一个 chunk 同时包含:
研究背景 + 方法设计 + 实验设置 + 结论
用户只问“实验设置”,但系统把其他内容也带进来,会影响回答质量。
7.4 chunk size 太小有什么问题
chunk 太小时:
- 语义不完整;
- 上下文断裂;
- 检索到的片段不够回答问题;
- 需要更大的 top-k 补充上下文;
- 模型可能误解片段含义。
例如:
chunk A:该方法包含三个模块。
chunk B:第一个模块是视觉编码器。
chunk C:第二个模块是文本编码器。
chunk D:第三个模块是跨模态注意力模块。
如果只召回 chunk A,模型不知道三个模块是什么。
7.5 overlap 重叠窗口
overlap 指相邻 chunk 之间保留一部分重复内容。
例如:
chunk 1:A B C D E
chunk 2:D E F G H
chunk 3:G H I J K
D、E 和 G、H 就是 overlap。
overlap 的作用:
- 避免重要信息被切断;
- 保持上下文连续;
- 提高召回质量;
- 减少“上一段提到的 this / it / they 指代不清”的问题。
7.6 常见切块策略
| 策略 | 说明 | 优点 | 缺点 |
|---|---|---|---|
| 固定长度切块 | 按字符或 token 数切 | 简单稳定 | 可能切断语义 |
| 段落切块 | 按自然段切 | 语义较完整 | 段落长度不稳定 |
| 标题 / 章节切块 | 按 Abstract、Method 等切 | 适合论文 | 实现复杂 |
| 递归切块 | 先按章节,再按段落,再按句子 | 效果较好 | 参数较多 |
| 语义切块 | 根据语义边界切 | 质量高 | 实现成本高 |
学习项目推荐:
第一版:固定长度切块 + overlap
第二版:递归切块
第三版:章节 / 标题感知切块
7.7 推荐参数
对于中文文档:
chunk_size = 500 ~ 1000
chunk_overlap = 100 ~ 200
对于英文论文:
chunk_size = 800 ~ 1200
chunk_overlap = 150 ~ 250
第一版可以使用:
chunk_size = 800
chunk_overlap = 150
面试解释:
我选择 chunk size 800、overlap 150,是为了在语义完整性和检索精度之间做平衡。chunk 太小会导致上下文不足,chunk 太大又会引入噪声。overlap 可以避免重要内容被切断。
8. Embedding 向量化
8.1 什么是 embedding
embedding 是把文本转成一组数字向量。
例如:
文本:RAG 是检索增强生成
向量:[0.12, -0.35, 0.78, ...]
这些数字不是随机的,而是表示文本的语义特征。
语义相近的文本,向量距离也更近。
例如:
句子 A:RAG 可以减少幻觉
句子 B:检索增强生成可以降低模型编造答案的概率
这两个句子字面不同,但语义接近,所以 embedding 后的向量也会接近。
8.2 为什么 embedding 能做语义搜索
关键词搜索依赖字面匹配:
用户问:如何降低幻觉?
文档写:减少模型编造事实。
如果没有“幻觉”这个词,关键词搜索可能找不到。
embedding 搜索看语义:
降低幻觉 ≈ 减少编造事实
因此可以召回相关内容。
8.3 embedding 在 RAG 中的作用
RAG 中有两类文本需要 embedding:
- 文档 chunk;
- 用户问题。
入库时:
chunk 文本 → embedding → 存入 vector store
查询时:
用户问题 → embedding → 和数据库中的 chunk 向量比较相似度
8.4 embedding 模型选择
不同 embedding 模型会影响检索效果。
选择时关注:
- 是否支持中文;
- 向量维度;
- 速度;
- 成本;
- 本地部署还是 API 调用;
- 对长文本的支持程度。
学习项目可以先使用 API embedding 或开源 embedding 模型。
9. Vector Store 向量数据库
9.1 Vector Store 是什么
Vector Store 是专门用于存储和检索向量的数据库。
它一般保存:
- 原始文本 chunk;
- chunk 对应的 embedding;
- metadata 元数据;
- 文档 ID。
示例:
{
"id": "paper1_page3_chunk2",
"text": "本文提出了一种基于 Transformer 的多模态融合方法...",
"embedding": [0.12, -0.35, 0.78],
"metadata": {
"source": "paper1.pdf",
"page": 3,
"section": "Method"
}
}
9.2 常见向量数据库
| 工具 | 适用场景 |
|---|---|
| Chroma | 学习项目、本地小型知识库 |
| FAISS | 高性能本地向量检索 |
| Milvus | 大规模生产级向量检索 |
| Weaviate | 支持丰富 schema 和混合检索 |
| Qdrant | 开源、性能好、工程友好 |
| Pinecone | 云服务,使用方便 |
你的学习项目推荐:
Chroma
原因:
- 本地运行简单;
- LangChain 支持好;
- 适合多 PDF 小型知识库;
- 不需要复杂部署;
- 便于演示。
10. Semantic Search 与 Keyword Search
10.1 Keyword Search 关键词搜索
关键词搜索基于字面匹配。
例如用户问:
Transformer 的注意力机制是什么?
系统会找包含:
Transformer
注意力机制
这些词的文档。
常见技术:
- 倒排索引;
- BM25;
- Elasticsearch;
- OpenSearch。
优点:
- 对专有名词、编号、公式、代码很准;
- 可解释性强;
- 查询速度快;
- 不需要 embedding。
缺点:
- 不理解同义表达;
- 用户说法和文档说法不同可能搜不到;
- 对自然语言问题不够友好。
10.2 Semantic Search 语义搜索
语义搜索基于 embedding 相似度。
它不只看字面词,而是看语义接近程度。
例如:
用户问:如何降低模型胡说?
文档写:RAG 可以减少 hallucination。
关键词不同,但语义相关,语义搜索可以召回。
优点:
- 能处理同义表达;
- 适合自然语言问题;
- 对总结、解释类问题效果好;
- 是 RAG 中最常见的检索方式。
缺点:
- 对公式、编号、精确术语可能不如关键词搜索;
- 可能召回“看起来语义相似但事实不相关”的内容;
- 依赖 embedding 模型质量。
10.3 Hybrid Search 混合检索
实际项目中,常结合:
关键词搜索 + 语义搜索
即 Hybrid Search。
适合:
- 论文问答;
- 技术文档问答;
- 法律合同问答;
- API 文档问答;
- 企业内部知识库。
升级路线:
第一版:向量检索
第二版:关键词检索 + 向量检索
第三版:混合检索 + rerank
11. Top-k、相似度和检索质量
11.1 什么是 top-k
top-k 指检索时返回最相关的前 k 个 chunk。
例如:
top_k = 5
表示返回最相关的 5 个片段。
11.2 top-k 太小的问题
如果 top-k 太小:
- 可能漏掉关键信息;
- 对综合性问题回答不完整;
- 多文档对比问题效果差。
例如用户问:
这几篇论文的方法有什么区别?
如果只返回 3 个 chunk,可能覆盖不到所有论文。
11.3 top-k 太大的问题
如果 top-k 太大:
- 无关内容变多;
- prompt 变长;
- token 成本上升;
- 模型容易被噪声干扰;
- 响应延迟增加。
11.4 top-k 推荐设置
| 问题类型 | 推荐 top-k |
|---|---|
| 事实型问题 | 3 |
| 普通解释型问题 | 5 |
| 总结型问题 | 6-8 |
| 多文档对比 | 8-12 |
| 长文档综述 | 先分阶段检索,不建议一次 top-k 过大 |
第一版项目默认:
top_k = 5
11.5 相似度分数
向量检索通常会返回相似度分数。
你可以用它判断检索结果是否可靠。
例如:
[
{"content": "...", "score": 0.88},
{"content": "...", "score": 0.82},
{"content": "...", "score": 0.41}
]
如果最高分都很低,说明知识库可能没有相关内容。
可以设置阈值:
score_threshold = 0.5
如果低于阈值:
根据当前知识库无法找到足够相关的资料。
这样可以减少模型胡编。
12. Metadata Filter 元数据过滤
12.1 metadata 是什么
metadata 是每个 chunk 附带的结构化信息。
常见字段:
{
"source": "paper1.pdf",
"page": 12,
"section": "Experiment",
"chunk_id": "paper1_p12_c03",
"created_at": "2026-04-24"
}
12.2 metadata 的作用
1. 来源追踪
回答时显示:
来源:paper1.pdf,第 12 页
2. 限定检索范围
例如只查某个文件:
filter = {"source": "paper1.pdf"}
3. 文档管理
可以删除某个 PDF 对应的所有 chunk:
delete where source = "old_paper.pdf"
4. 高级筛选
例如:
只查 Method 章节
只查 2024 年后的论文
只查某个作者的文档
12.3 面试回答
面试官问 metadata 有什么用,可以回答:
metadata 用来解决过滤和追溯问题。我在每个 chunk 入库时保存 source、page、chunk_id 等信息。检索时可以根据文件名、章节等字段过滤范围;生成答案后,也可以把对应文件名和页码返回给用户,保证答案可验证、可追溯。
13. 引用来源与可追溯性
13.1 为什么 RAG 必须返回来源
一个合格的 RAG 系统不应该只返回答案,还应该返回依据。
原因:
- 用户可以验证答案;
- 系统更可信;
- 方便调试检索效果;
- 避免模型无依据总结;
- 面试时能体现工程完整性。
13.2 推荐返回格式
{
"answer": "根据论文内容,该方法主要包括三个模块:视觉编码器、文本编码器和跨模态注意力模块。",
"sources": [
{
"file": "paper1.pdf",
"page": 5,
"content": "The proposed method consists of three components..."
},
{
"file": "paper1.pdf",
"page": 6,
"content": "The cross-modal attention module is used to..."
}
]
}
13.3 来源展示原则
好的来源展示应该包含:
- 文件名;
- 页码;
- chunk 片段;
- 相似度分数;
- 必要时包含章节名。
例如:
来源 1
文件:rag_survey.pdf
页码:第 8 页
相似度:0.86
片段:Retrieval-augmented generation combines retrieval with generation...
14. PDF RAG 项目设计
14.1 项目名称
推荐名称:
基于 RAG 的论文知识库智能问答系统
或者:
PDF Knowledge Base QA System Based on RAG
14.2 项目目标
实现一个支持多 PDF 的知识库问答系统:
- 用户上传 PDF;
- 系统解析 PDF;
- 文档切块;
- 生成 embedding;
- 存入 Chroma;
- 用户输入问题;
- 系统检索相关片段;
- 大模型生成答案;
- 返回答案和引用来源。
14.3 系统架构
14.4 技术选型
| 模块 | 推荐技术 |
|---|---|
| 后端服务 | FastAPI |
| PDF 解析 | PyMuPDF / pypdf |
| 文本切块 | LangChain TextSplitter |
| Embedding | OpenAI Embedding / BGE / M3E |
| 向量数据库 | Chroma |
| 大模型调用 | OpenAI API / 其他兼容接口 |
| 配置管理 | python-dotenv |
| 数据校验 | Pydantic |
| 日志 | logging |
| 部署 | Docker / docker-compose |
15. FastAPI 接口设计
15.1 上传 PDF 接口
POST /documents/upload
功能:
- 接收 PDF 文件;
- 保存到本地;
- 解析文本;
- 切块;
- 向量化;
- 写入 Chroma。
返回示例:
{
"message": "PDF uploaded and indexed successfully",
"filename": "rag_paper.pdf",
"chunks": 128
}
15.2 批量上传接口
POST /documents/batch-upload
功能:
- 支持多个 PDF;
- 每个文件单独解析;
- 统一入库;
- 返回每个文件的 chunk 数量。
15.3 问答接口
POST /qa
请求:
{
"question": "这篇论文的核心方法是什么?",
"top_k": 5,
"source": "paper1.pdf"
}
返回:
{
"answer": "根据检索到的论文内容,核心方法是……",
"sources": [
{
"file": "paper1.pdf",
"page": 3,
"content": "本文提出了一种……",
"score": 0.87
}
]
}
15.4 检索接口
POST /search
只返回检索结果,不调用大模型。
作用:
- 调试检索质量;
- 观察 top-k 结果;
- 判断是检索问题还是生成问题。
请求:
{
"query": "RAG 如何减少幻觉?",
"top_k": 5
}
返回:
{
"results": [
{
"file": "rag_intro.pdf",
"page": 2,
"content": "Retrieval augmented generation reduces hallucination by...",
"score": 0.89
}
]
}
这个接口很重要,因为它能让你定位问题:
如果检索结果不相关,是检索问题;
如果检索结果相关但答案错误,是生成问题或 prompt 问题。
16. 项目代码结构建议
推荐目录结构:
pdf-rag-demo/
├── app/
│ ├── main.py # FastAPI 入口
│ ├── config.py # 配置文件
│ ├── schemas.py # Pydantic 请求和响应模型
│ ├── services/
│ │ ├── pdf_loader.py # PDF 解析
│ │ ├── text_cleaner.py # 文本清洗
│ │ ├── splitter.py # 文档切块
│ │ ├── vector_store.py # Chroma 入库与检索
│ │ ├── rag_service.py # RAG 主流程
│ │ └── llm_service.py # 大模型调用
│ ├── utils/
│ │ ├── logger.py # 日志
│ │ └── file_utils.py # 文件工具
│ └── prompts/
│ └── rag_prompt.py # Prompt 模板
├── data/
│ ├── uploads/ # 上传 PDF
│ └── chroma/ # Chroma 持久化目录
├── tests/
│ ├── test_splitter.py
│ ├── test_retriever.py
│ └── test_rag.py
├── requirements.txt
├── .env
├── README.md
└── Dockerfile
17. 核心代码骨架
以下代码是学习用骨架,重点理解流程,不要求一开始完全照搬。
17.1 PDF 解析
from pathlib import Path
import fitz # PyMuPDF
def load_pdf(file_path: str) -> list[dict]:
"""
解析 PDF,每一页返回一个 dict。
保留 source 和 page,方便后续引用来源。
"""
pages = []
path = Path(file_path)
doc = fitz.open(file_path)
for page_index, page in enumerate(doc):
text = page.get_text()
if not text or not text.strip():
continue
pages.append({
"text": text.strip(),
"metadata": {
"source": path.name,
"page": page_index + 1
}
})
return pages
17.2 文本清洗
import re
def clean_text(text: str) -> str:
"""
简单文本清洗:
1. 去除多余空白
2. 合并连续空行
3. 保留基本段落结构
"""
text = text.replace("\r", "\n")
text = re.sub(r"\n{3,}", "\n\n", text)
text = re.sub(r"[ \t]+", " ", text)
return text.strip()
17.3 文档切块
from langchain_text_splitters import RecursiveCharacterTextSplitter
def split_pages(pages: list[dict]) -> list[dict]:
splitter = RecursiveCharacterTextSplitter(
chunk_size=800,
chunk_overlap=150,
separators=["\n\n", "\n", "。", ".", " ", ""]
)
chunks = []
for page in pages:
text = page["text"]
metadata = page["metadata"]
split_texts = splitter.split_text(text)
for idx, chunk_text in enumerate(split_texts):
chunks.append({
"text": chunk_text,
"metadata": {
**metadata,
"chunk_index": idx
}
})
return chunks
17.4 向量化入库
from langchain_chroma import Chroma
from langchain_openai import OpenAIEmbeddings
def build_vector_store(chunks: list[dict]):
embeddings = OpenAIEmbeddings()
texts = [chunk["text"] for chunk in chunks]
metadatas = [chunk["metadata"] for chunk in chunks]
vector_store = Chroma(
collection_name="pdf_knowledge_base",
embedding_function=embeddings,
persist_directory="./data/chroma"
)
vector_store.add_texts(
texts=texts,
metadatas=metadatas
)
return vector_store
17.5 检索
def retrieve(vector_store, question: str, top_k: int = 5, source: str | None = None):
"""
根据问题检索 top-k 个相关 chunk。
如果指定 source,则只检索某个 PDF。
"""
search_kwargs = {"k": top_k}
if source:
search_kwargs["filter"] = {"source": source}
retriever = vector_store.as_retriever(
search_kwargs=search_kwargs
)
docs = retriever.invoke(question)
return docs
17.6 构造上下文
def build_context(docs) -> str:
context_parts = []
for i, doc in enumerate(docs):
source = doc.metadata.get("source", "unknown")
page = doc.metadata.get("page", "unknown")
content = doc.page_content
context_parts.append(
f"[来源 {i + 1}] 文件:{source},页码:{page}\n{content}"
)
return "\n\n".join(context_parts)
17.7 Prompt 模板
RAG_PROMPT = """
你是一个严谨的论文知识库问答助手。
请只根据下面的参考资料回答用户问题。
如果参考资料中没有足够信息,请回答:
“根据当前资料无法确定。”
用户问题:
{question}
参考资料:
{context}
回答要求:
1. 使用中文回答;
2. 回答要准确、简洁、有条理;
3. 不要编造参考资料中没有的信息;
4. 如果资料不足,请明确说明;
5. 最后列出引用来源。
"""
17.8 RAG 主流程
def answer_question(vector_store, llm, question: str, top_k: int = 5, source: str | None = None):
docs = retrieve(
vector_store=vector_store,
question=question,
top_k=top_k,
source=source
)
if not docs:
return {
"answer": "根据当前知识库无法找到相关资料。",
"sources": []
}
context = build_context(docs)
prompt = RAG_PROMPT.format(
question=question,
context=context
)
answer = llm.invoke(prompt)
sources = []
for doc in docs:
sources.append({
"file": doc.metadata.get("source"),
"page": doc.metadata.get("page"),
"chunk_index": doc.metadata.get("chunk_index"),
"content": doc.page_content[:300]
})
return {
"answer": answer,
"sources": sources
}
18. RAG 效果优化方法
18.1 优化 PDF 解析
检查:
- 是否有乱码;
- 是否解析出页眉页脚;
- 双栏论文顺序是否错乱;
- 表格是否丢失;
- 公式是否被破坏。
优化方式:
- 换 PDF 解析工具;
- 对页眉页脚做规则过滤;
- 对表格单独处理;
- 对扫描版 PDF 使用 OCR;
- 把图片、表格、公式作为后续升级点。
18.2 优化 chunk
可以实验:
chunk_size = 500
chunk_size = 800
chunk_size = 1200
观察:
- 检索结果是否完整;
- 片段是否包含太多无关内容;
- 回答是否准确;
- token 成本是否可接受。
18.3 优化 top-k
可以实验:
top_k = 3
top_k = 5
top_k = 8
观察:
- top-k 太小时是否漏信息;
- top-k 太大时是否引入噪声;
- 回答是否变得啰嗦;
- 响应速度是否下降。
18.4 增加 score threshold
如果检索分数低于阈值,拒绝回答:
当前知识库中没有找到足够相关的内容。
这样可以防止模型基于弱相关资料胡编。
18.5 增加 rerank
基础流程:
向量检索 top-5 → 生成答案
升级流程:
向量检索 top-20 → rerank 重排序 → 取 top-5 → 生成答案
rerank 的作用:
对初步召回结果重新排序,把真正和问题最相关的内容放到前面。
面试中提到 rerank 是加分点。
18.6 增加 Hybrid Search
升级前:
只用向量检索
升级后:
BM25 关键词检索 + 向量检索 + rerank
适合处理:
- 公式;
- 表格编号;
- 专有名词;
- 算法名称;
- API 名称;
- 论文中的 Figure、Table、Equation。
18.7 优化 Prompt
差的 prompt:
根据资料回答问题。
更好的 prompt:
请只根据参考资料回答。
如果参考资料中没有答案,请明确说明无法确定。
不要使用外部知识补充。
回答后列出引用来源。
Prompt 的核心目标:
- 限制模型不要编造;
- 明确回答格式;
- 要求引用来源;
- 允许模型在资料不足时拒答。
19. 常见问题与排查思路
19.1 答案不准确,怎么排查?
按照顺序排查:
1. 原始 PDF 解析是否正确?
2. chunk 是否切得合理?
3. 检索结果是否相关?
4. top-k 是否合适?
5. prompt 是否限制了模型?
6. 模型是否基于资料回答?
最重要的是先看检索结果。
如果检索结果本身不相关:
问题在检索阶段。
如果检索结果相关,但答案错误:
问题在 prompt 或生成阶段。
19.2 检索结果不相关怎么办?
可能原因:
- chunk 太大;
- chunk 太小;
- embedding 模型不适合中文 / 领域文档;
- 用户问题表达太模糊;
- PDF 解析质量差;
- top-k 参数不合适;
- 文档库中确实没有相关内容。
优化方式:
- 调整 chunk size;
- 增加 overlap;
- 换 embedding 模型;
- 加 metadata filter;
- 加 hybrid search;
- 加 rerank;
- 增加检索结果展示接口方便调试。
19.3 为什么回答有来源,但内容还是错?
可能原因:
- 来源片段不够支持答案;
- 模型过度总结;
- prompt 约束不够;
- 多个来源之间存在冲突;
- 模型引用了来源,但补充了来源中没有的信息。
优化方式:
- 要求模型逐条依据来源回答;
- 要求“不确定就说不确定”;
- 来源片段按相关性排序;
- 限制模型不能加入外部知识;
- 对答案做事实一致性检查。
20. 面试讲法与高频问题
20.1 一句话介绍项目
我做了一个基于 RAG 的论文知识库问答系统,支持多 PDF 上传。系统会解析 PDF 文本,按重叠窗口切成 chunk,然后生成 embedding 存入 Chroma 向量数据库。用户提问时,系统先进行语义检索,召回 top-k 个相关片段,再把这些片段作为上下文交给大模型生成答案,并返回文件名、页码和原文片段作为来源,保证答案可追溯。
20.2 面试官问:RAG 的完整流程是什么?
回答:
RAG 分为离线入库和在线问答两个阶段。离线阶段包括文档解析、文本清洗、chunk 切块、embedding 向量化和写入向量数据库。在线阶段中,用户输入问题后,系统把问题向量化,在向量数据库中做相似度检索,取回 top-k 个相关 chunk,然后把这些 chunk 拼接成上下文交给大模型生成答案,最后返回答案和来源引用。
20.3 面试官问:为什么 RAG 可以减少幻觉?
回答:
普通大模型回答问题时主要依赖参数知识,遇到未知或私有文档内容时容易编造。RAG 在生成前增加了检索步骤,让模型先从外部知识库中找到相关资料,再基于资料回答。同时通过 prompt 限制模型只根据参考资料回答,并返回来源供用户验证,所以可以降低幻觉风险。但 RAG 不能完全消除幻觉,如果检索结果不准或 prompt 约束不够,仍然可能出现错误。
20.4 面试官问:chunk size 怎么设计?
回答:
chunk size 需要在语义完整性和检索精度之间平衡。chunk 太小会导致上下文不完整,chunk 太大又会包含太多无关信息。我当前使用固定长度切块加 overlap,比如 chunk size 800、overlap 150,保证每个 chunk 有相对完整的语义,同时避免重要信息在切分边界丢失。后续可以升级为按章节、标题或段落进行语义切块。
20.5 面试官问:top-k 怎么选?
回答:
top-k 表示检索时返回最相关的前 k 个 chunk。top-k 太小容易漏掉关键信息,top-k 太大又会引入噪声、增加 token 成本并影响生成质量。我一般从 3 到 5 开始调试,事实型问题可以小一些,总结类或多文档对比问题可以适当增大。最终会根据召回结果相关性、答案完整性和响应延迟综合选择。
20.6 面试官问:metadata 有什么用?
回答:
metadata 用于过滤、管理和来源追踪。每个 chunk 入库时我会保存 source、page、chunk_id 等信息。用户如果只想查询某篇 PDF,可以通过 source 做过滤。生成答案时,也可以根据 metadata 返回文件名和页码,让答案可验证、可追溯。
20.7 面试官问:如果检索不准,你怎么优化?
回答:
我会先看原始检索结果,判断是检索问题还是生成问题。如果检索结果不相关,我会检查 PDF 解析质量、chunk 切分是否合理、embedding 模型是否适合当前文档、top-k 是否过大或过小。然后可以尝试调整 chunk size 和 overlap,引入 metadata filter,或者增加 BM25 + 向量检索的混合检索。进一步还可以在召回后加入 rerank,提高最终上下文质量。
20.8 面试官问:RAG 和微调有什么区别?
回答:
RAG 是在推理时检索外部知识,把相关资料放进上下文中,让模型基于资料回答。微调是改变模型参数,让模型学习某种风格、格式或任务能力。对于频繁更新的知识、企业内部文档、论文知识库,更适合 RAG,因为文档可以随时更新;对于固定任务格式、特定输出风格或领域表达习惯,可以考虑微调。很多实际系统也会组合使用 RAG 和微调。
20.9 面试官问:你的项目有什么不足?
回答:
当前版本主要实现了基础 RAG 链路,使用固定长度切块和向量检索,适合小规模 PDF 问答。不足是对复杂 PDF 的表格、公式和图片支持有限;检索上主要依赖语义向量,对精确术语和公式编号的召回可能不够好。后续可以加入章节感知切块、Hybrid Search、rerank、表格解析和检索评估指标,进一步提升效果。
21. 简历写法
21.1 基础版本
基于 RAG 的论文知识库问答系统
- 基于 LangChain / Chroma 构建多 PDF 知识库问答系统,支持 PDF 解析、文本切块、向量化入库和语义检索。
- 设计 chunk size + overlap 的文档切块策略,并为每个 chunk 维护 source、page、chunk_id 等 metadata,实现答案来源追踪。
- 用户提问时先通过向量检索召回 top-k 相关片段,再将检索结果注入 Prompt,引导大模型基于上下文生成回答,降低幻觉风险。
- 支持返回答案引用来源,包括文件名、页码和原文片段,提升系统可解释性与结果可信度。
21.2 强化工程版本
基于 RAG 的论文知识库智能问答系统
- 使用 FastAPI + LangChain + Chroma 搭建端到端 PDF RAG 系统,支持多 PDF 上传、自动解析、切块、向量化入库和知识库问答。
- 设计文档处理流水线,将 PDF 页面内容切分为带有 source、page、chunk_id 的文本块,支持基于 metadata 的检索过滤和答案来源追踪。
- 实现 Retriever + Prompt + LLM 的问答链路,用户提问后先召回 top-k 相关片段,再基于上下文生成答案,并返回引用片段以降低幻觉。
- 提供上传、检索、问答等 RESTful API,支持检索结果展示,便于分析召回质量和定位问答错误原因。
21.3 可扩展优化版本
- 针对检索效果进行 chunk size、overlap、top-k 等参数调优,并设计 Hybrid Search / Rerank 作为后续优化方向,提高复杂问题下的召回准确率。
22.3 最核心的一句话
RAG 的本质是:
先检索外部知识,再让大模型基于检索到的资料生成答案。
PDF RAG 项目的核心链路是:
PDF → 解析 → 清洗 → 切块 → Embedding → Vector Store → 检索 → Context → LLM → 答案 + 来源
面试中你最需要体现的是:
我不是只会调库,而是理解 RAG 系统每个环节的作用、问题和优化方向。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)