- API Key鉴权: /v1/* 与 /mcp 接口支持 Bearer 鉴权(OpenAI兼容), 系统配置页可启停/增删Key (GET/PUT /api/admin/apikeys, key脱敏显示; /health /status 不受限; 内部自调用自动带key) - Function Calling: tools/tool_calls 全透传上游, 实测 GLM-5.3-flash 返回 tool_calls(get_weather) - MCP Server: /mcp 端点(Streamable HTTP + SSE兼容), 暴露 text_complete/vision_complete/image_generate 工具 (initialize/ping/tools/list/tools/call/prompts.list/resources.list; 实测生图返回图片URL) - 默认生成 API Key: sk-hz4th-<hex>
476 lines
14 KiB
Python
476 lines
14 KiB
Python
"""
|
||
大模型API中转系统配置 - 支持动态修改
|
||
v2.1.0 - 能力(Capability)体系: 每个模型标记能力,AUTO配置按能力绑定
|
||
|
||
能力类型:
|
||
text 文本推理
|
||
vision 视觉能力 (多模态输入)
|
||
audio_out 语音输出 (TTS)
|
||
audio_in 语音输入 (ASR)
|
||
image_gen 图片生成
|
||
video_gen 视频生成
|
||
"""
|
||
|
||
import json
|
||
from pathlib import Path
|
||
|
||
# 配置文件路径
|
||
CONFIG_FILE = Path(__file__).parent.parent / 'data' / 'config.json'
|
||
|
||
# 能力定义
|
||
CAPABILITY_DEFS = {
|
||
"text": "文本推理",
|
||
"vision": "视觉能力",
|
||
"audio_out": "语音输出",
|
||
"audio_in": "语音输入",
|
||
"image_gen": "图片生成",
|
||
"video_gen": "视频生成",
|
||
}
|
||
|
||
# 默认上游模型配置
|
||
# capabilities: 该提供商下所有模型的默认能力(模型管理页可对单个模型覆盖)
|
||
DEFAULT_PROVIDERS = [
|
||
{
|
||
"id": "local-qwen",
|
||
"name": "Local Qwen",
|
||
"priority": 1,
|
||
"base_url": "http://121.40.164.32:18003/v1",
|
||
"api_key": "sk-xxxx",
|
||
"models": [
|
||
{"name": "unsloth/Qwen3.8-27B-Q6_K", "capabilities": ["text", "vision"]},
|
||
{"name": "unsloth/Qwen3.8-27B-Q4_K_M", "capabilities": ["text", "vision"]},
|
||
],
|
||
"default_model": "unsloth/Qwen3.8-27B-Q6_K",
|
||
"capabilities": ["text", "vision"],
|
||
"timeout": 180,
|
||
"enabled": True,
|
||
},
|
||
{
|
||
"id": "siliconflow-llm",
|
||
"name": "SiliconFlow LLM",
|
||
"priority": 2,
|
||
"base_url": "https://api.siliconflow.cn/v1",
|
||
"api_key": "sk-fhpoexpptvjghpnphtaxbkhjwulzovoqfffbckcfscjmwhcg",
|
||
"models": [
|
||
{"name": "deepseek-ai/DeepSeek-V4-Flash", "capabilities": ["text"]},
|
||
{"name": "meituan-longcat/LongCat-2.0", "capabilities": ["text"]},
|
||
],
|
||
"default_model": "deepseek-ai/DeepSeek-V4-Flash",
|
||
"capabilities": ["text"],
|
||
"timeout": 180,
|
||
"enabled": True,
|
||
},
|
||
{
|
||
"id": "autodl",
|
||
"name": "Autodl",
|
||
"priority": 3,
|
||
"base_url": "https://www.autodl.art/api/v1",
|
||
"api_key": "F9MBfolzuapqTsD4KmUf9qen720rXvUZ3Sp3IrWiCTukqonx",
|
||
"models": [
|
||
{"name": "qwen3.6-plus", "capabilities": ["text", "vision"]},
|
||
{"name": "GLM-5.3-flash", "capabilities": ["text", "vision"]},
|
||
],
|
||
"default_model": "GLM-5.3-flash",
|
||
"capabilities": ["text", "vision"],
|
||
"timeout": 180,
|
||
"enabled": True,
|
||
},
|
||
{
|
||
"id": "autodl-image",
|
||
"name": "Autodl Image",
|
||
"priority": 4,
|
||
"base_url": "https://www.autodl.art/api/v1",
|
||
"api_key": "F9MBfolzuapqTsD4KmUf9qen720rXvUZ3Sp3IrWiCTukqonx",
|
||
"models": [
|
||
{"name": "Qwen-Image", "capabilities": ["image_gen"]},
|
||
],
|
||
"default_model": "Qwen-Image",
|
||
"capabilities": ["image_gen"],
|
||
"timeout": 180,
|
||
"enabled": True,
|
||
},
|
||
]
|
||
|
||
# 默认模型别名
|
||
DEFAULT_MODEL_ALIASES = {
|
||
"auto": "auto",
|
||
"auto-text": "auto-text",
|
||
"auto-vision": "auto-vision",
|
||
"auto-image": "auto-image",
|
||
"auto-voice-out": "auto-voice-out",
|
||
"auto-voice-in": "auto-voice-in",
|
||
"auto-video": "auto-video",
|
||
"auto-vlm": "auto-vision", # 兼容旧配置
|
||
"qwen": "unsloth/Qwen3.8-27B-Q6_K",
|
||
"qwen3.8": "unsloth/Qwen3.8-27B-Q6_K",
|
||
"local": "unsloth/Qwen3.8-27B-Q6_K",
|
||
"deepseek": "deepseek-ai/DeepSeek-V4-Flash",
|
||
"deepseek-v4-flash": "deepseek-ai/DeepSeek-V4-Flash",
|
||
"longcat": "meituan-longcat/LongCat-2.0",
|
||
"glm": "GLM-5.3-flash",
|
||
"qwen3.6-plus": "qwen3.6-plus",
|
||
"gpt-4": "GLM-5.3-flash",
|
||
}
|
||
|
||
# 默认Auto配置:每个auto配置固定绑定一个能力
|
||
# capability: 固定此auto的功能类型(取自 CAPABILITY_DEFS)
|
||
# models: 有序的**具体模型**列表(按优先级,可拖动排序);为空时回退为按提供商能力选择
|
||
# providers: 候选提供商(* 表示所有启用的、且具备该能力模型的提供商)
|
||
DEFAULT_AUTO_PROFILES = {
|
||
"auto": {
|
||
"name": "默认Auto",
|
||
"description": "文本推理 - 按模型列表顺序自动选择",
|
||
"capability": "text",
|
||
"models": [
|
||
"unsloth/Qwen3.8-27B-Q6_K",
|
||
"unsloth/Qwen3.8-27B-Q4_K_M",
|
||
"deepseek-ai/DeepSeek-V4-Flash",
|
||
"meituan-longcat/LongCat-2.0",
|
||
"qwen3.6-plus",
|
||
"GLM-5.3-flash",
|
||
],
|
||
"providers": ["*"],
|
||
"strategy": "priority",
|
||
},
|
||
"auto-text": {
|
||
"name": "文本推理",
|
||
"description": "纯文本推理,按模型列表顺序自动选择",
|
||
"capability": "text",
|
||
"models": [
|
||
"unsloth/Qwen3.8-27B-Q6_K",
|
||
"unsloth/Qwen3.8-27B-Q4_K_M",
|
||
"deepseek-ai/DeepSeek-V4-Flash",
|
||
"meituan-longcat/LongCat-2.0",
|
||
"qwen3.6-plus",
|
||
"GLM-5.3-flash",
|
||
],
|
||
"providers": ["*"],
|
||
"strategy": "priority",
|
||
},
|
||
"auto-vision": {
|
||
"name": "视觉能力",
|
||
"description": "多模态视觉理解,按模型列表顺序自动选择",
|
||
"capability": "vision",
|
||
"models": [
|
||
"unsloth/Qwen3.8-27B-Q6_K",
|
||
"unsloth/Qwen3.8-27B-Q4_K_M",
|
||
"qwen3.6-plus",
|
||
"GLM-5.3-flash",
|
||
],
|
||
"providers": ["*"],
|
||
"strategy": "priority",
|
||
},
|
||
"auto-image": {
|
||
"name": "图片生成",
|
||
"description": "文生图,按模型列表顺序自动选择",
|
||
"capability": "image_gen",
|
||
"models": ["Qwen-Image"],
|
||
"providers": ["*"],
|
||
"strategy": "priority",
|
||
},
|
||
"auto-voice-out": {
|
||
"name": "语音输出",
|
||
"description": "语音合成(TTS),按模型列表顺序自动选择",
|
||
"capability": "audio_out",
|
||
"models": [],
|
||
"providers": ["*"],
|
||
"strategy": "priority",
|
||
},
|
||
"auto-voice-in": {
|
||
"name": "语音输入",
|
||
"description": "语音识别(ASR),按模型列表顺序自动选择",
|
||
"capability": "audio_in",
|
||
"models": [],
|
||
"providers": ["*"],
|
||
"strategy": "priority",
|
||
},
|
||
"auto-video": {
|
||
"name": "视频生成",
|
||
"description": "文生视频,按模型列表顺序自动选择",
|
||
"capability": "video_gen",
|
||
"models": [],
|
||
"providers": ["*"],
|
||
"strategy": "priority",
|
||
},
|
||
}
|
||
|
||
# 路由缓存配置(历史上下文优先复用上次模型,命中前缀缓存节省成本)
|
||
ROUTING_CONFIG = {
|
||
"prefer_cache_model": True, # 开启后:带历史上下文的 auto 请求优先复用上次使用的模型
|
||
"cache_ttl_seconds": 3600, # 记忆有效期(秒)
|
||
}
|
||
|
||
# 邮件通知配置(auto 调用全部失败时发邮件通知)
|
||
EMAIL_CONFIG = {
|
||
"enabled": True, # 总开关
|
||
"notify_on_auto_failure": True, # auto 调用全部失败时发邮件
|
||
"smtp_host": "mail.tphai.com",
|
||
"smtp_port": 587,
|
||
"smtp_mode": "plain", # plain | starttls | ssl
|
||
"smtp_user": "hz4th_coder@tphai.com",
|
||
"smtp_password": "hz4th_coder@!",
|
||
"from_name": "LLM Proxy",
|
||
"from_addr": "hz4th_coder@tphai.com",
|
||
"to_addrs": ["wlq@tphai.com"],
|
||
"cooldown_seconds": 300, # 失败通知最小间隔(防轰炸)
|
||
}
|
||
|
||
# 接口 API Key 鉴权(OpenAI 兼容:Authorization: Bearer <key>)
|
||
# 应用于 /v1/* 与 /mcp 接口;后台 /admin 不受此控制
|
||
API_AUTH = {
|
||
"enabled": True, # 是否启用鉴权
|
||
"keys": [], # 允许的 key 列表(可多个)
|
||
}
|
||
|
||
|
||
def load_api_auth():
|
||
"""加载 API 鉴权配置(未配置 key 时自动生成一个)"""
|
||
import hashlib
|
||
config = load_config()
|
||
merged = {**API_AUTH, **config.get("api_auth", {})}
|
||
if not merged.get("keys"):
|
||
import time
|
||
merged["keys"] = ["sk-hz4th-" + hashlib.md5((str(time.time()) + "llm-proxy").encode()).hexdigest()[:16]]
|
||
return merged
|
||
|
||
|
||
def save_api_auth(data):
|
||
"""保存 API 鉴权配置"""
|
||
config = load_config()
|
||
merged = {**load_api_auth(), **data}
|
||
if not merged.get("keys"):
|
||
merged["keys"] = API_AUTH["keys"]
|
||
config["api_auth"] = merged
|
||
save_config(config)
|
||
return merged
|
||
|
||
|
||
def load_email_config():
|
||
"""加载邮件通知配置(运行时配置优先)"""
|
||
config = load_config()
|
||
return {**EMAIL_CONFIG, **config.get("email_config", {})}
|
||
|
||
|
||
def save_email_config(data):
|
||
"""保存邮件通知配置"""
|
||
config = load_config()
|
||
merged = {**EMAIL_CONFIG, **data}
|
||
config["email_config"] = merged
|
||
save_config(config)
|
||
return merged
|
||
|
||
|
||
def load_routing_config():
|
||
"""加载路由缓存配置(运行时配置优先)"""
|
||
config = load_config()
|
||
return {**ROUTING_CONFIG, **config.get("routing_config", {})}
|
||
|
||
|
||
def save_routing_config(data):
|
||
"""保存路由缓存配置"""
|
||
config = load_config()
|
||
config["routing_config"] = {**ROUTING_CONFIG, **data}
|
||
save_config(config)
|
||
return config["routing_config"]
|
||
|
||
|
||
def load_config():
|
||
"""加载配置"""
|
||
if CONFIG_FILE.exists():
|
||
try:
|
||
data = json.loads(CONFIG_FILE.read_text(encoding='utf-8'))
|
||
return data
|
||
except:
|
||
pass
|
||
return {
|
||
"providers": DEFAULT_PROVIDERS,
|
||
"model_aliases": DEFAULT_MODEL_ALIASES,
|
||
}
|
||
|
||
|
||
def save_config(config):
|
||
"""保存配置"""
|
||
CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||
CONFIG_FILE.write_text(json.dumps(config, ensure_ascii=False, indent=2), encoding='utf-8')
|
||
|
||
|
||
def get_providers():
|
||
"""获取提供商列表"""
|
||
config = load_config()
|
||
return config.get("providers", DEFAULT_PROVIDERS)
|
||
|
||
|
||
def get_provider(provider_id):
|
||
"""获取单个提供商"""
|
||
providers = get_providers()
|
||
for p in providers:
|
||
if p.get("id") == provider_id:
|
||
return p
|
||
return None
|
||
|
||
|
||
def add_provider(provider):
|
||
"""添加提供商"""
|
||
config = load_config()
|
||
providers = config.get("providers", [])
|
||
|
||
# 生成ID
|
||
if not provider.get("id"):
|
||
provider["id"] = provider["name"].lower().replace(" ", "-").replace(".", "-")
|
||
|
||
providers.append(provider)
|
||
config["providers"] = providers
|
||
save_config(config)
|
||
return provider
|
||
|
||
|
||
def update_provider(provider_id, data):
|
||
"""更新提供商"""
|
||
config = load_config()
|
||
providers = config.get("providers", [])
|
||
|
||
for i, p in enumerate(providers):
|
||
if p.get("id") == provider_id:
|
||
providers[i] = {**p, **data}
|
||
config["providers"] = providers
|
||
save_config(config)
|
||
return providers[i]
|
||
return None
|
||
|
||
|
||
def delete_provider(provider_id):
|
||
"""删除提供商"""
|
||
config = load_config()
|
||
providers = config.get("providers", [])
|
||
providers = [p for p in providers if p.get("id") != provider_id]
|
||
config["providers"] = providers
|
||
save_config(config)
|
||
return True
|
||
|
||
|
||
def update_priority(provider_ids):
|
||
"""更新优先级顺序"""
|
||
config = load_config()
|
||
providers = config.get("providers", [])
|
||
|
||
# 按新顺序设置优先级
|
||
for i, pid in enumerate(provider_ids):
|
||
for p in providers:
|
||
if p.get("id") == pid:
|
||
p["priority"] = i + 1
|
||
|
||
config["providers"] = providers
|
||
save_config(config)
|
||
return providers
|
||
|
||
|
||
def get_model_aliases():
|
||
"""获取模型别名"""
|
||
config = load_config()
|
||
return config.get("model_aliases", DEFAULT_MODEL_ALIASES)
|
||
|
||
|
||
def update_model_alias(alias, target):
|
||
"""更新模型别名"""
|
||
config = load_config()
|
||
aliases = config.get("model_aliases", {})
|
||
aliases[alias] = target
|
||
config["model_aliases"] = aliases
|
||
save_config(config)
|
||
return aliases
|
||
|
||
|
||
def delete_model_alias(alias):
|
||
"""删除模型别名"""
|
||
if alias == "auto":
|
||
return False # 不能删除默认的auto
|
||
config = load_config()
|
||
aliases = config.get("model_aliases", {})
|
||
if alias in aliases:
|
||
del aliases[alias]
|
||
config["model_aliases"] = aliases
|
||
save_config(config)
|
||
return True
|
||
return False
|
||
|
||
|
||
def get_auto_profiles():
|
||
"""获取Auto配置列表"""
|
||
config = load_config()
|
||
return config.get("auto_profiles", DEFAULT_AUTO_PROFILES)
|
||
|
||
|
||
def get_auto_profile(profile_name):
|
||
"""获取单个Auto配置"""
|
||
profiles = get_auto_profiles()
|
||
return profiles.get(profile_name)
|
||
|
||
|
||
def add_auto_profile(profile_name, profile_data):
|
||
"""添加Auto配置"""
|
||
config = load_config()
|
||
profiles = config.get("auto_profiles", {})
|
||
profiles[profile_name] = profile_data
|
||
config["auto_profiles"] = profiles
|
||
save_config(config)
|
||
return profiles
|
||
|
||
|
||
def update_auto_profile(profile_name, profile_data):
|
||
"""更新Auto配置"""
|
||
config = load_config()
|
||
profiles = config.get("auto_profiles", {})
|
||
if profile_name in profiles:
|
||
profiles[profile_name] = {**profiles[profile_name], **profile_data}
|
||
config["auto_profiles"] = profiles
|
||
save_config(config)
|
||
return profiles[profile_name]
|
||
return None
|
||
|
||
|
||
def delete_auto_profile(profile_name):
|
||
"""删除Auto配置"""
|
||
if profile_name == "auto":
|
||
return False # 不能删除默认的auto
|
||
config = load_config()
|
||
profiles = config.get("auto_profiles", {})
|
||
if profile_name in profiles:
|
||
del profiles[profile_name]
|
||
config["auto_profiles"] = profiles
|
||
save_config(config)
|
||
return True
|
||
return False
|
||
|
||
|
||
# 初始化配置
|
||
config = load_config()
|
||
UPSTREAM_PROVIDERS = config.get("providers", DEFAULT_PROVIDERS)
|
||
MODEL_ALIASES = config.get("model_aliases", DEFAULT_MODEL_ALIASES)
|
||
|
||
# 服务配置
|
||
SERVER_CONFIG = {
|
||
"host": "0.0.0.0",
|
||
"port": 16003,
|
||
"debug": False,
|
||
}
|
||
|
||
# 日志配置
|
||
LOG_CONFIG = {
|
||
"log_dir": "logs",
|
||
"log_requests": True,
|
||
"log_errors": True,
|
||
}
|
||
|
||
# 重试/熔断配置
|
||
RETRY_CONFIG = {
|
||
"max_retries": 3,
|
||
"retry_delay": 1,
|
||
"cooldown_seconds": 60, # 熔断冷却期,到期自动恢复(半开)
|
||
"retry_on_errors": [
|
||
"connection_error",
|
||
"timeout",
|
||
"rate_limit",
|
||
"server_error",
|
||
],
|
||
}
|