Files
label_ai_service/README.md
2026-04-15 11:09:09 +08:00

420 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 和设计文档