Files
Neo-ZQYY/docs/specs/P14-ai-dashscope-migration/tasks.md
Neo 70324d8542 chore: 文档与 IDE 配置整理
- .kiro/specs/ → docs/specs/(41 个历史需求 spec 迁移,移除 .config.kiro)
- CLAUDE.md 三层拆分:根文件精简 + apps/backend/CLAUDE.md + .claude/commands/
- 新增 /spec-close、/pre-change 两个工作流命令
- DDL 基线刷新(从测试库重新导出 11 个文件,dws 35→38 表,biz 18→21 表)
- BD_Manual → BD_manual 命名统一(48 个文件)
- 修复 3 处文档与数据库不一致(auth.users.status 默认值、scheduled_tasks 字段、RLS 视图数)
- 新增 BD_manual_public_rbac_tables.md(public schema 8 张 RBAC/工作流表)
- 合并 biz.trigger_jobs 文档(10→12 字段,归档独立文档)
- docs/database/README.md 索引更新

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 00:02:37 +08:00

253 lines
13 KiB
Markdown
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.
# 实施计划P14 — AI 模块改造 — DashScope 迁移 + 调度器完善
## 概述
按依赖关系从底层到顶层逐步实施:环境变量/配置 → DashScope 客户端 → 防护层(熔断/限流/预算) → 运行日志 → 调度器 → API 路由 → 服务适配 → ETL 触发 → 数据库迁移 → 收尾。每个任务构建在前序任务之上,确保无孤立代码。
## 任务
- [x] 1. 环境变量与配置基础
- [x] 1.1 重写 `apps/backend/app/ai/config.py`,实现 `AIConfig` dataclass
- 定义所有 `DASHSCOPE_*` 环境变量字段和 `INTERNAL_API_TOKEN`
- 实现 `from_env()` 类方法,缺失必需变量时立即抛出异常
- 删除所有 `BAILIAN_*` 引用
- _需求: 2.1, 2.2, 2.3, 2.5_
- [x] 1.2 编写属性测试:环境变量校验完整性
- **Property 2: 环境变量校验完整性**
- 对任意必需变量子集缺失,`from_env()` 应抛出异常,不返回含空字符串的配置
- **验证: 需求 2.5**
- [x] 1.3 更新 `.env``.env.template``pyproject.toml`
- `.env` / `.env.template``BAILIAN_*``DASHSCOPE_*`,新增 `DASHSCOPE_WORKSPACE_ID``INTERNAL_API_TOKEN`
- `pyproject.toml`:移除 `openai` 依赖,新增 `dashscope` 依赖
- _需求: 2.4, 1.7_
- [x] 2. DashScopeClient 核心客户端
- [x] 2.1 创建 `apps/backend/app/ai/dashscope_client.py`,实现 `DashScopeClient`
- `call_app()` — App2~8 单轮调用,`asyncio.to_thread()` 包装,返回 `(parsed_json, tokens_used, new_session_id)`
- `call_app_stream()` — App1 流式调用,线程消费同步迭代器 + `asyncio.Queue` 桥接 async generator
- `_call_with_retry()` — 指数退避重试1s→2s→4sHTTP 4xx 不重试5xx/超时/连接错误重试
- 非合法 JSON 响应纯重试(最大 3 次),不做本地修复
- _需求: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6_
- [x] 2.2 定义异常层级
- `DashScopeError` 基类及子类:`DashScopeApiError``DashScopeAuthError``DashScopeTimeoutError``DashScopeJsonParseError``CircuitOpenError``RateLimitExceededError``BudgetExceededError`
- _需求: 1.5, 5.6, 6.3, 7.3_
- [x] 2.3 编写属性测试:重试策略正确性
- **Property 1: 重试策略正确性**
- 4xx 立即抛出不重试5xx/超时/连接错误最多重试 3 次,非法 JSON 触发重试
- **验证: 需求 1.5, 1.6**
- [x] 2.4 编写属性测试Application API 响应解析
- **Property 20: Application API 响应解析**
- 合法 JSON 正确解析为 dict非法 JSON 触发重试
- **验证: 需求 4.4**
- [x] 3. 检查点 — 确保所有测试通过
- 确保所有测试通过ask the user if questions arise.
- [x] 4. 防护层熔断器、限流器、Token 预算
- [x] 4.1 创建 `apps/backend/app/ai/circuit_breaker.py`,实现 `CircuitBreaker`
-`app_id` 独立计数,`_BreakerState` 内部状态
- `check()` / `record_success()` / `record_failure()` 方法
- 状态机CLOSED → OPEN连续 5 次失败)→ HALF_OPEN60 秒后)→ CLOSED/OPEN
- _需求: 5.1, 5.2, 5.3, 5.4, 5.5, 5.6_
- [x] 4.2 编写属性测试:熔断器 app_id 隔离 + 状态机转换
- **Property 5: 熔断器 app_id 隔离**
- **Property 6: 熔断器状态机转换**
- 不同 app_id 互不影响;状态转换符合 CLOSED→OPEN→HALF_OPEN→CLOSED/OPEN 规则
- **验证: 需求 5.1, 5.2, 5.3, 5.4, 5.5**
- [x] 4.3 创建 `apps/backend/app/ai/rate_limiter.py`,实现 `RateLimiter`
- 滑动窗口内存计数器
- `check_user_rate()` — App1 每用户每分钟 10 次
- `check_store_rate()` — App2~8 每门店每小时 100 次
- _需求: 6.1, 6.2, 6.3, 6.4_
- [x] 4.4 编写属性测试:限流器窗口控制
- **Property 7: 限流器窗口控制**
- 窗口内未超阈值允许,超阈值拒绝;窗口外历史不影响当前判断
- **验证: 需求 6.1, 6.2**
- [x] 4.5 创建 `apps/backend/app/ai/budget_tracker.py`,实现 `BudgetTracker`
- `check_budget()``ai_run_logs` 聚合日/月 token 消耗
- 日预算 100,000 / 月预算 2,000,000
- 返回 `BudgetStatus(allowed, daily_used, monthly_used, reason)`
- _需求: 7.1, 7.2, 7.3, 7.4, 7.5_
- [x] 4.6 编写属性测试Token 预算检查正确性
- **Property 8: Token 预算检查正确性**
- 日聚合 = 当日 success 记录 tokens_used 之和;超限时 allowed=false
- **验证: 需求 7.1, 7.3**
- [x] 5. AI 运行日志服务
- [x] 5.1 创建 `apps/backend/app/ai/run_log_service.py`,实现 `AIRunLogService`
- `create_log()` — 创建 pending 记录
- `update_running()` / `update_success()` / `update_failed()` / `update_timeout()` — 状态转换
- `get_daily_token_usage()` / `get_monthly_token_usage()` — 聚合查询
- `request_prompt` 截断为前 2000 字符
- _需求: 16.1, 16.2, 16.3, 16.4, 16.5, 16.6_
- [x] 5.2 编写属性测试AI 运行日志状态机 + Prompt 截断
- **Property 18: AI 运行日志状态机**
- **Property 19: Prompt 截断不变量**
- 状态转换正确pending→running→success/failed/timeoutprompt ≤ 2000 字符
- **验证: 需求 16.1, 16.2, 16.3, 16.4, 16.5, 16.6**
- [x] 6. 检查点 — 确保所有测试通过
- 确保所有测试通过ask the user if questions arise.
- [x] 7. Dispatcher 调度器重写
- [x] 7.1 重写 `apps/backend/app/ai/dispatcher.py`,实现 `AIDispatcher`
- 移除所有 `asyncio.run()``asyncio.new_event_loop()` 调用
- 所有入口改为 `async def`,用 `asyncio.create_task()` 发起后台任务
- 超时用 `asyncio.wait_for()`
- 集成 CircuitBreaker、RateLimiter、BudgetTracker
- `_run_step()` — 单步执行:熔断检查→限流检查→预算检查→调用→记录日志
- _需求: 8.1, 8.2, 8.3, 8.4_
- [x] 7.2 实现事件触发链编排
- `_handle_consumption()` — 消费事件App3→App8→App7无助教/ +App4→App5有助教
- `_handle_note()` — 备注事件App6→App8
- `_handle_task_assigned()` — 任务分配App4→App5
- `_handle_dws_completed()` — DWS 完成App2 预生成8 个时间维度)
- 调用链某步失败时记录错误,后续步骤使用已有缓存继续
- _需求: 9.1, 9.2, 9.3, 9.4, 9.5, 11.1, 11.2, 11.3, 11.4_
- [x] 7.3 实现幂等去重逻辑
- `_check_dedup()` — 按 `(event_type, member_id, site_id, date)` 去重
- `is_forced=true` 时跳过去重检查
- 写入 `ai_trigger_jobs` 记录
- _需求: 12.1, 12.2_
- [x] 7.4 编写属性测试:事件类型到调用链映射
- **Property 9: 事件类型到调用链映射**
- 各事件类型映射到正确的 App 调用链
- **验证: 需求 9.1, 9.2, 9.3, 11.1**
- [x] 7.5 编写属性测试:调用链容错不中断
- **Property 10: 调用链容错不中断**
- 任意步骤失败后后续步骤仍继续执行
- **验证: 需求 9.4**
- [x] 7.6 编写属性测试:事件去重与强制执行
- **Property 12: 事件去重与强制执行**
- 重复自动事件跳过;`is_forced=true` 正常执行
- **验证: 需求 12.1, 12.2**
- [x] 8. 检查点 — 确保所有测试通过 ✅ 76 passed
- 确保所有测试通过ask the user if questions arise.
- [x] 9. API 路由与端点
- [x] 9.1 创建 `apps/backend/app/routers/internal_ai.py`,实现内部触发 API
- `POST /api/internal/ai/trigger` 端点
- `TriggerRequest` Pydantic 模型event_type, connector_type, site_id, member_id, payload
- `verify_internal_token()` 依赖注入,校验 `Authorization: Internal-Token {token}`
- 写入 `ai_trigger_jobs` 后立即返回 `{trigger_job_id, status: "pending"}`,后台异步执行
- 在 FastAPI app 中注册路由
- _需求: 10.1, 10.2, 10.3, 10.4, 10.5_
- [x] 9.2 编写属性测试:内部 API 认证
- **Property 11: 内部 API 认证**
- token 缺失/不匹配返回 401匹配时正常处理
- **验证: 需求 10.2, 10.3**
- [x] 9.3 适配 `apps/backend/app/routers/xcx_chat.py` SSE 端点
- 替换 `_get_bailian_client()``_get_dashscope_client()`
- 调用 `client.call_app_stream(app_id, prompt, session_id, biz_params)`
- 构建 `prompt` + `biz_params`User_ID, Role, Nickname
- 保持 SSE 事件格式:`event: message` / `event: done` / `event: error`
- 流结束后记录 `ai_run_logs`
- _需求: 15.1, 15.2, 15.3, 15.4, 3.3_
- [x] 9.4 编写属性测试SSE 事件流格式
- **Property 17: SSE 事件流格式**
- 零或多个 `event: message`,最终以恰好一个 `done``error` 结束
- **验证: 需求 15.2**
- [x] 10. 服务层适配
- [x] 10.1 适配 `apps/backend/app/services/ai/chat_service.py` — session_id 双轨逻辑
- 新对话生成 session_id格式 `conv_{conversation_id}_{created_timestamp}`
- 每条消息同时写入本地 `ai_messages`
- session_id 过期时从本地加载最近 20 条历史重建
- 保持对话复用规则task 无时限、customer/coach 3 天、general 新建
- _需求: 3.1, 3.2, 3.4, 3.5, 3.6_
- [x] 10.2 编写属性测试session_id 格式 + 对话复用规则
- **Property 3: session_id 格式不变量**
- **Property 4: 对话复用规则**
- session_id 匹配 `^conv_\d+_\d+$`;复用规则按入口类型正确判断
- **验证: 需求 3.1, 3.6**
- [x] 10.3 适配 `apps/backend/app/services/ai/cache_service.py` — 缓存状态与过期策略
- 新增 `status` 字段处理valid/expired/invalidated/generating
- 写入前设 `generating`,完成后设 `valid`
- 查询仅返回 `status='valid'` 且未过期的记录
- 按 App 类型设置过期时间App2 当日 23:59:59、App3~5/7/8 七天、App6 三十天)
- App2~8 每 App 保留最新 20,000 条,超限清理最旧记录
- _需求: 13.1, 13.2, 13.3, 13.4, 13.5_
- [x] 10.4 编写属性测试:缓存过期策略 + 查询过滤 + 保留上限
- **Property 14: 缓存过期策略正确性**
- **Property 15: 缓存查询过滤**
- **Property 16: 缓存保留上限**
- 过期时间匹配策略;查询仅返回 valid 且未过期;每 App ≤ 20,000 条
- **验证: 需求 13.1, 13.4, 13.5**
- [x] 10.5 实现 App8 幂等写入 `member_retention_clue`
- 事务中 DELETE + INSERT同一 member 同一天只执行一次
- 事务失败自动回滚,记录错误到 `ai_trigger_jobs.error_message`
- _需求: 12.3, 12.4_
- [x] 10.6 编写属性测试App8 幂等写入
- **Property 13: App8 幂等写入**
- 多次执行后同一 member 同一天记录数恒为 1
- **验证: 需求 12.3**
- [x] 11. 检查点 — 确保所有测试通过 ✅ 106 passed
- 确保所有测试通过ask the user if questions arise.
- [x] 12. ETL 触发集成
- [x] 12.1 修改 `apps/etl/connectors/feiqiu/tasks/` 相关 DWS 任务
- DWS 任务完成后通过 `requests` 发送 `POST /api/internal/ai/trigger`
- 携带 `Authorization: Internal-Token {INTERNAL_API_TOKEN}` Header
- 事件类型:`dws_completed`App2 预生成)/ `consumption`(消费事件链)
- 新增 `utils/ai_trigger.py` 工具函数 + `BaseDwsTask.AI_TRIGGER_EVENT` 类属性
- `FinanceDailyTask``dws_completed``MemberConsumptionTask``consumption`
- _需求: 10.6_
- [x] 13. 数据库迁移
- [x] 13.1 编写迁移脚本 `db/zqyy_app/migrations/2026-03-22__p14_ai_module.sql`
- CREATE TABLE `biz.ai_run_logs`(含 3 个索引)
- CREATE TABLE `biz.ai_trigger_jobs`(含 3 个索引,含去重部分索引)
- ALTER TABLE `biz.ai_conversations` ADD COLUMN `session_id`
- ALTER TABLE `biz.ai_cache` ADD COLUMN `status` + CHECK 约束
- 编写对应回滚脚本(逆序 DROP/ALTER+ 验证 SQL7 条)
- _需求: 14.1, 14.2, 14.3, 14.4, 14.5, 14.6, 14.7_
- [x] 14. 收尾:文档与集成验证
- [x] 14.1 更新 BD 手册
- 更新 `docs/database/BD_Manual_ai_tables.md`:新增 `ai_run_logs``ai_trigger_jobs` 表结构
- 更新 `ai_cache.status``ai_conversations.session_id` 字段说明
- _需求: 14.1, 14.2, 14.3, 14.4_
- [x] 14.2 文档同步
- 更新 `docs/prd/ai-app-prompts.md`:环境变量映射 BAILIAN_* → DASHSCOPE_*
- 更新 `apps/backend/README.md`AI 模块架构说明
- 更新 `docs/DOCUMENTATION-MAP.md`:新增文档条目
- _需求: 2.4_
- [x] 15. 最终检查点 — 确保所有测试通过 ✅ 505 passedP14 全部 15 个测试文件通过80 个预存失败均非 P14 相关)
- 确保所有测试通过ask the user if questions arise.
## 说明
- 标记 `*` 的子任务为可选(属性测试),可跳过以加速 MVP
- 每个任务关联了具体的需求编号,确保可追溯
- 属性测试任务标注了对应的 Property 编号和验证的需求条款
- 检查点确保增量验证,避免问题累积
- 实现语言Python与设计文档一致