feat: sync latest local version as authoritative codebase

Complete rewrite/sync of comfyui_o1key custom nodes.
Treat this commit as the current canonical version.

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
This commit is contained in:
o1key
2026-04-03 16:18:45 +08:00
co-authored by Claude Sonnet 4.5
commit 9ab209b2b7
46 changed files with 16757 additions and 0 deletions
+896
View File
@@ -0,0 +1,896 @@
"""
模型配置中心
用于集中管理所有支持的 Gemini 模型
使用方式:
1. 添加新模型: 在对应的模型列表中添加新的模型字典
2. 临时关闭模型: 将模型的 enabled 字段设为 False
3. 重新启用模型: 将模型的 enabled 字段改回 True
模型类型:
- GEMINI_MODELS: Nano Banana 图像生成模型
- GEMINI_FLASH_MODELS: Google Gemini Flash 文本生成模型
示例:
添加新模型:
{
"id": "gemini-新模型名称",
"description": "模型说明和特点",
"enabled": True,
"endpoint_type": "standard",
"endpoint": "/v1beta/models/gemini-新模型名称:generateContent",
"thinking_config": {
"不思考": None,
"": "low",
"": None,
"": "high"
}
}
临时关闭模型:
将对应模型的 "enabled": True 改为 "enabled": False
"""
from typing import List, Dict, Optional, Tuple
# ============================================================
# 模型配置列表
# ============================================================
# ============================================================
# Nano Banana 图像生成模型
# ============================================================
GEMINI_MODELS = [
{
"id": "nano-banana-pro-限时特价",
"description": "Nano Banana Pro 限时特价,根据分辨率自动选择端点 (1K/2K/4K),高性能图像生成模型",
"enabled": True,
"endpoint_type": "dynamic",
"endpoint": None, # 动态端点,由代码根据分辨率选择
"supported_aspect_ratios": [
"1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"
],
"supported_resolutions": ["1K", "2K", "4K"]
},
{
"id": "nano-banana-pro-官方计费",
"description": "Nano Banana Pro 官方计费,按分辨率路由 (1K/2K/4K),使用官方计费通道",
"enabled": True,
"endpoint_type": "dynamic",
"endpoint": None, # 动态端点,由代码根据分辨率选择
"supported_aspect_ratios": [
"1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"
],
"supported_resolutions": ["1K", "2K", "4K"]
},
{
"id": "nano-banana-2-限时特价",
"description": "Nano Banana 2 限时特价,固定端点,图像生成模型",
"enabled": True,
"endpoint_type": "standard",
"endpoint": "/v1beta/models/nano-banana-2:generateContent",
"supported_aspect_ratios": [
"1:1", "1:4", "1:8", "2:3", "3:2", "3:4", "4:1", "4:3", "4:5", "5:4",
"8:1", "9:16", "16:9", "21:9"
],
"supported_resolutions": ["512", "1K", "2K", "4K"]
},
{
"id": "nano-banana-2-官方计费",
"description": "Nano Banana 2 官方计费,按分辨率路由 (512/1K/2K/4K),使用官方计费通道",
"enabled": True,
"endpoint_type": "dynamic",
"endpoint": None, # 动态端点,由代码根据分辨率选择
"supported_aspect_ratios": [
"1:1", "1:4", "1:8", "2:3", "3:2", "3:4", "4:1", "4:3", "4:5", "5:4",
"8:1", "9:16", "16:9", "21:9"
],
"supported_resolutions": ["512", "1K", "2K", "4K"]
},
{
"id": "gemini-3-pro-image-preview",
"description": "标准模式,固定端点,适用于常规图像生成",
"enabled": False,
"endpoint_type": "standard",
"endpoint": "/v1beta/models/gemini-3-pro-image-preview:generateContent",
"supported_aspect_ratios": [
"1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"
],
"supported_resolutions": ["1K", "2K", "4K"]
},
{
"id": "gemini-3.1-flash-image-preview",
"description": "Gemini 3.1 Flash 图像生成,固定端点,快速图像生成模型",
"enabled": False,
"endpoint_type": "standard",
"endpoint": "/v1beta/models/gemini-3.1-flash-image-preview:generateContent",
"supported_aspect_ratios": [
"1:1", "1:4", "1:8", "2:3", "3:2", "3:4", "4:1", "4:3", "4:5", "5:4",
"8:1", "9:16", "16:9", "21:9"
],
"supported_resolutions": ["512", "1K", "2K", "4K"]
}
]
# ============================================================
# Google Gemini Flash 文本生成模型
# ============================================================
GEMINI_FLASH_MODELS = [
{
"id": "gemini-3-flash-preview",
"description": "Gemini 3 Flash,快速多模态文本生成,通过 thinkingConfig 控制思考等级",
"enabled": True,
"endpoint_type": "standard",
"endpoint": "/v1beta/models/gemini-3-flash-preview:generateContent",
"thinking_config": {
"": "low",
"": "medium",
"": "high"
}
},
{
"id": "gemini-3.1-pro-preview",
"description": "Gemini 3.1 Pro,高性能多模态文本生成,通过 thinkingConfig 控制思考等级",
"enabled": True,
"endpoint_type": "standard",
"endpoint": "/v1beta/models/gemini-3.1-pro-preview:generateContent",
"thinking_config": {
"": "low",
"": "high"
}
},
{
"id": "gemini-3.1-flash-lite-preview",
"description": "Gemini 3.1 Flash Lite,轻量级多模态文本生成,通过 thinkingConfig 控制思考等级",
"enabled": True,
"endpoint_type": "standard",
"endpoint": "/v1beta/models/gemini-3.1-flash-lite-preview:generateContent",
"thinking_config": {
"": "low",
"": "medium",
"": "high"
}
}
]
# ============================================================
# 工具函数
# ============================================================
def get_enabled_models() -> List[str]:
"""
获取所有启用的模型 ID 列表
Returns:
启用的模型 ID 列表
Example:
>>> get_enabled_models()
['gemini-3-pro-image-preview-url', 'gemini-3-pro-image-preview', ...]
"""
return [model["id"] for model in GEMINI_MODELS if model.get("enabled", False)]
def get_all_models() -> List[str]:
"""
获取所有模型 ID 列表(包括已禁用的)
Returns:
所有模型 ID 列表
Example:
>>> get_all_models()
['gemini-3-pro-image-preview-url', 'gemini-3-pro-image-preview', ...]
"""
return [model["id"] for model in GEMINI_MODELS]
def get_model_config(model_id: str) -> Optional[Dict]:
"""
根据模型 ID 获取完整的模型配置
Args:
model_id: 模型 ID
Returns:
模型配置字典,如果未找到则返回 None
Example:
>>> config = get_model_config("gemini-3-pro-image-preview-url")
>>> print(config["description"])
URL 模式,根据分辨率自动选择端点 (1K/2K/4K)
"""
for model in GEMINI_MODELS:
if model["id"] == model_id:
return model
return None
def is_model_enabled(model_id: str) -> bool:
"""
检查指定模型是否启用
Args:
model_id: 模型 ID
Returns:
True 如果模型启用,False 如果禁用或不存在
Example:
>>> is_model_enabled("gemini-3-pro-image-preview-url")
True
"""
config = get_model_config(model_id)
if config is None:
return False
return config.get("enabled", False)
def get_model_description(model_id: str) -> str:
"""
获取模型的描述信息
Args:
model_id: 模型 ID
Returns:
模型描述,如果未找到则返回空字符串
Example:
>>> get_model_description("gemini-3-pro-image-preview")
'标准模式,固定端点,适用于常规图像生成'
"""
config = get_model_config(model_id)
if config is None:
return ""
return config.get("description", "")
def get_model_supported_aspect_ratios(model_id: str) -> List[str]:
"""
获取模型支持的宽高比列表
Args:
model_id: 模型 ID
Returns:
支持的宽高比字符串列表,如果未配置则返回空列表
Example:
>>> get_model_supported_aspect_ratios("gemini-3-pro-image-preview")
['1:1', '2:3', '3:2', ...]
"""
config = get_model_config(model_id)
if config is None:
return []
return config.get("supported_aspect_ratios", [])
def get_all_supported_aspect_ratios() -> List[str]:
"""
获取所有启用模型支持的宽高比(去重合并)
Returns:
所有启用模型支持的宽高比列表(保持顺序、去重)
Example:
>>> get_all_supported_aspect_ratios()
['1:1', '4:3', '3:4', '16:9', '9:16', '2:3', '3:2', '4:5', '5:4', '21:9', '1:4', '4:1', '1:8', '8:1']
"""
seen = set()
result = []
for model in GEMINI_MODELS:
if not model.get("enabled", False):
continue
for ratio in model.get("supported_aspect_ratios", []):
if ratio not in seen:
seen.add(ratio)
result.append(ratio)
return result
def get_model_supported_resolutions(model_id: str) -> List[str]:
"""
获取模型支持的分辨率列表
Args:
model_id: 模型 ID
Returns:
支持的分辨率字符串列表,如果未配置则返回空列表
Example:
>>> get_model_supported_resolutions("gemini-3.1-flash-image-preview")
['512', '1K', '2K', '4K']
>>> get_model_supported_resolutions("gemini-3-pro-image-preview")
['1K', '2K', '4K']
"""
config = get_model_config(model_id)
if config is None:
return []
return config.get("supported_resolutions", [])
def get_all_supported_resolutions() -> List[str]:
"""
获取所有启用模型支持的分辨率(去重合并,按从小到大固定顺序排列)
Returns:
所有启用模型支持的分辨率列表(按 512 → 1K → 2K → 4K 顺序)
Example:
>>> get_all_supported_resolutions()
['512', '1K', '2K', '4K']
"""
_ORDER = ["512", "1K", "2K", "4K"]
seen = set()
for model in GEMINI_MODELS:
if not model.get("enabled", False):
continue
for res in model.get("supported_resolutions", []):
seen.add(res)
return [res for res in _ORDER if res in seen]
def get_endpoint_type(model_id: str) -> Optional[str]:
"""
获取模型的端点类型
Args:
model_id: 模型 ID
Returns:
端点类型 ("dynamic", "standard", "flatfee"),如果未找到则返回 None
Example:
>>> get_endpoint_type("gemini-3-pro-image-preview-url")
'dynamic'
"""
config = get_model_config(model_id)
if config is None:
return None
return config.get("endpoint_type")
def get_model_endpoint(model_id: str) -> Optional[str]:
"""
获取模型的 API 端点
Args:
model_id: 模型 ID
Returns:
API 端点路径,如果未找到或为动态端点则返回 None
Example:
>>> get_model_endpoint("gemini-3-pro-image-preview")
'/v1beta/models/gemini-3-pro-image-preview:generateContent'
>>> get_model_endpoint("gemini-3-pro-image-preview-url")
None # 动态端点
"""
config = get_model_config(model_id)
if config is None:
return None
return config.get("endpoint")
# ============================================================
# Gemini Flash 模型工具函数
# ============================================================
# ============================================================
# Sora 视频生成模型
# ============================================================
SORA_MODELS = [
{
"id": "sora-2",
"description": "Sora 2 官方模型,支持标准时长和分辨率",
"enabled": True,
"supported_seconds": [4, 8, 10, 12, 15],
"supported_sizes": ["720x1280", "1280x720"],
"seconds_category": "官方", # 用于界面显示标签
},
{
"id": "sora-2-pro",
"description": "Sora 2 Pro 增强模型,支持扩展时长和竖屏/横屏高清分辨率",
"enabled": True,
"supported_seconds": [4, 8, 12, 15, 25],
"supported_sizes": ["720x1280", "1280x720", "1024x1792", "1792x1024"],
"seconds_category": "扩展", # Pro 模型支持全部时长
},
]
# 秒数显示标签配置(用于界面下拉菜单)
# key: 实际秒数, value: 显示文本
SECONDS_DISPLAY_MAP = {
4: "4",
8: "8",
12: "12",
10: "10",
15: "15",
25: "25(pro)",
}
# 分辨率显示标签配置
# key: 实际分辨率, value: (显示P数, 显示方向)
RESOLUTION_DISPLAY_MAP = {
"720x1280": ("720P", "竖屏"),
"1280x720": ("720P", "横屏"),
"1024x1792": ("1080P", "竖屏"),
"1792x1024": ("1080P", "横屏"),
}
# ============================================================
# Sora 模型工具函数
# ============================================================
def get_enabled_sora_models() -> List[str]:
"""获取所有启用的 Sora 模型 ID 列表"""
return [model["id"] for model in SORA_MODELS if model.get("enabled", False)]
def get_sora_model_config(model_id: str) -> Optional[Dict]:
"""根据模型 ID 获取 Sora 模型的完整配置"""
for model in SORA_MODELS:
if model["id"] == model_id:
return model
return None
def get_sora_supported_seconds(model_id: str) -> List[int]:
"""获取 Sora 模型支持的视频时长列表(秒)"""
config = get_sora_model_config(model_id)
if config is None:
return []
return config.get("supported_seconds", [])
def get_sora_supported_sizes(model_id: str) -> List[str]:
"""获取 Sora 模型支持的分辨率列表"""
config = get_sora_model_config(model_id)
if config is None:
return []
return config.get("supported_sizes", [])
def get_all_sora_seconds() -> List[int]:
"""获取所有启用 Sora 模型支持的时长(去重、升序)"""
seen = set()
for model in SORA_MODELS:
if not model.get("enabled", False):
continue
for s in model.get("supported_seconds", []):
seen.add(s)
return sorted(seen)
def get_all_sora_sizes() -> List[str]:
"""获取所有启用 Sora 模型支持的分辨率(去重、保持顺序)"""
seen = set()
result = []
for model in SORA_MODELS:
if not model.get("enabled", False):
continue
for size in model.get("supported_sizes", []):
if size not in seen:
seen.add(size)
result.append(size)
return result
def get_sora_seconds_with_labels(model_id: str) -> List[Tuple[str, int]]:
"""
获取指定模型支持的秒数列表(带标签显示)
Returns:
列表项为 (显示文本, 实际秒数),如 [("4(官方)", 4), ("10(特殊)", 10)]
"""
config = get_sora_model_config(model_id)
if config is None:
return []
seconds_list = config.get("supported_seconds", [])
result = []
for s in seconds_list:
category = SECONDS_CATEGORIES.get(s, "")
label = f"{s}{category}" if category else str(s)
result.append((label, s))
return result
def get_sora_sizes_with_labels(model_id: str) -> List[Tuple[str, str]]:
"""
获取指定模型支持的分辨率列表(带独占标识)
Returns:
列表项为 (显示文本, 实际分辨率),如 [("720P 9:16 (720x1280)", "720x1280")]
"""
from math import gcd
config = get_sora_model_config(model_id)
if config is None:
return []
sizes = config.get("supported_sizes", [])
result = []
# 检查哪些分辨率是独占的(仅该模型支持)
all_sizes_count = {}
for m in SORA_MODELS:
if not m.get("enabled", False):
continue
for size in m.get("supported_sizes", []):
all_sizes_count[size] = all_sizes_count.get(size, 0) + 1
for size in sizes:
# 解析分辨率
parts = size.lower().split("x")
w, h = int(parts[0]), int(parts[1])
short_side = min(w, h)
# 分辨率等级
if short_side >= 1792:
res = "2K+"
elif short_side >= 1080:
res = "1K+"
elif short_side >= 720:
res = "720P"
else:
res = f"{short_side}P"
# 比例
g = gcd(w, h)
ratio = f"{w // g}:{h // g}"
# 检查是否独占
exclusive = all_sizes_count.get(size, 0) == 1
exclusive_tag = " [Pro独占]" if exclusive else ""
# 方向
orientation = "竖屏" if h > w else "横屏" if w > h else "方形"
label = f"{res} {ratio} {orientation}{exclusive_tag} ({size})"
result.append((label, size))
return result
# ============================================================
# Google Veo 视频生成模型
# ============================================================
VEO_MODELS = [
{
"id": "Veo3.1",
"description": "Google Veo 3.1 视频生成模型,支持文生视频和图生视频",
"enabled": True,
},
]
# Veo 分辨率映射表
# key: "分辨率_宽高比", value: 实际分辨率字符串
VEO_RESOLUTION_MAP = {
# 720p
"720p_9:16": "720x1280",
"720p_16:9": "1280x720",
# 1080p
"1080p_9:16": "1080x1920",
"1080p_16:9": "1920x1080",
# 4K
"4K_9:16": "2160x3840",
"4K_16:9": "3840x2160",
}
# ============================================================
# Veo 模型工具函数
# ============================================================
def get_enabled_veo_models() -> List[str]:
"""获取所有启用的 Veo 模型 ID 列表"""
return [model["id"] for model in VEO_MODELS if model.get("enabled", False)]
def get_veo_model_config(model_id: str) -> Optional[Dict]:
"""根据模型 ID 获取 Veo 模型的完整配置"""
for model in VEO_MODELS:
if model["id"] == model_id:
return model
return None
# ============================================================
# Gemini Flash 模型工具函数
# ============================================================
def get_enabled_flash_models() -> List[str]:
"""
获取所有启用的 Flash 模型 ID 列表
Returns:
启用的 Flash 模型 ID 列表
Example:
>>> get_enabled_flash_models()
['gemini-3-flash-preview']
"""
return [model["id"] for model in GEMINI_FLASH_MODELS if model.get("enabled", False)]
def get_all_flash_models() -> List[str]:
"""
获取所有 Flash 模型 ID 列表(包括已禁用的)
Returns:
所有 Flash 模型 ID 列表
"""
return [model["id"] for model in GEMINI_FLASH_MODELS]
def get_flash_model_config(model_id: str) -> Optional[Dict]:
"""
根据模型 ID 获取 Flash 模型的完整配置
Args:
model_id: 模型 ID
Returns:
模型配置字典,如果未找到则返回 None
Example:
>>> config = get_flash_model_config("gemini-3-flash-preview")
>>> print(config["description"])
'Gemini 3 Flash,快速多模态文本生成,支持图片和视频输入'
"""
for model in GEMINI_FLASH_MODELS:
if model["id"] == model_id:
return model
return None
def is_flash_model_enabled(model_id: str) -> bool:
"""
检查指定 Flash 模型是否启用
Args:
model_id: 模型 ID
Returns:
True 如果模型启用,False 如果禁用或不存在
"""
config = get_flash_model_config(model_id)
if config is None:
return False
return config.get("enabled", False)
def get_flash_model_endpoint(model_id: str) -> Optional[str]:
"""
获取 Flash 模型的 API 端点
Args:
model_id: 模型 ID
Returns:
API 端点路径,如果未找到则返回 None
Example:
>>> get_flash_model_endpoint("gemini-3-flash-preview")
'/v1beta/models/gemini-3-flash-preview:generateContent'
"""
config = get_flash_model_config(model_id)
if config is None:
return None
return config.get("endpoint")
def get_flash_model_description(model_id: str) -> str:
"""
获取 Flash 模型的描述信息
Args:
model_id: 模型 ID
Returns:
模型描述,如果未找到则返回空字符串
"""
config = get_flash_model_config(model_id)
if config is None:
return ""
return config.get("description", "")
def get_flash_model_thinking_level_value(model_id: str, thinking_level: str) -> Optional[str]:
"""
获取指定模型在给定思考等级下应传入请求体的 thinkingLevel 值。
仅对 endpoint_type="standard" 且配置了 thinking_config 的模型有效。
返回 None 表示该等级不受支持,请求体中不应包含 thinkingConfig。
Args:
model_id: 模型 ID
thinking_level: 思考等级中文名(不思考/低/中/高)
Returns:
API thinkingLevel 值(如 "low"/"medium"/"high"),或 None(不传参)
Example:
>>> get_flash_model_thinking_level_value("gemini-3-pro-preview", "")
'low'
>>> get_flash_model_thinking_level_value("gemini-3-pro-preview", "")
None # 不受支持,省略 thinkingConfig
"""
config = get_flash_model_config(model_id)
if config is None:
return None
thinking_config = config.get("thinking_config")
if not thinking_config:
return None
return thinking_config.get(thinking_level)
# 已弃用:动态端点模式下不再需要这些函数
# def get_flash_model_thinking_levels(model_id: str) -> List[str]:
# """
# 获取 Flash 模型支持的思考等级列表
#
# Args:
# model_id: 模型 ID
#
# Returns:
# 思考等级列表(中文),如果未找到则返回空列表
#
# Example:
# >>> get_flash_model_thinking_levels("gemini-3-flash-preview")
# ['默认', '最低', '低', '中', '高']
# """
# config = get_flash_model_config(model_id)
# if config is None:
# return []
#
# thinking_levels = config.get("thinking_levels", {})
# return list(thinking_levels.keys())
# def get_thinking_level_value(model_id: str, thinking_level: str) -> Optional[str]:
# """
# 获取思考等级对应的 API 参数值
#
# Args:
# model_id: 模型 ID
# thinking_level: 思考等级(中文)
#
# Returns:
# API 参数值(英文),如果未找到则返回 None
#
# Example:
# >>> get_thinking_level_value("gemini-3-flash-preview", "默认")
# 'high'
# >>> get_thinking_level_value("gemini-3-flash-preview", "最低")
# 'minimal'
# """
# config = get_flash_model_config(model_id)
# if config is None:
# return None
#
# thinking_levels = config.get("thinking_levels", {})
# return thinking_levels.get(thinking_level)
# ============================================================
# 向后兼容性检查
# ============================================================
def validate_models_config() -> None:
"""
验证模型配置的完整性
检查:
- 每个模型必须有 id, description, enabled, endpoint_type, endpoint 字段
- 非动态端点模型必须配置有效的 endpoint
- 至少有一个模型是启用的
Raises:
ValueError: 如果配置不合法
"""
if not GEMINI_MODELS:
raise ValueError("GEMINI_MODELS 列表不能为空")
required_fields = ["id", "description", "enabled", "endpoint_type", "endpoint"]
valid_endpoint_types = ["dynamic", "standard", "flatfee"]
for i, model in enumerate(GEMINI_MODELS):
# 检查必需字段
for field in required_fields:
if field not in model:
raise ValueError(f"模型 #{i} 缺少必需字段: {field}")
# 检查 endpoint_type 是否合法
if model["endpoint_type"] not in valid_endpoint_types:
raise ValueError(
f"模型 {model['id']} 的 endpoint_type '{model['endpoint_type']}' 不合法。"
f"必须是: {', '.join(valid_endpoint_types)}"
)
# 检查非动态端点模型必须有有效的 endpoint
if model["endpoint_type"] != "dynamic" and not model.get("endpoint"):
raise ValueError(
f"模型 {model['id']} 的 endpoint_type 为 '{model['endpoint_type']}'"
f"但未配置有效的 endpoint 字段"
)
# 检查端点格式(如果配置了)
endpoint = model.get("endpoint")
if endpoint and not endpoint.startswith("/v1beta/models/"):
raise ValueError(
f"模型 {model['id']} 的 endpoint '{endpoint}' 格式不正确。"
f"应以 '/v1beta/models/' 开头"
)
# 检查至少有一个启用的模型
if not get_enabled_models():
raise ValueError("至少需要启用一个模型")
def validate_flash_models_config() -> None:
"""
验证 Flash 模型配置的完整性
检查:
- 每个模型必须有 id, description, enabled 字段
- 每个模型必须有 endpoint 字段且格式正确
- 至少有一个模型是启用的
Raises:
ValueError: 如果配置不合法
"""
if not GEMINI_FLASH_MODELS:
raise ValueError("GEMINI_FLASH_MODELS 列表不能为空")
required_fields = ["id", "description", "enabled"]
for i, model in enumerate(GEMINI_FLASH_MODELS):
# 检查必需字段
for field in required_fields:
if field not in model:
raise ValueError(f"Flash 模型 #{i} 缺少必需字段: {field}")
# 检查端点配置
if "endpoint" not in model:
raise ValueError(f"Flash 模型 {model['id']} 缺少 'endpoint' 字段")
endpoint = model.get("endpoint", "")
if not endpoint or not endpoint.startswith("/v1beta/models/"):
raise ValueError(
f"Flash 模型 {model['id']} 的 endpoint '{endpoint}' 格式不正确。"
f"应以 '/v1beta/models/' 开头"
)
# 检查至少有一个启用的模型
if not get_enabled_flash_models():
raise ValueError("至少需要启用一个 Flash 模型")
# 在模块加载时验证配置
try:
validate_models_config()
except ValueError as e:
print(f"⚠️ 图像模型配置验证失败: {str(e)}")
print(f"⚠️ 请检查 models_config.py 文件")
try:
validate_flash_models_config()
except ValueError as e:
print(f"⚠️ Flash 模型配置验证失败: {str(e)}")
print(f"⚠️ 请检查 models_config.py 文件")