一、第三阶段基本信息
项目名称:
ResearchPilot------可追溯科研文献分析与问答系统
第三阶段版本:
V0.3.0:PDF 文本切分、向量化、语义检索与基础 RAG 问答
第三阶段是在 V0.2 已经完成"PDF 上传、解析、保存和查看"的基础上,把普通 PDF 文本进一步转换成可以被人工智能检索和问答的知识库。
第三阶段完整流程为:
PDF逐页文本
↓
文本清理
↓
文本切分
↓
文本块保存到SQLite
↓
Embedding向量化
↓
向量保存到ChromaDB
↓
用户问题向量化
↓
语义检索相关文本块
↓
将检索结果发送给DeepSeek
↓
生成带页码和文本块引用的回答
二、第三阶段开发目标
V0.2 解决的是:
如何把一篇 PDF 读取出来,并把逐页文本保存到数据库。
V0.3 解决的是:
如何从一篇长论文中找到与用户问题最相关的内容,并基于这些内容生成可追溯回答。
一篇论文可能有几万甚至十几万字符,不能直接把整篇论文一次性发送给大模型。因此,第三阶段首先将论文拆成较小的文本块,再对文本块建立向量索引。
三、V0.3.1:PDF 文本切分
1. 新增文本切分模块
新增文件:
src/text_chunker.py
主要负责:
清理PDF文本
寻找自然切分位置
按照指定长度切分
保留相邻文本块重叠区域
记录原始PDF页码
生成全文连续文本块编号
核心函数包括:
normalize_text()
split_text()
build_document_chunks()
2. 文本清理
PDF 提取出的文本通常存在:
连续空格
多余换行
制表符
全角空格
不同操作系统换行符
因此先进行标准化处理,例如:
text = text.replace("\r\n", "\n")
text = text.replace("\r", "\n")
再通过正则表达式合并连续空格和换行。
文本清理的目的不是改变论文含义,而是减少无效字符,提升切分和检索效果。
3. 文本块长度与重叠区域
当前默认参数为:
每个文本块最大字符数:800
相邻文本块重叠字符数:150
切分效果类似:
文本块1:字符0~800
文本块2:字符650~1450
文本块3:字符1300~2100
重叠区域的作用是防止一个完整句子或知识点刚好在切分边界被截断。
如果完全没有重叠:
文本块1结尾:该方法主要采用......
文本块2开头:卷积神经网络进行特征提取
单独检索任意一个文本块时,语义可能不完整。
增加重叠后,相邻文本块会保留一部分共同内容,使上下文更加连续。
4. 自然切分位置
程序并不是机械地每 800 个字符直接切断,而是尝试在最大长度附近寻找:
段落
换行
句号
问号
感叹号
分号
逗号
空格
例如,在第 760 个字符附近存在句号时,会优先在句号后切分,而不是强制切到第 800 个字符。
这种方式可以提高文本块的完整性。
5. 保留页码
当前版本规定:
一个文本块只属于一页 PDF,不跨页切分。
每个文本块包含:
{
"chunk_index": 1,
"page_chunk_index": 1,
"page_number": 3,
"chunk_text": "文本块正文",
"character_count": 782,
}
其中:
chunk_index:整篇论文中的文本块编号;page_chunk_index:当前页面中的文本块编号;page_number:原始 PDF 页码;chunk_text:文本块内容;character_count:文本块字符数。
保留页码是实现可追溯回答的重要基础。
四、pdf_chunks 数据库设计
第三阶段在 SQLite 中新增:
pdf_chunks
主要字段包括:
| 字段 | 作用 |
|---|---|
| id | 文本块主键 |
| document_id | 所属 PDF 文档编号 |
| page_number | 原始 PDF 页码 |
| chunk_index | 整篇 PDF 中的文本块编号 |
| page_chunk_index | 当前页中的文本块编号 |
| chunk_text | 文本块正文 |
| character_count | 文本块字符数 |
| chunk_size | 切分时设置的最大字符数 |
| overlap_size | 切分时设置的重叠字符数 |
| created_at | 创建时间 |
数据库关系变为:
pdf_documents
├── pdf_pages
└── pdf_chunks
一篇 PDF 对应多条页面记录,也对应多条文本块记录。
删除 PDF 时,SQLite 会通过外键级联删除:
PDF基本信息
PDF逐页文本
PDF文本块
重新切分机制
如果用户修改参数:
800 / 150
改为:
1000 / 200
系统不会直接在旧数据后继续添加,而是:
删除这篇PDF原来的文本块
↓
按照新参数重新切分
↓
保存新的文本块
这样可以避免同一篇论文同时存在多套重复文本块。
五、V0.3.2:Embedding 向量化
1. 新增向量化模块
新增文件:
src/embedding_service.py
主要负责:
加载Embedding模型
把论文文本块转换成向量
把用户问题转换成向量
缓存已经加载的模型
返回向量维度
当前使用的多语言 Embedding 模型支持中文和英文检索。
向量维度为:
384维
也就是说,一段论文文本最终会转换为类似:
[
0.013,
-0.027,
0.041,
...
]
总共 384 个浮点数。
2. 什么是 Embedding
Embedding 可以理解为:
把文字转换成计算机可以比较的数字坐标。
例如
阻塞性睡眠呼吸暂停的治疗方法
和:
OSA有哪些干预措施
虽然使用的文字不同,但语义相近,因此它们转换成向量后,在向量空间中的距离也会比较近。
传统关键词检索主要比较:
是否出现相同词语
向量检索主要比较:
语义是否相似
3. query 和 passage 前缀
当前 Embedding 模型在检索场景中使用:
passage: 论文文本块
query: 用户问题
例如:
passage_text = f"passage: {chunk_text}"
query_text = f"query: {question}"
这是为了告诉模型:
这段文字是待检索文档
这段文字是用户查询
从而获得更适合检索任务的向量。
4. 向量归一化
程序生成向量时使用了归一化:
normalize_embeddings=True
归一化后,向量长度被统一,后续可以更稳定地使用余弦距离比较语义相似度。
六、ChromaDB 向量数据库
1. 新增向量数据库模块
新增文件:
src/vector_store.py
主要负责:
初始化ChromaDB
创建向量集合
保存文本块向量
删除旧向量
统计向量数量
执行PDF内语义检索
ChromaDB 本地数据保存在:
data/chroma/
该目录已经加入 .gitignore,不会上传 GitHub。
其他电脑拉取项目后,可以重新根据 SQLite 文本块生成向量索引。
2. SQLite 与 ChromaDB 的区别
SQLite 保存的是结构化原始数据:
PDF信息
逐页文本
文本块正文
页码
文本块编号
ChromaDB 保存的是:
文本块向量
文本块元数据
用于相似度检索的索引
两者职责不同:
SQLite:保存真实、可读的数据
ChromaDB:负责高效语义检索
3. 向量唯一标识
每个文本块生成稳定的向量 ID,例如:
document_2_chunk_32
表示:
PDF文档ID:2
文本块编号:32
重新生成向量时,相同文本块可以通过相同 ID 覆盖,不会无限产生重复数据。
4. 当前测试结果
当前 PDF 的数据为:
SQLite文本块数量:111
ChromaDB向量数量:111
这说明:
111个文本块
→ 全部完成向量化
→ 全部保存到ChromaDB
两边数量一致,是向量化完整性的重要检查标准。
七、PDF 语义检索
用户输入问题后,系统执行:
用户问题
↓
转换成384维查询向量
↓
在指定PDF的111个向量中搜索
↓
返回距离最近的若干文本块
检索结果包含:
排名
PDF页码
文本块编号
文本块原文
向量距离
近似相似度
例如:
第1名
PDF第3页
文本块32
相似度约79.73%
需要注意:
相似度不是答案正确率,也不是大模型置信度。
它只表示:
用户问题向量
与
论文文本块向量
在向量空间中的接近程度
八、V0.3.3:基础 RAG 论文问答
1. 新增 RAG 模块
新增文件:
src/rag_service.py
主要负责:
调用向量检索
整理检索文本块
构造论文上下文
调用DeepSeek
生成完整回答
返回引用来源
处理API异常
统计Token使用量
2. RAG 的含义
RAG 全称是:
Retrieval-Augmented Generation
检索增强生成
完整流程为:
用户问题
↓
Retrieval:检索论文相关内容
↓
Augmented:把检索内容加入大模型上下文
↓
Generation:大模型根据上下文生成回答
它与直接调用大模型的区别是:
普通大模型问答:
问题 → 大模型自身知识 → 回答
RAG问答:
问题 → 检索指定论文 → 大模型根据论文回答
3. RAG 上下文构造
检索到的每个文本块会被整理为:
[第3页-文本块32]
具体论文内容......
[第5页-文本块48]
具体论文内容......
然后与用户问题一起发送给 DeepSeek。
这样模型在回答时可以知道每段材料的来源。
4. 防止模型编造
系统提示词要求模型:
只能根据检索到的论文内容回答
不得使用外部知识补充
资料不足时必须明确说明
不得虚构作者、数据、方法和结论
每个重要结论必须标注来源
优先使用中文回答
当用户询问论文中不存在的信息时,例如:
作者小时候在哪所小学读书?
系统应回答:
当前检索到的论文内容不足以回答这个问题。
而不是凭空编造。
5. 可追溯引用
回答中的引用格式为:
[第3页-文本块32]
页面下方还会展示:
引用依据1
PDF第3页
文本块32
原始论文内容
相似度
用户可以展开引用依据,检查大模型回答是否真正来源于论文原文。
这使系统具备了初步的可解释性和可追溯性。
九、第三阶段新增和修改的主要文件
第三阶段主要新增:
src/text_chunker.py
src/embedding_service.py
src/vector_store.py
src/rag_service.py
.streamlit/config.toml
主要修改:
src/database.py
frontend/streamlit_app.py
requirements.txt
requirements-lock.txt
.gitignore
.env.example
本地但不上传 GitHub:
.env
data/research_pilot.db
data/chroma/
Embedding模型缓存
十、第三阶段遇到的问题及解决方法
1. Hugging Face 镜像无法连接
曾出现:
无法连接 https://hf-mirror.com
原因是电脑环境中存在:
HF_ENDPOINT=https://hf-mirror.com
清除该环境变量后,恢复访问 Hugging Face 官方地址,并成功下载模型。
该问题体现了:
操作系统环境变量
Conda环境变量
项目.env配置
三者可能同时影响程序行为。
2. 模型首次下载较慢
Embedding 模型首次运行需要下载几百 MB 文件。
下载完成后会保存在本地缓存,后续启动一般不需要重新下载。
3. Windows 符号链接警告
Hugging Face 提示 Windows 无法使用符号链接。
该警告不会影响模型运行,只可能增加缓存占用空间。
4. torchvision 报错
终端曾大量出现:
ModuleNotFoundError: No module named 'torchvision'
原因不是文本向量模型必须依赖图像处理,而是 Streamlit 文件监视器扫描 Transformers 中大量可选视觉模块。
解决方式是关闭 Streamlit 文件监视器:
[server]
fileWatcherType = "none"
配置文件为:
.streamlit/config.toml
因此不需要为了文本 Embedding 功能额外安装图像处理依赖。
5. GitHub 连接被重置
开发过程中多次出现:
Recv failure: Connection was reset
这属于网络问题,而不是代码错误。
正确处理方式是:
提交成功、推送失败
→ 不重复提交
→ 只重新执行git push
十一、第三阶段核心知识点
第三阶段主要学习并实践了:
长文本清理
滑动窗口切分
文本重叠
自然边界切分
Embedding向量
向量归一化
余弦相似度
ChromaDB持久化
向量检索
元数据过滤
Top-K召回
RAG检索增强生成
Prompt约束
大模型API调用
Token统计
引用溯源
异常处理
本地缓存
环境变量
敏感密钥管理
十二、第三阶段成果总结
第三阶段最终完成了:
PDF逐页文本
→ 文本清理
→ 文本切分
→ SQLite文本块存储
→ Embedding向量化
→ ChromaDB向量存储
→ PDF内语义检索
→ DeepSeek生成论文回答
→ 页码与文本块引用
→ 引用原文展示
ResearchPilot 已经从普通 PDF 管理工具升级为一个具备基础 RAG 能力的科研文献问答系统。
V0.1 解决:
如何搜索论文。
V0.2 解决:
如何读取和保存论文全文。
V0.3 解决:
如何从论文中检索相关内容,并生成可追溯回答。