# 审计记录：后端 DashScope tokens_used 提取修复

**日期**：2026-04-30
**会话**：处理接管台账 `A1-02`，修复 DashScope `usage.models` 嵌套结构下 `tokens_used=0` 的预算追踪问题
**影响范围**：`apps/backend/app/ai/dashscope_client.py`、`apps/backend/tests/tests/unit/test_dashscope_client_usage.py`

---

## 变更背景

AI 验收文档和历史审计均记录 `tokens_used=0` 问题：DashScope Application API 返回的 usage 不是旧的顶层 `input_tokens/output_tokens`，而是 `ApplicationUsage(models=[ApplicationModelUsage(...)])`。如果无法正确提取 token 计数，会影响：

- `biz.ai_run_logs.tokens_used` 写入
- admin-web AI 调用记录和预算展示
- `BudgetTracker` 的日/月 token 用量判断

调研时发现当前工作区已有一段未提交的半修复：可处理 SDK 对象形态 `usage.models`，但普通 dict 形态 `{"models": [...]}` 仍会漏算为 0。

---

## 变更摘要

### `apps/backend/app/ai/dashscope_client.py`

- 新增 `_field_value()`，统一读取 dict、DashScope `DictMixin`、普通对象字段。
- 新增 `_safe_int()`，对 token 字段做安全整数转换，异常值按 0 处理。
- 新增 `_extract_tokens_used()`，按以下优先级提取 token：
  - `usage.models[*].input_tokens/output_tokens`
  - `usage.total_tokens`
  - `usage.input_tokens/output_tokens`
- `DashScopeClient.call_app()` 改为调用 `_extract_tokens_used(response.usage)`，避免分支逻辑散落在主流程中。

### `apps/backend/tests/tests/unit/test_dashscope_client_usage.py`

- 新增 5 个单元测试，覆盖：
  - SDK `ApplicationUsage(models=[...])`
  - 普通 dict `{"models": [...]}`
  - 顶层 dict `input_tokens/output_tokens`
  - 对象 `total_tokens`
  - usage 缺失时返回 0

---

## TDD 记录

先新增测试并运行 RED：

```powershell
cd apps/backend
C:\Project\NeoZQYY\.venv\Scripts\python.exe -m pytest tests/tests/unit/test_dashscope_client_usage.py -q
```

RED 结果：5 个测试中 1 个失败，失败用例为 `test_call_app_sums_tokens_from_plain_dict_models`，实际返回 `0`，符合预期复现。

修复后再次运行同一测试：

```powershell
C:\Project\NeoZQYY\.venv\Scripts\python.exe -m pytest tests/tests/unit/test_dashscope_client_usage.py -q
```

GREEN 结果：5/5 通过。

---

## 验证

已执行：

```powershell
cd apps/backend
C:\Project\NeoZQYY\.venv\Scripts\python.exe -m compileall app/ai/dashscope_client.py
C:\Project\NeoZQYY\.venv\Scripts\python.exe -m pytest tests/tests/unit/test_dashscope_client_usage.py tests/tests/unit/test_xcx_chat_ai_fallback.py::TestAIFallback::test_ai_success_returns_real_reply -q
C:\Project\NeoZQYY\.venv\Scripts\python.exe -m pytest tests/tests/integration/test_ai_full_chain.py::test_note_chain -q
C:\Project\NeoZQYY\.venv\Scripts\python.exe -m pytest tests/tests/integration/test_ai_full_chain.py::test_failure_logging -q
C:\Project\NeoZQYY\.venv\Scripts\python.exe -m pytest tests/tests/test_ai_prompts_smoke.py::test_dispatcher_registers_5_handlers -q
```

结果：

- `compileall`：通过。
- token 提取与对话成功路径：6/6 通过。
- AI note chain：通过。
- failure logging：通过。
- dispatcher handler 注册：通过。

补充验证：

- `tests/tests/test_ai_dispatcher.py` 全文件运行 124 秒超时。
- 单独运行 `TestProperty10ChainOrder::test_note_event` 时，失败原因为 Hypothesis `DeadlineExceeded`：单例耗时约 3.6s，超过默认 200ms；不是断言失败，也不是本次 token 提取逻辑失败。该测试债未在本次修复中处理。

---

## 风险与影响

| 风险 | 结论 |
|------|------|
| 预算追踪 | 新成功调用可从 `usage.models` 正确累加 token，改善日/月预算统计可信度 |
| 旧数据 | 已写入为 0 的历史 run log 不会自动回填；如需历史修正需另做数据方案 |
| DashScope SDK 形态变化 | 覆盖 SDK 对象、普通 dict 和旧 `total_tokens` 形态，兼容性较当前实现更强 |
| 真实外部调用 | 本次未消耗真实 DashScope token；仍需后续用真实 APP 调用验证 `success AND tokens_used>0` |

---

## 回滚

如需回滚本次修复：

```powershell
git restore apps/backend/app/ai/dashscope_client.py
Remove-Item -LiteralPath apps/backend/tests/tests/unit/test_dashscope_client_usage.py
```

回滚后普通 dict `models` 形态会重新漏算为 0；若回到 HEAD 基线，SDK `ApplicationUsage.models` 形态也会重新漏算。
