--- 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"` } ```