Two defects that only surface against the real DeepSeek API, both of which break the cost model rather than the code: deepseek-v4-flash enables thinking mode by default. The same single-turn question took 13.5s with it on and 1.8s with it off, and the thinking tokens are billed at the output rate. That misses NFR-PERF-02b (15s per turn) and inflates every conversational call. SceneConfig now carries an explicit `thinking` flag, all four V1 scenes set it to false, and the provider always sends the parameter rather than relying on the upstream default. Reasoning tokens are recorded separately in Usage.tokens_reasoning so the cost of ever turning it back on is visible. DeepSeek moves to peak/off-peak pricing on 2026-08-17, with peak (Beijing 09:00-12:00 and 14:00-18:00) at twice the off-peak rate. Reports are generated mostly during peak hours, so a single blended rate would leave ob_ai_call -- the only input to V2 pricing -- off by nearly a factor of two. PeakAwareRateTable resolves the rate at call time; cost is computed and stored then, never recomputed later at whatever the current rate happens to be. Also here: - Replace the live API key in the redaction test with a synthetic one. The test that proves we redact secrets was itself a secret leak waiting for its first commit. - Correct LLM_COST_RATES_JSON, which held placeholder rates. - Make ids.SnowflakeGenerator take an injectable clock so rollover and clock-rollback are testable without patching a slots class. - Rename exceptions to the -Error suffix (N818) and clear the remaining ruff, mypy and import-linter findings, including two dead lines: a duplicate pytestmark assignment and a no-op assert on a None return. Gates: ruff, mypy (96 files incl. tests), 4/4 import contracts, detect-secrets, 166 unit + 3 integration tests, migration up/down round-trip.
61 lines
2.9 KiB
Markdown
Executable File
61 lines
2.9 KiB
Markdown
Executable File
# meida_backend · 美搭服务端
|
||
|
||
「美搭」智能穿搭美学助手的服务端。当前状态是**工程地基 + 一条打通的纵向切片**
|
||
(登录取 token → 配额预扣 → 真实调用 DeepSeek → 记成本 → 提交配额),
|
||
业务域大多只有目录与 README,见各 `packages/*/README.md`。
|
||
|
||
技术选型与分层依据见仓库外的《技术架构文档》《技术选型与调研报告》,本文只讲怎么跑起来。
|
||
|
||
## 快速开始
|
||
|
||
```bash
|
||
cp .env.example .env # 填入 DEEPSEEK_API_KEY
|
||
make install # uv sync + 装 pre-commit
|
||
make up # PostgreSQL(pgvector) + Redis,端口 55432 / 56379
|
||
make migrate # 建表并创建 vector 扩展
|
||
make run # http://localhost:8000/api/v1/docs
|
||
```
|
||
|
||
验证真实链路(**会产生真实 API 费用**,是唯一花钱的入口):
|
||
|
||
```bash
|
||
make smoke
|
||
```
|
||
|
||
它会起依赖、迁移、拉起 API、发一次真实对话,然后把 `ob_ai_call`
|
||
的成本行与 `bl_ledger` 的配额流水打出来。跑通即证明「计量 + 配额 + 大模型」三者串通。
|
||
|
||
## 目录结构
|
||
|
||
| 目录 | 职责 |
|
||
| --- | --- |
|
||
| `apps/api` | HTTP 入口。只做鉴权、校验、幂等、配额、统一响应,不放业务规则 |
|
||
| `apps/worker` | Celery 长耗时任务入口 |
|
||
| `packages/domain` | 纯领域逻辑,不许 import 任何框架与 I/O |
|
||
| `packages/llm` | 大模型网关:场景路由、Prompt 版本、结构化输出、成本计量、预算熔断 |
|
||
| `packages/shared` | 配置、日志脱敏、DB、Redis、错误码、中间件 |
|
||
| `packages/<业务域>` | identity / billing / task / agent / kb / retrieval / report … |
|
||
| `services/` | 需要独立伸缩的重型服务(换装、抠图、视觉),V1 为占位 |
|
||
| `prompts/` | 按 `<prompt_id>/<version>.txt` 归档,与 `ob_ai_call.prompt_version` 对应 |
|
||
| `migrations/` | Alembic;CI 强制 upgrade → downgrade → upgrade 往返可用 |
|
||
|
||
依赖方向由 `lint-imports` 强制(`pyproject.toml` 的 `[tool.importlinter]`)。
|
||
`apps → packages → domain` 是单向的,业务包也不得绕过网关直连 provider。
|
||
|
||
## 提交前
|
||
|
||
```bash
|
||
make check # lint + typecheck + imports + test + secrets,与 CI 同一套
|
||
```
|
||
|
||
单测不连库、不发网络请求。需要真实中间件的用例在 `tests/integration`,
|
||
默认跳过,用 `make test-integration` 显式开启。
|
||
|
||
## 两件容易踩的事
|
||
|
||
- **Prompt 固定前缀不许含时间戳、随机值等易变内容。** 命中缓存的输入单价只有未命中的
|
||
约 1/30,前缀被污染会让输入成本翻数十倍。`LLM_CACHE_PREFIX_MODE=strict` 下启动即失败。
|
||
- **DeepSeek 自 2026-08-17 起峰谷定价**,高峰(北京时间 09:00-12:00、14:00-18:00)
|
||
单价是空闲时段的 2 倍。单价配两套,由 `packages/llm/cost.py` 按调用时刻选择;
|
||
成本在调用当时算好落库,事后不按当前价重算。
|