first commit
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
---
|
||||
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"`
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user