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