8.9 KiB
8.9 KiB
name, overview, todos
| name | overview | todos | |||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| classification-server-completion | 完成衣物分类服务 Fashion-CLIP 的端到端落地:实现 pkg/classification Go 客户端与配置接线,补齐依赖,启动 8000 分类服务并用 image.png 跑通真实分类测试,完全对标 matting 的完成方式。 |
|
用户需求
参照 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 导致NewClientpanic。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"`
}