Contents

我用纯 Python 手搓了一个 RAG 知识库:不用 LangChain,也能让文档开口说话

我用纯 Python 手搓了一个 RAG 知识库:不用 LangChain,也能让文档开口说话

全文约 3000 字,阅读需要 6 分钟


起因

最近工作里有个很具体的痛点:文档太多,而且太杂。

合同扫描件、行业报告、内部资料、会议截图、Word、Markdown、PDF,格式五花八门。真正要用的时候,最麻烦的不是“有没有这份资料”,而是“我到底在哪份资料里见过这句话”。

我想要的东西其实很简单:

把这些文档丢进一个系统里,以后直接用自然语言提问,它能从资料里找依据、给答案、贴出处。

市面上当然有很多方案。但试了一圈,总觉得不完全合适:

  • 有的太重,依赖链很长,调一个小功能要理解一堆抽象;
  • 有的太贵,OCR、Embedding、LLM 全走 API,文档量一大成本就上来了;
  • 有的隐私边界不够清楚,公司资料直接传第三方服务器,这事很难放心。

于是我干脆自己写了一个。

项目名叫 RagLite:轻量、透明、尽量把重计算放在自己手里。


它能干什么?

一句话:把你的文档变成一个可对话的知识库。

比如扔进去 35 张截图,或者一本 459 页的扫描版 PDF,等它完成 OCR、分块、向量化和入库之后,就可以直接问:

“将军崖岩画是什么?”

它会先从知识库里检索相关片段,再调用大模型生成回答,并把来源一起贴出来。这样你既能快速得到结论,也能回到原文核对。

目前支持这些文件:

  • Markdown / 纯文本
  • Word(.docx
  • PDF,包括纯图片扫描件
  • 图片截图(PNG / JPG)

我最看重的是两点:

第一,原始文件不必交给第三方平台。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 做了一层混合检索:

  • 向量相似度负责语义召回;
  • BM25 负责关键词匹配;
  • 中文分词用 jieba;
  • 最后把两路结果融合排序。

目前默认权重是向量 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}'

返回结果会包含两部分:

  • AI 生成的回答;
  • 命中的原始文档片段和来源。

这点很重要。RAG 系统不能只给“看起来像答案”的回答,还要让你能回到证据本身。


性能数据

下面是我在单 GPU 环境里的测试结果,显存约 8.6GB,Ollama 本地推理。

场景数据量耗时
35 张截图入库72 个文本块约 2 分钟
459 页扫描 PDF882 个文本块约 21 分钟
单次查询检索 + 排序 + 生成约 5 秒

主要瓶颈在 OCR。那本 459 页 PDF 平均每页约 2.7 秒,向量化反而很快,882 个文本块大约 39 秒完成。

这些数据不是实验室基准,只是我自己这台机器上的实测结果。不同显卡、模型、文档版面,差异会很大。


隐私边界

这是我写这个项目的核心原因之一,所以需要说清楚。

RagLite 默认尽量把敏感步骤放在本机:

  • OCR:本地 Ollama 推理,原始文件不需要上传第三方平台;
  • 向量化:默认走本地 bge-m3;
  • 存储:ChromaDB 可以部署在本机,也可以放在自管 VPS;
  • 生成:如果使用云端大模型,只发送检索后的片段和问题,不发送整份原始文档。

所以更准确的说法是:

原始文档不出你的可控环境。是否完全不出本机,取决于你把服务端部署在哪里,以及生成模型是否使用外部 API。

如果你对隐私要求更高,可以把 FastAPI、ChromaDB 和 LLM 全部放在本地。这样整条链路都不需要外部服务。


技术栈

选型尽量保持简单,都是 Python 生态里比较成熟的组件。

组件选型
OCROllama(glm-ocr + deepseek-ocr)
Embeddingbge-m3
向量数据库ChromaDB
APIFastAPI
检索向量 + BM25 融合
生成glm-5 或本地 LLM
分词jieba
环境管理uv

依赖不多,uv sync 基本就能把环境装起来。


适合谁?

我觉得 RagLite 适合这几类人:

  • 有大量文档需要检索的个人开发者;
  • 不想把原始资料交给第三方平台的小团队;
  • 想真正理解 RAG 流程、愿意自己动手改代码的学习者;
  • 手里有一台带 GPU 的电脑,想把算力用起来的技术用户。

它暂时不适合这些场景:

  • 企业级权限管理;
  • 百万级文档库;
  • 高并发在线查询;
  • 复杂多租户系统。

这些场景应该上更完整的工程方案,比如专门的权限系统、队列、监控、对象存储、Elasticsearch、Milvus 等基础设施。RagLite 的目标不是包打天下,而是把一个轻量 RAG 系统的主链路跑清楚。


下一步计划

  • 支持更多文件格式,比如 Excel、PPT;
  • 加入 reranker 精排模型,提升复杂问题的检索质量;
  • 做一个 Web UI 管理界面;
  • 提供 Docker 一键部署;
  • 增加更完整的评测脚本,方便比较不同分块和检索策略。

最后

RagLite 不是为了替代 LangChain,也不是想做一个大而全的平台。

它更像是一个可读、可改、可部署的参考实现:用尽量少的代码,把 RAG 里最核心的几个环节串起来。

你能看到文档怎么被 OCR,怎么被切块,怎么变成向量,怎么被检索出来,最后又怎么交给大模型生成答案。

对我来说,这种透明感很重要。

因为 RAG 系统最终不是“让 AI 看起来会回答问题”,而是让我们能在自己的资料里更快找到依据,并且知道答案从哪里来。

如果你也在折腾个人知识库、企业资料检索,或者只是想把 RAG 的主流程拆开看明白,可以试试这个思路。

有问题欢迎留言交流。


作者:Trevan
技术栈:Python + Ollama + ChromaDB + FastAPI