From 01ecc3fa6d6ecff4a53f6d1eb223fc71b80c3026 Mon Sep 17 00:00:00 2001 From: EduBrain Dev Date: Tue, 14 Apr 2026 13:44:35 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0=20README.md=20?= =?UTF-8?q?=E5=92=8C=20IMAGE=5FAPI=5FGUIDE.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit README.md: - 新增企业微信机器人功能说明(搜索/图片入库/集成启动) - 新增图片去重和管理功能说明 - 新增可配置参数说明 - 更新 API 概览(删除/去重/机器人管理接口) - 更新项目结构和技术栈 IMAGE_API_GUIDE.md: - 新增删除图片接口文档 - 新增一键去重接口文档 - 新增企业微信机器人相关接口文档 - 更新上传接口返回格式(含 duplicate 状态) - 更新注意事项(去重/删除相关) --- README.md | 69 +++++++++++++++-- docs/IMAGE_API_GUIDE.md | 164 +++++++++++++++++++++++++++++++++++----- 2 files changed, 205 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index cd003d2..ebed8ab 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # EduBrain - 中文直播教育知识库系统 -基于 FastAPI 的中文直播教育知识库系统,支持直播文字稿导入、OCR 截图识别、AI 语义搜索等功能。 +基于 FastAPI 的中文直播教育知识库系统,支持直播文字稿导入、OCR 截图识别、AI 语义搜索、企业微信机器人等功能。 ## 功能特性 @@ -13,13 +13,23 @@ ### 图片 OCR - **OCR 识别**: DeepSeek Vision 模型识别图片文字 - **关键词提取**: MiniMax M2.7 自动提取内容标签 -- **批量处理**: 支持拖拽上传、选择文件、选择文件夹,按文件名去重 +- **批量处理**: 支持拖拽上传、选择文件、选择文件夹 +- **上传去重**: OCR 识别后自动检查内容是否已存在,避免重复录入 +- **图片管理**: 网页端管理所有图片,支持一键去重、手动删除 - **BM25 索引**: 内置倒排索引搜索引擎,jieba 中文分词 ### AI 搜索 - **知识库搜索**: 向量语义搜索 + 全文检索混合排序 - **图片搜索**: MiniMax M2.7 语义匹配,SSE 流式实时返回结果 -- **并发池**: 最多 10 个 LLM 请求并发,每批 10 张图片判断 +- **并发池**: 最多 10 个 LLM 请求并发,每批数量可配置 +- **可配置参数**: 搜索返回数量、LLM 批量判断数量均可在网页端设置 + +### 企业微信机器人 +- **WebSocket 长连接**: 基于官方 SDK,无需公网 IP +- **关键词搜索**: 用户发文字 → 搜索图片 → 返回匹配结果 +- **图片自动入库**: 用户发图片 → 自动 OCR 识别 → 录入知识库(自动去重) +- **集成启动**: 后端启动时自动检测配置并拉起机器人 +- **网页端管理**: 在设置页面配置 Bot ID / Secret,支持在线重启 ### 其他 - **MCP 协议**: 支持 Model Context Protocol,可被 AI Agent 调用 @@ -36,6 +46,7 @@ | OCR | DeepSeek Vision / PaddleOCR / 阿里云 / 腾讯云 | | LLM | MiniMax M2.7 / DeepSeek / Qwen3-8B | | 搜索 | BM25 倒排索引 + 向量语义搜索 | +| 企业微信 | wecom-aibot-python-sdk (WebSocket 长连接) | | 部署 | Docker + Docker Compose | ## 项目结构 @@ -43,7 +54,7 @@ ``` edu-brain/ ├── app/ -│ ├── main.py # FastAPI 入口,路由注册,生命周期管理 +│ ├── main.py # FastAPI 入口,路由注册,生命周期管理,BotManager │ ├── config.py # pydantic-settings 配置管理 │ ├── database.py # 数据库连接(PG/SQLite 双后端) │ ├── models/base.py # ORM 模型(KnowledgePage, OCRImage 等) @@ -51,9 +62,9 @@ edu-brain/ │ ├── api/v1/ # API 路由 │ │ ├── pages.py # 知识页面 CRUD │ │ ├── search.py # 语义搜索 -│ │ ├── images.py # 图片 OCR + AI 搜索(SSE) +│ │ ├── images.py # 图片 OCR + AI 搜索(SSE)+ 去重 + 删除 │ │ ├── import_export.py # 文件导入导出 -│ │ └── settings.py # 系统设置 +│ │ └── settings.py # 系统设置 + 机器人管理 │ ├── services/ │ │ ├── ocr_service.py # OCR 识别服务(多 Provider) │ │ ├── llm_service.py # LLM 服务(标签提取/搜索匹配) @@ -62,6 +73,7 @@ edu-brain/ │ │ ├── embedding_service.py # 嵌入模型服务 │ │ ├── import_service.py # 文件导入服务 │ │ └── page_service.py # 页面 CRUD 服务 +│ ├── wework_bot.py # 企业微信智能机器人服务 │ └── mcp/server.py # MCP Server 实现 ├── static/index.html # 前端单页应用 ├── data/images/ # 图片存储目录 @@ -106,6 +118,11 @@ DEEPSEEK_OCR_MODEL=deepseek-ocr:latest MINIMAX_API_KEY=your-minimax-api-key MINIMAX_BASE_URL=https://api.minimaxi.com/v1 MINIMAX_CHAT_MODEL=MiniMax-M2.7 + +# 企业微信机器人(可选,不配则不启动) +WEWORK_BOT_ENABLED=true +WEWORK_BOT_ID=your-bot-id +WEWORK_BOT_SECRET=your-bot-secret ``` ### 3. 启动服务 @@ -114,6 +131,8 @@ MINIMAX_CHAT_MODEL=MiniMax-M2.7 uvicorn app.main:app --host 0.0.0.0 --port 8765 ``` +启动后会自动检测企业微信机器人配置,如果 `WEWORK_BOT_ENABLED=true` 且 `WEWORK_BOT_ID` / `WEWORK_BOT_SECRET` 已配置,机器人会自动在后台线程启动。 + 访问 http://localhost:8765 即可使用。 ### 4. Docker 部署 @@ -127,16 +146,39 @@ docker-compose up -d ``` 用户上传图片 → DeepSeek Vision OCR 识别文字 + → 检查 OCR 内容是否已存在(去重) → MiniMax M2.7 提取关键词标签 → 存入 SQLite + BM25 索引 用户搜索 "孩子不想上学" - → 从 DB 倒序取图片(每批 10 张) - → 并发池(最多 10 个请求)发给 MiniMax M2.7 判断 + → 从 DB 倒序取图片(每批 N 张,可配置) + → 并发池发给 MiniMax M2.7 判断 → 匹配结果通过 SSE 实时推送到前端 → 找够指定数量或遍历完 DB 后结束 ``` +## 企业微信机器人 + +### 功能 + +| 场景 | 说明 | +|------|------| +| 单聊发关键词 | 搜索图片知识库,返回匹配结果(图片+预览文字) | +| 单聊发图片 | 自动 OCR 识别并录入知识库(自动去重) | +| 进入会话 | 自动发送欢迎语 | + +### 配置 + +1. 在企业微信管理后台创建智能机器人,开启 API 模式(长连接) +2. 获取 Bot ID 和 Secret +3. 在 `.env` 中配置或通过网页端设置页面配置 + +### 注意事项 + +- 图片消息**仅支持单聊**,群聊中发图片不会触发机器人 +- 机器人支持多人同时使用(asyncio 异步架构) +- 配置修改后可通过网页端「重启机器人」按钮生效 + ## API 概览 | 方法 | 路径 | 说明 | @@ -144,12 +186,18 @@ docker-compose up -d | POST | `/api/v1/images/recognize-direct` | 上传并识别图片 | | POST | `/api/v1/images/batch-recognize` | 批量上传并识别 | | GET | `/api/v1/images/search` | AI 搜索图片(SSE 流式) | +| GET | `/api/v1/images` | 获取图片列表(分页) | | GET | `/api/v1/images/{id}` | 获取图片识别结果 | +| DELETE | `/api/v1/images/{id}` | 删除图片 | +| POST | `/api/v1/images/dedup` | 一键去重 | | GET | `/api/v1/pages` | 知识页面列表 | | POST | `/api/v1/pages` | 创建知识页面 | | POST | `/api/v1/search` | 语义搜索知识库 | | POST | `/api/v1/import/file` | 导入文件 | | GET | `/api/v1/settings` | 获取系统设置 | +| PUT | `/api/v1/settings` | 更新系统设置 | +| GET | `/api/v1/settings/bot/status` | 获取机器人状态 | +| POST | `/api/v1/settings/bot/restart` | 重启机器人 | ## 配置说明 @@ -173,4 +221,9 @@ docker-compose up -d | `dashscope` | 阿里云 DashScope | `DASHSCOPE_API_KEY` | | `local_bge` | 本地 BGE 模型 | `LOCAL_BGE_MODEL_PATH` | +### 网页端可配置参数 +| 参数 | 说明 | 默认值 | 范围 | +|------|------|--------|------| +| 搜索返回数量 | 每次搜索最多返回多少条匹配结果 | 3 | 1-10 | +| LLM 批量判断数量 | 每批发给 LLM 判断的图片数量 | 10 | 1-50 | diff --git a/docs/IMAGE_API_GUIDE.md b/docs/IMAGE_API_GUIDE.md index bd14c2d..b70b72c 100644 --- a/docs/IMAGE_API_GUIDE.md +++ b/docs/IMAGE_API_GUIDE.md @@ -20,8 +20,10 @@ BASE_URL = http://localhost:8765 | POST | `/batch-recognize` | 批量上传并识别 | multipart/form-data | | POST | `/import-paths` | 从服务器路径导入 | JSON | | GET | `/search` | AI 搜索图片(SSE 流式) | query params | -| GET | `/{image_id}` | 获取单张图片详情 | path param | | GET | `` | 获取图片列表(分页) | query params | +| GET | `/{image_id}` | 获取单张图片详情 | path param | +| DELETE | `/{image_id}` | 删除图片 | path param | +| POST | `/dedup` | 一键去重 | 无 | --- @@ -42,6 +44,8 @@ Content-Type: multipart/form-data ### 返回 +**正常识别:** + ```json { "id": 386, @@ -58,6 +62,21 @@ Content-Type: multipart/form-data } ``` +**内容重复(自动去重):** + +```json +{ + "id": 386, + "file_path": "data/images/test.png", + "original_filename": "test.png", + "status": "duplicate", + "duplicate_of": 100, + "ocr_text": "识别出的文字内容..." +} +``` + +> `duplicate_of` 指向数据库中已存在的相同内容记录 ID。 + ### Python 调用示例 ```python @@ -70,7 +89,10 @@ async def recognize_image(file_path: str) -> dict: f"{BASE_URL}/api/v1/images/recognize-direct", files={"file": (file_path, f)} ) - return resp.json() + result = resp.json() + if result.get("status") == "duplicate": + print(f"内容已存在,重复于 ID: {result['duplicate_of']}") + return result ``` --- @@ -96,7 +118,8 @@ Content-Type: multipart/form-data { "total": 3, "success": 2, - "failed": 1, + "failed": 0, + "duplicates": 1, "results": [ { "id": 387, @@ -110,8 +133,15 @@ Content-Type: multipart/form-data }, { "id": 388, - "filename": "img2.jpg", - "file_path": "data/images/img2.jpg", + "filename": "img2.png", + "status": "duplicate", + "duplicate_of": 100, + "ocr_text": "重复内容..." + }, + { + "id": 389, + "filename": "img3.jpg", + "file_path": "data/images/img3.jpg", "status": "failed", "message": "OCR 识别失败: ..." } @@ -160,7 +190,7 @@ GET /api/v1/images/search?keyword=搜索词&limit=5&sort=time_desc | 参数 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | keyword | string | 是 | - | 搜索关键词 | -| limit | int | 否 | 5 | 最多返回多少条匹配结果 | +| limit | int | 否 | 3 | 最多返回多少条匹配结果(可在设置中配置默认值) | | sort | string | 否 | time_desc | 排序方式:`time_desc`(时间倒序)、`time_asc`(时间正序)、`random`(随机) | ### 搜索原理 @@ -168,7 +198,7 @@ GET /api/v1/images/search?keyword=搜索词&limit=5&sort=time_desc ``` 用户搜索 "孩子不想上学" → 从数据库取所有已识别图片(按 sort 排序) - → 每批 10 张发给 MiniMax M2.7 判断是否匹配 + → 每批 N 张发给 MiniMax M2.7 判断(N 可在设置中配置,默认 10) → 并发池最多 10 个 LLM 请求同时执行 → 匹配结果通过 SSE 实时推送(找到就立刻返回) → 找够 limit 条或遍历完所有图片后结束 @@ -260,7 +290,7 @@ async def search_images( ) -> AsyncGenerator[dict, None]: """ AI 搜索图片(SSE 流式) - + Yields: dict: 每个 SSE 事件解析后的字典 """ @@ -281,23 +311,23 @@ async def search_images( async def demo_search(): results = [] - + async for event in search_images("孩子不想上学", limit=5): event_type = event.get("type") - + if event_type == "start": print(f"开始搜索: {event['keyword']}") - + elif event_type == "progress": print(f"进度: {event['checked']}/{event['total']},已找到 {event['found']} 条") - + elif event_type == "result": results.append(event) print(f"找到匹配: [{event['id']}] {event['ocr_text_preview'][:50]}") - + elif event_type == "done": print(f"搜索完成: 共找到 {event['total_found']} 条,检查了 {event['total_checked']} 张") - + return results ``` @@ -310,10 +340,10 @@ from google.adk import Tool class ImageSearchTool(Tool): """搜索图片知识库""" - + name = "search_images" description = "搜索已识别的图片库,返回与查询语义相关的图片及其 Base64 数据。支持自然语言查询。" - + async def execute(self, keyword: str, limit: int = 5) -> str: results = [] async for event in search_images(keyword, limit=limit): @@ -327,10 +357,10 @@ class ImageSearchTool(Tool): }) elif event["type"] == "done": pass # 搜索结束 - + if not results: return f"未找到与 '{keyword}' 相关的图片" - + # 返回 JSON,方便 Agent 后续处理(如发送到企业微信) return json.dumps({ "keyword": keyword, @@ -413,7 +443,55 @@ GET /api/v1/images?page=1&page_size=20&status=completed --- -## 八、图片文件访问 +## 八、删除图片 + +### 接口 + +``` +DELETE /api/v1/images/{image_id} +``` + +### 返回 + +成功返回 `204 No Content`。 + +### 说明 + +删除操作会同时清理: +1. BM25 倒排索引中的记录 +2. 磁盘上的图片文件 +3. 数据库中的记录 + +--- + +## 九、一键去重 + +### 接口 + +``` +POST /api/v1/images/dedup +``` + +### 返回 + +```json +{ + "total_checked": 385, + "duplicates_found": 12, + "deleted": 12, + "kept": 373 +} +``` + +### 说明 + +- 按 OCR 识别文本精确匹配去重 +- 每组重复内容保留最早录入的记录 +- 删除操作与单条删除相同(清理索引 + 磁盘文件 + 数据库记录) + +--- + +## 十、图片文件访问 识别后的图片可通过静态文件路径访问: @@ -428,7 +506,51 @@ http://localhost:8765/data/images/001.png --- -## 九、注意事项 +## 十一、企业微信机器人相关接口 + +### 获取机器人状态 + +``` +GET /api/v1/settings/bot/status +``` + +```json +{ + "enabled": true, + "configured": true, + "running": true, + "bot_id": "aibPl3zE65xj..." +} +``` + +### 重启机器人 + +``` +POST /api/v1/settings/bot/restart +``` + +```json +{ + "success": true, + "message": "机器人已启动" +} +``` + +### 系统设置(含机器人配置) + +``` +GET /api/v1/settings +PUT /api/v1/settings +``` + +机器人相关字段: +- `wework_bot_enabled` (bool): 是否启用 +- `wework_bot_id` (string): Bot ID +- `wework_bot_secret` (string): Secret(更新时传入明文,获取时返回脱敏值) + +--- + +## 十二、注意事项 1. **搜索接口是 SSE 流式**,不能用普通的 `requests.get()` 或 `httpx.get()` 直接获取 JSON,必须用流式读取(`stream()` / `iter_lines()`) 2. **搜索耗时较长**:385 张图片全部遍历约需 2-5 分钟,建议设置较长的超时(300 秒) @@ -436,3 +558,5 @@ http://localhost:8765/data/images/001.png 4. **limit 参数控制返回数量**:找到 `limit` 条匹配后自动停止搜索 5. **OCR 识别耗时**:单张图片约 5-15 秒,建议设置 120 秒超时 6. **文件格式**:仅支持 .png .jpg .jpeg .bmp .webp +7. **上传自动去重**:所有上传接口(recognize-direct、batch-recognize、import-paths)都会在 OCR 识别后检查内容是否已存在 +8. **删除不可恢复**:删除操作会同时清理索引、磁盘文件和数据库记录