Files
Neo-ZQYY/docs/specs/P14-ai-dashscope-migration/requirements.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

247 lines
19 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.
# 需求文档 — P14AI 模块改造 — DashScope 迁移 + 调度器完善
## 简介
当前 AI 模块使用 `openai` SDK 的通用模型 API`chat.completions.create`),但项目的 8 个 App 均为百炼控制台创建的智能体应用(各有独立 `app_id`)。通用模型 API 不接受 `app_id`,等于绕过了百炼控制台配置的 System Prompt、MCP 工具等全部能力。
本 spec 将 SDK 从 `openai` 切换到 `dashscope`Application API一步到位完成迁移同时修复调度器 asyncio 嵌套问题、打通事件触发链、新增熔断/限流/Token 预算控制,并完成相关数据库变更。
### 依赖
- P5AI 集成层)— 现有 AI 模块基础架构、8 个 App 实现、缓存/对话服务
- RNS1.4CHAT 对齐)— CHAT 路径迁移、SSE 端点、对话复用规则
### 来源文档
- `docs/prd/specs/P14-ai-dashscope-migration.md` — PRD 主文档
- `docs/reports/2026-03-21__ai_module_issues.md` — 18 个问题清单4 P0 / 6 P1 / 5 P2 / 3 P3
### 不在本 spec 范围
- admin-web AI 监控后台P15
- 全链路测试重建与历史回填P15
- 多门店支持BACKLOG当前写死 `2790685415443269`
- 消息队列替代 HTTP 触发(单独 PRD
- Prompt 版本管理BACKLOG
- 前端刷新机制(前端改动,不在本 spec
## 术语表
- **Backend**FastAPI 后端应用,位于 `apps/backend/`
- **ETL**:飞球 Connector ETL 管道,位于 `apps/etl/connectors/feiqiu/`
- **DashScope_Client**:新的 DashScope Application API 客户端,替代现有 `BailianClient`
- **Application_API**`dashscope.Application.call()` 方法,百炼智能体应用的原生调用接口
- **App1**:通用对话应用(流式,支持多轮 session_id
- **App2**财务洞察应用单轮DWS 完成后预生成)
- **App3**:维客线索应用(单轮,消费事件触发)
- **App4**:关系分析应用(单轮,消费/任务事件触发)
- **App5**:话术参考应用(单轮,依赖 App4 结果)
- **App6**:备注分析应用(单轮,备注事件触发)
- **App7**:客户分析应用(单轮,消费事件触发)
- **App8**:维客线索整理应用(单轮,整合 App3/App6 线索)
- **session_id**:百炼云端对话管理标识,格式 `conv_{conversation_id}_{created_timestamp}`
- **Circuit_Breaker**:熔断器,按 app_id 独立计数,连续失败后暂停请求
- **Rate_Limiter**:限流器,按用户/门店维度控制请求频率
- **Budget_Tracker**Token 预算追踪器,按日/月聚合 token 消耗
- **Internal_AI_API**:内部触发接口 `POST /api/internal/ai/trigger`ETL 通过此接口触发 AI 调用链
- **Dispatcher**AI 事件调度与调用链编排器,位于 `apps/backend/app/ai/dispatcher.py`
- **ai_run_logs**AI 运行记录表,记录每次 Application API 调用的输入/输出/耗时/token
- **ai_trigger_jobs**:调度运行记录表,记录事件触发的调用链执行状态
## 需求
### 需求 1SDK 替换 — openai 切换到 dashscope Application API
**用户故事:** 作为后端开发者,我希望将 AI 客户端从 `openai` SDK 切换到 `dashscope` Application API以便 8 个百炼智能体应用能通过各自的 `app_id` 调用,使用百炼控制台配置的 System Prompt 和 MCP 工具。
#### 验收标准
1. THE DashScope_Client SHALL 使用 `dashscope.Application.call()` 替代 `openai.AsyncOpenAI.chat.completions.create()`,所有 8 个 App 通过各自的 `app_id` 参数调用 Application API
2. THE DashScope_Client SHALL 使用 `asyncio.to_thread()` 包装同步的 `Application.call()` 方法,避免阻塞 FastAPI 事件循环
3. WHEN App1 进行流式调用时THE DashScope_Client SHALL 在独立线程中消费 `Application.call(stream=True)` 返回的同步迭代器,通过 `asyncio.Queue` 桥接到 async generator逐 chunk 输出文本
4. WHEN App2~8 进行单轮调用时THE DashScope_Client SHALL 通过 `prompt` 参数传入后端拼好的完整数据 JSON不使用 `messages` 数组
5. THE DashScope_Client SHALL 保留指数退避重试机制:最多 3 次重试,间隔 1s → 2s → 4sHTTP 4xx 不重试5xx/超时/连接错误重试
6. WHEN Application API 返回非合法 JSON 时THE DashScope_Client SHALL 纯重试(最大 3 次),不做本地解析修复
7. THE Backend SHALL 在 `pyproject.toml` 中移除 `openai` 依赖,新增 `dashscope` 依赖
### 需求 2环境变量统一 — BAILIAN_* 迁移到 DASHSCOPE_*
**用户故事:** 作为运维人员,我希望环境变量从 `BAILIAN_*` 统一迁移到 `DASHSCOPE_*` 前缀,以便与 DashScope SDK 的命名规范保持一致。
#### 验收标准
1. THE Backend SHALL 废弃并删除以下环境变量:`BAILIAN_API_KEY``BAILIAN_BASE_URL``BAILIAN_MODEL`
2. THE Backend SHALL 新增以下环境变量:`DASHSCOPE_API_KEY`DashScope API Key`DASHSCOPE_WORKSPACE_ID`(百炼工作空间 ID可选
3. THE Backend SHALL 将 8 个 App ID 环境变量从 `BAILIAN_APP_ID_*` 前缀重命名为 `DASHSCOPE_APP_ID_*` 前缀(`DASHSCOPE_APP_ID_1_CHAT``DASHSCOPE_APP_ID_8_CONSOLIDATE`
4. THE Backend SHALL 更新 `.env``.env.template` 文件,反映所有环境变量变更
5. THE Backend SHALL 在启动时校验必需环境变量(`DASHSCOPE_API_KEY` 和 8 个 `DASHSCOPE_APP_ID_*`),缺失时立即报错,禁止静默回退空字符串
### 需求 3App1 对话管理 — session_id 云端 + 本地双轨
**用户故事:** 作为助教用户,我希望与 AI 助手的多轮对话能通过百炼 session_id 保持上下文连贯,同时本地持久化消息记录,以便在 session 过期后仍能恢复对话。
#### 验收标准
1. WHEN App1 创建新对话时THE Backend SHALL 生成 session_id格式 `conv_{conversation_id}_{created_timestamp}`),存储到 `ai_conversations.session_id` 字段
2. WHEN App1 发送消息时THE DashScope_Client SHALL 携带 `session_id` 参数调用 Application API由百炼云端管理对话上下文
3. THE DashScope_Client SHALL 通过 `biz_params.user_prompt_params` 传递用户信息:`User_ID`(用户 ID`Role`(角色)、`Nickname`(昵称)
4. THE Backend SHALL 同时将每条消息写入本地 `ai_messages` 表,实现云端 + 本地双轨持久化
5. IF session_id 过期(百炼返回 session 无效错误THEN THE Backend SHALL 从本地 `ai_messages` 加载最近 20 条历史消息,用 `messages` 数组(不带 session_id重新调用百炼并将百炼返回的新 session_id 更新到本地
6. THE Backend SHALL 保持现有对话复用规则不变task 入口无时限复用、customer/coach 入口 3 天时限、general 入口始终新建
### 需求 4App2~8 单轮 Prompt 调用
**用户故事:** 作为后端开发者,我希望 App2~8 统一使用单轮 `prompt` 调用模式,以便简化调用逻辑并充分利用百炼控制台配置的 System Prompt。
#### 验收标准
1. THE Backend SHALL 为 App2~8 的每次调用使用 `build_prompt()` 函数拼好完整数据 JSON通过 `Application.call(app_id=..., prompt=data_json)` 传入
2. THE Backend SHALL 不再为 App2~8 在代码中维护 System Prompt以百炼控制台配置为准
3. THE Backend SHALL 不再为 App2~8 使用 `messages` 数组或 `response_format` 参数
4. WHEN Application API 返回结果时THE Backend SHALL 解析 `response.output.text` 字段获取 JSON 内容,解析失败时按需求 1 第 6 条重试
5. THE Backend SHALL 为每次 App2~8 调用记录 `ai_run_logs`(详见需求 10
### 需求 5熔断器
**用户故事:** 作为系统管理员,我希望 AI 调用具备熔断保护,以便在百炼服务异常时快速降级,避免无效请求堆积。
#### 验收标准
1. THE Circuit_Breaker SHALL 按 `app_id` 独立计数App1 熔断不影响 App2~8反之亦然
2. WHEN 某个 app_id 连续 5 次调用失败时THE Circuit_Breaker SHALL 进入熔断状态,持续 60 秒内所有该 app_id 的请求直接返回降级响应
3. WHEN 熔断 60 秒后THE Circuit_Breaker SHALL 进入半开状态,放行 1 个请求进行探测
4. WHEN 半开状态的探测请求成功时THE Circuit_Breaker SHALL 关闭熔断,恢复正常调用
5. WHEN 半开状态的探测请求失败时THE Circuit_Breaker SHALL 重新进入熔断状态,再等待 60 秒
6. WHILE Circuit_Breaker 处于熔断状态THE Backend SHALL 对 App1 请求返回友好提示"AI 服务暂时不可用,请稍后重试",对 App2~8 后台任务记录 `circuit_open` 状态并跳过执行
### 需求 6限流
**用户故事:** 作为系统管理员,我希望 AI 调用具备限流保护,以便防止单个用户或门店过度消耗 AI 资源。
#### 验收标准
1. THE Rate_Limiter SHALL 对 App1 实施每用户每分钟 10 次的请求频率限制
2. THE Rate_Limiter SHALL 对 App2~8 实施每门店每小时 100 次(合计)的请求频率限制
3. WHEN 请求超过限流阈值时THE Rate_Limiter SHALL 对 App1 返回友好提示"请求过于频繁,请稍后再试",对 App2~8 后台任务记录 `rate_limited` 状态并跳过执行
4. THE Rate_Limiter SHALL 使用内存计数器实现(单实例部署),不依赖外部 Redis
### 需求 7Token 预算控制
**用户故事:** 作为系统管理员,我希望 AI 调用具备 Token 预算控制,以便防止 API 费用失控。
#### 验收标准
1. THE Budget_Tracker SHALL 从 `ai_run_logs.tokens_used` 按日/月聚合计算已消耗 token 数
2. THE Budget_Tracker SHALL 支持日预算上限(默认 100,000 tokens和月预算上限默认 2,000,000 tokens
3. WHEN 日预算或月预算超限时THE Backend SHALL 对 App1 用户对话返回友好提示"AI 服务今日额度已用完,请明天再试"
4. WHEN 日预算或月预算超限时THE Backend SHALL 对 App2~8 后台任务跳过执行,记录 `budget_exceeded` 状态到 `ai_run_logs`
5. THE Budget_Tracker SHALL 在每次 AI 调用前检查预算,调用后更新 token 消耗记录
### 需求 8调度器 asyncio 修复
**用户故事:** 作为后端开发者,我希望修复 dispatcher.py 中的 asyncio 嵌套问题,以便事件处理器在 FastAPI 事件循环中正常工作,不再因 `asyncio.run()` 嵌套而报错。
#### 验收标准
1. THE Dispatcher SHALL 移除所有 `asyncio.run()``asyncio.new_event_loop()` 调用
2. THE Dispatcher SHALL 将所有事件处理器入口改为 `async def`,使用 `asyncio.create_task()` 发起后台异步任务
3. THE Dispatcher SHALL 使用 `asyncio.wait_for()` 实现超时控制,替代同步超时机制
4. THE Dispatcher SHALL 确保事件处理器在 FastAPI lifespan 中注册后,能在已有事件循环中正常执行调用链
### 需求 9事件触发链打通
**用户故事:** 作为系统管理员,我希望消费事件、备注事件、任务分配事件能正确触发对应的 AI 调用链,以便 AI 分析结果能自动生成,无需人工干预。
#### 验收标准
1. WHEN ETL DWS 任务完成后发送消费事件时THE Dispatcher SHALL 执行调用链App3 → App8 → App7无助教时或 App3 → App8 → App7 + App4 → App5有助教时
2. WHEN 小程序助教提交备注时THE Dispatcher SHALL 执行调用链App6 → App8
3. WHEN task_manager 自动分配任务时THE Dispatcher SHALL 执行调用链App4 → App5
4. THE Dispatcher SHALL 在调用链中某步失败时记录错误日志,后续应用使用已有缓存继续执行,不中断整条链
5. THE Dispatcher SHALL 将每次事件触发记录到 `ai_trigger_jobs` 表,包含事件类型、执行链、状态、耗时
### 需求 10ETL → 后端内部触发 API
**用户故事:** 作为 ETL 开发者,我希望 DWS 任务完成后能通过 HTTP 接口触发后端 AI 调用链,以便实现 ETL 与 AI 模块的自动联动。
#### 验收标准
1. THE Backend SHALL 实现 `POST /api/internal/ai/trigger` 端点,接受 JSON 请求体包含:`event_type`(事件类型)、`connector_type`(连接器类型,默认 `feiqiu`)、`site_id`(门店 ID`member_id`(会员 ID可选`payload`(事件附加数据)
2. THE Internal_AI_API SHALL 使用独立的 `INTERNAL_API_TOKEN` 环境变量进行认证,通过 HTTP Header `Authorization: Internal-Token {token}` 传递,不走 JWT
3. IF 认证 token 缺失或不匹配THEN THE Internal_AI_API SHALL 返回 HTTP 401
4. THE Internal_AI_API SHALL 将事件写入 `ai_trigger_jobs` 表后立即返回 `{ trigger_job_id, status: "pending" }`,调用链在后台异步执行
5. THE Internal_AI_API SHALL 设计为连接器无关接口,`connector_type` 字段标识来源,为多平台扩展预留
6. THE ETL SHALL 在 DWS 任务完成后通过 HTTP POST 调用 Internal_AI_API传递消费事件或 DWS 完成事件
### 需求 11App2 预生成
**用户故事:** 作为门店管理者我希望财务洞察App2在每日 DWS 数据刷新后自动预生成,以便打开页面时能立即看到最新分析结果,无需等待。
#### 验收标准
1. WHEN ETL 通过 Internal_AI_API 发送 `event_type: "dws_completed"` 事件时THE Dispatcher SHALL 触发 App2 预生成任务
2. THE Dispatcher SHALL 为当前门店(`site_id: 2790685415443269`)生成 8 个时间维度的财务洞察:今日、昨日、本周、上周、本月、上月、本季、上季
3. THE Dispatcher SHALL 将 App2 预生成结果写入 `ai_cache`cache_type: `app2_finance`),过期时间为当日 23:59:59
4. THE Dispatcher SHALL 确保 App2 预生成每日调用量为 1 门店 × 8 维度 = 8 次
### 需求 12幂等与去重
**用户故事:** 作为系统管理员,我希望 AI 事件触发具备幂等去重能力,以便重复事件不会导致重复执行和资源浪费。
#### 验收标准
1. THE Dispatcher SHALL 对自动触发的事件按 `(event_type, member_id, site_id, date)` 进行去重,重复事件跳过执行并记录 `skipped_duplicate` 状态
2. WHEN 手动重跑时(`is_forced = true`THE Dispatcher SHALL 允许强制执行,跳过去重检查,在 `ai_trigger_jobs` 中明确标记 `is_forced`
3. THE Backend SHALL 对 App8 写入 `member_retention_clue` 业务表时实施强幂等:在事务中先 DELETE 再 INSERT同一 member 同一天只执行一次
4. IF App8 幂等写入事务失败THEN THE Backend SHALL 自动回滚,记录错误到 `ai_trigger_jobs.error_message`
### 需求 13缓存策略
**用户故事:** 作为后端开发者,我希望 AI 缓存按 App 类型设置不同的过期时间和状态管理,以便缓存数据的新鲜度与业务需求匹配。
#### 验收标准
1. THE Backend SHALL 为每个 App 设置独立的缓存过期策略App2 当日 23:59:59、App3/App4/App5/App7/App8 为 7 天、App6 为 30 天
2. THE Backend SHALL 在 `ai_cache` 表新增 `status` 字段,支持四种状态:`valid`(有效)、`expired`(已过期)、`invalidated`(手动失效)、`generating`(生成中)
3. WHEN 写入新缓存前THE Backend SHALL 将 `status` 设为 `generating`,写入完成后更新为 `valid`,防止并发读取到不完整数据
4. THE Backend SHALL 在查询缓存时仅返回 `status = 'valid'` 且未过期(`expires_at > now()``expires_at IS NULL`)的记录
5. THE Backend SHALL 对 App2~8 每个 App 保留最新 20,000 条 `ai_cache` 记录超限时清理最旧记录App1 对话记录不自动删除
### 需求 14数据库变更
**用户故事:** 作为后端开发者,我希望新增必要的数据库表和字段,以便支持 AI 运行日志、事件调度记录、session_id 管理和缓存状态管理。
#### 验收标准
1. THE Backend SHALL 在 `biz` schema 新增 `ai_run_logs` 表,包含字段:`id`BIGSERIAL PK`site_id`BIGINT NOT NULL`app_type`VARCHAR(30))、`trigger_type`VARCHAR(20))、`member_id`BIGINT 可空)、`request_prompt`TEXT截断前 2000 字符)、`response_text`TEXT`tokens_used`INTEGER`latency_ms`INTEGER`status`VARCHAR(20))、`error_message`TEXT`session_id`VARCHAR(100))、`created_at`TIMESTAMPTZ`finished_at`TIMESTAMPTZ
2. THE Backend SHALL 在 `biz` schema 新增 `ai_trigger_jobs` 表,包含字段:`id`BIGSERIAL PK`site_id`BIGINT NOT NULL`event_type`VARCHAR(30))、`connector_type`VARCHAR(30) 默认 `feiqiu`)、`member_id`BIGINT 可空)、`payload`JSONB`status`VARCHAR(20))、`is_forced`BOOLEAN 默认 false`app_chain`VARCHAR(100))、`started_at`TIMESTAMPTZ`finished_at`TIMESTAMPTZ`error_message`TEXT`created_at`TIMESTAMPTZ
3. THE Backend SHALL 在 `ai_conversations` 表新增 `session_id` 字段VARCHAR(100)),用于存储百炼 session_id
4. THE Backend SHALL 在 `ai_cache` 表新增 `status` 字段VARCHAR(20) 默认 `valid`CHECK 约束限制为 `valid`/`expired`/`invalidated`/`generating`
5. THE Backend SHALL 为 `ai_run_logs` 创建索引:`(site_id, app_type)``(created_at)``(status)`
6. THE Backend SHALL 为 `ai_trigger_jobs` 创建索引:`(site_id, event_type)`、去重索引 `(event_type, member_id, site_id, created_at::date)` WHERE status NOT IN ('skipped_duplicate')、`(status)`
7. THE Backend SHALL 编写迁移脚本 `db/zqyy_app/migrations/YYYYMMDD_p14_ai_module.sql`,包含所有 DDL 变更,并编写对应的回滚脚本
### 需求 15SSE 端点适配
**用户故事:** 作为助教用户,我希望 AI 对话的 SSE 流式端点能适配 DashScope Application API 的流式输出,以便继续获得逐字显示的对话体验。
#### 验收标准
1. THE Backend SHALL 适配 SSE 端点(`POST /api/xcx/chat/stream`),将 DashScope_Client 的 async generator 输出转换为 SSE 事件流
2. THE Backend SHALL 保持现有 SSE 事件格式不变:`event: message`(逐 token`event: done`(流结束)、`event: error`(错误)
3. WHEN DashScope_Client 流式调用过程中发生错误时THE Backend SHALL 发送 `event: error` 事件并关闭连接,不导致客户端挂起
4. THE Backend SHALL 在流式调用完成后记录 `ai_run_logs`,包含 token 消耗和响应耗时
### 需求 16AI 运行日志记录
**用户故事:** 作为系统管理员,我希望每次 AI 调用都有完整的运行日志,以便追踪调用状态、排查问题和统计 token 消耗。
#### 验收标准
1. THE Backend SHALL 在每次 Application API 调用前创建 `ai_run_logs` 记录status: `pending`),调用开始时更新为 `running`
2. WHEN 调用成功时THE Backend SHALL 更新 `ai_run_logs` 状态为 `success`,记录 `response_text``tokens_used``latency_ms``finished_at`
3. WHEN 调用失败时THE Backend SHALL 更新 `ai_run_logs` 状态为 `failed`,记录 `error_message``latency_ms``finished_at`
4. WHEN 调用超时时THE Backend SHALL 更新 `ai_run_logs` 状态为 `timeout`
5. WHEN 预算超限跳过执行时THE Backend SHALL 创建 `ai_run_logs` 记录,状态为 `budget_exceeded`
6. THE Backend SHALL 将 `request_prompt` 截断为前 2000 字符后存储,避免大 prompt 占用过多存储空间