Files
zt-closet-backend/.codebuddy/plans/classification-server-completion_ed2c7660.md
T
2026-09-11 01:41:52 +08:00

153 lines
8.9 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.
---
name: classification-server-completion
overview: 完成衣物分类服务 Fashion-CLIP 的端到端落地:实现 pkg/classification Go 客户端与配置接线,补齐依赖,启动 8000 分类服务并用 image.png 跑通真实分类测试,完全对标 matting 的完成方式。
todos:
- id: implement-classify-client
content: 新建 pkg/classification/classification.go,实现 Client、Classify、HealthCheck 与 ClassifyResult 结构体
status: completed
- id: wire-classify-config-and-deps
content: 接线 ClassificationModel 配置并修复依赖(main.go、config.yaml、config.example.yaml、requirements.txt)
status: completed
- id: write-classify-test
content: 新建 classification_test.go,用 image.png 断言分类关键字段非空
status: completed
dependencies:
- implement-classify-client
- wire-classify-config-and-deps
- id: run-classify-service-verify
content: 复用 matting venv 启动 8000 分类服务并跑 go test 完成真实验证
status: completed
dependencies:
- implement-classify-client
- wire-classify-config-and-deps
- write-classify-test
---
## 用户需求
参照 matting_server 的完成方式,将衣物分类服务(Fashion-CLIP)也完整落地,使其端到端可运行、可测试。
## 核心内容
- 补齐分类服务的 Go 客户端封装,对接 Python 服务 `POST /classify` 与 `GET /healthz`。
- 完成配置接线:`main.go` 读取 `ClassificationModel` 配置;`config.yaml` 端口改为 8000 并移除结构体不支持的 `retry` 字段;`config.example.yaml` 补示例段。
- 修复 `classification_server/requirements.txt`,补上 FastAPI multipart 表单必需的 `python-multipart` 依赖。
- 编写分类服务端到端测试,用仓库根目录 `image.png` 调用真实服务并断言关键分类字段非空。
- 启动 8000 分类服务(模型已在本地缓存,离线加载),跑通 `go test` 完成真实验证。
## 技术栈
- Go 客户端:复用标准库 `net/http`、`mime/multipart`、`encoding/json`、`context`,对齐 `pkg/matting` 客户端模式。
- Python 服务端:复用已有 `thirdparty/classification_server/main.py`(已实现两级加载,模型 `patrickjohncyh/fashion-clip` 已缓存于 `~/.cache/huggingface`,581MB,`local_files_only=True` 可离线加载)。
- 运行环境:直接复用 `thirdparty/matting_server/.venv`(python3.12,已含 torch/transformers/fastapi/uvicorn/pillow/python-multipart),避免重复安装 torch。
## 实现方案
### Go 客户端设计(新建 `pkg/classification/classification.go`)
- 对齐 `pkg/matting/matting.go` 的轻量客户端模式,不引入第三方 HTTP 库。
- `Client` 持有 `baseURL` 与 `httpClient`(Timeout 建议 120s,CLIP 单次前向 210 个提示词在 CPU 上耗时较长)。
- `NewClient()` 从 `global.ClassificationModelSetting.Host/Port` 拼接地址;`NewClientWithAddr(host string, port int)` 便于测试解耦。
- `Classify(ctx, imageData []byte) (*ClassifyResult, error)`:构造 multipart/form-data(字段名 `file`,与 Python 端 `UploadFile` 对齐),POST 到 `/classify`,将 JSON 响应解析为 `ClassifyResult`;非 200 时返回状态码与响应体摘要(复用截断逻辑,避免打印大 JSON)。
- `HealthCheck(ctx) error`:GET `/healthz`,供测试前置就绪检查。
### 配置接线与依赖修复
- `main.go` 的 `setupSetting()` 仿照 `MattingModel` 增加 `setting.ReadSection("ClassificationModel", &global.ClassificationModelSetting)`,避免配置为 nil 导致 `NewClient` panic。
- `configs/config.yaml`:`ClassificationModel.Port` 由 80 改为 8000;移除 `retry: 3`(`ClassificationModelSettingS` 无此字段)。
- `configs/config.example.yaml`:补充 `ClassificationModel`(Host 127.0.0.1 / Port 8000)示例段。
- `thirdparty/classification_server/requirements.txt`:补 `python-multipart>=0.0.9`,并保留与 matting 一致的注释说明。
### 测试设计(新建 `pkg/classification/classification_test.go`)
- 读取 `../../image.png`(测试文件位于 `pkg/classification/` 下)。
- `NewClientWithAddr("127.0.0.1", 8000)`,先 `HealthCheck` 确认服务就绪(失败则 `t.Skipf` 明确提示),再调用 `Classify`。
- 断言:返回结果非空、`CategoryMain` 非空、`Formality` 与 `VisualImpact` 落在 `1`~`5`;可选记录 `CategorySub`、`ColorMain` 等关键字段便于人工核验。
- context 超时设 180s,覆盖 CPU 推理耗时。
### 服务启动与验证
- 复用 matting venv 启动服务:`thirdparty/matting_server/.venv/bin/python thirdparty/classification_server/main.py`(监听 8000),日志重定向到 `/tmp/classification_server.log`。
- 轮询 `GET /healthz` 直到返回 `{"status":"ok"}`(模型离线加载完成后才就绪)。
- 执行 `go test ./pkg/classification/ -run TestClassify -v -timeout 600s` 完成真实分类验证。
## 实现注意事项
- **性能**:CLIP 单次批量前向(约 210 文本 + 1 图)在 CPU 上约几十秒,客户端 Timeout 与测试 context 需相应放宽;模型已在本地缓存,启动零网络请求。
- **日志**:Python 端已有「命中本地缓存/自动下载」日志;Go 客户端错误信息需截断响应体(如 512 字节),避免打印大 JSON 造成日志噪声。
- **兼容性**:fashion-clip 为标准 `CLIPModel`,非 `trust_remote_code` 自定义代码,不会触发 BiRefNet 在 transformers 5.x 下的兼容问题;CPU 加载保持 fp32,无需额外 dtype 转换。
- **blast radius**:仅新增客户端/测试文件,配置改动最小化;不引入新第三方依赖,不触碰现有 matting 与业务逻辑。
## 目录结构
```
thirdparty/classification_server/
├── requirements.txt # [MODIFY] 补 python-multipart 依赖
pkg/classification/
├── classification.go # [NEW] 分类服务 Go 客户端(Client/NewClient/NewClientWithAddr/Classify/HealthCheck/ClassifyResult)
└── classification_test.go # [NEW] 端到端测试,用 image.png 验证真实分类
main.go # [MODIFY] setupSetting 增加 ClassificationModel 配置读取
configs/config.yaml # [MODIFY] ClassificationModel.Port 改 8000,移除 retry 字段
configs/config.example.yaml # [MODIFY] 补 ClassificationModel 示例段
```
## 关键代码结构
```
type DetailItem struct {
Label string `json:"label"`
Confidence float64 `json:"confidence"`
}
type Details struct {
CategoryMain []DetailItem `json:"category_main"`
CategorySub []DetailItem `json:"category_sub"`
Style []DetailItem `json:"style"`
ColorMain []DetailItem `json:"color_main"`
Thickness []DetailItem `json:"thickness"`
Version []DetailItem `json:"version"`
Material []DetailItem `json:"material"`
Pattern []DetailItem `json:"pattern"`
SleeveType []DetailItem `json:"sleeve_type"`
ClothesType []DetailItem `json:"clothes_type"`
NecklineType []DetailItem `json:"neckline_type"`
Formality []DetailItem `json:"formality"`
Occasions []DetailItem `json:"occasions"`
VisualImpact []DetailItem `json:"visual_impact"`
}
type ClassifyResult struct {
CategoryMain string `json:"category_main"`
CategoryMainConfidence float64 `json:"category_main_confidence"`
CategorySub string `json:"category_sub"`
CategorySubConfidence float64 `json:"category_sub_confidence"`
Style string `json:"style"`
StyleConfidence float64 `json:"style_confidence"`
ColorMain string `json:"color_main"`
ColorMainConfidence float64 `json:"color_main_confidence"`
ColorSub string `json:"color_sub"`
ColorSubConfidence float64 `json:"color_sub_confidence"`
Thickness string `json:"thickness"`
ThicknessConfidence float64 `json:"thickness_confidence"`
Version string `json:"version"`
VersionConfidence float64 `json:"version_confidence"`
Material string `json:"material"`
MaterialConfidence float64 `json:"material_confidence"`
Pattern string `json:"pattern"`
PatternConfidence float64 `json:"pattern_confidence"`
SleeveType string `json:"sleeve_type"`
SleeveTypeConfidence float64 `json:"sleeve_type_confidence"`
ClothesType string `json:"clothes_type"`
ClothesTypeConfidence float64 `json:"clothes_type_confidence"`
NecklineType string `json:"neckline_type"`
NecklineTypeConfidence float64 `json:"neckline_type_confidence"`
Formality string `json:"formality"`
FormalityLabel string `json:"formality_label"`
FormalityConfidence float64 `json:"formality_confidence"`
Occasions string `json:"occasions"`
VisualImpact string `json:"visual_impact"`
VisualImpactLabel string `json:"visual_impact_label"`
VisualImpactConfidence float64 `json:"visual_impact_confidence"`
Details Details `json:"details"`
}
```