我用纯 Python 手搓了一个 RAG 知识库:不用 LangChain,也能让文档开口说话
全文约 3000 字,阅读需要 6 分钟
起因
最近工作里有个很具体的痛点:文档太多,而且太杂。
合同扫描件、行业报告、内部资料、会议截图、Word、Markdown、PDF,格式五花八门。真正要用的时候,最麻烦的不是“有没有这份资料”,而是“我到底在哪份资料里见过这句话”。
我想要的东西其实很简单:
把这些文档丢进一个系统里,以后直接用自然语言提问,它能从资料里找依据、给答案、贴出处。
市面上当然有很多方案。但试了一圈,总觉得不完全合适:
- 有的太重,依赖链很长,调一个小功能要理解一堆抽象;
- 有的太贵,OCR、Embedding、LLM 全走 API,文档量一大成本就上来了;
- 有的隐私边界不够清楚,公司资料直接传第三方服务器,这事很难放心。
于是我干脆自己写了一个。
项目名叫 RagLite:轻量、透明、尽量把重计算放在自己手里。
它能干什么?
一句话:把你的文档变成一个可对话的知识库。
比如扔进去 35 张截图,或者一本 459 页的扫描版 PDF,等它完成 OCR、分块、向量化和入库之后,就可以直接问:
“将军崖岩画是什么?”
它会先从知识库里检索相关片段,再调用大模型生成回答,并把来源一起贴出来。这样你既能快速得到结论,也能回到原文核对。
目前支持这些文件:
我最看重的是两点:
第一,原始文件不必交给第三方平台。OCR 和向量化默认都可以在本机完成。
第二,代码路径足够短。出了问题,你能直接打开对应文件看懂它在做什么。
为什么不用 LangChain?
不是 LangChain 不好。
它解决的是“快速搭建复杂 LLM 应用”的问题,生态、集成、抽象都很完整。但我的需求没有那么复杂:文档解析、OCR、分块、Embedding、检索、生成,链路清楚,能跑稳就行。
很多时候,框架的抽象层会在前期让你觉得省事,到了调试阶段又把问题藏起来。链条、Agent、Tool、Memory,每个概念都包了一层。检索结果不对时,你可能要一路穿过好几层封装,才能找到真正影响结果的那个参数。
RagLite 的思路更朴素:
每个模块自包含,能少一层就少一层。
项目结构大概是这样:
config.py # 配置,一个 dataclass
ingest/
ocr.py # OCR,调用本地 Ollama
chunker.py # 文档分块
embed.py # 向量化,支持多种模式
pipeline.py # 入库流水线
serve/
store.py # ChromaDB 存储
retriever.py # 混合检索
api.py # FastAPI 接口
mcp_server.py # MCP 协议
没有太多魔法。哪一步出问题,就看哪一个文件。
架构怎么设计?
RagLite 采用的是“本地重计算 + 自管服务”的方式。
你的电脑(有 GPU) 自管 VPS 或本机服务
┌──────────────────┐ ┌──────────────────┐
│ Ollama │ │ FastAPI │
│ ├─ OCR 模型 │ 向量推送 │ ├─ 查询接口 │
│ └─ 嵌入模型 │ ────────→ │ ├─ 入库接口 │
│ │ │ └─ ChromaDB │
│ 文档入库流水线 │ │ │
└──────────────────┘ │ MCP Server │
└──────────────────┘
核心思路是:重活在本地干,服务端只负责存储和查询。
- 本地:OCR 识别文字、文档分块、向量化、推送入库;
- 服务端:运行 FastAPI 和 ChromaDB,提供查询、入库和 MCP 工具接口;
- 网络:可以通过 EasyTier 这类虚拟组网工具走内网连接,避免暴露公网端口。
如果你更在意隐私,也可以把服务端直接放在同一台机器上。这样原始文档、向量库、检索服务都在本地。
我自己的使用方式是:本地机器负责 GPU 计算,自管 VPS 负责轻量服务。VPS 不需要 GPU,2GB 内存的小机器也能跑。
几个关键技术决策
1. OCR 双模型,按场景选择
RagLite 支持本地跑两个 OCR 模型:
- glm-ocr:适合常规文本、表格、公式,速度较快;
- deepseek-ocr 3.3B:适合复杂版面、多栏 PDF、扫描件。
通过 Ollama 统一调用,不需要额外部署 OCR 服务。
我用一本 459 页的扫描版 PDF 做过测试,完整处理约 21 分钟,过程中没有失败页。这个速度不算“秒开”,但对一次性入库来说完全能接受。
2. Embedding 支持多种模式
不同环境对向量化的要求不一样,所以这里没有把路堵死。
| 模式 |
适用场景 |
特点 |
| Ollama 本地 |
日常入库 |
原始内容不出本机,配置简单 |
| 远程 API |
云端查询 |
省本地算力,VPS 更轻 |
| transformers 本地 |
离线环境 |
完全自包含,可控性最高 |
默认使用本地 bge-m3 做向量化。大批量文档入库时,这个选择比全部调用云端 API 更可控,成本也低很多。
3. 检索不用只押宝向量
纯向量检索有时会“看起来很聪明,实际没抓住关键词”。
比如“苏豪”和“苏豪弘业”,语义上接近,但在业务资料里可能是完全不同的实体。只靠向量相似度,容易把相近但不该混在一起的内容召回。
所以 RagLite 做了一层混合检索:
目前默认权重是向量 60%、BM25 40%。这个比例不是圣旨,后续可以根据具体资料类型调整。
4. 同时支持 HTTP 和 MCP
我希望它既能给普通脚本调用,也能给 AI Agent 调用,所以做了两套入口:
- FastAPI(HTTP):标准 REST API,curl、Python、其他语言都能接;
- MCP Server(stdio):可以接入 Claude、Hermes 等支持 MCP 的 Agent。
MCP 的好处是,Agent 可以像调用工具一样查询你的知识库。
你对 Agent 说“帮我查一下上周会议纪要里关于预算的内容”,它就可以自动调用 RagLite 的检索工具,而不是只凭上下文硬猜。
实际跑一下
入库
# 拉取模型,具体模型名按你的 Ollama 环境调整
ollama pull glm-ocr
ollama pull bge-m3
# 入库单个文档
EMBEDDING_PROVIDER=ollama uv run scripts/ingest_doc.py --file 报告.pdf
# 入库整个目录
EMBEDDING_PROVIDER=ollama uv run scripts/ingest_dir.py --dir ~/documents/ --pattern "*.md"
查询
uv run scripts/query_cli.py "将军崖岩画是什么"
也可以启动 API 服务,用 HTTP 查询:
curl -X POST http://localhost:8000/query \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"query": "将军崖岩画是什么", "top_k": 5}'
返回结果会包含两部分:
这点很重要。RAG 系统不能只给“看起来像答案”的回答,还要让你能回到证据本身。
性能数据
下面是我在单 GPU 环境里的测试结果,显存约 8.6GB,Ollama 本地推理。
| 场景 |
数据量 |
耗时 |
| 35 张截图入库 |
72 个文本块 |
约 2 分钟 |
| 459 页扫描 PDF |
882 个文本块 |
约 21 分钟 |
| 单次查询 |
检索 + 排序 + 生成 |
约 5 秒 |
主要瓶颈在 OCR。那本 459 页 PDF 平均每页约 2.7 秒,向量化反而很快,882 个文本块大约 39 秒完成。
这些数据不是实验室基准,只是我自己这台机器上的实测结果。不同显卡、模型、文档版面,差异会很大。
隐私边界
这是我写这个项目的核心原因之一,所以需要说清楚。
RagLite 默认尽量把敏感步骤放在本机:
- OCR:本地 Ollama 推理,原始文件不需要上传第三方平台;
- 存储:ChromaDB 可以部署在本机,也可以放在自管 VPS;
- 生成:如果使用云端大模型,只发送检索后的片段和问题,不发送整份原始文档。
所以更准确的说法是:
原始文档不出你的可控环境。是否完全不出本机,取决于你把服务端部署在哪里,以及生成模型是否使用外部 API。
如果你对隐私要求更高,可以把 FastAPI、ChromaDB 和 LLM 全部放在本地。这样整条链路都不需要外部服务。
技术栈
选型尽量保持简单,都是 Python 生态里比较成熟的组件。
| 组件 |
选型 |
| OCR |
Ollama(glm-ocr + deepseek-ocr) |
| Embedding |
bge-m3 |
| 向量数据库 |
ChromaDB |
| API |
FastAPI |
| 检索 |
向量 + BM25 融合 |
| 生成 |
glm-5 或本地 LLM |
| 分词 |
jieba |
| 环境管理 |
uv |
依赖不多,uv sync 基本就能把环境装起来。
适合谁?
我觉得 RagLite 适合这几类人:
- 想真正理解 RAG 流程、愿意自己动手改代码的学习者;
- 手里有一台带 GPU 的电脑,想把算力用起来的技术用户。
它暂时不适合这些场景:
这些场景应该上更完整的工程方案,比如专门的权限系统、队列、监控、对象存储、Elasticsearch、Milvus 等基础设施。RagLite 的目标不是包打天下,而是把一个轻量 RAG 系统的主链路跑清楚。
下一步计划
- 加入 reranker 精排模型,提升复杂问题的检索质量;
- 增加更完整的评测脚本,方便比较不同分块和检索策略。
最后
RagLite 不是为了替代 LangChain,也不是想做一个大而全的平台。
它更像是一个可读、可改、可部署的参考实现:用尽量少的代码,把 RAG 里最核心的几个环节串起来。
你能看到文档怎么被 OCR,怎么被切块,怎么变成向量,怎么被检索出来,最后又怎么交给大模型生成答案。
对我来说,这种透明感很重要。
因为 RAG 系统最终不是“让 AI 看起来会回答问题”,而是让我们能在自己的资料里更快找到依据,并且知道答案从哪里来。
如果你也在折腾个人知识库、企业资料检索,或者只是想把 RAG 的主流程拆开看明白,可以试试这个思路。
有问题欢迎留言交流。
作者:Trevan
技术栈:Python + Ollama + ChromaDB + FastAPI