From 3f463f2f981ce7ca02f0855ba3cf113edf9ec8f3 Mon Sep 17 00:00:00 2001 From: hubian <908234780@qq.com> Date: Thu, 9 Apr 2026 17:46:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89Auto=E9=85=8D=E7=BD=AE=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增功能: - Auto配置管理页面 (/auto-profiles) - 创建自定义auto模式,如auto-fast, auto-cheap - 指定候选提供商和优先级 - 支持按优先级/随机选择策略 API: - GET/POST /api/auto-profiles - GET/PUT/DELETE /api/auto-profiles/ 改进: - 主服务支持自定义auto模式路由 - 模型列表显示所有auto配置 --- .gitignore | 5 +- admin/app.py | 149 +++++++++++- admin/templates/auto-profiles.html | 359 +++++++++++++++++++++++++++++ admin/templates/config.html | 3 + admin/templates/index.html | 3 + admin/templates/models.html | 3 + admin/templates/providers.html | 3 + app.py | 83 +++++-- config/settings.py | 53 +++++ 9 files changed, 634 insertions(+), 27 deletions(-) create mode 100644 admin/templates/auto-profiles.html diff --git a/.gitignore b/.gitignore index 5b64f94..57a87a0 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,7 @@ __pycache__/ # 环境 venv/ -.env \ No newline at end of file +.env + +# 数据文件 +data/ \ No newline at end of file diff --git a/admin/app.py b/admin/app.py index d3ce915..51b6ef8 100644 --- a/admin/app.py +++ b/admin/app.py @@ -16,12 +16,13 @@ import requests # 添加父目录到路径 sys.path.insert(0, str(Path(__file__).parent.parent)) from config.settings import ( - DEFAULT_PROVIDERS, DEFAULT_MODEL_ALIASES, + DEFAULT_PROVIDERS, DEFAULT_MODEL_ALIASES, DEFAULT_AUTO_PROFILES, SERVER_CONFIG, LOG_CONFIG, RETRY_CONFIG, load_config, save_config, get_providers, get_provider, add_provider, update_provider, delete_provider, update_priority, get_model_aliases, - update_model_alias + update_model_alias, get_auto_profiles, get_auto_profile, + add_auto_profile, update_auto_profile, delete_auto_profile ) app = Flask(__name__) @@ -93,6 +94,10 @@ def logs_page(): def config_page(): return render_template('config.html') +@app.route('/auto-profiles') +def auto_profiles_page(): + return render_template('auto-profiles.html') + # ============ API路由 ============ @app.route('/api/stats') @@ -432,6 +437,146 @@ def api_recent_requests(): # 模拟数据 return jsonify([]) + +# ============ Auto配置管理 ============ + +@app.route('/api/auto-profiles') +def api_auto_profiles(): + """获取所有Auto配置""" + profiles = get_auto_profiles() + providers = get_providers() + + result = [] + for name, profile in profiles.items(): + # 解析允许的提供商信息 + allowed_providers = profile.get('providers', ['*']) + provider_details = [] + + if '*' in allowed_providers: + provider_details = [{'id': '*', 'name': '所有启用的提供商'}] + else: + for p in providers: + if p.get('id') in allowed_providers or p['name'] in allowed_providers: + provider_details.append({ + 'id': p.get('id'), + 'name': p['name'], + 'priority': p['priority'] + }) + + result.append({ + 'name': name, + 'display_name': profile.get('name', name), + 'description': profile.get('description', ''), + 'strategy': profile.get('strategy', 'priority'), + 'providers': allowed_providers, + 'provider_details': provider_details, + }) + + return jsonify(result) + + +@app.route('/api/auto-profiles/', methods=['GET']) +def api_auto_profile_detail(profile_name): + """获取单个Auto配置详情""" + profile = get_auto_profile(profile_name) + + if not profile: + return jsonify({'error': 'Profile not found'}), 404 + + providers = get_providers() + allowed_providers = profile.get('providers', ['*']) + provider_details = [] + + if '*' in allowed_providers: + provider_details = [{'id': '*', 'name': '所有启用的提供商', 'selected': True}] + for p in providers: + provider_details.append({ + 'id': p.get('id'), + 'name': p['name'], + 'priority': p['priority'], + 'selected': True + }) + else: + for p in providers: + selected = p.get('id') in allowed_providers or p['name'] in allowed_providers + provider_details.append({ + 'id': p.get('id'), + 'name': p['name'], + 'priority': p['priority'], + 'selected': selected + }) + + return jsonify({ + 'name': profile_name, + 'display_name': profile.get('name', profile_name), + 'description': profile.get('description', ''), + 'strategy': profile.get('strategy', 'priority'), + 'providers': allowed_providers, + 'provider_details': provider_details, + }) + + +@app.route('/api/auto-profiles', methods=['POST']) +def api_add_auto_profile(): + """添加新的Auto配置""" + data = request.get_json() + + if not data or not data.get('name'): + return jsonify({'error': 'Missing profile name'}), 400 + + profile_name = data['name'].lower().replace(' ', '-').replace('.', '-') + + if profile_name in get_auto_profiles(): + return jsonify({'error': 'Profile already exists'}), 400 + + profile_data = { + 'name': data.get('display_name', data['name']), + 'description': data.get('description', ''), + 'providers': data.get('providers', ['*']), + 'strategy': data.get('strategy', 'priority'), + } + + result = add_auto_profile(profile_name, profile_data) + + return jsonify({'success': True, 'profile': {profile_name: profile_data}}) + + +@app.route('/api/auto-profiles/', methods=['PUT']) +def api_update_auto_profile(profile_name): + """更新Auto配置""" + data = request.get_json() + + if not data: + return jsonify({'error': 'Invalid request body'}), 400 + + profile_data = {} + if 'display_name' in data: + profile_data['name'] = data['display_name'] + if 'description' in data: + profile_data['description'] = data['description'] + if 'providers' in data: + profile_data['providers'] = data['providers'] + if 'strategy' in data: + profile_data['strategy'] = data['strategy'] + + result = update_auto_profile(profile_name, profile_data) + + if not result: + return jsonify({'error': 'Profile not found'}), 404 + + return jsonify({'success': True, 'profile': result}) + + +@app.route('/api/auto-profiles/', methods=['DELETE']) +def api_delete_auto_profile(profile_name): + """删除Auto配置""" + result = delete_auto_profile(profile_name) + + if not result: + return jsonify({'error': 'Cannot delete default auto profile or profile not found'}), 400 + + return jsonify({'success': True}) + if __name__ == '__main__': print("=" * 50) print("大模型API中转系统 - 后台管理") diff --git a/admin/templates/auto-profiles.html b/admin/templates/auto-profiles.html new file mode 100644 index 0000000..9a5dbec --- /dev/null +++ b/admin/templates/auto-profiles.html @@ -0,0 +1,359 @@ + + + + + + Auto配置管理 - LLM Proxy + + + + + + + +
+
+

