Files
meida_backend/README.md
T
xsl da0314fd08 chore: bootstrap repo tooling, dependency and quality gates
uv-managed PEP 621 project with ruff, mypy (strict on packages/domain and
packages/llm), pytest with coverage, and import-linter contracts that
mechanically enforce the dependency rules of 架构文档 §4.2 -- including the
two forbidden edges (compliance and identity must not depend on business
domains). CI, pre-commit and a detect-secrets baseline gate every push;
.env is gitignored before anything is ever staged.
2026-08-14 00:26:39 +08:00

167 lines
7.6 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 美搭 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](https://docs.astral.sh/uv/)。
```bash
# 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/*` 作为等价别名同样可用
(内部脚本与部分文档用的是后者,见下方「与设计文档的偏差」)。
```bash
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/`。
本仓库尚未实现它们。
---
## 与设计文档的偏差 / 文档待补
记录在此,以便回灌设计文档:
1. **业务域包的位置**:架构 §16.1 的目录树列出了 `packages/{domain,llm,retrieval,
rules,memory,compliance,shared}`,但 §4.1 定义了 11 个业务域。由于
`compliance` 同时出现在两处,本仓库按「业务域各自是 `packages/<域>/`,
`packages/domain/` 只放纯逻辑」来落地。建议在 §16.1 补全域目录。
2. **接口前缀**:§9.1 规定 `/api/v1`,但多处任务描述使用 `/v1`。本仓库以
`/api/v1` 为准,并在 ASGI 层把 `/v1/*` 重写为等价路径。建议文档统一。
3. **`bl_ledger` 缺 `status` 列**:§6.2 的列清单没有 `status`,但 §7.4 的三段式
明确要求 `reserved → committed / refunded`。本仓库补了该列。
4. **`ob_ai_call` 缺 Prompt 版本列**:§8.1「可复现」要求记录 prompt 版本,
§6.2 的列清单里没有。本仓库补了 `prompt_id` / `prompt_version`。
5. **日成本熔断是「拒绝」而非「告警」**:§13 写的是「超预算自动降级并告警」。
本仓库把它实现为硬闸门(非关键场景直接拒绝),因为重试风暴产生成本的速度
远快于人响应告警的速度。关键场景通过 `SceneConfig.critical` 豁免。
6. **单位成本费率是占位值**:`.env.example` 里的 `LLM_COST_RATES_JSON` 需要用
供应商实际价目表校准,否则 `ob_ai_call.cost_cents` 只有相对意义。
7. **`.env` 里的 JSON 值必须用单引号包住**。`scripts/smoke.sh` 会 `source .env`,
裸双引号会被 bash 剥掉,JSON 随即损坏且报错发生在 pydantic-settings 内部,
现场很难看出根因。模板里已经这么写了,改的时候别改回去。