Files
llm-proxy/config/settings.py
T
hz4th_coder 86198e83c5 v2.2.0: API Key鉴权(可配置) + Function Calling透传 + MCP Server标准接口
- 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>
2026-08-28 15:24:18 +08:00

476 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
大模型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",
],
}