一、为什么要“多模态 RAG”?

传统 RAG 只能啃文本,现实里却充斥着扫描件、截图、PDF。
今天我们把 OCR 塞进经典 RAG 流水线,让大模型「先看见、再回答」。


二、最终效果

  1. 打开浏览器,拖拽一张截图 → 自动识别文字 → 秒级建库
  2. 直接问图里的内容,模型给出带来源文件名的答案
  3. 支持多文件、多集合,管理员可随时切换知识库
  4. 全程 HTTP 接口 + Web 聊天,前后端分离,可内网部署

三、技术栈(全部开源)

模块 选用方案 说明
LLM DeepSeek / Moonshot 兼容 OpenAI-API,无需翻墙
Embedding BAAI/bge-small-zh 中文语义足够,100 MB 级
向量库 Milvus Lite pip install milvus,零配置
OCR Umi-OCR v2 离线、免费、开箱即带 HTTP 服务
前端 Chainlit 一行命令挂载到 FastAPI
框架 LlamaIndex 胶水层,10 行代码完成 RAG

四、30 分钟部署手册

① 环境准备

# 1. 创建虚拟环境
python -m venv venv && source venv/bin/activate   # Windows 用 activate.bat

# 2. 一键安装依赖
pip install -r https://raw.githubusercontent.com/yourname/mini-ocr-rag/main/requirements.txt

② 拉起 Milvus(CPU 版)

docker run -d --name milvus -p 19530:19530 milvusdb/milvus:v2.3.10-lite

③ 拉起 Umi-OCR(Windows / Linux 均提供绿色包)

# 绿色包下载后双击 Umi-OCR.exe
# 默认会启 127.0.0.1:1224,可改端口

④ 配置环境变量

在项目根目录新建 .env,照抄改 key 即可:

DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxx
MILVUS_URI=http://127.0.0.1:19530
OCR_BASE_URL=http://127.0.0.1:1224
OCR_DOWNLOAD_PATH=./data/ocr

⑤ 启动服务

python app.py
# 浏览器自动打开 http://localhost:8080

上传文件 → 输入集合名 → 点 Build Index → 跳到 /chainlit 开聊!


五、核心代码解读

1. OCR 一行函数

def ocr_image_to_text(path: str) -> str:
    b64 = base64.b64encode(Path(path).read_bytes()).decode()
    r = requests.post(f"{OCR_URL}/api/ocr",
                      json={"base64": b64, "options": {"data.format": "text"}},
                      headers={"Content-Type": "application/json"})
    return r.json().get("data", "")

2. RAG 打包类

class TraditionalRAG:
    async def create_index(self, files: list[str], collection: str):
        docs = []
        for f in files:
            text = ocr_image_to_text(f) if Path(f).suffix in IMG_EXT else ocr_image_to_text(f)
            docs.append(Document(text=text, metadata={"file": Path(f).name}))

        vector_store = MilvusVectorStore(uri=MILVUS_URI, collection_name=collection, dim=512)
        return VectorStoreIndex.from_documents(docs, vector_store=vector_store)

3. FastAPI + Chainlit 胶水

app = FastAPI()
mount_chainlit(app, target="chat.py", path="/chainlit")

@app.post("/upload")
async def upload(files: list[UploadFile], collection: str = Form(...)):
    saved = [save_file(f) for f in files]
    await TraditionalRAG().create_index(saved, collection)
    return {"msg": "indexed", "count": len(saved)}

六、常见坑 & 秒解方案

现象 原因 一句话解决
上传后一直“Indexing” 单线程阻塞 create_index 扔线程池 asyncio.to_thread
OCR 返回空 图片太大 Umi-OCR → 全局设置 → 内存限制调到 4096
Milvus 连不上 端口未暴露 docker run -p 19530:19530 别漏 -p
中文乱码 系统缺字体 Linux apt install fonts-noto-cjk

七、下一步可以玩的花活

  1. 多集合切换:把 collection 做成下拉列表,存到 Postgres
  2. 权限系统:Chainlit 自带 @password_auth_callback,5 行代码加登录
  3. 分段摘要:LlamaIndex SentenceWindowNodeParser 让长图也能精准定位
  4. 离线 LLM:换上 chatglm2-6b + xinference,彻底断网运行

八、总结

今天我们把「OCR → 向量化 → 检索 → 生成」四个步骤压缩成 2 个文件、不到 200 行代码,不挑硬件、不挑系统、不挑网络

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