# 模型调用与动态 Fallback 完成说明 > 状态:已完成 > > 最后核对:2026-07-21 > > 用途:说明当前模型调用、重试、动态切换、计费和日志流程。 ## 1. 当前调用流程 ```text 用户选择模型 / 系统默认模型 → 创建一个 AITask → 预留一次积分 → 调用主模型 → 明确失败:按配置重试主模型,仍失败且允许时动态 Fallback → 远端结果不确定:停止自动重试和 Fallback → 成功:保存结果并结算一次 → 最终失败或结果不确定:释放一次预留 ``` 主模型始终是用户本次选择的模型;Fallback 不修改用户选择、默认模型或下次调用。 ## 2. Retry 与 Fallback - `Retry`:再次调用当前模型。 - `Fallback`:切换到其他兼容模型。 - `调用审计`:记录每次真实调用,不参与模型评分或自动学习。 当前默认值: | 能力 | 主模型重试 | 单次超时 | 总时限 | |---|---:|---:|---:| | 文本 | 2 次,等待 1 / 3 秒 | 120 秒;流式 300 秒 | 480 秒 | | 图片 | 1 次,等待 3 秒 | 300 秒 | 900 秒 | | 配音 | 1 次,等待 2 秒 | 60 秒 | 180 秒 | | 视频提交 | 1 次,等待 3 秒 | 120 秒 | 300 秒 | | 视频成片 | 不重新提交 | 单次轮询 60 秒 | 以供应商终态为准,无本地硬总时限 | 单个逻辑任务最多尝试 3 个模型、发出 5 次真实请求。 ### 2.1 远端结果确定性保护 统一执行器先判断本次失败是否能确认供应商没有成功处理: - 明确失败:保留现有 Retry 与动态 Fallback。 - 结果不确定:记录当前一次失败后立即停止,不查询候选、不等待退避、不再次调用 Provider。 - `ReadTimeout`、请求发送后连接中断、HTTP `502/504`、响应缺失且无法确认结果,按结果不确定处理。 - DNS、连接拒绝、建立连接超时和明确 `429` 仍按现有策略重试或 Fallback。 - 结果不确定记录在 `AIModelAttempt.response_summary.outcome_unknown=true`,不新增表字段。 - 该保护不改变超时数值、普通用户提示、模型选择、动态排序或视频长轮询规则。 ## 3. 配置位置 全局策略在: ```text core/backend/.env core/backend/airshelf/settings/base.py → MODEL_ROUTING_POLICY ``` 控制重试、提交超时、最多模型数、最多调用数和后处理退避。视频取得远端任务 ID 后不设本地成片硬总时限。修改后必须同时重启 API 与 Celery Worker。 每个模型的开关保存在数据库 `ModelConfig.metadata.routing`: ```json { "fallback_on_failure": false, "fallback_candidate": true } ``` - `fallback_on_failure`:当前模型失败后是否允许切到其他模型。 - `fallback_candidate`:当前模型是否允许被其他失败模型选中。 - 开关按模型独立配置,不在代码中写死供应商或模型名单。 ## 4. 火山 / 豆包直连现状 文本、配音、视频都已接入统一 Fallback 代码,但当前直连模型统一配置为: ```text fallback_on_failure = false fallback_candidate = true ``` 当前行为: - 允许原模型重试。 - 不允许从直连模型主动切出。 - 已启用且能力匹配时,可以作为其他模型的候选。 - 以后只改模型 metadata 即可开启,无需重新开发路由代码。 ## 5. 动态候选规则 候选每次失败后实时读取数据库,必须同时满足: - 供应商已启用。 - 模型已启用。 - `fallback_candidate=true`。 - 能力一致:`text`、`image`、`audio` 或 `video`。 - 操作要求一致:流式、结构化、图片编辑、参考素材数量、比例、分辨率、时长、音色映射等。 - 本任务尚未尝试过。 排序规则: ```text 火山 / 豆包直连 → YunQi → 其他 OpenAI API 兼容供应商 → 同层级按 ModelConfig.updated_at 从新到旧 ``` 除现有火山 / 豆包专用适配器外,新增供应商只支持 OpenAI API 兼容协议。 ## 6. 任务与计费 一次用户操作始终只有: - 一个 `AITask`。 - 一次积分预留。 - 一次最终扣费或释放。 每次真实模型调用只新增一条 `AIModelAttempt`,不会单独预留或扣费。 - Fallback 成功:按原逻辑任务结算一次。 - 全部失败:释放原预留,用户扣费为 0。 - 多次尝试产生的上游费用累计为平台成本,不转换成多笔用户扣费。 ## 7. 调用日志 `AIModelAttempt` 记录: - 调用顺序。 - 供应商与真实模型快照。 - 首次调用、重试或 Fallback。 - 成功 / 失败、耗时和错误分类。 - Provider 任务 ID、用量和平台成本。 - 脱敏请求 / 响应摘要。 管理员在 `/admin/tasks` 的任务详情查看完整调用链。普通用户端保持静默,不显示内部供应商、真实候选、失败原因或尝试次数。 公开别名保持不变: - Gemini 3.1 Pro → `AirShelf Script` - `gpt-image-2` → `AirShelf Image` - 火山 / 豆包直连继续显示原公开名称。 ## 8. 视频特殊规则 - 只有尚未取得远端任务 ID,且明确提交失败时,才允许重试或 Fallback。 - 提交读取超时或响应缺少任务 ID 视为“远端状态未知”,禁止重复提交。 - 获得远端任务 ID 后固定使用真正提交成功的模型轮询和结算。 - 普通轮询超时只继续轮询,不切模型、不重新提交。 - 本地等待时长不产生失败终态;只有供应商明确返回 `failed`、`expired` 或 `cancelled` 才失败并释放预留。 - Worker 有限轮询用尽后只停止本轮兜底,业务任务仍保持生成中;用户重新打开页面可继续查询。 - 供应商最终成功时正常转存资产并结算一次,即使成片超过 30 分钟。 - 下载、上传、TOS 或资产保存失败只重试后处理,不重新调用模型。 旧版 30 分钟硬终止误判任务可使用以下管理命令核对: ```text python manage.py reconcile_video_timeouts --task-id python manage.py reconcile_video_timeouts --task-id --apply ``` 默认只读;`--apply` 仅恢复远端已成功结果,不重新提交模型、不追扣用户积分,并保证重复执行不会重复创建版本或资产。 ## 9. 已接入入口 - 脚本生成、脚本优化、单镜优化。 - 显式实体提取、脚本实体同步兼容入口。 - 独立图片生成 / 编辑、模特上身图、平台套图。 - 商品 / 人物 / 场景基础资产、项目与模特三视图。 - 故事板分镜生成。 - 配音生成。 - 视频片段生成。 - 自由创作 / 免费视频生成。 ## 10. 发布要求 - 数据库迁移:`0028_seed_model_routing_metadata`、`0029_aimodelattempt`。 - API 与 Celery Worker 必须使用同一版后端镜像。 - API 与 Worker 必须读取同一份路由配置。 - 发布后至少冒烟验证文本、图片、配音、视频和 `/admin/tasks` 调用链。 - 回滚代码时不要直接反向执行 `0028`,避免删除已维护的模型路由 metadata。 ## 11. 关键代码 ```text core/backend/apps/ai/routing_policy.py 全局策略读取与校验 core/backend/apps/ai/generation_errors.py 错误分类与远端结果确定性判断 core/backend/apps/ai/model_routing.py 能力匹配与动态候选排序 core/backend/apps/ai/routing_executor.py 重试、Fallback 与调用审计 core/backend/apps/ai/models.py AITask / AIModelAttempt core/backend/apps/ai/services.py 各业务入口编排 core/backend/apps/ai/free_video.py 自由视频提交与轮询 core/backend/apps/ai/script_agent.py 脚本流式调用 ``` ## 12. 非本次范围 - 未修改 `/admin/providers` 页面。 - 未实现用户反馈学习、模型评分或自动调权。 - 未写死备用模型链。 - 未支持新增非 OpenAI API 私有协议供应商。