436 lines
14 KiB
Python
436 lines
14 KiB
Python
import json
|
|
import os
|
|
|
|
# 加载配置文件
|
|
def load_config():
|
|
"""加载完整的config.json配置文件"""
|
|
config_path = os.path.join(os.path.dirname(__file__), '..', "config.json")
|
|
# 检查配置文件是否存在
|
|
if not os.path.exists(config_path):
|
|
raise FileNotFoundError(f"Config file not found: {config_path}")
|
|
try:
|
|
with open(config_path, 'r', encoding='utf-8') as f:
|
|
config = json.load(f)
|
|
return config
|
|
except Exception as e:
|
|
raise ValueError(f"Config loading error: {str(e)}")
|
|
|
|
# 根据 key 获取对应的 config 配置段
|
|
def get_config_section(section_key):
|
|
"""
|
|
根据 key 获取对应的 config 配置段
|
|
Args:
|
|
section_key: 配置段的键名,例如 'ollama-vlm', 'gemini-image'
|
|
Returns:
|
|
对应的配置段字典,如果不存在则返回 None
|
|
"""
|
|
try:
|
|
config = load_config()
|
|
return config.get(section_key, None)
|
|
except Exception:
|
|
return None
|
|
# 根据配置段 key 获取模型列表
|
|
def get_models_list(section_key):
|
|
try:
|
|
section_config = get_config_section(section_key)
|
|
# 验证配置是否存在
|
|
if not section_config:
|
|
raise ValueError(f"Missing {section_key} section in config file")
|
|
|
|
# 直接获取models列表
|
|
if 'models' not in section_config:
|
|
raise ValueError("Missing 'models' in section")
|
|
|
|
models = section_config['models']
|
|
|
|
# 验证models是否为列表且不为空
|
|
if not isinstance(models, list):
|
|
raise ValueError("'models' must be a list")
|
|
|
|
if not models:
|
|
raise ValueError("'models' list cannot be empty")
|
|
return models
|
|
except Exception as e:
|
|
raise ValueError(f"Failed to load models: {str(e)}")
|
|
|
|
|
|
OPENAI_PROTOCOLS = {"openai-completions", "openai-responses"}
|
|
|
|
|
|
def get_openai_apis(section_key="openai-text"):
|
|
"""Return normalized OpenAI-compatible API configurations.
|
|
|
|
``openai-text`` is normally an array. A legacy single mapping is also
|
|
accepted so existing installations can migrate without breaking nodes.
|
|
"""
|
|
raw = get_config_section(section_key)
|
|
if raw is None:
|
|
raise ValueError(f"Missing {section_key} section in config file")
|
|
if isinstance(raw, dict):
|
|
raw_items = [raw]
|
|
elif isinstance(raw, list):
|
|
raw_items = raw
|
|
else:
|
|
raise ValueError(f"{section_key} must be an object or array")
|
|
if not raw_items:
|
|
raise ValueError(f"{section_key} cannot be empty")
|
|
|
|
result = []
|
|
names = set()
|
|
for index, item in enumerate(raw_items):
|
|
if not isinstance(item, dict):
|
|
raise ValueError(f"{section_key}[{index}] must be an object")
|
|
name = item.get("api-name") or item.get("api_name")
|
|
# Legacy single-object configurations have no name. Give them a
|
|
# stable display name while retaining the original fields.
|
|
if not name:
|
|
if len(raw_items) == 1:
|
|
name = "default"
|
|
else:
|
|
raise ValueError(f"{section_key}[{index}] missing 'api-name'")
|
|
name = str(name).strip()
|
|
if not name:
|
|
raise ValueError(f"{section_key}[{index}] api-name cannot be empty")
|
|
if name in names:
|
|
raise ValueError(f"Duplicate api-name: {name}")
|
|
names.add(name)
|
|
|
|
base_url = str(item.get("base_url", "")).strip()
|
|
if not base_url:
|
|
raise ValueError(f"{section_key}[{index}] base_url cannot be empty")
|
|
models = item.get("models")
|
|
if not isinstance(models, list) or not models:
|
|
raise ValueError(f"{section_key}[{index}] 'models' must be a non-empty list")
|
|
models = [str(model).strip() for model in models if str(model).strip()]
|
|
if not models:
|
|
raise ValueError(f"{section_key}[{index}] 'models' cannot be empty")
|
|
protocol = item.get("api_protocol", "openai-completions")
|
|
protocol = str(protocol).strip() or "openai-completions"
|
|
if protocol not in OPENAI_PROTOCOLS:
|
|
raise ValueError(f"Unsupported api_protocol '{protocol}' in {section_key}[{index}]")
|
|
timeout = item.get("timeout", 120)
|
|
try:
|
|
timeout = int(timeout)
|
|
except (TypeError, ValueError):
|
|
timeout = 120
|
|
if timeout <= 0:
|
|
raise ValueError(f"{section_key}[{index}] timeout must be positive")
|
|
|
|
result.append({
|
|
"api-name": name,
|
|
"base_url": base_url,
|
|
"api_key": str(item.get("api_key", "") or "").strip(),
|
|
"timeout": timeout,
|
|
"api_protocol": protocol,
|
|
"models": models,
|
|
})
|
|
return result
|
|
|
|
|
|
def get_api_names(section_key="openai-text"):
|
|
return [item["api-name"] for item in get_openai_apis(section_key)]
|
|
|
|
|
|
def get_api_config(api_name, section_key="openai-text"):
|
|
for item in get_openai_apis(section_key):
|
|
if item["api-name"] == api_name:
|
|
return item
|
|
raise ValueError(f"Unknown API name: {api_name}")
|
|
|
|
|
|
DEFAULT_GROK_MODELS = [
|
|
"grok-imagine-image-2.0",
|
|
"grok-imagine-image-quality",
|
|
"grok-imagine-image-pro",
|
|
"grok-imagine-image",
|
|
]
|
|
|
|
|
|
def get_grok_apis(section_key="grok-image"):
|
|
"""Return normalized Grok API configurations.
|
|
|
|
``grok-image`` is normally an array. A legacy single mapping is also
|
|
accepted so existing installations can migrate without breaking nodes.
|
|
"""
|
|
raw = get_config_section(section_key)
|
|
if raw is None:
|
|
return [{
|
|
"api-name": "default",
|
|
"base_url": "https://api.x.ai/v1",
|
|
"api_key": "",
|
|
"timeout": 120,
|
|
"models": list(DEFAULT_GROK_MODELS),
|
|
}]
|
|
if isinstance(raw, dict):
|
|
raw_items = [raw]
|
|
elif isinstance(raw, list):
|
|
raw_items = raw
|
|
else:
|
|
raise ValueError(f"{section_key} must be an object or array")
|
|
if not raw_items:
|
|
raise ValueError(f"{section_key} cannot be empty")
|
|
|
|
result = []
|
|
names = set()
|
|
for index, item in enumerate(raw_items):
|
|
if not isinstance(item, dict):
|
|
raise ValueError(f"{section_key}[{index}] must be an object")
|
|
name = item.get("api-name") or item.get("api_name")
|
|
if not name:
|
|
if len(raw_items) == 1:
|
|
name = "default"
|
|
else:
|
|
raise ValueError(f"{section_key}[{index}] missing 'api-name'")
|
|
name = str(name).strip()
|
|
if not name:
|
|
raise ValueError(f"{section_key}[{index}] api-name cannot be empty")
|
|
if name in names:
|
|
raise ValueError(f"Duplicate api-name: {name}")
|
|
names.add(name)
|
|
|
|
base_url = str(item.get("base_url", "") or "https://api.x.ai/v1").strip()
|
|
if not base_url:
|
|
base_url = "https://api.x.ai/v1"
|
|
|
|
models = item.get("models")
|
|
if not isinstance(models, list) or not models:
|
|
models = list(DEFAULT_GROK_MODELS)
|
|
else:
|
|
models = [str(model).strip() for model in models if str(model).strip()]
|
|
if not models:
|
|
models = list(DEFAULT_GROK_MODELS)
|
|
|
|
timeout = item.get("timeout", 120)
|
|
try:
|
|
timeout = int(timeout)
|
|
except (TypeError, ValueError):
|
|
timeout = 120
|
|
if timeout <= 0:
|
|
timeout = 120
|
|
|
|
result.append({
|
|
"api-name": name,
|
|
"base_url": base_url,
|
|
"api_key": str(item.get("api_key", "") or "").strip(),
|
|
"timeout": timeout,
|
|
"models": models,
|
|
})
|
|
return result
|
|
|
|
|
|
def get_grok_api_names(section_key="grok-image"):
|
|
return [item["api-name"] for item in get_grok_apis(section_key)]
|
|
|
|
|
|
def get_grok_api_config(api_name=None, section_key="grok-image"):
|
|
apis = get_grok_apis(section_key)
|
|
if not apis:
|
|
raise ValueError(f"No configured APIs found in {section_key}")
|
|
if not api_name:
|
|
return apis[0]
|
|
for item in apis:
|
|
if item["api-name"] == api_name:
|
|
return item
|
|
raise ValueError(f"Unknown API name: {api_name}")
|
|
|
|
|
|
DEFAULT_OPENAI_IMAGE_MODELS = [
|
|
"gpt-image-2",
|
|
"gpt-image-1.5",
|
|
"gpt-image-1",
|
|
"gpt-image-1-mini",
|
|
]
|
|
|
|
|
|
def get_openai_image_apis(section_key="openai-image"):
|
|
"""Return normalized OpenAI Image API configurations.
|
|
|
|
``openai-image`` is normally an array. A legacy single mapping is also
|
|
accepted so existing installations can migrate without breaking nodes.
|
|
"""
|
|
raw = get_config_section(section_key)
|
|
if raw is None:
|
|
return [{
|
|
"api-name": "default",
|
|
"base_url": "https://api.openai.com/v1",
|
|
"api_key": "",
|
|
"timeout": 120,
|
|
"models": list(DEFAULT_OPENAI_IMAGE_MODELS),
|
|
}]
|
|
if isinstance(raw, dict):
|
|
raw_items = [raw]
|
|
elif isinstance(raw, list):
|
|
raw_items = raw
|
|
else:
|
|
raise ValueError(f"{section_key} must be an object or array")
|
|
if not raw_items:
|
|
raise ValueError(f"{section_key} cannot be empty")
|
|
|
|
result = []
|
|
names = set()
|
|
for index, item in enumerate(raw_items):
|
|
if not isinstance(item, dict):
|
|
raise ValueError(f"{section_key}[{index}] must be an object")
|
|
name = item.get("api-name") or item.get("api_name")
|
|
if not name:
|
|
if len(raw_items) == 1:
|
|
name = "default"
|
|
else:
|
|
raise ValueError(f"{section_key}[{index}] missing 'api-name'")
|
|
name = str(name).strip()
|
|
if not name:
|
|
raise ValueError(f"{section_key}[{index}] api-name cannot be empty")
|
|
if name in names:
|
|
raise ValueError(f"Duplicate api-name: {name}")
|
|
names.add(name)
|
|
|
|
base_url = str(item.get("base_url", "") or "https://api.openai.com/v1").strip()
|
|
if not base_url:
|
|
base_url = "https://api.openai.com/v1"
|
|
|
|
models = item.get("models")
|
|
if not isinstance(models, list) or not models:
|
|
models = list(DEFAULT_OPENAI_IMAGE_MODELS)
|
|
else:
|
|
models = [str(model).strip() for model in models if str(model).strip()]
|
|
if not models:
|
|
models = list(DEFAULT_OPENAI_IMAGE_MODELS)
|
|
|
|
timeout = item.get("timeout", 120)
|
|
try:
|
|
timeout = int(timeout)
|
|
except (TypeError, ValueError):
|
|
timeout = 120
|
|
if timeout <= 0:
|
|
timeout = 120
|
|
|
|
result.append({
|
|
"api-name": name,
|
|
"base_url": base_url,
|
|
"api_key": str(item.get("api_key", "") or "").strip(),
|
|
"timeout": timeout,
|
|
"models": models,
|
|
})
|
|
return result
|
|
|
|
|
|
def get_openai_image_api_names(section_key="openai-image"):
|
|
return [item["api-name"] for item in get_openai_image_apis(section_key)]
|
|
|
|
|
|
def get_openai_image_api_config(api_name=None, section_key="openai-image"):
|
|
apis = get_openai_image_apis(section_key)
|
|
if not apis:
|
|
raise ValueError(f"No configured APIs found in {section_key}")
|
|
if not api_name:
|
|
return apis[0]
|
|
for item in apis:
|
|
if item["api-name"] == api_name:
|
|
return item
|
|
raise ValueError(f"Unknown API name: {api_name}")
|
|
|
|
|
|
DEFAULT_MODELSCOPE_IMAGE_MODELS = [
|
|
"Tongyi-MAI/Z-Image-Turbo",
|
|
"black-forest-labs/FLUX.1-Krea-dev",
|
|
"Qwen/Qwen-Image-2512",
|
|
"ideogram-ai/ideogram-4-fp8",
|
|
"krea/Krea-2-Turbo",
|
|
"Qwen/Qwen-Image-Edit-2511",
|
|
"black-forest-labs/FLUX.2-klein-9B",
|
|
"FireRedTeam/FireRed-Image-Edit-1.1",
|
|
]
|
|
|
|
|
|
def get_modelscope_image_apis(section_key="modelscope-image"):
|
|
"""Return normalized ModelScope image API configurations.
|
|
|
|
``modelscope-image`` is normally an array. A legacy single mapping is also
|
|
accepted so existing generation configurations keep working after the
|
|
generation and editing nodes are merged.
|
|
"""
|
|
raw = get_config_section(section_key)
|
|
if raw is None:
|
|
return [{
|
|
"api-name": "default",
|
|
"base_url": "https://api-inference.modelscope.cn",
|
|
"api_key": "",
|
|
"timeout": 300,
|
|
"models": list(DEFAULT_MODELSCOPE_IMAGE_MODELS),
|
|
}]
|
|
if isinstance(raw, dict):
|
|
raw_items = [raw]
|
|
elif isinstance(raw, list):
|
|
raw_items = raw
|
|
else:
|
|
raise ValueError(f"{section_key} must be an object or array")
|
|
if not raw_items:
|
|
raise ValueError(f"{section_key} cannot be empty")
|
|
|
|
result = []
|
|
names = set()
|
|
for index, item in enumerate(raw_items):
|
|
if not isinstance(item, dict):
|
|
raise ValueError(f"{section_key}[{index}] must be an object")
|
|
|
|
name = item.get("api-name") or item.get("api_name")
|
|
if not name:
|
|
if len(raw_items) == 1:
|
|
name = "default"
|
|
else:
|
|
raise ValueError(f"{section_key}[{index}] missing 'api-name'")
|
|
name = str(name).strip()
|
|
if not name:
|
|
raise ValueError(f"{section_key}[{index}] api-name cannot be empty")
|
|
if name in names:
|
|
raise ValueError(f"Duplicate api-name: {name}")
|
|
names.add(name)
|
|
|
|
base_url = str(
|
|
item.get("base_url", "") or "https://api-inference.modelscope.cn"
|
|
).strip()
|
|
if not base_url:
|
|
base_url = "https://api-inference.modelscope.cn"
|
|
|
|
models = item.get("models")
|
|
if not isinstance(models, list) or not models:
|
|
models = list(DEFAULT_MODELSCOPE_IMAGE_MODELS)
|
|
else:
|
|
models = [str(model).strip() for model in models if str(model).strip()]
|
|
if not models:
|
|
models = list(DEFAULT_MODELSCOPE_IMAGE_MODELS)
|
|
|
|
timeout = item.get("timeout", 300)
|
|
try:
|
|
timeout = int(timeout)
|
|
except (TypeError, ValueError):
|
|
timeout = 300
|
|
if timeout <= 0:
|
|
timeout = 300
|
|
|
|
result.append({
|
|
"api-name": name,
|
|
"base_url": base_url,
|
|
"api_key": str(item.get("api_key", "") or "").strip(),
|
|
"timeout": timeout,
|
|
"models": models,
|
|
})
|
|
return result
|
|
|
|
|
|
def get_modelscope_image_api_names(section_key="modelscope-image"):
|
|
return [item["api-name"] for item in get_modelscope_image_apis(section_key)]
|
|
|
|
|
|
def get_modelscope_image_api_config(api_name=None, section_key="modelscope-image"):
|
|
apis = get_modelscope_image_apis(section_key)
|
|
if not apis:
|
|
raise ValueError(f"No configured APIs found in {section_key}")
|
|
if not api_name:
|
|
return apis[0]
|
|
for item in apis:
|
|
if item["api-name"] == api_name:
|
|
return item
|
|
raise ValueError(f"Unknown API name: {api_name}")
|
|
|