[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-80980":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":14,"subscribersCount":14,"size":14,"stars1d":14,"stars7d":15,"stars30d":16,"stars90d":14,"forks30d":14,"starsTrendScore":14,"compositeScore":17,"rankGlobal":9,"rankLanguage":9,"license":9,"archived":18,"fork":18,"defaultBranch":19,"hasWiki":20,"hasPages":18,"topics":21,"createdAt":9,"pushedAt":9,"updatedAt":22,"readmeContent":23,"aiSummary":24,"trendingCount":14,"starSnapshotCount":14,"syncStatus":15,"lastSyncTime":25,"discoverSource":26},80980,"Multimodal-RAG","liangdabiao\u002FMultimodal-RAG","liangdabiao","基于多模态 Embedding + Zilliz + Qwen 视觉理解的多模态 RAG 系统。支持 **Cohere \u002F DashScope Embedding** 和 **DashScope \u002F OpenRouter LLM** 双引擎切换。上传 PDF，用自然语言提问，系统自动检索最相关的页面并由 AI 生成回答。  与传统 RAG 不同，本系统**不做文本提取和 OCR**，而是直接将 PDF 页面当作图片处理，通过视觉 Embedding 模型编码，完整保留表格、图表、排版、手写批注等所有视觉信息。",null,"Python",33,7,30,0,2,3,41.01,false,"main",true,[],"2026-06-11 04:07:22","# Multimodal RAG - PDF Intelligent Q&A\n\n基于多模态 Embedding + Zilliz + Qwen 视觉理解的多模态 RAG 系统。支持 **Cohere \u002F DashScope Embedding** 和 **DashScope \u002F OpenRouter LLM** 双引擎切换。上传 PDF，用自然语言提问，系统自动检索最相关的页面并由 AI 生成回答。\n\n与传统 RAG 不同，本系统**不做文本提取和 OCR**，而是直接将 PDF 页面当作图片处理，通过视觉 Embedding 模型编码，完整保留表格、图表、排版、手写批注等所有视觉信息。\n\n## 工作原理\n\n```\nPDF 文档\n  │\n  ▼ (PyMuPDF, 150 DPI)\n页面图片（不做任何文本提取）\n  │\n  ▼ (Embedding API, 云端调用)\n  │   ├─ Cohere embed-v4.0 → 1 个 1024 维向量\n  │   └─ DashScope tongyi-embedding-vision-plus → 1 个 1152 维向量\n每页图片 → 1 个向量\n  │\n  ▼ (写入 Zilliz Serverless 云向量数据库)\ndoc_name + page_idx + vector\n  │\n  ══════════════ 用户提问时 ══════════════\n  │\n  ▼ (Embedding API 编码查询文本)\n查询 → 1 个向量\n  │\n  ▼ (Zilliz 内积相似度搜索)\nTop-K 最相关页面\n  │\n  ▼ (页面原始图片 + 问题 → 视觉大模型生成回答)\n  │   ├─ DashScope: qwen3.5-flash \u002F qwen3.5-plus \u002F qwen3-vl-plus（国内推荐）\n  │   └─ OpenRouter: qwen\u002Fqwen3.5-397b-a17b（海外可用）\nAI 直接\"看图\"回答问题\n```\n\n### 实际演示：\n\n![](.\u002FScreenShot_2026-05-05_164841_191.png)\n![](.\u002FScreenShot_2026-05-05_180009_428.png) \n\n**核心优势**：\n- 无需本地 GPU，无需安装 PyTorch 或 poppler\n- 所有 AI 计算通过云端 API 完成（Embedding + LLM 均支持 DashScope 和 OpenRouter）\n- Embedding 和 LLM 引擎均可独立切换，国内用户推荐全部使用 DashScope（无需代理）\n- 安装依赖只需几秒钟，9 个轻量 Python 包\n- 面对扫描件 PDF、图文混排文档、含公式与表格的专业资料，都能完整保留所有视觉信息\n\n## 技术栈\n\n| 组件 | 技术 | 说明 |\n|---|---|---|\n| PDF → 图片 | PyMuPDF (fitz) | 纯 Python，无需 poppler，跨平台直接可用 |\n| 图片\u002F文本编码 | [Cohere embed-v4.0](https:\u002F\u002Fdocs.cohere.com\u002Fdocs\u002Fembeddings) \u002F [DashScope](https:\u002F\u002Fdashscope.aliyun.com) | 云端多模态 Embedding，通过 `EMBED_PROVIDER` 切换引擎 |\n| 向量数据库 | [Zilliz Serverless](https:\u002F\u002Fcloud.zilliz.com) (云 Milvus) | IVF_FLAT 索引，内积相似度，零运维 |\n| 生成模型 | [DashScope Qwen](https:\u002F\u002Fdashscope.console.aliyun.com) \u002F [OpenRouter](https:\u002F\u002Fopenrouter.ai) | 多模态视觉大模型，通过 `LLM_PROVIDER` 切换 |\n| Web 界面 | Flask + 原生 HTML\u002FJS | 轻量无框架依赖，无 telemetry，本地运行 |\n\n\n### Embed 4：更懂企业数据的“大力士”\n\n这Embed 4可不是简单的升级，它在前代Embed 3的基础上，狠狠地提升了一把。特别是在处理那些乱七八糟的非结构化数据时，简直就是一把好手。更厉害的是，它拥有高达128,000个token的超长上下文窗口，简单来说，就是能记住更多东西，理论上能给大概200页的文档生成嵌入！\n\nCohere自己也说了，之前的嵌入模型在理解企业那些复杂、多格式的数据时，总是差口气，导致企业得花大量时间做数据预处理，效果还不咋地。Embed 4就是为了解决这个问题而生的，帮助企业员工从一大堆乱七八糟的信息里，快速找到关键信息。\n\n## 项目结构\n\n```\nD:\u002FPDF-AI\u002F\n├── .env                    # 密钥和连接配置（不入库）\n├── .env.example            # 配置模板\n├── .gitignore\n├── requirements.txt        # Python 依赖（9 个包，无 PyTorch）\n├── config.py               # 配置中心，从 .env 加载\n├── app.py                  # Flask Web 服务 + API 路由（端口 7860）\n├── api_server.py           # 独立 API 问答服务（端口 7861，供外部调用）\n├── static\u002F\n│   └── index.html          # 前端页面\n│\n├── core\u002F\n│   ├── embedder.py         # 双引擎 Embedding（Cohere \u002F DashScope）+ 工厂函数\n│   ├── vector_store.py     # Zilliz 向量库：集合管理、插入、搜索\n│   ├── retriever.py        # 单向量检索：编码查询 → 搜索 Zilliz → Top-K 页面\n│   └── generator.py        # 双引擎 LLM 生成（DashScope \u002F OpenRouter）\n│\n└── utils\u002F\n    ├── pdf_processor.py    # PDF 转图片（PyMuPDF 封装，无需 poppler）\n    └── image_utils.py      # 图片转 base64（data URI 供 Cohere，纯 base64 供 DashScope）\n```\n\n## API 接口\n\n### Web 服务（端口 7860）\n\n| 方法 | 路径 | 说明 | 请求体 |\n|---|---|---|---|\n| GET | `\u002Fapi\u002Fdocs` | 获取所有已上传的文档列表 | 无 |\n| POST | `\u002Fapi\u002Fupload` | 上传 PDF 文件 | `multipart\u002Fform-data`，字段 `file` |\n| POST | `\u002Fapi\u002Fencode` | 编码已上传的 PDF 并写入向量库 | `{\"doc_name\": \"xxx.pdf\"}` |\n| POST | `\u002Fapi\u002Fsearch` | 检索相关页面并生成回答 | `{\"question\": \"...\", \"doc_name\": \"xxx.pdf\"}` |\n| POST | `\u002Fapi\u002Fclear` | 清空所有数据（Zilliz + 本地文件） | 无 |\n\n搜索接口返回示例：\n```json\n{\n  \"pages\": [\n    {\"label\": \"report.pdf - 第5页 (相似度: 0.8234)\", \"doc_name\": \"report.pdf\", \"page_idx\": 4},\n    {\"label\": \"report.pdf - 第12页 (相似度: 0.7891)\", \"doc_name\": \"report.pdf\", \"page_idx\": 11}\n  ],\n  \"answer\": \"根据文档内容，该系统的核心区别在于...\"\n}\n```\n\n### 独立 API 问答服务（端口 7861）\n\n`api_server.py` 提供轻量级问答接口，供外部程序集成调用，与 Web 服务共享相同的 Embedding、Zilliz、LLM 流程。\n\n**启动**：\n```bash\npython api_server.py\n```\n\n**查询接口**：\n\n```\nPOST http:\u002F\u002F127.0.0.1:7861\u002Fapi\u002Fquery\nContent-Type: application\u002Fjson\n\n{\n    \"question\": \"什么是积木？\",\n    \"doc_name\": \"LEGO.pdf\"\n}\n```\n\n- `question`（必需）：要问的问题\n- `doc_name`（可选）：限定搜索的文档，不传则搜索全部文档\n\n**返回示例**：\n```json\n{\n    \"answer\": \"积木是一种可拼接的玩具组件...\",\n    \"pages\": [\n        {\"doc_name\": \"LEGO.pdf\", \"page_idx\": 5, \"score\": 0.82},\n        {\"doc_name\": \"LEGO.pdf\", \"page_idx\": 12, \"score\": 0.75}\n    ]\n}\n```\n\n**文档列表接口**：\n```\nGET http:\u002F\u002F127.0.0.1:7861\u002Fapi\u002Fdocs\n```\n\n**调用示例（curl）**：\n```bash\ncurl -X POST http:\u002F\u002F127.0.0.1:7861\u002Fapi\u002Fquery \\\n  -H \"Content-Type: application\u002Fjson\" \\\n  -d '{\"question\": \"文档的主要内容是什么？\"}'\n```\n\n## 功能流程详解\n\n### 数据存储架构\n\n系统有三级存储，各司其职：\n\n```\n┌─────────────────────────────────────────────────────┐\n│  data\u002Fuploads\u002F  (本地磁盘)                            │\n│  ├── report.pdf          原始 PDF 文件，持久保存        │\n│  ├── paper.pdf           服务重启\u002F页面刷新后仍在        │\n│  └── ...                                             │\n├─────────────────────────────────────────────────────┤\n│  Zilliz Cloud  (云端向量库)                           │\n│  集合: pdf_rag_cohere \u002F pdf_rag_dashscope（按引擎自动选择）│\n│  每条记录: doc_name + page_idx + vector(由引擎决定维度)│\n│  已 encode 的文档向量永久保存，不受重启影响             │\n├─────────────────────────────────────────────────────┤\n│  内存 (_image_cache)  (进程级缓存)                     │\n│  doc_name → [PIL.Image, PIL.Image, ...]              │\n│  加速图片访问，按需从 PDF 重新加载                      │\n└─────────────────────────────────────────────────────┘\n```\n\n**为什么刷新页面后仍可搜索**：\n- PDF 文件保存在 `data\u002Fuploads\u002F`，不是临时文件\n- 向量数据存在 Zilliz 云端，不在本地内存\n- 页面加载时，前端调用 `GET \u002Fapi\u002Fdocs` 扫描 `data\u002Fuploads\u002F` 获取文档列表\n- 搜索时，如果图片不在内存缓存，自动从 PDF 文件按需加载\n\n### Zilliz 集合 Schema\n\n```\n集合名: pdf_rag_cohere (Cohere) \u002F pdf_rag_dashscope (DashScope)\n  (可通过 .env 的 COHERE_COLLECTION_NAME \u002F DASHSCOPE_COLLECTION_NAME 配置)\n\n字段:\n  id       INT64       自增主键\n  doc_name VARCHAR(256) PDF 文件名，用于区分不同文档\n  page_idx INT64       页码（从 0 开始）\n  vector   FLOAT_VECTOR(N)     N 由引擎决定：Cohere 1024 \u002F DashScope 1152\n\n索引:\n  字段: vector\n  类型: IVF_FLAT\n  相似度: IP (内积)\n  参数: nlist=128, nprobe=10\n```\n\n### 流程一：文档入库（Upload → Encode → Index）\n\n```\n用户选择 PDF 文件\n  │\n  ▼ POST \u002Fapi\u002Fupload\nFlask 接收文件，保存到 data\u002Fuploads\u002F{filename}.pdf\n  ├── 同名文件会被覆盖\n  └── 清除该文档的内存图片缓存\n  │\n  ▼ 前端下拉框显示文档名\n  │\n用户点击 \"Encode & Index\"\n  │\n  ▼ POST \u002Fapi\u002Fencode {doc_name: \"xxx.pdf\"}\n  │\n  ├── Step 1: PDF → 图片\n  │   PyMuPDF (fitz) 打开 PDF，逐页渲染为 RGB 图片\n  │   DPI 由 config.pdf_dpi 控制（默认 150）\n  │   图片缓存在内存 _image_cache[doc_name]\n  │\n  ├── Step 2: 图片 → 向量 (Embedding API)\n  │   将每张图片缩放到 max_image_size (默认 1200px)\n  │   ├─ DashScope: 转为 JPEG base64，按 8 张\u002F批调用 MultiModalEmbedding.call()\n  │   │   模型: tongyi-embedding-vision-plus，产出 1152 维向量\n  │   └─ Cohere: 转为 JPEG base64 data URI，按 96 张\u002F批调用 embed()\n  │       模型: embed-v4.0，input_type=search_document，产出 1024 维向量\n  │   每页产出 1 个向量（维度由引擎决定）\n  │\n  └── Step 3: 写入 Zilliz\n      每页一条记录: {doc_name, page_idx, vector}\n      如果该文档之前已 encode 过，向量会重复插入\n      （建议 clear 后重新 encode，或后续优化为 upsert）\n```\n\n### 流程二：提问检索（Search → Retrieve → Generate）\n\n```\n用户输入问题，点击 \"Search & Answer\"\n  │\n  ▼ POST \u002Fapi\u002Fsearch {question: \"...\", doc_name: \"xxx.pdf\" | \"__all__\"}\n  │\n  ├── Step 1: 查询编码 (Embedding API)\n  │   ├─ DashScope: MultiModalEmbedding.call(input=[{text: ...}]) → 1152 维\n  │   └─ Cohere: embed(texts=[...], input_type=\"search_query\") → 1024 维\n  │   问题文本 → 1 个向量\n  │\n  ├── Step 2: 向量搜索 (Zilliz)\n  │   用查询向量在 Zilliz 中做内积相似度搜索\n  │   如果指定了 doc_name，添加过滤条件 filter='doc_name == \"xxx\"'\n  │   如果选 \"All Documents\"，不加过滤，跨文档搜索\n  │   返回 Top-K (默认 3) 条结果，每条包含 doc_name + page_idx + score\n  │\n  ├── Step 3: 页面扩展 ±2\n  │   图书内容通常是连续的，一个主题可能跨越多页\n  │   对每个命中页，取其前后各 2 页（即 ±2，共 5 页）\n  │   自动去重：如果两个命中页距离 ≤4 页，交集部分不重复\n  │   自动边界检查：不会超出 PDF 实际页数\n  │   示例：命中第 18 页 → 展开为第 16、17、18、19、20 页\n  │\n  ├── Step 4: 加载页面图片\n  │   根据 (doc_name, page_idx) 获取页面图片:\n  │   优先从内存缓存 _image_cache 读取\n  │   缓存未命中时，从 data\u002Fuploads\u002F{doc_name} 用 PyMuPDF 按需加载\n  │   图片转 PNG base64，返回给前端展示\n  │\n  └── Step 5: LLM 生成回答\n      构建多模态消息:\n      content = [\n        {type: \"image_url\", image_url: {url: \"data:image\u002Fpng;base64,...\"}},\n        ...每张检索到的页面图片...\n        {type: \"text\", text: \"Above are N retrieved document pages. Question: ...\"}\n      ]\n      ├─ DashScope (默认): 调用 dashscope.aliyuncs.com OpenAI 兼容接口\n      │   模型: qwen3.5-flash \u002F qwen3.5-plus \u002F qwen3-vl-plus 等\n      └─ OpenRouter: 调用 openrouter.ai\n          模型: qwen\u002Fqwen3.5-397b-a17b\n      超时: 120 秒\n      LLM 直接\"看\"页面图片生成回答\n```\n\n### 流程三：清空数据（Clear）\n\n```\n用户点击 \"Clear All\"\n  │\n  ▼ POST \u002Fapi\u002Fclear\n  │\n  ├── 删除 Zilliz 集合并重建空集合\n  ├── 删除 data\u002Fuploads\u002F 下所有 PDF 文件\n  └── 清空内存图片缓存\n```\n\n## 快速开始\n\n### 1. 环境要求\n\n- Python 3.10+\n- 无需 GPU、无需 poppler、无需 CUDA\n- 网络能访问 DashScope API、Zilliz Cloud（国内用户推荐全选 DashScope，只需一个 API Key）\n\n### 2. 安装依赖\n\n```bash\npip install -r requirements.txt\n```\n\n共 9 个轻量包（cohere、dashscope、pymilvus、openai、PyMuPDF、pillow、flask、numpy、python-dotenv），安装通常在 1 分钟内完成。\n\n### 3. 获取 API Key\n\n**DashScope**（Embedding + LLM，默认引擎，国内推荐）：前往 [https:\u002F\u002Fdashscope.console.aliyun.com](https:\u002F\u002Fdashscope.console.aliyun.com) 获取 API Key。一个 Key 同时用于 Embedding 和视觉理解。\n\n**Cohere**（Embedding，可选引擎）：前往 [https:\u002F\u002Fdashboard.cohere.com\u002Fapi-keys](https:\u002F\u002Fdashboard.cohere.com\u002Fapi-keys) 注册获取，有免费额度。\n\n**OpenRouter**（LLM，可选引擎）：前往 [https:\u002F\u002Fopenrouter.ai\u002Fsettings\u002Fkeys](https:\u002F\u002Fopenrouter.ai\u002Fsettings\u002Fkeys) 注册获取。\n\n**Zilliz Cloud**（向量数据库，必需）：前往 [https:\u002F\u002Fcloud.zilliz.com](https:\u002F\u002Fcloud.zilliz.com) 创建 Serverless 集群，获取连接地址和 Token。\n\n### 4. 配置 .env\n\n复制 `.env.example` 为 `.env`，填入实际值：\n\n```env\n# Embedding 引擎（dashscope 或 cohere）\nEMBED_PROVIDER=dashscope\n\n# DashScope API Key（Embedding + LLM 共用，国内推荐）\nDASHSCOPE_API_KEY=your-dashscope-api-key\n\n# Cohere API Key（使用 Cohere Embedding 时必需）\nCOHERE_API_KEY=your-cohere-api-key\n\n# LLM 引擎（dashscope 或 openrouter）\nLLM_PROVIDER=dashscope\n\n# DashScope 视觉模型（qwen3.5-flash \u002F qwen3.5-plus \u002F qwen3-vl-plus）\nDASHSCOPE_VL_MODEL=qwen3.5-flash\n\n# OpenRouter API Key（使用 OpenRouter LLM 时必需）\nOPENROUTER_API_KEY=sk-or-v1-your-key-here\n\n# Zilliz Serverless 连接信息（必需）\nMILVUS_HOST=https:\u002F\u002Fyour-cluster-id.serverless.ali-cn-hangzhou.cloud.zilliz.com.cn\nMILVUS_TOKEN=your-zilliz-token-here\n\n# 向量集合名（每个引擎独立，不会冲突）\nCOHERE_COLLECTION_NAME=pdf_rag_cohere\nDASHSCOPE_COLLECTION_NAME=pdf_rag_dashscope\n\n# 索引类型\nINDEX=IVF_FLAT\n```\n\n### 5. 启动\n\n```bash\npython app.py\n```\n\n浏览器访问 `http:\u002F\u002F127.0.0.1:7860`。\n\n## 使用方法\n\n### 第一步：上传并编码 PDF\n\n1. 在 **文档管理** 区域选择 PDF 文件\n2. 点击 **索引入库** 按钮（自动编码刚上传的文件）\n3. 等待状态栏显示完成（编码速度取决于 API 引擎和网络）\n\n### 第二步：提问\n\n1. 在 **提问** 区域输入问题\n2. 通过下拉框选择搜索范围（特定文档或 **全部文档** 跨文档搜索）\n3. 点击 **搜索回答** 按钮\n4. 查看下方 **检索到的页面**（页面图片及相似度分数）和 **回答**（AI 生成）\n\n## 配置参数\n\n所有参数在 `config.py` 中定义，可通过 `.env` 覆盖：\n\n| 参数 | .env 变量 | 默认值 | 说明 |\n|---|---|---|---|\n| `embed_provider` | `EMBED_PROVIDER` | `dashscope` | Embedding 引擎：`dashscope`（国内推荐）或 `cohere` |\n| `dashscope_api_key` | `DASHSCOPE_API_KEY` | — | DashScope API 密钥（Embedding + LLM 共用） |\n| `cohere_api_key` | `COHERE_API_KEY` | — | Cohere API 密钥（使用 Cohere 时必需） |\n| `embed_model` | — | 由 provider 决定 | DashScope: `tongyi-embedding-vision-plus` \u002F Cohere: `embed-v4.0` |\n| `embed_dim` | — | 由 provider 决定 | DashScope: 1152 \u002F Cohere: 1024 |\n| `cohere_batch_size` | — | `96` | Cohere 每批编码页数（DashScope 固定 8 张\u002F次） |\n| `llm_provider` | `LLM_PROVIDER` | `dashscope` | LLM 引擎：`dashscope`（国内推荐）或 `openrouter` |\n| `dashscope_vl_model` | `DASHSCOPE_VL_MODEL` | `qwen3.5-flash` | DashScope 视觉模型（`qwen3.5-flash` \u002F `qwen3.5-plus` \u002F `qwen3-vl-plus`） |\n| `openrouter_api_key` | `OPENROUTER_API_KEY` | — | OpenRouter API 密钥（使用 OpenRouter 时必需） |\n| `generation_model` | — | 由 provider 决定 | DashScope: `qwen3.5-flash` \u002F OpenRouter: `qwen\u002Fqwen3.5-397b-a17b` |\n| `top_k` | — | `3` | 检索返回的页面数 |\n| `llm_max_tokens` | — | `1024` | LLM 最大输出 token 数 |\n| `llm_temperature` | — | `0.7` | LLM 生成温度 |\n| `pdf_dpi` | — | `150` | PDF 渲染 DPI |\n| `max_image_size` | — | `1200` | 图片最大边长 px |\n| `milvus_uri` | `MILVUS_HOST` | — | Zilliz 连接地址（必需） |\n| `milvus_token` | `MILVUS_TOKEN` | — | Zilliz 认证 Token（必需） |\n| `cohere_collection_name` | `COHERE_COLLECTION_NAME` | `pdf_rag_cohere` | Cohere 引擎的向量集合名 |\n| `dashscope_collection_name` | `DASHSCOPE_COLLECTION_NAME` | `pdf_rag_dashscope` | DashScope 引擎的向量集合名 |\n| `index_type` | `INDEX` | `IVF_FLAT` | 向量索引类型 |\n\n## 常见问题\n\n### Q: 启动报错 `No module named 'cohere'` \u002F `No module named 'fitz'`\n\n```bash\npip install -r requirements.txt\n```\n\n确保使用 Python 3.10+ 并在正确的虚拟环境中安装。\n\n### Q: 编码成功但搜索无结果 \u002F 显示 0 vectors\n\n检查集合名是否正确，切换 Embedding 引擎后需重新编码文档。\n\n### Q: Cohere API 报错 401 \u002F 429\n\n- 401：API Key 无效，检查 `COHERE_API_KEY` 是否正确\n- 429：超出免费额度，前往 [Cohere 控制台](https:\u002F\u002Fdashboard.cohere.com) 查看用量\n- 403：网络被拦截（国内常见），切换为 DashScope 引擎：`EMBED_PROVIDER=dashscope`\n\n### Q: DashScope API 报错\n\n- 检查 `DASHSCOPE_API_KEY` 是否正确\n- 前往 [DashScope 控制台](https:\u002F\u002Fdashscope.console.aliyun.com) 查看用量和余额\n\n### Q: 切换 Embedding 引擎后搜索报错维度不匹配\n\n不会发生。每个引擎使用独立的集合（`pdf_rag_cohere` \u002F `pdf_rag_dashscope`），切换引擎后自动连接对应集合，数据互不干扰。但每个集合的文档需要独立 encode。\n\n### Q: Zilliz 连接失败\n\n检查 `.env` 中的 `MILVUS_HOST` 和 `MILVUS_TOKEN`。确认 Zilliz Serverless 集群已创建且处于运行状态。\n\n### Q: Zilliz 报错 `Insert missed a field`\n\n集合 schema 与代码不匹配（可能是旧集合）。在 `.env` 中更换 `COHERE_COLLECTION_NAME` 或 `DASHSCOPE_COLLECTION_NAME` 为一个新名称即可。\n\n### Q: OpenRouter 返回错误\n\n检查 `OPENROUTER_API_KEY` 是否有效，账户是否有余额。如国内访问不稳定，可切换 `LLM_PROVIDER=dashscope`。\n\n### Q: DashScope 视觉模型返回错误\n\n- 检查 `DASHSCOPE_API_KEY` 是否正确（与 Embedding 共用同一个 Key）\n- 确认 `DASHSCOPE_VL_MODEL` 模型名称有效（`qwen3.5-flash` \u002F `qwen3.5-plus` \u002F `qwen3-vl-plus`）\n\n### Q: 图片太大导致 Cohere 报错\n\nCohere 限制单张图片最大 5MB。系统默认将图片缩放到 1200px，如仍超出可调小 `config.py` 中的 `max_image_size`。\n\n### Q: 检索结果不准确\n\n- 尝试增大 `top_k`（如改为 5）\n- 确保问题语言与 PDF 内容语言匹配\n- 对于扫描件，适当提高 `pdf_dpi`（如 200 或 300）\n\n## 开发指南\n\n### 模块依赖关系\n\n```\napp.py (Flask Web 服务:7860, 5 个 API 路由 — 文档管理 + 问答)\napi_server.py (独立 API 服务:7861, 2 个 API 路由 — 问答接口，供外部调用)\n  ├── config.py (配置中心, 从 .env 加载变量)\n  ├── utils\u002Fpdf_processor.py (PDF → 图片, PyMuPDF)\n  ├── core\u002Fvector_store.py (Zilliz 操作)\n  ├── core\u002Fembedder.py (双引擎 Embedding: Cohere \u002F DashScope, create_embedder() 工厂)\n  │     ├── utils\u002Fimage_utils.py (图片 → base64, data URI \u002F 纯 base64 两种格式)\n  │     └── config.py\n  ├── core\u002Fretriever.py (单向量检索)\n  │     ├── core\u002Fembedder.py\n  │     └── core\u002Fvector_store.py\n  └── core\u002Fgenerator.py (双引擎 LLM: DashScope \u002F OpenRouter)\n        ├── utils\u002Fimage_utils.py\n        └── config.py\n```\n\n### 各模块职责\n\n**`config.py`** — 配置中心。`Settings` dataclass 定义所有参数，`_load_env()` 从 `.env` 读取变量覆盖默认值，`_resolve_provider()` 根据 `EMBED_PROVIDER` 和 `LLM_PROVIDER` 分别解析 Embedding 和 LLM 的活跃配置。全局单例 `settings` 供所有模块导入。\n\n**`utils\u002Fpdf_processor.py`** — `pdf_to_images(pdf_path, dpi)` 用 PyMuPDF 将 PDF 逐页渲染为 PIL Image 列表。`get_page_count(pdf_path)` 快速获取 PDF 总页数（不加载图片），用于页面扩展的边界检查。无需 poppler。\n\n**`utils\u002Fimage_utils.py`** — `image_to_data_uri(img, max_size, fmt)` 将 PIL Image 缩放后转为 base64 data URI（Cohere 使用）。`pil_to_base64(img, max_size, fmt)` 返回纯 base64 字符串（DashScope 使用）。\n\n**`core\u002Fembedder.py`** — 双引擎 Embedding 封装。`BaseEmbedder` 定义接口，`CohereEmbedder` 封装 Cohere embed-v4.0 API，`DashScopeEmbedder` 封装 DashScope tongyi-embedding-vision-plus API。`create_embedder()` 工厂函数根据 `settings.embed_provider` 返回对应实例。两个引擎均实现 `encode_images()` 和 `encode_query()`，并内置速率限制。\n\n**`core\u002Fvector_store.py`** — `VectorStore` 封装 Zilliz 操作。首次连接自动创建集合。`insert_pages()` 按页插入向量，`search()` 支持按 doc_name 过滤。用 MilvusClient 的 search 方法做内积搜索。\n\n**`core\u002Fretriever.py`** — `Retriever` 组合 embedder + vector_store。`retrieve()` 依次调用编码查询 → 搜索 Zilliz → 返回 `RetrievalResult` 列表。`expand_pages()` 对命中结果做 ±2 页扩展，自动去重和边界检查，让 LLM 获得更完整的上下文。\n\n**`core\u002Fgenerator.py`** — 双引擎 LLM 生成。`AnswerGenerator` 根据 `settings.llm_provider` 初始化 OpenAI 兼容客户端：DashScope 走 `dashscope.aliyuncs.com\u002Fcompatible-mode\u002Fv1`，OpenRouter 走 `openrouter.ai\u002Fapi\u002Fv1`。两者消息格式完全相同，`generate()` 构建多模态消息（图片 base64 + 文本 prompt），发给视觉大模型生成回答。\n\n**`app.py`** — Flask Web 服务（端口 7860）。`data\u002Fuploads\u002F` 持久化 PDF 文件，`_image_cache` 内存缓存页面图片（按需从 PDF 加载）。5 个 API 路由，每个都有 try\u002Fexcept + logging。组件懒加载（首次使用时初始化）。\n\n**`api_server.py`** — 独立 API 问答服务（端口 7861）。提供 `\u002Fapi\u002Fquery` 和 `\u002Fapi\u002Fdocs` 两个接口，供外部程序集成调用。与 `app.py` 共享相同的 Embedding、Zilliz、LLM 流程和 `data\u002Fuploads\u002F` 数据目录，但不包含文档管理和前端。两个服务可同时运行，互不冲突。\n\n### 扩展方向\n\n- **添加新 LLM 后端**：在 `core\u002Fgenerator.py` 中添加新的 `base_url` 分支，支持 OpenAI \u002F Azure \u002F 本地模型\n- **添加新 Embedding 引擎**：在 `core\u002Fembedder.py` 中继承 `BaseEmbedder`，实现 `encode_images()` 和 `encode_query()`，并在 `create_embedder()` 工厂中注册\n- **本地 Embedding 回退**：如需离线使用，可继承 `BaseEmbedder` 接入 ColQwen2 本地模型（需安装 PyTorch）\n- **添加对话历史**：在 `app.py` 中增加会话管理，维护多轮对话上下文\n- **前端升级**：`static\u002Findex.html` 为纯原生 HTML\u002FJS，可按需引入 Vue\u002FReact 或替换为其他前端框架\n\n### 参考和致谢\n\n本项目参考了：https:\u002F\u002Fmp.weixin.qq.com\u002Fs\u002Fqf_u3eAseYNyMlyTpXk51Q ，但是技术选型和功能都不同，可以看看文章内容。\n致谢： https:\u002F\u002Flinux.do ，感谢佬友一直支持\n\n## License\n\nMIT\n","Multimodal-RAG 是一个基于多模态 Embedding 和 Zilliz 的视觉理解系统，支持 Cohere\u002FDashScope Embedding 和 DashScope\u002FOpenRouter LLM 双引擎切换。用户上传 PDF 文件后，可以通过自然语言提问，系统会自动检索最相关的页面并由 AI 生成回答。与传统 RAG 不同，该项目直接将 PDF 页面当作图片处理，通过视觉 Embedding 模型编码，完整保留表格、图表、排版和手写批注等所有视觉信息。该系统适合处理扫描件 PDF、图文混排文档以及含公式和表格的专业资料，特别适用于需要保留文档视觉信息的场景。技术上，项目使用 PyMuPDF 将 PDF 转为图片，利用云端 API 完成所有 AI 计算，并通过 Zilliz Serverless 云向量数据库进行高效索引和搜索，无需本地 GPU 和复杂依赖。","2026-06-11 04:03:04","CREATED_QUERY"]