Auto配置管理

+ +
+ +
+ + Auto配置允许您创建自定义的自动选择模式,指定哪些提供商参与选择以及优先级顺序。 + 例如:auto-fast 只选择响应快的模型,auto-cheap 只选择便宜的模型。 +
+ +
+
+
+

加载中...

+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/admin/templates/config.html b/admin/templates/config.html index c2caa54..c99eb96 100644 --- a/admin/templates/config.html +++ b/admin/templates/config.html @@ -30,6 +30,9 @@ 模型管理 + + Auto配置 + 日志查看 diff --git a/admin/templates/index.html b/admin/templates/index.html index bbd3852..2702741 100644 --- a/admin/templates/index.html +++ b/admin/templates/index.html @@ -31,6 +31,9 @@ 模型管理 + + Auto配置 + 日志查看 diff --git a/admin/templates/models.html b/admin/templates/models.html index da75a0a..5084ef4 100644 --- a/admin/templates/models.html +++ b/admin/templates/models.html @@ -30,6 +30,9 @@ 模型管理 + + Auto配置 + 日志查看 diff --git a/admin/templates/providers.html b/admin/templates/providers.html index cd22010..5e9c5f8 100644 --- a/admin/templates/providers.html +++ b/admin/templates/providers.html @@ -32,6 +32,9 @@ 模型管理 + + Auto配置 + 日志查看 diff --git a/app.py b/app.py index 1939fe3..278b91e 100644 --- a/app.py +++ b/app.py @@ -16,7 +16,7 @@ import sys # 添加配置路径 sys.path.insert(0, str(Path(__file__).parent)) from config.settings import ( - get_providers, get_model_aliases, SERVER_CONFIG, + get_providers, get_model_aliases, get_auto_profiles, SERVER_CONFIG, LOG_CONFIG, RETRY_CONFIG ) @@ -28,15 +28,17 @@ CONFIG_CACHE_TTL = 5 _last_config_load = 0 _cached_providers = [] _cached_aliases = {} +_cached_auto_profiles = {} def refresh_config(): """动态刷新配置(支持后台管理修改)""" - global _last_config_load, _cached_providers, _cached_aliases, provider_status + global _last_config_load, _cached_providers, _cached_aliases, _cached_auto_profiles, provider_status current_time = time.time() if current_time - _last_config_load > CONFIG_CACHE_TTL: _cached_providers = get_providers() _cached_aliases = get_model_aliases() + _cached_auto_profiles = get_auto_profiles() _last_config_load = current_time # 更新提供商状态缓存(新增的提供商) @@ -78,8 +80,8 @@ def get_provider_for_model(model_name): resolved_model = _cached_aliases.get(model_name, model_name) # auto模式:按优先级选择可用提供商 - if resolved_model == 'auto': - return get_available_provider() + if resolved_model == 'auto' or resolved_model.startswith('auto-'): + return get_available_provider_for_auto(resolved_model) # 查找支持该模型的提供商 sorted_providers = sorted(_cached_providers, key=lambda x: x['priority']) @@ -105,21 +107,52 @@ def get_provider_for_model(model_name): return None, None -def get_available_provider(): - """获取可用的提供商(按优先级)""" +def get_available_provider_for_auto(auto_name='auto'): + """获取auto模式下的可用提供商(支持自定义auto配置)""" refresh_config() + # 获取auto配置 + profile = _cached_auto_profiles.get(auto_name, _cached_auto_profiles.get('auto', {})) + + # 获取允许的提供商 + allowed_providers = profile.get('providers', ['*']) + + # 筛选提供商 sorted_providers = sorted(_cached_providers, key=lambda x: x['priority']) + candidates = [] for provider in sorted_providers: - if provider['enabled'] and provider_status.get(provider['name'], {}).get('available', True): - return provider, provider['default_model'] + if not provider['enabled']: + continue + if not provider_status.get(provider['name'], {}).get('available', True): + continue + + # 检查是否在允许列表中 + if '*' in allowed_providers: + candidates.append(provider) + elif provider.get('id') in allowed_providers or provider['name'] in allowed_providers: + candidates.append(provider) - # 如果都不可用,返回第一个尝试(让错误信息传递) - if sorted_providers: - return sorted_providers[0], sorted_providers[0]['default_model'] + # 根据策略选择 + strategy = profile.get('strategy', 'priority') - return None, None + if not candidates: + # 如果没有候选,返回第一个尝试(让错误信息传递) + if sorted_providers: + return sorted_providers[0], sorted_providers[0]['default_model'] + return None, None + + if strategy == 'priority': + # 按优先级选择第一个 + return candidates[0], candidates[0]['default_model'] + elif strategy == 'random': + # 随机选择 + import random + selected = random.choice(candidates) + return selected, selected['default_model'] + else: + # 默认按优先级 + return candidates[0], candidates[0]['default_model'] def mark_provider_error(provider_name, error): @@ -225,6 +258,18 @@ def list_models(): models_list = [] added_models = set() + # 添加所有auto配置 + for profile_name, profile in _cached_auto_profiles.items(): + if profile_name not in added_models: + models_list.append({ + "id": profile_name, + "object": "model", + "created": int(time.time()), + "owned_by": "proxy", + "description": profile.get('description', 'Auto-select available model') + }) + added_models.add(profile_name) + for provider in _cached_providers: if not provider['enabled']: continue @@ -238,16 +283,6 @@ def list_models(): }) added_models.add(model) - # 添加auto模型 - if "auto" not in added_models: - models_list.insert(0, { - "id": "auto", - "object": "model", - "created": int(time.time()), - "owned_by": "proxy", - "description": "Auto-select available model by priority" - }) - return jsonify({ "object": "list", "data": models_list @@ -310,7 +345,7 @@ def chat_completions(): tried_providers.append(provider['name']) # 尝试下一个提供商 - next_provider, next_model = get_available_provider() + next_provider, next_model = get_available_provider_for_auto('auto') if next_provider and next_provider['name'] not in tried_providers: provider = next_provider resolved_model = next_model @@ -328,7 +363,7 @@ def chat_completions(): tried_providers.append(provider['name']) # 尝试下一个提供商 - next_provider, next_model = get_available_provider() + next_provider, next_model = get_available_provider_for_auto('auto') if next_provider and next_provider['name'] not in tried_providers: provider = next_provider resolved_model = next_model diff --git a/config/settings.py b/config/settings.py index a33bdb6..54c5650 100644 --- a/config/settings.py +++ b/config/settings.py @@ -45,6 +45,16 @@ DEFAULT_MODEL_ALIASES = { "deepseek-v3.2": "Pro/deepseek-ai/DeepSeek-V3.2", } +# 默认Auto配置(自定义候选模型和优先级) +DEFAULT_AUTO_PROFILES = { + "auto": { + "name": "默认Auto", + "description": "所有启用的提供商按优先级自动选择", + "providers": ["*"], # * 表示所有启用的提供商 + "strategy": "priority", # priority | random | round-robin + } +} + def load_config(): """加载配置""" if CONFIG_FILE.exists(): @@ -141,6 +151,49 @@ def update_model_alias(alias, target): save_config(config) return aliases +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)