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.
apps/ — 可部署的进程
| 目录 | 进程 |
|---|---|
api/ |
FastAPI 应用:路由、依赖注入、中间件 |
worker/ |
Celery 任务定义与四队列配置(gpu / cpu / io / beat) |
admin/ |
极简运营后台(占位,FR-ADM-01~05) |
API 层的职责边界(架构 §3.1 L3)
只做编排:认证鉴权、参数校验、幂等、配额校验与预扣、统一响应、OpenAPI。
不能放什么
- 业务规则。路由函数里出现
if判断业务条件,几乎总是应该下沉到packages/<域>/service.py或packages/domain/。 - 直接写 SQL 或用 ORM 查表。走域的
service。 - 直接调用外部 API。LLM 走
packages/llm;未来的审核、视觉、换装同理。 - 裸
dict返回。所有响应必须经过packages.shared.response.ok()/fail()。
契约先行(NFR-MAINT-01)
先在 apps/api/schemas.py 定义 Pydantic schema,再写实现。客户端类型由
OpenAPI 生成,schema 一变客户端立刻可见。
Worker 的规矩
- 每个长耗时任务都必须落
tk_task,走packages/task的状态机。 不要自己维护状态字段。 - 失败必须退还配额(
billing.refund(),FR-BILL-05)。apps/worker/tasks.py里的ping_task是形状示例。 - 队列按资源画像分:GPU 推理进
gpu,视觉/渲染进cpu,外部调用进io。 混队列会让「换装排队把报告拖慢」。