docs: apply eng review findings to design doc and impl plan

Architecture fixes:
- Image QA: presigned URL → base64 (RustFS is internal, GLM-4V is cloud)
- Add GET /health endpoint + Docker healthcheck
- Video size limit: add get_object_size() to StorageClient ABC, check before background task
- Video size configurable via MAX_VIDEO_SIZE_MB env var (no image rebuild needed)
- Fix image_service.py except clause redundancy (Exception absorbs KeyError/TypeError)

Config additions:
- video.max_file_size_mb: 200 in config.yaml
- MAX_VIDEO_SIZE_MB env override in _ENV_OVERRIDES
This commit is contained in:
wh
2026-04-10 14:34:41 +08:00
parent f9f84937db
commit 3892c6e60f
2 changed files with 145 additions and 31 deletions

View File

@@ -125,6 +125,7 @@ backend: {} # callback_url 由 .env 注入
video:
frame_sample_count: 8 # 视频转文本时均匀抽取的代表帧数
max_file_size_mb: 200 # 视频文件大小上限(超过则拒绝,防止 OOM
models:
default_text: "glm-4-flash"
@@ -139,6 +140,7 @@ STORAGE_ACCESS_KEY=minioadmin
STORAGE_SECRET_KEY=minioadmin
STORAGE_ENDPOINT=http://rustfs:9000
BACKEND_CALLBACK_URL=http://backend:8080/internal/video-job/callback
# MAX_VIDEO_SIZE_MB=200 # 可选,覆盖 config.yaml 中的视频大小上限
```
### 3.4 config 模块实现
@@ -154,12 +156,13 @@ _ROOT = Path(__file__).parent.parent.parent
# 环境变量 → YAML 路径映射
_ENV_OVERRIDES = {
"ZHIPUAI_API_KEY": ["zhipuai", "api_key"],
"STORAGE_ACCESS_KEY": ["storage", "access_key"],
"STORAGE_SECRET_KEY": ["storage", "secret_key"],
"STORAGE_ENDPOINT": ["storage", "endpoint"],
"BACKEND_CALLBACK_URL": ["backend", "callback_url"],
"LOG_LEVEL": ["server", "log_level"],
"ZHIPUAI_API_KEY": ["zhipuai", "api_key"],
"STORAGE_ACCESS_KEY": ["storage", "access_key"],
"STORAGE_SECRET_KEY": ["storage", "secret_key"],
"STORAGE_ENDPOINT": ["storage", "endpoint"],
"BACKEND_CALLBACK_URL": ["backend", "callback_url"],
"LOG_LEVEL": ["server", "log_level"],
"MAX_VIDEO_SIZE_MB": ["video", "max_file_size_mb"],
}
def _set_nested(d: dict, keys: list[str], value: str):
@@ -351,6 +354,17 @@ app = FastAPI(title="Label AI Service", lifespan=lifespan)
统一前缀:`/api/v1`。FastAPI 自动生成 Swagger 文档(`/docs`)。
### 5.0 健康检查
**`GET /health`**
```json
// 响应200 OK
{"status": "ok"}
```
用于 Docker healthcheck、Nginx 上游探测、运维监控。无需认证,不访问外部依赖。
### 5.1 文本三元组提取
**`POST /api/v1/text/extract`**
@@ -541,7 +555,7 @@ POST {BACKEND_CALLBACK_URL}
}
```
图像 QA 生成时AI 服务通过 `get_presigned_url` 获取裁剪图临时访问 URL构造多模态消息后调用 GLM-4V。
图像 QA 生成时AI 服务通过 `storage.download_bytes` 重新下载裁剪图base64 编码后直接嵌入多模态消息,避免 RustFS 内网 presigned URL 无法被云端 GLM-4V 访问的问题
### 5.7 提交微调任务
@@ -628,6 +642,8 @@ def extract_text(data: bytes, filename: str) -> str:
**抽帧BackgroundTask**
```
0. storage.get_object_size(bucket, file_path) → 字节数
超过 video.max_file_size_mb 限制 → 回调 FAILED路由层提前校验返回 400
1. storage.download_bytes → bytes → 写入 tempfile
2. cv2.VideoCapture 打开临时文件
3. interval 模式:按 frame_interval 步进读帧
@@ -659,9 +675,10 @@ def extract_text(data: bytes, filename: str) -> str:
图像 QA
遍历四元组列表
storage.get_presigned_url(cropped_image_path) → 临时 URL
构造多模态消息(image_url + 问题指令)
storage.download_bytes(bucket, cropped_image_path) → bytes → base64 编码
构造多模态消息(data:image/jpeg;base64,... + 问题指令)
llm.chat_vision → 解析 → 含 image_path 的 QAPairList
(注:不使用 presigned URL因 RustFS 为内网部署,云端 GLM-4V 无法访问内网地址)
```
### 6.5 finetune_service — GLM 微调对接
@@ -764,6 +781,12 @@ ai-service:
- backend
networks:
- label-net
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
```
### 10.3 requirements.txt
@@ -799,9 +822,9 @@ ZhipuAI 官方 SDK 是同步阻塞调用,直接 `await` 不生效。通过 `lo
项目规模适中,视频处理任务由 ADMIN 手动触发并发量可控。FastAPI `BackgroundTasks` 无需额外中间件Redis 队列、Celery Worker部署简单任务状态通过回调接口传递给 Java 后端管理,符合整体架构风格。
### 11.4 为何图像 QA 生成用 presigned URL 而非 base64
### 11.4 为何图像 QA 生成用 base64 而非 presigned URL
裁剪图已存储在 RustFSGLM-4V 支持通过 URL 直接访问图片。presigned URL 避免将图片内容重新加载到 AI 服务内存后再 base64 编码,减少内存压力,适合多张图片批量生成的场景
RustFS 部署在 Docker 内网(`http://rustfs:9000`presigned URL 指向内网地址,云端 GLM-4V API 无法访问,会导致所有图像 QA 请求失败。因此将裁剪图重新下载为 bytesbase64 编码后直接嵌入多模态消息体,与 `image_service` 处理原图的方式保持一致,无需 RustFS 有公网地址
### 11.5 config.yaml + .env 分层配置的原因