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

8.9 KiB
Raw Permalink Blame History

name, overview, todos
name overview todos
classification-server-completion 完成衣物分类服务 Fashion-CLIP 的端到端落地:实现 pkg/classification Go 客户端与配置接线,补齐依赖,启动 8000 分类服务并用 image.png 跑通真实分类测试,完全对标 matting 的完成方式。
id content status
implement-classify-client 新建 pkg/classification/classification.go,实现 Client、Classify、HealthCheck 与 ClassifyResult 结构体 completed
id content status
wire-classify-config-and-deps 接线 ClassificationModel 配置并修复依赖(main.go、config.yaml、config.example.yaml、requirements.txt) completed
id content status dependencies
write-classify-test 新建 classification_test.go,用 image.png 断言分类关键字段非空 completed
implement-classify-client
wire-classify-config-and-deps
id content status dependencies
run-classify-service-verify 复用 matting venv 启动 8000 分类服务并跑 go test 完成真实验证 completed
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"`
}