feat: 后台管理增加提供商动态管理功能

- 新增:添加新的大模型接口提供商
- 新增:编辑已有提供商的参数(API地址、Key、模型列表等)
- 新增:删除提供商
- 新增:拖拽排序调整auto模式的优先级顺序
- 新增:启用/禁用提供商开关
- 优化:主服务动态读取配置,后台修改实时生效
This commit is contained in:
2026-04-08 18:14:09 +08:00
parent 292ff7b03e
commit 82100cdf00
5 changed files with 819 additions and 124 deletions
+201 -46
View File
@@ -1,20 +1,27 @@
""" """
大模型API中转系统 - 后台管理系统 大模型API中转系统 - 后台管理系统
支持动态添加、编辑、删除提供商和优先级调整
""" """
from flask import Flask, render_template, jsonify, request from flask import Flask, render_template, jsonify, request
from flask_cors import CORS from flask_cors import CORS
import json import json
import time import time
import uuid
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
import sys import sys
import requests
# 添加父目录到路径 # 添加父目录到路径
sys.path.insert(0, str(Path(__file__).parent.parent)) sys.path.insert(0, str(Path(__file__).parent.parent))
from config.settings import ( from config.settings import (
UPSTREAM_PROVIDERS, MODEL_ALIASES, SERVER_CONFIG, DEFAULT_PROVIDERS, DEFAULT_MODEL_ALIASES,
LOG_CONFIG, RETRY_CONFIG 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
) )
app = Flask(__name__) app = Flask(__name__)
@@ -24,20 +31,26 @@ CORS(app)
DATA_DIR = Path(__file__).parent.parent / 'data' DATA_DIR = Path(__file__).parent.parent / 'data'
DATA_DIR.mkdir(exist_ok=True) DATA_DIR.mkdir(exist_ok=True)
STATS_FILE = DATA_DIR / 'stats.json' STATS_FILE = DATA_DIR / 'stats.json'
CONFIG_FILE = DATA_DIR / 'config.json'
LOGS_DIR = Path(__file__).parent.parent / 'logs' LOGS_DIR = Path(__file__).parent.parent / 'logs'
# 提供商状态(从主程序同步) # 提供商状态缓存
provider_status = {} provider_status = {}
for provider in UPSTREAM_PROVIDERS:
provider_status[provider['name']] = { def refresh_provider_status():
'available': True, """刷新提供商状态"""
'last_check': None, providers = get_providers()
'error_count': 0, for provider in providers:
'last_error': None, if provider['name'] not in provider_status:
'request_count': 0, provider_status[provider['name']] = {
'success_count': 0, 'available': True,
'total_tokens': 0, 'last_check': None,
} 'error_count': 0,
'last_error': None,
'request_count': 0,
'success_count': 0,
'total_tokens': 0,
}
def load_stats(): def load_stats():
"""加载统计数据""" """加载统计数据"""
@@ -86,40 +99,43 @@ def config_page():
def api_stats(): def api_stats():
"""获取统计数据""" """获取统计数据"""
stats = load_stats() stats = load_stats()
providers = get_providers()
refresh_provider_status()
# 统计提供商状态 # 统计提供商状态
available_count = sum(1 for p in UPSTREAM_PROVIDERS if provider_status.get(p['name'], {}).get('available', True)) available_count = sum(1 for p in providers if provider_status.get(p['name'], {}).get('available', True))
# 今日请求
today = datetime.now().strftime('%Y-%m-%d')
return jsonify({ return jsonify({
'total_requests': stats.get('total_requests', 0), 'total_requests': stats.get('total_requests', 0),
'total_success': stats.get('total_success', 0), 'total_success': stats.get('total_success', 0),
'total_errors': stats.get('total_errors', 0), 'total_errors': stats.get('total_errors', 0),
'total_tokens': stats.get('total_tokens', 0), 'total_tokens': stats.get('total_tokens', 0),
'providers_count': len(UPSTREAM_PROVIDERS), 'providers_count': len(providers),
'available_providers': available_count, 'available_providers': available_count,
'models_count': len(MODEL_ALIASES), 'models_count': len(get_model_aliases()),
'uptime': time.time(), 'uptime': time.time(),
}) })
@app.route('/api/providers') @app.route('/api/providers')
def api_providers(): def api_providers():
"""获取提供商列表""" """获取提供商列表"""
providers = get_providers()
refresh_provider_status()
stats = load_stats() stats = load_stats()
providers_data = [] providers_data = []
for provider in sorted(UPSTREAM_PROVIDERS, key=lambda x: x['priority']): for provider in sorted(providers, key=lambda x: x['priority']):
p_stats = stats.get('providers', {}).get(provider['name'], {}) p_stats = stats.get('providers', {}).get(provider['name'], {})
p_status = provider_status.get(provider['name'], {}) p_status = provider_status.get(provider['name'], {})
providers_data.append({ providers_data.append({
'id': provider.get('id', provider['name'].lower().replace(' ', '-')),
'name': provider['name'], 'name': provider['name'],
'priority': provider['priority'], 'priority': provider['priority'],
'enabled': provider['enabled'], 'enabled': provider['enabled'],
'available': p_status.get('available', True), 'available': p_status.get('available', True),
'base_url': provider['base_url'], 'base_url': provider['base_url'],
'api_key': provider['api_key'],
'models': provider['models'], 'models': provider['models'],
'default_model': provider['default_model'], 'default_model': provider['default_model'],
'timeout': provider.get('timeout', 120), 'timeout': provider.get('timeout', 120),
@@ -132,17 +148,17 @@ def api_providers():
return jsonify(providers_data) return jsonify(providers_data)
@app.route('/api/providers/<name>', methods=['GET']) @app.route('/api/providers/<provider_id>', methods=['GET'])
def api_provider_detail(name): def api_provider_detail(provider_id):
"""获取提供商详情""" """获取提供商详情"""
provider = next((p for p in UPSTREAM_PROVIDERS if p['name'] == name), None) provider = get_provider(provider_id)
if not provider: if not provider:
return jsonify({'error': 'Provider not found'}), 404 return jsonify({'error': 'Provider not found'}), 404
stats = load_stats() stats = load_stats()
p_stats = stats.get('providers', {}).get(name, {}) p_stats = stats.get('providers', {}).get(provider['name'], {})
p_status = provider_status.get(name, {}) p_status = provider_status.get(provider['name'], {})
return jsonify({ return jsonify({
**provider, **provider,
@@ -155,18 +171,117 @@ def api_provider_detail(name):
} }
}) })
@app.route('/api/providers/<name>/toggle', methods=['POST']) @app.route('/api/providers', methods=['POST'])
def api_toggle_provider(name): def api_add_provider():
"""切换提供商启用状态""" """添加新提供商"""
# 这里需要修改配置文件,简化处理只返回成功 data = request.get_json()
return jsonify({'success': True, 'message': f'Provider {name} toggled'})
@app.route('/api/providers/<name>/test', methods=['POST'])
def api_test_provider(name):
"""测试提供商连接"""
import requests
provider = next((p for p in UPSTREAM_PROVIDERS if p['name'] == name), None) if not data:
return jsonify({'error': 'Invalid request body'}), 400
# 验证必填字段
required = ['name', 'base_url', 'api_key', 'models']
for field in required:
if not data.get(field):
return jsonify({'error': f'Missing required field: {field}'}), 400
# 构建提供商数据
providers = get_providers()
max_priority = max([p['priority'] for p in providers]) if providers else 0
new_provider = {
'id': data.get('id') or data['name'].lower().replace(' ', '-').replace('.', '-'),
'name': data['name'],
'priority': data.get('priority', max_priority + 1),
'base_url': data['base_url'].rstrip('/'),
'api_key': data['api_key'],
'models': data['models'] if isinstance(data['models'], list) else data['models'].split(','),
'default_model': data.get('default_model', data['models'][0] if isinstance(data['models'], list) else data['models'].split(',')[0]),
'timeout': data.get('timeout', 120),
'enabled': data.get('enabled', True),
}
# 添加到配置
result = add_provider(new_provider)
# 初始化状态
provider_status[result['name']] = {
'available': True,
'last_check': None,
'error_count': 0,
'last_error': None,
}
return jsonify({'success': True, 'provider': result})
@app.route('/api/providers/<provider_id>', methods=['PUT'])
def api_update_provider(provider_id):
"""更新提供商"""
data = request.get_json()
if not data:
return jsonify({'error': 'Invalid request body'}), 400
# 处理models字段
if 'models' in data and isinstance(data['models'], str):
data['models'] = [m.strip() for m in data['models'].split(',') if m.strip()]
result = update_provider(provider_id, data)
if not result:
return jsonify({'error': 'Provider not found'}), 404
return jsonify({'success': True, 'provider': result})
@app.route('/api/providers/<provider_id>', methods=['DELETE'])
def api_delete_provider(provider_id):
"""删除提供商"""
result = delete_provider(provider_id)
if not result:
return jsonify({'error': 'Provider not found'}), 404
# 清理状态
providers = get_providers()
for p in providers:
if p.get('id') == provider_id:
if p['name'] in provider_status:
del provider_status[p['name']]
break
return jsonify({'success': True})
@app.route('/api/providers/priority', methods=['POST'])
def api_update_priority():
"""更新优先级顺序(拖拽排序)"""
data = request.get_json()
if not data or 'order' not in data:
return jsonify({'error': 'Missing order field'}), 400
# order 是提供商ID列表,按新顺序排列
provider_ids = data['order']
result = update_priority(provider_ids)
return jsonify({'success': True, 'providers': result})
@app.route('/api/providers/<provider_id>/toggle', methods=['POST'])
def api_toggle_provider(provider_id):
"""切换提供商启用状态"""
provider = get_provider(provider_id)
if not provider:
return jsonify({'error': 'Provider not found'}), 404
new_enabled = not provider.get('enabled', True)
result = update_provider(provider_id, {'enabled': new_enabled})
return jsonify({'success': True, 'enabled': new_enabled})
@app.route('/api/providers/<provider_id>/test', methods=['POST'])
def api_test_provider(provider_id):
"""测试提供商连接"""
provider = get_provider(provider_id)
if not provider: if not provider:
return jsonify({'success': False, 'error': 'Provider not found'}), 404 return jsonify({'success': False, 'error': 'Provider not found'}), 404
@@ -179,23 +294,52 @@ def api_test_provider(name):
response = requests.get(url, headers=headers, timeout=10) response = requests.get(url, headers=headers, timeout=10)
if response.status_code == 200: if response.status_code == 200:
provider_status[name]['available'] = True provider_status[provider['name']] = {
provider_status[name]['last_check'] = datetime.now().isoformat() 'available': True,
return jsonify({'success': True, 'message': 'Connection successful'}) 'last_check': datetime.now().isoformat(),
'error_count': 0,
'last_error': None,
}
# 尝试解析返回的模型列表
models_data = []
try:
resp_json = response.json()
models_data = resp_json.get('data', [])
except:
pass
return jsonify({
'success': True,
'message': 'Connection successful',
'models_count': len(models_data)
})
else: else:
provider_status[provider['name']] = {
'available': False,
'last_check': datetime.now().isoformat(),
'error_count': provider_status.get(provider['name'], {}).get('error_count', 0) + 1,
'last_error': f'HTTP {response.status_code}',
}
return jsonify({ return jsonify({
'success': False, 'success': False,
'error': f'HTTP {response.status_code}: {response.text[:200]}' 'error': f'HTTP {response.status_code}: {response.text[:200]}'
}) })
except Exception as e: except Exception as e:
provider_status[name]['available'] = False provider_status[provider['name']] = {
provider_status[name]['last_error'] = str(e) 'available': False,
'last_check': datetime.now().isoformat(),
'error_count': provider_status.get(provider['name'], {}).get('error_count', 0) + 1,
'last_error': str(e),
}
return jsonify({'success': False, 'error': str(e)}) return jsonify({'success': False, 'error': str(e)})
@app.route('/api/models') @app.route('/api/models')
def api_models(): def api_models():
"""获取模型列表""" """获取模型列表"""
providers = get_providers()
aliases = get_model_aliases()
models_list = [] models_list = []
added = set() added = set()
@@ -208,7 +352,7 @@ def api_models():
added.add('auto') added.add('auto')
# 从提供商获取模型 # 从提供商获取模型
for provider in UPSTREAM_PROVIDERS: for provider in sorted(providers, key=lambda x: x['priority']):
for model in provider['models']: for model in provider['models']:
if model not in added: if model not in added:
models_list.append({ models_list.append({
@@ -220,11 +364,11 @@ def api_models():
added.add(model) added.add(model)
# 添加别名 # 添加别名
for alias, target in MODEL_ALIASES.items(): for alias, target in aliases.items():
if alias != 'auto' and alias not in added: if alias != 'auto' and alias not in added:
# 找到目标模型对应的提供商 # 找到目标模型对应的提供商
provider_name = None provider_name = None
for p in UPSTREAM_PROVIDERS: for p in providers:
if target in p['models']: if target in p['models']:
provider_name = p['name'] provider_name = p['name']
break break
@@ -255,22 +399,33 @@ def api_logs():
@app.route('/api/config') @app.route('/api/config')
def api_config(): def api_config():
"""获取配置""" """获取配置"""
providers = get_providers()
aliases = get_model_aliases()
return jsonify({ return jsonify({
'providers': [{ 'providers': [{
'id': p.get('id', p['name'].lower().replace(' ', '-')),
'name': p['name'], 'name': p['name'],
'priority': p['priority'], 'priority': p['priority'],
'base_url': p['base_url'], 'base_url': p['base_url'],
'models': p['models'], 'models': p['models'],
'timeout': p.get('timeout', 120), 'timeout': p.get('timeout', 120),
'enabled': p['enabled'], 'enabled': p['enabled'],
} for p in UPSTREAM_PROVIDERS], } for p in providers],
'model_aliases': MODEL_ALIASES, 'model_aliases': aliases,
'retry_config': RETRY_CONFIG, 'retry_config': RETRY_CONFIG,
'server_config': { 'server_config': {
'port': SERVER_CONFIG['port'], 'port': SERVER_CONFIG['port'],
} }
}) })
@app.route('/api/reload', methods=['POST'])
def api_reload_config():
"""通知主服务重新加载配置"""
# 这个接口可以被主服务调用以重新加载配置
# 这里只返回成功,实际重载由主服务自己处理
return jsonify({'success': True, 'message': 'Config saved, restart main service to apply'})
@app.route('/api/requests/recent') @app.route('/api/requests/recent')
def api_recent_requests(): def api_recent_requests():
"""获取最近请求记录""" """获取最近请求记录"""
+399 -28
View File
@@ -8,6 +8,8 @@
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
<style> <style>
.gradient-bg { background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); } .gradient-bg { background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); }
.dragging { opacity: 0.5; transform: scale(1.02); }
.drag-over { border: 2px dashed #6366f1; }
</style> </style>
</head> </head>
<body class="bg-gray-50 min-h-screen"> <body class="bg-gray-50 min-h-screen">
@@ -40,58 +42,162 @@
</aside> </aside>
<main class="ml-64 flex-1 p-8"> <main class="ml-64 flex-1 p-8">
<h1 class="text-2xl font-bold text-gray-800 mb-6">提供商管理</h1> <!-- 顶部操作栏 -->
<div class="flex justify-between items-center mb-6">
<div>
<h1 class="text-2xl font-bold text-gray-800">提供商管理</h1>
<p class="text-gray-500 text-sm mt-1">拖拽卡片调整优先级顺序(auto模式选择顺序)</p>
</div>
<button onclick="showAddModal()" class="px-4 py-2 gradient-bg text-white rounded-lg hover:opacity-90 transition">
<i class="ri-add-line mr-1"></i> 添加提供商
</button>
</div>
<!-- 提供商列表 --> <!-- 提供商列表(可拖拽) -->
<div id="providerList" class="space-y-4"> <div id="providerList" class="space-y-4">
<p class="text-gray-500">加载中...</p> <p class="text-gray-500">加载中...</p>
</div> </div>
<!-- 优先级排序提示 -->
<div class="mt-6 p-4 bg-blue-50 rounded-lg text-sm text-blue-600">
<i class="ri-information-line mr-1"></i>
<strong>Auto模式优先级:</strong> 当使用 model="auto" 时,系统会按优先级顺序依次尝试可用的提供商。拖拽上方卡片可调整顺序。
</div>
</main> </main>
</div> </div>
<!-- 添加/编辑提供商弹窗 -->
<div id="editModal" class="fixed inset-0 bg-black/50 hidden items-center justify-center z-50 p-4">
<div class="bg-white rounded-xl w-full max-w-xl max-h-[90vh] overflow-hidden flex flex-col">
<div class="p-6 border-b flex justify-between items-center">
<h2 class="text-xl font-bold text-gray-800" id="modalTitle">添加提供商</h2>
<button onclick="closeModal()" class="text-gray-400 hover:text-gray-600">
<i class="ri-close-line text-2xl"></i>
</button>
</div>
<form id="providerForm" class="p-6 overflow-auto flex-1 space-y-4">
<input type="hidden" id="providerId" name="id">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">提供商名称 *</label>
<input type="text" id="providerName" name="name" required
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500"
placeholder="例如: OpenAI GPT-4">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">API地址 *</label>
<input type="text" id="providerBaseUrl" name="base_url" required
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500"
placeholder="例如: https://api.openai.com/v1">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">API Key *</label>
<input type="text" id="providerApiKey" name="api_key" required
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500"
placeholder="例如: sk-xxx...">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">支持模型(逗号分隔)*</label>
<input type="text" id="providerModels" name="models" required
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500"
placeholder="例如: gpt-4, gpt-3.5-turbo">
<p class="text-xs text-gray-500 mt-1">多个模型用逗号分隔</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">默认模型</label>
<input type="text" id="providerDefaultModel" name="default_model"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500"
placeholder="留空则使用第一个模型">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">超时时间(秒)</label>
<input type="number" id="providerTimeout" name="timeout" value="120"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500">
</div>
<div class="flex items-center gap-2">
<input type="checkbox" id="providerEnabled" name="enabled" checked
class="w-4 h-4 text-purple-600 rounded focus:ring-purple-500">
<label class="text-sm text-gray-700">启用此提供商</label>
</div>
</form>
<div class="p-6 border-t bg-gray-50 flex justify-end gap-3">
<button onclick="closeModal()" class="px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-100">
取消
</button>
<button onclick="testConnection()" class="px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600">
<i class="ri-refresh-line mr-1"></i> 测试连接
</button>
<button onclick="saveProvider()" class="px-4 py-2 gradient-bg text-white rounded-lg hover:opacity-90">
<i class="ri-save-line mr-1"></i> 保存
</button>
</div>
</div>
</div>
<!-- 详情弹窗 --> <!-- 详情弹窗 -->
<div id="detailModal" class="fixed inset-0 bg-black/50 hidden items-center justify-center z-50 p-4"> <div id="detailModal" class="fixed inset-0 bg-black/50 hidden items-center justify-center z-50 p-4">
<div class="bg-white rounded-xl w-full max-w-2xl max-h-[80vh] overflow-hidden flex flex-col"> <div class="bg-white rounded-xl w-full max-w-2xl max-h-[80vh] overflow-hidden flex flex-col">
<div class="p-6 border-b flex justify-between items-center"> <div class="p-6 border-b flex justify-between items-center">
<h2 class="text-xl font-bold text-gray-800" id="modalTitle">提供商详情</h2> <h2 class="text-xl font-bold text-gray-800" id="detailModalTitle">提供商详情</h2>
<button onclick="closeModal()" class="text-gray-400"><i class="ri-close-line text-2xl"></i></button> <button onclick="closeDetailModal()" class="text-gray-400 hover:text-gray-600">
<i class="ri-close-line text-2xl"></i>
</button>
</div> </div>
<div class="p-6 overflow-auto flex-1" id="modalContent"></div> <div class="p-6 overflow-auto flex-1" id="detailModalContent"></div>
</div> </div>
</div> </div>
<script> <script>
let providers = [];
let draggedItem = null;
// 加载提供商列表
async function loadProviders() { async function loadProviders() {
const res = await fetch('/api/providers'); const res = await fetch('/api/providers');
const providers = await res.json(); providers = await res.json();
const container = document.getElementById('providerList'); const container = document.getElementById('providerList');
if (providers.length === 0) { if (providers.length === 0) {
container.innerHTML = '<p class="text-gray-500">暂无提供商</p>'; container.innerHTML = `
<div class="text-center py-12">
<i class="ri-server-line text-6xl text-gray-300 mb-4"></i>
<p class="text-gray-500 mb-4">暂无提供商,点击上方按钮添加</p>
</div>
`;
return; return;
} }
container.innerHTML = providers.map(p => ` container.innerHTML = providers.map(p => `
<div class="bg-white rounded-xl border border-gray-100 overflow-hidden"> <div class="provider-card bg-white rounded-xl border border-gray-100 overflow-hidden cursor-move"
data-id="${p.id}" draggable="true"
ondragstart="handleDragStart(event)" ondragend="handleDragEnd(event)"
ondragover="handleDragOver(event)" ondrop="handleDrop(event)">
<div class="p-6"> <div class="p-6">
<div class="flex items-center justify-between mb-4"> <div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<span class="w-10 h-10 ${p.available ? 'bg-green-500' : 'bg-red-500'} text-white rounded-lg flex items-center justify-center text-lg font-bold"> <span class="w-10 h-10 ${p.available ? 'bg-green-500' : 'bg-red-500'} text-white rounded-lg flex items-center justify-center text-lg font-bold cursor-grab">
${p.priority} ${p.priority}
</span> </span>
<div> <div>
<h3 class="font-semibold text-gray-800 text-lg">${p.name}</h3> <h3 class="font-semibold text-gray-800 text-lg">${p.name}</h3>
<p class="text-sm text-gray-500">优先级 ${p.priority}</p> <p class="text-sm text-gray-500">优先级 ${p.priority} · ${p.enabled ? '已启用' : '已禁用'}</p>
</div> </div>
</div> </div>
<div class="flex items-center gap-3"> <div class="flex items-center gap-2">
<span class="px-3 py-1 rounded-full text-sm ${p.available ? 'bg-green-100 text-green-600' : 'bg-red-100 text-red-600'}"> <span class="px-3 py-1 rounded-full text-sm ${p.available ? 'bg-green-100 text-green-600' : 'bg-red-100 text-red-600'}">
${p.available ? '● 可用' : '○ 不可用'} ${p.available ? '● 可用' : '○ 不可用'}
</span> </span>
<span class="px-3 py-1 rounded-full text-sm ${p.enabled ? 'bg-blue-100 text-blue-600' : 'bg-gray-100 text-gray-600'}"> <button onclick="toggleProvider('${p.id}')"
${p.enabled ? '已启用' : '已禁用'} class="px-3 py-1 rounded-full text-sm ${p.enabled ? 'bg-blue-100 text-blue-600' : 'bg-gray-100 text-gray-600'} hover:opacity-80">
</span> ${p.enabled ? 'ON' : 'OFF'}
</button>
</div> </div>
</div> </div>
@@ -117,12 +223,18 @@
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
<button onclick="testProvider('${p.name}')" class="px-4 py-2 bg-green-500 text-white rounded-lg text-sm hover:bg-green-600"> <button onclick="testProvider('${p.id}')" class="px-4 py-2 bg-green-500 text-white rounded-lg text-sm hover:bg-green-600">
<i class="ri-refresh-line mr-1"></i> 测试连接 <i class="ri-refresh-line mr-1"></i> 测试
</button> </button>
<button onclick="viewDetail('${p.name}')" class="px-4 py-2 border border-gray-300 text-gray-700 rounded-lg text-sm hover:bg-gray-50"> <button onclick="editProvider('${p.id}')" class="px-4 py-2 bg-blue-500 text-white rounded-lg text-sm hover:bg-blue-600">
<i class="ri-edit-line mr-1"></i> 编辑
</button>
<button onclick="viewDetail('${p.id}')" class="px-4 py-2 border border-gray-300 text-gray-700 rounded-lg text-sm hover:bg-gray-50">
<i class="ri-eye-line mr-1"></i> 详情 <i class="ri-eye-line mr-1"></i> 详情
</button> </button>
<button onclick="deleteProvider('${p.id}')" class="px-4 py-2 bg-red-500 text-white rounded-lg text-sm hover:bg-red-600">
<i class="ri-delete-bin-line mr-1"></i> 删除
</button>
</div> </div>
${p.last_error ? ` ${p.last_error ? `
@@ -135,17 +247,214 @@
`).join(''); `).join('');
} }
async function testProvider(name) { // 拖拽相关
const btn = event.target; function handleDragStart(e) {
draggedItem = e.target.closest('.provider-card');
draggedItem.classList.add('dragging');
e.dataTransfer.effectAllowed = 'move';
}
function handleDragEnd(e) {
e.target.closest('.provider-card').classList.remove('dragging');
document.querySelectorAll('.provider-card').forEach(card => {
card.classList.remove('drag-over');
});
}
function handleDragOver(e) {
e.preventDefault();
const card = e.target.closest('.provider-card');
if (card && card !== draggedItem) {
card.classList.add('drag-over');
}
}
function handleDrop(e) {
e.preventDefault();
const targetCard = e.target.closest('.provider-card');
if (!targetCard || targetCard === draggedItem) return;
targetCard.classList.remove('drag-over');
// 重新排序
const container = document.getElementById('providerList');
const cards = [...container.querySelectorAll('.provider-card')];
const draggedIndex = cards.indexOf(draggedItem);
const targetIndex = cards.indexOf(targetCard);
if (draggedIndex < targetIndex) {
targetCard.after(draggedItem);
} else {
targetCard.before(draggedItem);
}
// 更新优先级
updatePriority();
}
async function updatePriority() {
const cards = document.querySelectorAll('.provider-card');
const order = [...cards].map(card => card.dataset.id);
try {
const res = await fetch('/api/providers/priority', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ order })
});
const data = await res.json();
if (data.success) {
// 重新加载以更新显示的优先级数字
loadProviders();
}
} catch (e) {
console.error('更新优先级失败:', e);
}
}
// 弹窗操作
function showAddModal() {
document.getElementById('modalTitle').textContent = '添加提供商';
document.getElementById('providerForm').reset();
document.getElementById('providerId').value = '';
document.getElementById('providerEnabled').checked = true;
document.getElementById('providerTimeout').value = 120;
document.getElementById('editModal').classList.remove('hidden');
document.getElementById('editModal').classList.add('flex');
}
async function editProvider(id) {
const provider = providers.find(p => p.id === id);
if (!provider) return;
document.getElementById('modalTitle').textContent = '编辑提供商';
document.getElementById('providerId').value = provider.id;
document.getElementById('providerName').value = provider.name;
document.getElementById('providerBaseUrl').value = provider.base_url;
document.getElementById('providerApiKey').value = provider.api_key;
document.getElementById('providerModels').value = provider.models.join(', ');
document.getElementById('providerDefaultModel').value = provider.default_model || '';
document.getElementById('providerTimeout').value = provider.timeout || 120;
document.getElementById('providerEnabled').checked = provider.enabled;
document.getElementById('editModal').classList.remove('hidden');
document.getElementById('editModal').classList.add('flex');
}
function closeModal() {
document.getElementById('editModal').classList.add('hidden');
document.getElementById('editModal').classList.remove('flex');
}
async function saveProvider() {
const id = document.getElementById('providerId').value;
const name = document.getElementById('providerName').value.trim();
const base_url = document.getElementById('providerBaseUrl').value.trim();
const api_key = document.getElementById('providerApiKey').value.trim();
const modelsStr = document.getElementById('providerModels').value.trim();
const default_model = document.getElementById('providerDefaultModel').value.trim();
const timeout = parseInt(document.getElementById('providerTimeout').value) || 120;
const enabled = document.getElementById('providerEnabled').checked;
// 验证
if (!name || !base_url || !api_key || !modelsStr) {
alert('请填写所有必填字段!');
return;
}
const models = modelsStr.split(',').map(m => m.trim()).filter(m => m);
const data = {
name,
base_url,
api_key,
models,
default_model: default_model || models[0],
timeout,
enabled
};
try {
let res;
if (id) {
// 编辑
res = await fetch(`/api/providers/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
} else {
// 新增
res = await fetch('/api/providers', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
}
const result = await res.json();
if (result.success) {
closeModal();
loadProviders();
alert(id ? '✅ 提供商已更新!' : '✅ 提供商已添加!');
} else {
alert('❌ 操作失败: ' + result.error);
}
} catch (e) {
alert('保存失败: ' + e.message);
}
}
async function testConnection() {
const base_url = document.getElementById('providerBaseUrl').value.trim();
const api_key = document.getElementById('providerApiKey').value.trim();
if (!base_url || !api_key) {
alert('请先填写 API 地址和 Key');
return;
}
try {
const url = base_url.replace(/\/$/, '') + '/models';
const res = await fetch(url, {
headers: { 'Authorization': `Bearer ${api_key}` }
});
if (res.ok) {
const data = await res.json();
const models = data.data || [];
alert(`✅ 连接成功!找到 ${models.length} 个模型`);
// 如果模型列表为空,提示可以填入
if (models.length > 0 && !document.getElementById('providerModels').value.trim()) {
const modelIds = models.map(m => m.id).slice(0, 5).join(', ');
document.getElementById('providerModels').value = modelIds;
}
} else {
alert(`❌ 连接失败: HTTP ${res.status}`);
}
} catch (e) {
alert('❌ 连接失败: ' + e.message);
}
}
async function testProvider(id) {
const provider = providers.find(p => p.id === id);
if (!provider) return;
const btn = event.target.closest('button');
btn.disabled = true; btn.disabled = true;
btn.innerHTML = '<i class="ri-loader-4-line animate-spin mr-1"></i> 测试中...'; btn.innerHTML = '<i class="ri-loader-4-line animate-spin mr-1"></i> 测试中...';
try { try {
const res = await fetch(`/api/providers/${name}/test`, { method: 'POST' }); const res = await fetch(`/api/providers/${id}/test`, { method: 'POST' });
const data = await res.json(); const data = await res.json();
if (data.success) { if (data.success) {
alert('✅ 连接成功!'); alert(`✅ 连接成功!找到 ${data.models_count || '?'} 个模型`);
} else { } else {
alert('❌ 连接失败: ' + data.error); alert('❌ 连接失败: ' + data.error);
} }
@@ -154,18 +463,58 @@
} }
btn.disabled = false; btn.disabled = false;
btn.innerHTML = '<i class="ri-refresh-line mr-1"></i> 测试连接'; btn.innerHTML = '<i class="ri-refresh-line mr-1"></i> 测试';
loadProviders(); loadProviders();
} }
async function viewDetail(name) { async function toggleProvider(id) {
const res = await fetch(`/api/providers/${name}`); try {
const res = await fetch(`/api/providers/${id}/toggle`, { method: 'POST' });
const data = await res.json();
if (data.success) {
loadProviders();
}
} catch (e) {
alert('切换失败: ' + e.message);
}
}
async function deleteProvider(id) {
const provider = providers.find(p => p.id === id);
if (!provider) return;
if (!confirm(`确定删除提供商 "${provider.name}"?此操作不可恢复!`)) {
return;
}
try {
const res = await fetch(`/api/providers/${id}`, { method: 'DELETE' });
const data = await res.json();
if (data.success) {
loadProviders();
alert('✅ 提供商已删除');
} else {
alert('❌ 删除失败: ' + data.error);
}
} catch (e) {
alert('删除失败: ' + e.message);
}
}
async function viewDetail(id) {
const res = await fetch(`/api/providers/${id}`);
const data = await res.json(); const data = await res.json();
document.getElementById('modalTitle').textContent = data.name; document.getElementById('detailModalTitle').textContent = data.name;
document.getElementById('modalContent').innerHTML = ` document.getElementById('detailModalContent').innerHTML = `
<div class="space-y-4"> <div class="space-y-4">
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4">
<div>
<p class="text-sm text-gray-500">ID</p>
<p class="font-medium">${data.id}</p>
</div>
<div> <div>
<p class="text-sm text-gray-500">API地址</p> <p class="text-sm text-gray-500">API地址</p>
<p class="font-medium">${data.base_url}</p> <p class="font-medium">${data.base_url}</p>
@@ -182,6 +531,12 @@
<p class="text-sm text-gray-500">超时</p> <p class="text-sm text-gray-500">超时</p>
<p class="font-medium">${data.timeout}s</p> <p class="font-medium">${data.timeout}s</p>
</div> </div>
<div>
<p class="text-sm text-gray-500">状态</p>
<p class="font-medium ${data.enabled ? 'text-green-600' : 'text-gray-500'}">
${data.enabled ? '已启用' : '已禁用'}
</p>
</div>
</div> </div>
<div> <div>
@@ -192,7 +547,7 @@
</div> </div>
<div class="p-4 bg-gray-50 rounded-lg"> <div class="p-4 bg-gray-50 rounded-lg">
<h4 class="font-medium mb-2">状态信息</h4> <h4 class="font-medium mb-2">运行状态</h4>
<div class="grid grid-cols-2 gap-4 text-sm"> <div class="grid grid-cols-2 gap-4 text-sm">
<div> <div>
<p class="text-gray-500">可用状态</p> <p class="text-gray-500">可用状态</p>
@@ -212,8 +567,23 @@
<p class="text-gray-500">成功次数</p> <p class="text-gray-500">成功次数</p>
<p>${data.status.success_count}</p> <p>${data.status.success_count}</p>
</div> </div>
${data.status.last_error ? `
<div class="col-span-2">
<p class="text-gray-500">最后错误</p>
<p class="text-red-600">${data.status.last_error}</p>
</div>
` : ''}
</div> </div>
</div> </div>
<div class="flex gap-2 pt-4">
<button onclick="closeDetailModal(); editProvider('${data.id}')" class="px-4 py-2 bg-blue-500 text-white rounded-lg text-sm hover:bg-blue-600">
<i class="ri-edit-line mr-1"></i> 编辑
</button>
<button onclick="testProvider('${data.id}'); closeDetailModal()" class="px-4 py-2 bg-green-500 text-white rounded-lg text-sm hover:bg-green-600">
<i class="ri-refresh-line mr-1"></i> 测试连接
</button>
</div>
</div> </div>
`; `;
@@ -221,11 +591,12 @@
document.getElementById('detailModal').classList.add('flex'); document.getElementById('detailModal').classList.add('flex');
} }
function closeModal() { function closeDetailModal() {
document.getElementById('detailModal').classList.add('hidden'); document.getElementById('detailModal').classList.add('hidden');
document.getElementById('detailModal').classList.remove('flex'); document.getElementById('detailModal').classList.remove('flex');
} }
// 初始化
loadProviders(); loadProviders();
</script> </script>
</body> </body>
+66 -29
View File
@@ -16,13 +16,39 @@ import sys
# 添加配置路径 # 添加配置路径
sys.path.insert(0, str(Path(__file__).parent)) sys.path.insert(0, str(Path(__file__).parent))
from config.settings import ( from config.settings import (
UPSTREAM_PROVIDERS, MODEL_ALIASES, SERVER_CONFIG, get_providers, get_model_aliases, SERVER_CONFIG,
LOG_CONFIG, RETRY_CONFIG LOG_CONFIG, RETRY_CONFIG
) )
app = Flask(__name__) app = Flask(__name__)
CORS(app) CORS(app)
# 配置缓存时间(秒)
CONFIG_CACHE_TTL = 5
_last_config_load = 0
_cached_providers = []
_cached_aliases = {}
def refresh_config():
"""动态刷新配置(支持后台管理修改)"""
global _last_config_load, _cached_providers, _cached_aliases, provider_status
current_time = time.time()
if current_time - _last_config_load > CONFIG_CACHE_TTL:
_cached_providers = get_providers()
_cached_aliases = get_model_aliases()
_last_config_load = current_time
# 更新提供商状态缓存(新增的提供商)
for provider in _cached_providers:
if provider['name'] not in provider_status:
provider_status[provider['name']] = {
'available': True,
'last_check': None,
'error_count': 0,
'last_error': None,
}
# 配置日志 # 配置日志
log_dir = Path(__file__).parent / LOG_CONFIG['log_dir'] log_dir = Path(__file__).parent / LOG_CONFIG['log_dir']
log_dir.mkdir(exist_ok=True) log_dir.mkdir(exist_ok=True)
@@ -39,31 +65,29 @@ logger = logging.getLogger(__name__)
# 提供商状态缓存 # 提供商状态缓存
provider_status = {} provider_status = {}
for provider in UPSTREAM_PROVIDERS:
provider_status[provider['name']] = { # 初始化
'available': True, refresh_config()
'last_check': None,
'error_count': 0,
'last_error': None,
}
def get_provider_for_model(model_name): def get_provider_for_model(model_name):
"""根据模型名获取提供商""" """根据模型名获取提供商"""
refresh_config()
# 解析别名 # 解析别名
resolved_model = MODEL_ALIASES.get(model_name, model_name) resolved_model = _cached_aliases.get(model_name, model_name)
# auto模式:按优先级选择可用提供商 # auto模式:按优先级选择可用提供商
if resolved_model == 'auto': if resolved_model == 'auto':
return get_available_provider() return get_available_provider()
# 查找支持该模型的提供商 # 查找支持该模型的提供商
sorted_providers = sorted(UPSTREAM_PROVIDERS, key=lambda x: x['priority']) sorted_providers = sorted(_cached_providers, key=lambda x: x['priority'])
for provider in sorted_providers: for provider in sorted_providers:
if not provider['enabled']: if not provider['enabled']:
continue continue
if not provider_status[provider['name']]['available']: if not provider_status.get(provider['name'], {}).get('available', True):
continue continue
if resolved_model in provider['models']: if resolved_model in provider['models']:
return provider, resolved_model return provider, resolved_model
@@ -72,7 +96,7 @@ def get_provider_for_model(model_name):
for provider in sorted_providers: for provider in sorted_providers:
if not provider['enabled']: if not provider['enabled']:
continue continue
if not provider_status[provider['name']]['available']: if not provider_status.get(provider['name'], {}).get('available', True):
continue continue
for m in provider['models']: for m in provider['models']:
if resolved_model.lower() in m.lower() or m.lower() in resolved_model.lower(): if resolved_model.lower() in m.lower() or m.lower() in resolved_model.lower():
@@ -83,10 +107,12 @@ def get_provider_for_model(model_name):
def get_available_provider(): def get_available_provider():
"""获取可用的提供商(按优先级)""" """获取可用的提供商(按优先级)"""
sorted_providers = sorted(UPSTREAM_PROVIDERS, key=lambda x: x['priority']) refresh_config()
sorted_providers = sorted(_cached_providers, key=lambda x: x['priority'])
for provider in sorted_providers: for provider in sorted_providers:
if provider['enabled'] and provider_status[provider['name']]['available']: if provider['enabled'] and provider_status.get(provider['name'], {}).get('available', True):
return provider, provider['default_model'] return provider, provider['default_model']
# 如果都不可用,返回第一个尝试(让错误信息传递) # 如果都不可用,返回第一个尝试(让错误信息传递)
@@ -194,10 +220,12 @@ def index():
@app.route('/v1/models', methods=['GET']) @app.route('/v1/models', methods=['GET'])
def list_models(): def list_models():
"""列出可用模型""" """列出可用模型"""
refresh_config()
models_list = [] models_list = []
added_models = set() added_models = set()
for provider in UPSTREAM_PROVIDERS: for provider in _cached_providers:
if not provider['enabled']: if not provider['enabled']:
continue continue
for model in provider['models']: for model in provider['models']:
@@ -328,21 +356,26 @@ def chat_completions():
@app.route('/v1/embeddings', methods=['POST']) @app.route('/v1/embeddings', methods=['POST'])
def embeddings(): def embeddings():
"""嵌入API(简单转发)""" """嵌入API(简单转发)"""
refresh_config()
try: try:
data = request.get_json() data = request.get_json()
model = data.get('model', 'text-embedding-ada-002') model = data.get('model', 'text-embedding-ada-002')
# 使用第一个可用提供商 # 使用第一个可用提供商
provider = UPSTREAM_PROVIDERS[0] if _cached_providers:
provider = _cached_providers[0]
url = f"{provider['base_url'].rstrip('/')}/embeddings"
headers = { url = f"{provider['base_url'].rstrip('/')}/embeddings"
"Content-Type": "application/json", headers = {
"Authorization": f"Bearer {provider['api_key']}" "Content-Type": "application/json",
} "Authorization": f"Bearer {provider['api_key']}"
}
response = requests.post(url, headers=headers, json=data, timeout=60)
return jsonify(response.json()), response.status_code response = requests.post(url, headers=headers, json=data, timeout=60)
return jsonify(response.json()), response.status_code
else:
return jsonify({"error": "No providers available"}), 503
except Exception as e: except Exception as e:
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
@@ -367,9 +400,11 @@ def health():
@app.route('/status', methods=['GET']) @app.route('/status', methods=['GET'])
def status(): def status():
"""详细状态""" """详细状态"""
refresh_config()
providers_detail = [] providers_detail = []
for provider in UPSTREAM_PROVIDERS: for provider in _cached_providers:
status_info = provider_status.get(provider['name'], {}) status_info = provider_status.get(provider['name'], {})
providers_detail.append({ providers_detail.append({
"name": provider['name'], "name": provider['name'],
@@ -385,7 +420,7 @@ def status():
"version": "1.0.0", "version": "1.0.0",
"uptime": time.time(), "uptime": time.time(),
"providers": providers_detail, "providers": providers_detail,
"model_aliases": MODEL_ALIASES, "model_aliases": _cached_aliases,
}) })
@@ -405,6 +440,8 @@ def engine_completions(model):
if __name__ == '__main__': if __name__ == '__main__':
refresh_config()
print("=" * 60) print("=" * 60)
print("大模型API中转系统") print("大模型API中转系统")
print("=" * 60) print("=" * 60)
@@ -412,12 +449,12 @@ if __name__ == '__main__':
print(f"API端点: http://localhost:{SERVER_CONFIG['port']}/v1/chat/completions") print(f"API端点: http://localhost:{SERVER_CONFIG['port']}/v1/chat/completions")
print("=" * 60) print("=" * 60)
print("上游提供商:") print("上游提供商:")
for p in sorted(UPSTREAM_PROVIDERS, key=lambda x: x['priority']): for p in sorted(_cached_providers, key=lambda x: x['priority']):
print(f" [{p['priority']}] {p['name']}: {p['base_url']}") print(f" [{p['priority']}] {p['name']}: {p['base_url']}")
print(f" 模型: {', '.join(p['models'])}") print(f" 模型: {', '.join(p['models'])}")
print("=" * 60) print("=" * 60)
print("支持的模型别名:") print("支持的模型别名:")
for alias, target in MODEL_ALIASES.items(): for alias, target in _cached_aliases.items():
print(f" {alias} -> {target}") print(f" {alias} -> {target}")
print("=" * 60) print("=" * 60)
+118 -21
View File
@@ -1,12 +1,19 @@
""" """
大模型API中转系统配置 大模型API中转系统配置 - 支持动态修改
""" """
# 上游模型配置(按优先级排序,从高到低) import json
UPSTREAM_PROVIDERS = [ from pathlib import Path
# 配置文件路径
CONFIG_FILE = Path(__file__).parent.parent / 'data' / 'config.json'
# 默认上游模型配置
DEFAULT_PROVIDERS = [
{ {
"name": "local-qwen", "id": "local-qwen",
"priority": 1, # 数字越小优先级越高 "name": "Local Qwen",
"priority": 1,
"base_url": "http://192.168.2.5:1234/v1", "base_url": "http://192.168.2.5:1234/v1",
"api_key": "sk-lm-fuP5tGU8:Hi7YU87jHyDP6Ay8Tl2j", "api_key": "sk-lm-fuP5tGU8:Hi7YU87jHyDP6Ay8Tl2j",
"models": ["qwen3.5-4b", "qwen3.5", "qwen"], "models": ["qwen3.5-4b", "qwen3.5", "qwen"],
@@ -15,7 +22,8 @@ UPSTREAM_PROVIDERS = [
"enabled": True, "enabled": True,
}, },
{ {
"name": "siliconflow-deepseek", "id": "siliconflow-deepseek",
"name": "SiliconFlow DeepSeek",
"priority": 2, "priority": 2,
"base_url": "https://api.siliconflow.cn/v1", "base_url": "https://api.siliconflow.cn/v1",
"api_key": "sk-fhpoexpptvjghpnphtaxbkhjwulzovoqfffbckcfscjmwhcg", "api_key": "sk-fhpoexpptvjghpnphtaxbkhjwulzovoqfffbckcfscjmwhcg",
@@ -26,22 +34,118 @@ UPSTREAM_PROVIDERS = [
}, },
] ]
# 模型别名映射 # 默认模型别名
MODEL_ALIASES = { DEFAULT_MODEL_ALIASES = {
# auto 自动选择可用模型
"auto": "auto", "auto": "auto",
# Qwen别名
"qwen": "qwen3.5-4b", "qwen": "qwen3.5-4b",
"qwen3.5": "qwen3.5-4b", "qwen3.5": "qwen3.5-4b",
"qwen3.5-4b": "qwen3.5-4b", "qwen3.5-4b": "qwen3.5-4b",
# DeepSeek别名
"deepseek": "Pro/deepseek-ai/DeepSeek-V3.2", "deepseek": "Pro/deepseek-ai/DeepSeek-V3.2",
"deepseek-v3": "Pro/deepseek-ai/DeepSeek-V3.2", "deepseek-v3": "Pro/deepseek-ai/DeepSeek-V3.2",
"deepseek-v3.2": "Pro/deepseek-ai/DeepSeek-V3.2", "deepseek-v3.2": "Pro/deepseek-ai/DeepSeek-V3.2",
} }
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
# 初始化配置
config = load_config()
UPSTREAM_PROVIDERS = config.get("providers", DEFAULT_PROVIDERS)
MODEL_ALIASES = config.get("model_aliases", DEFAULT_MODEL_ALIASES)
# 服务配置 # 服务配置
SERVER_CONFIG = { SERVER_CONFIG = {
"host": "0.0.0.0", "host": "0.0.0.0",
@@ -59,18 +163,11 @@ LOG_CONFIG = {
# 重试配置 # 重试配置
RETRY_CONFIG = { RETRY_CONFIG = {
"max_retries": 3, "max_retries": 3,
"retry_delay": 1, # 秒 "retry_delay": 1,
"retry_on_errors": [ "retry_on_errors": [
"connection_error", "connection_error",
"timeout", "timeout",
"rate_limit", "rate_limit",
"server_error", "server_error",
], ],
}
# 健康检查配置
HEALTH_CHECK = {
"enabled": True,
"interval": 60, # 秒
"timeout": 10,
} }
+35
View File
@@ -0,0 +1,35 @@
{
"providers": [
{
"id": "local-qwen",
"name": "Local Qwen",
"priority": 1,
"base_url": "http://192.168.2.5:1234/v1",
"api_key": "sk-lm-fuP5tGU8:Hi7YU87jHyDP6Ay8Tl2j",
"models": ["qwen3.5-4b", "qwen3.5", "qwen"],
"default_model": "qwen3.5-4b",
"timeout": 120,
"enabled": true
},
{
"id": "siliconflow-deepseek",
"name": "SiliconFlow DeepSeek",
"priority": 2,
"base_url": "https://api.siliconflow.cn/v1",
"api_key": "sk-fhpoexpptvjghpnphtaxbkhjwulzovoqfffbckcfscjmwhcg",
"models": ["Pro/deepseek-ai/DeepSeek-V3.2", "deepseek-v3", "deepseek"],
"default_model": "Pro/deepseek-ai/DeepSeek-V3.2",
"timeout": 120,
"enabled": true
}
],
"model_aliases": {
"auto": "auto",
"qwen": "qwen3.5-4b",
"qwen3.5": "qwen3.5-4b",
"qwen3.5-4b": "qwen3.5-4b",
"deepseek": "Pro/deepseek-ai/DeepSeek-V3.2",
"deepseek-v3": "Pro/deepseek-ai/DeepSeek-V3.2",
"deepseek-v3.2": "Pro/deepseek-ai/DeepSeek-V3.2"
}
}