From 1e327ea92f2d337614531291edaf054b897ef074 Mon Sep 17 00:00:00 2001 From: wh Date: Wed, 15 Apr 2026 11:09:09 +0800 Subject: [PATCH] =?UTF-8?q?=E5=81=9C=E6=AD=A2=E8=BF=BD=E8=B8=AApycache?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + README.md | 419 ++++++++++++++++++++++++++++++++++++++++++++++++++++ app/main.py | 4 + 3 files changed, 424 insertions(+) diff --git a/.gitignore b/.gitignore index 15c313d..fbbd2d6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ __pycache__/ *.pyo *.pyd .Python +*$py.class *.egg-info/ dist/ build/ diff --git a/README.md b/README.md index e69de29..13fca10 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,419 @@ +# label_ai_service + +`label_ai_service` 是知识图谱智能标注平台的 AI 计算服务,基于 FastAPI 提供独立部署的推理与预处理能力。它不直接访问数据库,而是通过 ZhipuAI GLM 系列模型完成结构化抽取,通过 RustFS 读写原始文件和处理结果,并通过 HTTP 回调把异步视频任务结果通知上游后端。 + +当前服务覆盖 6 类核心能力: + +- 文本三元组提取,支持 `TXT`、`PDF`、`DOCX` +- 图像四元组提取,并自动裁剪 `bbox` 区域图 +- 视频抽帧,支持固定间隔和近似关键帧两种模式 +- 视频转文本,将视频片段描述输出为文本文件 +- 基于文本或图片证据生成问答对 +- 向 ZhipuAI 提交微调任务并查询状态 + +## 适用场景 + +这个服务适合作为 `label-backend` 的 AI 能力侧车服务,也可以单独运行,用于验证文件解析、图像理解、视频预处理和问答生成流程。 + +典型调用链如下: + +1. Java 后端把原始文本、图片或视频上传到 RustFS。 +2. Java 后端调用 `label_ai_service` 的 REST API。 +3. AI 服务从 RustFS 读取文件,调用 GLM 模型做抽取或生成。 +4. 结果以 JSON 返回,或写回 RustFS 后通过回调通知上游。 + +## 功能概览 + +| 能力 | 接口 | 说明 | +|---|---|---| +| 健康检查 | `GET /health` | 用于容器存活探测和联调自检 | +| 文本三元组提取 | `POST /api/v1/text/extract` | 从文档中提取 `subject / predicate / object / source_snippet / source_offset` | +| 图像四元组提取 | `POST /api/v1/image/extract` | 从图片中提取 `subject / predicate / object / qualifier / bbox`,并输出裁剪图路径 | +| 视频抽帧 | `POST /api/v1/video/extract-frames` | 异步抽取视频帧,结果通过回调返回 | +| 视频转文本 | `POST /api/v1/video/to-text` | 异步抽样视频代表帧,生成中文描述文本并上传到对象存储 | +| 文本问答生成 | `POST /api/v1/qa/gen-text` | 基于三元组和原文证据生成问答对 | +| 图像问答生成 | `POST /api/v1/qa/gen-image` | 基于裁剪图和四元组生成问答对 | +| 微调任务提交 | `POST /api/v1/finetune/start` | 向 ZhipuAI 提交微调任务 | +| 微调状态查询 | `GET /api/v1/finetune/status/{job_id}` | 查询微调任务状态和进度 | + +## 技术栈 + +- Python 3.12 +- FastAPI +- Pydantic v2 +- ZhipuAI Python SDK +- boto3 +- OpenCV +- pdfplumber +- python-docx +- httpx +- pytest / pytest-asyncio + +## 架构说明 + +### 外部依赖 + +- ZhipuAI + - 文本与多模态推理 + - 微调任务提交与查询 +- RustFS 或任意 S3 兼容对象存储 + - 原始文件读取 + - 裁剪图、视频帧、视频描述文本写回 +- 上游回调接口 + - 视频任务完成后接收结果 + +### 处理边界 + +- 服务本身不负责文件上传,也不维护任务状态库。 +- 文本、图像接口是同步返回。 +- 视频接口是异步返回 `202 Accepted`,真实处理结果走回调。 +- 服务默认不做鉴权,通常由上游网关或后端负责访问控制。 + +## 项目结构 + +```text +label_ai_service/ +├── app/ +│ ├── main.py +│ ├── clients/ +│ │ ├── llm/ +│ │ └── storage/ +│ ├── core/ +│ ├── models/ +│ ├── routers/ +│ └── services/ +├── docs/ +│ └── superpowers/ +├── specs/ +├── tests/ +├── config.yaml +├── .env +├── Dockerfile +├── docker-compose.yml +├── requirements.txt +└── README.md +``` + +目录职责: + +- `app/main.py` + - FastAPI 应用入口,注册中间件、异常处理器和所有路由 +- `app/clients` + - 第三方依赖适配层,当前包含 ZhipuAI 和 RustFS +- `app/services` + - 业务核心逻辑,负责文件解析、提示词拼装、结果转换和异步任务处理 +- `app/routers` + - HTTP 接口层 +- `app/models` + - 请求与响应模型 +- `app/core` + - 配置、日志、中间件、异常等通用模块 +- `tests` + - Router、Service、Config 和 Client 的测试 + +## 配置说明 + +配置采用 `config.yaml + .env` 分层方式: + +- `config.yaml` + - 存放稳定、可提交的结构化配置 +- `.env` + - 存放密钥和环境差异项 + +环境变量会覆盖 `config.yaml` 中的同名配置。 + +### config.yaml + +当前项目默认配置如下: + +```yaml +server: + port: 8000 + log_level: INFO + +storage: + buckets: + source_data: "source-data" + finetune_export: "finetune-export" + +backend: {} + +video: + frame_sample_count: 8 + max_file_size_mb: 200 + keyframe_diff_threshold: 30.0 + +models: + default_text: "glm-4-flash" + default_vision: "glm-4v-flash" +``` + +### .env + +建议至少配置这些变量: + +| 变量名 | 必填 | 说明 | +|---|---|---| +| `ZHIPUAI_API_KEY` | 是 | ZhipuAI API Key | +| `STORAGE_ACCESS_KEY` | 是 | RustFS/S3 Access Key | +| `STORAGE_SECRET_KEY` | 是 | RustFS/S3 Secret Key | +| `STORAGE_ENDPOINT` | 是 | RustFS/S3 Endpoint,例如 `http://rustfs:9000` | +| `BACKEND_CALLBACK_URL` | 否 | 视频异步任务回调地址 | +| `LOG_LEVEL` | 否 | 日志级别,默认 `INFO` | +| `MAX_VIDEO_SIZE_MB` | 否 | 覆盖视频大小上限 | + +`.env` 示例: + +```ini +ZHIPUAI_API_KEY=your-zhipuai-api-key-here +STORAGE_ACCESS_KEY=your-storage-access-key +STORAGE_SECRET_KEY=your-storage-secret-key +STORAGE_ENDPOINT=http://rustfs:9000 +BACKEND_CALLBACK_URL=http://label-backend:8080/api/ai/callback +LOG_LEVEL=INFO +``` + +## 本地运行 + +### 方式一:直接运行 + +```bash +python -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt +python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload +``` + +Windows PowerShell 可以使用: + +```powershell +python -m venv .venv +.\.venv\Scripts\Activate.ps1 +pip install -r requirements.txt +python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload +``` + +启动后访问: + +- Swagger UI: `http://localhost:8000/docs` +- 健康检查: `http://localhost:8000/health` + +### 方式二:Docker Compose + +项目自带的 Compose 文件会启动: + +- `ai-service` +- `rustfs` + +启动命令: + +```bash +docker compose up --build +``` + +如果你要联调视频异步任务,请确保 `BACKEND_CALLBACK_URL` 指向一个可访问的后端地址。否则任务本身会继续处理,但回调会失败并记录错误日志。 + +## API 使用示例 + +### 1. 健康检查 + +```bash +curl http://localhost:8000/health +``` + +返回: + +```json +{"status":"ok"} +``` + +### 2. 文本三元组提取 + +```bash +curl -X POST http://localhost:8000/api/v1/text/extract \ + -H "Content-Type: application/json" \ + -d '{ + "file_path": "text/202404/123.txt", + "file_name": "设备规范.txt" + }' +``` + +### 3. 图像四元组提取 + +```bash +curl -X POST http://localhost:8000/api/v1/image/extract \ + -H "Content-Type: application/json" \ + -d '{ + "file_path": "image/202404/456.jpg", + "task_id": 789 + }' +``` + +### 4. 视频抽帧 + +```bash +curl -X POST http://localhost:8000/api/v1/video/extract-frames \ + -H "Content-Type: application/json" \ + -d '{ + "file_path": "video/202404/001.mp4", + "source_id": 10, + "job_id": 42, + "mode": "interval", + "frame_interval": 30 + }' +``` + +### 5. 视频转文本 + +```bash +curl -X POST http://localhost:8000/api/v1/video/to-text \ + -H "Content-Type: application/json" \ + -d '{ + "file_path": "video/202404/001.mp4", + "source_id": 10, + "job_id": 43, + "start_sec": 0, + "end_sec": 60 + }' +``` + +### 6. 文本问答生成 + +```bash +curl -X POST http://localhost:8000/api/v1/qa/gen-text \ + -H "Content-Type: application/json" \ + -d '{ + "items": [ + { + "subject": "变压器", + "predicate": "额定电压", + "object": "110kV", + "source_snippet": "该变压器额定电压为110kV" + } + ] + }' +``` + +### 7. 图像问答生成 + +```bash +curl -X POST http://localhost:8000/api/v1/qa/gen-image \ + -H "Content-Type: application/json" \ + -d '{ + "items": [ + { + "subject": "电缆接头", + "predicate": "位于", + "object": "配电箱左侧", + "cropped_image_path": "crops/1/0.jpg" + } + ] + }' +``` + +### 8. 微调任务提交 + +```bash +curl -X POST http://localhost:8000/api/v1/finetune/start \ + -H "Content-Type: application/json" \ + -d '{ + "jsonl_url": "https://example.com/train.jsonl", + "base_model": "glm-4-flash", + "hyperparams": { + "epochs": 3, + "learning_rate": 0.0001 + } + }' +``` + +## 数据输出约定 + +当前服务会主动写入这些派生结果: + +| 类型 | 路径模式 | 说明 | +|---|---|---| +| 图像裁剪图 | `crops/{task_id}/{index}.jpg` | 图像提取结果的局部证据图 | +| 视频抽帧图片 | `frames/{source_id}/{index}.jpg` | 视频帧提取结果 | +| 视频文本描述 | `video-text/{source_id}/{timestamp}.txt` | 视频转文本结果 | + +说明: + +- 这些对象默认写入 `storage.buckets.source_data` +- 原始文件的上传路径由上游系统决定 +- 服务不会替上游生成原始文件路径,只消费请求里传入的 `file_path` + +## 日志与错误处理 + +### 日志 + +日志使用 JSON 格式输出,适合直接接入容器日志平台。请求日志会带上: + +- `method` +- `path` +- `status` +- `duration_ms` + +LLM 调用和后台任务也会输出结构化字段,方便排查接口超时、回调失败和模型解析错误。 + +### 错误码 + +统一错误返回格式: + +```json +{"code":"ERROR_CODE","message":"具体描述"} +``` + +当前主要错误码: + +| 错误码 | HTTP 状态码 | 含义 | +|---|---|---| +| `UNSUPPORTED_FILE_TYPE` | 400 | 文本提取文件格式不支持 | +| `VIDEO_TOO_LARGE` | 400 | 视频大小超过限制 | +| `STORAGE_ERROR` | 502 | 对象存储访问失败 | +| `LLM_PARSE_ERROR` | 502 | 模型返回内容无法解析为预期 JSON | +| `LLM_CALL_ERROR` | 503 | 模型调用或微调接口调用失败 | +| `INTERNAL_ERROR` | 500 | 未捕获异常 | + +## 测试 + +安装依赖后可直接运行: + +```bash +python -m pytest +``` + +测试覆盖了这些主要模块: + +- 健康检查接口 +- 文本、图像、视频、QA、微调路由 +- 各 Service 的基本成功与异常路径 +- 配置加载和客户端适配 + +## 设计文档 + +项目内已有更详细的设计资料,可配合 README 阅读: + +- `docs/superpowers/specs/2026-04-10-ai-service-design.md` +- `docs/superpowers/plans/2026-04-10-ai-service-impl.md` +- `specs/001-ai-service-requirements/` + +如果你刚接手这个服务,建议阅读顺序是: + +1. 本 README,先搞清楚服务职责、接口和运行方式 +2. 设计文档,再看架构和设计决策 +3. `app/services` 与 `tests`,最后进入实现细节 + +## 已知约束 + +- 文本提取目前只支持 `txt`、`pdf`、`docx` +- 视频接口依赖对象存储可读取文件大小 +- 视频任务状态不持久化在本服务内,由上游系统负责管理 +- 图像问答采用 base64 内联图片,不依赖外网可访问的 presigned URL +- 如果 `.env` 中的回调地址不可达,视频任务会记录错误日志,但不会自动重试 + +## 开发建议 + +- 新增接口时同步补齐 Pydantic 模型、Router 测试和 README/API 文档 +- 如果替换模型厂商,优先扩展 `app/clients/llm` +- 如果替换存储实现,优先扩展 `app/clients/storage` +- 任何输出路径规则变更,都应同步更新 README 和设计文档 diff --git a/app/main.py b/app/main.py index 1ee4df0..2a4ab76 100644 --- a/app/main.py +++ b/app/main.py @@ -44,3 +44,7 @@ app.include_router(image.router, prefix="/api/v1") app.include_router(video.router, prefix="/api/v1") app.include_router(qa.router, prefix="/api/v1") app.include_router(finetune.router, prefix="/api/v1") + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000)