Creates the vector extension plus id_user, id_user_identity, id_consent, bl_quota_rule, bl_quota, bl_ledger, tk_task and ob_ai_call using the domain prefixes of 架构文档 §6.1, and seeds the quota rules. Reversible: verified by an upgrade/downgrade/upgrade round-trip, which CI now runs on every push.
美搭 backend — 工程地基 (M1)
「美搭」智能穿搭美学助手的服务端。本仓库当前是 工程地基,不是业务实现: 骨架、横切能力、以及一条打通全链路的最小垂直切片。
设计文档是唯一事实来源,在 Windows 侧的
G:\meida\docs\: 《技术架构文档》《需求规格说明》《开发任务拆解》。本 README 只讲怎么跑起来。
现在有什么 / 现在没有什么
| 已实现 | 状态 |
|---|---|
| 仓库结构与包边界(架构 §16.1) | ✅ |
| ruff / mypy strict / import-linter / pytest / 密钥扫描 全部进 CI(§16.2) | ✅ |
| 统一响应与错误码 + 异常处理(§9.2, FR-SYS-02) | ✅ |
| 结构化日志 + 请求/追踪 ID + 日志脱敏(NFR-SEC-03) | ✅ |
| 异步 SQLAlchemy + Alembic(含 pgvector)+ 首个迁移 | ✅ |
OTel + Prometheus + /healthz /readyz /metrics |
✅ |
| LLM 网关:路由 / 故障转移 / 熔断 / 结构化输出 / Prompt 版本 / 缓存前缀纪律 / 成本归因 / 日预算硬熔断 | ✅ |
| 配额三段式 reserve → commit → refund(§7.4, FR-BILL-05) | ✅ |
统一任务中心 状态机 + Celery 四队列 + 一个贯通的 ping 任务 |
✅ |
垂直切片 POST /v1/agent/chat |
✅ |
| 刻意留白(占位) | 归属任务 |
|---|---|
| 华为账号登录、手机号验证码、双令牌撤销、游客合并、注销 | T-P1-12 ~ T-P1-16 |
| 统一合规出口(审核 / 水印 / 元数据 / 内容编号) | T-P1-22、T-P1-23(CR-05/CR-06 不可裁剪) |
| 知识库、混合检索、规则引擎、记忆系统 | M2 |
| 报告生成、换装、发型、视觉服务、抠图 | M3 / M4 |
| 智谱 GLM provider(仅结构占位) | 需先核实备案模型清单 |
| SSE 流式对话、意图识别、生成后校验 | FR-AGT-01/02/05 |
开始写业务功能前请先读 packages/README.md 与对应域的 README —— 里面写了
什么能放进去、什么绝对不能。
快速开始
前置:Python 3.12、Docker、uv。
# 1. 依赖(本仓库用 uv 管理;uv.lock 锁定全部版本)
make install
# 2. 配置。复制模板后至少填 DEEPSEEK_API_KEY 与 JWT_SECRET
cp .env.example .env
$EDITOR .env
# 3. 起本地基础设施(PostgreSQL 16 + pgvector,Redis 7)
make up
make migrate
# 4. 跑起来
make dev # API → http://localhost:8000/api/v1/docs
make worker # Celery worker(另开一个终端)
# 5. 真实链路验证:会真的调用 DeepSeek,花真钱
make smoke
包管理器:本仓库使用 uv(uv sync --extra dev,锁文件 uv.lock)。
没有 uv 时 make install 会退回 python -m venv + pip install -e ".[dev]",
但 CI 与 Docker 镜像都走 uv,因此以 uv.lock 为准。
常用命令
| 命令 | 作用 |
|---|---|
make lint |
ruff check + format check |
make typecheck |
mypy(packages/domain、packages/llm 为 strict) |
make importlint |
校验架构 §4.2 的域间依赖规则 |
make test |
上面三项 + pytest(本地质量门,与 CI 一致) |
make migrate / make makemigration m="..." |
Alembic |
make up / make down |
本地 PG + Redis |
make smoke |
真实 DeepSeek 端到端验证 |
垂直切片:POST /v1/agent/chat
一次请求依次经过:
JWT 鉴权
→ billing.reserve() 配额预扣,写 bl_ledger(status=reserved)
→ LLM 网关 场景路由 → 缓存前缀校验 → 日预算闸门 → DeepSeek
→ ob_ai_call 每次 provider 调用(含失败与重试)写一行
→ billing.commit() / refund() 成功确认扣除,失败自动退还
→ 统一响应 {code, message, data, request_id}
接口基础路径是架构 §9.1 规定的 /api/v1;/v1/* 作为等价别名同样可用
(内部脚本与部分文档用的是后者,见下方「与设计文档的偏差」)。
TOKEN=$(curl -s -XPOST localhost:8000/v1/auth/dev-token \
-H 'Content-Type: application/json' -d '{"device_key":"me"}' \
| python3 -c 'import json,sys;print(json.load(sys.stdin)["data"]["access_token"])')
curl -s -XPOST localhost:8000/v1/agent/chat \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"message":"面试穿什么?"}' | python3 -m json.tool
地基验收结果(M1 出口)
以下均在本机实跑通过,不是「应该可以」:
| 项 | 结果 |
|---|---|
make lint / make typecheck / make test |
全绿;147 unit + 3 integration,packages/llm + packages/domain 覆盖率 97% |
| import-linter | 6 条契约全部 KEPT(含 compliance / identity 两条禁止依赖) |
make up + make migrate |
8 张表 + alembic_version 建成,vector 扩展存在,配额规则已播种 |
alembic downgrade base → upgrade head |
可逆,往返成功 |
make smoke(真实 DeepSeek) |
通过,模型 deepseek-v4-flash;第二次调用 tokens_cached=256,证明缓存前缀纪律真的生效 |
| Celery 全链路 | pending → queued → running → succeeded,tk_task.result 落库 |
GET https://api.deepseek.com/v1/models 当前只返回两个模型:
deepseek-v4-flash 与 deepseek-v4-pro。默认用前者,通过 DEEPSEEK_MODEL 切换,
调用点不写死。
密钥
.env在.gitignore第一条,永远不要提交。.env.example记录每一个变量的含义,不含任何真实值。- pre-commit 与 CI 都跑密钥扫描(detect-secrets + gitleaks)。
- 生产环境的密钥来自 SSM/KMS,不来自文件(架构 §11.2)。
本地 GPU
开发机带一块 RTX 5090(32GB VRAM)。这意味着 BiRefNet 抠图(TD-07)与
RTMPose ONNX 人体关键点(TD-05/TD-06)可以完全在本地开发和调参,不需要租云
GPU。相关占位见 services/vision/ 与 services/matting/。
本仓库尚未实现它们。
与设计文档的偏差 / 文档待补
记录在此,以便回灌设计文档:
- 业务域包的位置:架构 §16.1 的目录树列出了
packages/{domain,llm,retrieval, rules,memory,compliance,shared},但 §4.1 定义了 11 个业务域。由于compliance同时出现在两处,本仓库按「业务域各自是packages/<域>/,packages/domain/只放纯逻辑」来落地。建议在 §16.1 补全域目录。 - 接口前缀:§9.1 规定
/api/v1,但多处任务描述使用/v1。本仓库以/api/v1为准,并在 ASGI 层把/v1/*重写为等价路径。建议文档统一。 bl_ledger缺status列:§6.2 的列清单没有status,但 §7.4 的三段式 明确要求reserved → committed / refunded。本仓库补了该列。ob_ai_call缺 Prompt 版本列:§8.1「可复现」要求记录 prompt 版本, §6.2 的列清单里没有。本仓库补了prompt_id/prompt_version。- 日成本熔断是「拒绝」而非「告警」:§13 写的是「超预算自动降级并告警」。
本仓库把它实现为硬闸门(非关键场景直接拒绝),因为重试风暴产生成本的速度
远快于人响应告警的速度。关键场景通过
SceneConfig.critical豁免。 - 单位成本费率是占位值:
.env.example里的LLM_COST_RATES_JSON需要用 供应商实际价目表校准,否则ob_ai_call.cost_cents只有相对意义。 .env里的 JSON 值必须用单引号包住。scripts/smoke.sh会source .env, 裸双引号会被 bash 剥掉,JSON 随即损坏且报错发生在 pydantic-settings 内部, 现场很难看出根因。模板里已经这么写了,改的时候别改回去。