feat: expose generation model choices

This commit is contained in:
2026-05-25 11:02:13 +08:00
parent 6ba84a7603
commit dcc8abc812
5 changed files with 159 additions and 15 deletions

View File

@@ -4106,6 +4106,71 @@ def _image_model_candidates(force_fallback: bool = False, preference: str | None
return [GPT_IMAGE_MODEL, *fallbacks]
def image_model_options() -> list[dict]:
options = [
{
"id": "auto",
"label": "自动",
"model": GPT_IMAGE_MODEL,
"description": "优先 GPT Image 2必要时按后端熔断和兜底策略切到备用图片模型",
"available": bool(IMAGE_API_KEY),
},
{
"id": GPT_IMAGE_MODEL,
"label": "GPT Image 2",
"model": GPT_IMAGE_MODEL,
"description": "主生图模型,适合营销图和参考图重绘",
"available": bool(IMAGE_API_KEY),
},
]
if IMAGE_FALLBACK_ENABLED and IMAGE_FALLBACK_MODEL and IMAGE_FALLBACK_MODEL != GPT_IMAGE_MODEL:
options.append({
"id": IMAGE_FALLBACK_MODEL,
"label": "Gemini 图片",
"model": IMAGE_FALLBACK_MODEL,
"description": "备用图片模型,适合主模型慢或失败时手动选择",
"available": bool(IMAGE_API_KEY),
})
return options
def video_model_options() -> list[dict]:
label_map = {
"seedance": "Seedance",
"kling": "Kling",
"veo3": "Veo 3",
"veo": "Veo",
"voe": "Veo",
}
seen: set[str] = set()
options: list[dict] = []
for key in ["seedance", "kling", "veo3", "veo"]:
if key not in VIDEO_MODEL_ALIASES:
continue
model = VIDEO_MODEL_ALIASES[key]
unique_key = f"{key}:{model}"
if unique_key in seen:
continue
seen.add(unique_key)
options.append({
"id": key,
"label": label_map.get(key, key),
"model": model,
"description": "当前视频网关可选模型",
"available": bool(video_api_key()),
})
default_model = resolve_video_model(VIDEO_MODEL)
if not any(item["id"] == VIDEO_MODEL or item["model"] == default_model for item in options):
options.insert(0, {
"id": VIDEO_MODEL,
"label": label_map.get(VIDEO_MODEL, VIDEO_MODEL),
"model": default_model,
"description": "默认视频模型",
"available": bool(video_api_key()),
})
return options
def _image_failure_can_fallback(status_code: int, body: str, last_err: str) -> bool:
if status_code in (400, 401, 403, 404):
return False
@@ -5132,6 +5197,7 @@ def health() -> dict:
"image": IMAGE_MODEL,
"image_base_url": IMAGE_BASE_URL or LLM_BASE_URL or "openai-default",
"image_request_timeout_seconds": IMAGE_REQUEST_TIMEOUT_SECONDS,
"image_options": image_model_options(),
"ai_proxy_configured": bool(AI_HTTP_PROXY),
"image_fallbacks": _image_fallback_models(),
"image_circuit": _image_circuit_snapshot(),
@@ -5146,6 +5212,7 @@ def health() -> dict:
"voice_configured": bool(AZURE_OPENAI_API_KEY),
"video": VIDEO_MODEL,
"video_aliases": VIDEO_MODEL_ALIASES,
"video_options": video_model_options(),
"video_provider": video_provider_name(),
"video_base_url": video_api_base(),
"video_configured": bool(video_api_key()),
@@ -5598,7 +5665,7 @@ class GenerateReq(BaseModel):
prompt: str
extra_prompt: str = "" # ✓ 需要的元素(正向)
negative_prompt: str = "" # ✗ 不需要的元素(负向)
model: str = "" # 兼容旧前端字段;服务端强制使用 gpt-image-2
model: str = "auto" # auto / gpt-image-2 / gemini-3-pro-image-preview
mode: str = "edit" # "edit" 带参考图,"text" 纯文字
from_selected: bool = False # True 时优先用 frame.selected 的生成图作 reference迭代否则原关键帧
@@ -5649,8 +5716,8 @@ def generate_image(job_id: str, idx: int, req: GenerateReq) -> Job:
if req.mode == "edit":
img_bytes_in = reference_path.read_bytes()
# 尝试 i2i主模型上游异常时允许 Gemini 兜底。无兜底时保留旧的多次重试
model_candidates = _image_model_candidates()
# 尝试 i2iauto 允许按熔断策略兜底,显式模型只走用户所选模型
model_candidates = _image_model_candidates(preference=req.model)
plan: list[str] = ([req.mode] if model_candidates != [GPT_IMAGE_MODEL] else [req.mode] * 3) if req.mode == "edit" else [req.mode]
if req.mode == "edit":
plan.append("text") # i2i 都失败时自动降级