- 新增:添加新的大模型接口提供商 - 新增:编辑已有提供商的参数(API地址、Key、模型列表等) - 新增:删除提供商 - 新增:拖拽排序调整auto模式的优先级顺序 - 新增:启用/禁用提供商开关 - 优化:主服务动态读取配置,后台修改实时生效
443 lines
14 KiB
Python
443 lines
14 KiB
Python
"""
|
||
大模型API中转系统 - 后台管理系统
|
||
支持动态添加、编辑、删除提供商和优先级调整
|
||
"""
|
||
|
||
from flask import Flask, render_template, jsonify, request
|
||
from flask_cors import CORS
|
||
import json
|
||
import time
|
||
import uuid
|
||
from datetime import datetime
|
||
from pathlib import Path
|
||
import sys
|
||
import requests
|
||
|
||
# 添加父目录到路径
|
||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||
from config.settings import (
|
||
DEFAULT_PROVIDERS, DEFAULT_MODEL_ALIASES,
|
||
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__)
|
||
CORS(app)
|
||
|
||
# 数据目录
|
||
DATA_DIR = Path(__file__).parent.parent / 'data'
|
||
DATA_DIR.mkdir(exist_ok=True)
|
||
STATS_FILE = DATA_DIR / 'stats.json'
|
||
CONFIG_FILE = DATA_DIR / 'config.json'
|
||
LOGS_DIR = Path(__file__).parent.parent / 'logs'
|
||
|
||
# 提供商状态缓存
|
||
provider_status = {}
|
||
|
||
def refresh_provider_status():
|
||
"""刷新提供商状态"""
|
||
providers = get_providers()
|
||
for provider in providers:
|
||
if provider['name'] not in provider_status:
|
||
provider_status[provider['name']] = {
|
||
'available': True,
|
||
'last_check': None,
|
||
'error_count': 0,
|
||
'last_error': None,
|
||
'request_count': 0,
|
||
'success_count': 0,
|
||
'total_tokens': 0,
|
||
}
|
||
|
||
def load_stats():
|
||
"""加载统计数据"""
|
||
if STATS_FILE.exists():
|
||
return json.loads(STATS_FILE.read_text(encoding='utf-8'))
|
||
return {
|
||
'total_requests': 0,
|
||
'total_success': 0,
|
||
'total_errors': 0,
|
||
'total_tokens': 0,
|
||
'requests_today': 0,
|
||
'providers': {},
|
||
'last_updated': None
|
||
}
|
||
|
||
def save_stats(stats):
|
||
"""保存统计数据"""
|
||
stats['last_updated'] = datetime.now().isoformat()
|
||
STATS_FILE.write_text(json.dumps(stats, ensure_ascii=False, indent=2), encoding='utf-8')
|
||
|
||
# ============ 页面路由 ============
|
||
|
||
@app.route('/')
|
||
def index():
|
||
return render_template('index.html')
|
||
|
||
@app.route('/providers')
|
||
def providers_page():
|
||
return render_template('providers.html')
|
||
|
||
@app.route('/models')
|
||
def models_page():
|
||
return render_template('models.html')
|
||
|
||
@app.route('/logs')
|
||
def logs_page():
|
||
return render_template('logs.html')
|
||
|
||
@app.route('/config')
|
||
def config_page():
|
||
return render_template('config.html')
|
||
|
||
# ============ API路由 ============
|
||
|
||
@app.route('/api/stats')
|
||
def api_stats():
|
||
"""获取统计数据"""
|
||
stats = load_stats()
|
||
providers = get_providers()
|
||
refresh_provider_status()
|
||
|
||
# 统计提供商状态
|
||
available_count = sum(1 for p in providers if provider_status.get(p['name'], {}).get('available', True))
|
||
|
||
return jsonify({
|
||
'total_requests': stats.get('total_requests', 0),
|
||
'total_success': stats.get('total_success', 0),
|
||
'total_errors': stats.get('total_errors', 0),
|
||
'total_tokens': stats.get('total_tokens', 0),
|
||
'providers_count': len(providers),
|
||
'available_providers': available_count,
|
||
'models_count': len(get_model_aliases()),
|
||
'uptime': time.time(),
|
||
})
|
||
|
||
@app.route('/api/providers')
|
||
def api_providers():
|
||
"""获取提供商列表"""
|
||
providers = get_providers()
|
||
refresh_provider_status()
|
||
stats = load_stats()
|
||
providers_data = []
|
||
|
||
for provider in sorted(providers, key=lambda x: x['priority']):
|
||
p_stats = stats.get('providers', {}).get(provider['name'], {})
|
||
p_status = provider_status.get(provider['name'], {})
|
||
|
||
providers_data.append({
|
||
'id': provider.get('id', provider['name'].lower().replace(' ', '-')),
|
||
'name': provider['name'],
|
||
'priority': provider['priority'],
|
||
'enabled': provider['enabled'],
|
||
'available': p_status.get('available', True),
|
||
'base_url': provider['base_url'],
|
||
'api_key': provider['api_key'],
|
||
'models': provider['models'],
|
||
'default_model': provider['default_model'],
|
||
'timeout': provider.get('timeout', 120),
|
||
'request_count': p_stats.get('request_count', 0),
|
||
'success_count': p_stats.get('success_count', 0),
|
||
'error_count': p_status.get('error_count', 0),
|
||
'last_error': p_status.get('last_error'),
|
||
'last_check': p_status.get('last_check'),
|
||
})
|
||
|
||
return jsonify(providers_data)
|
||
|
||
@app.route('/api/providers/<provider_id>', methods=['GET'])
|
||
def api_provider_detail(provider_id):
|
||
"""获取提供商详情"""
|
||
provider = get_provider(provider_id)
|
||
|
||
if not provider:
|
||
return jsonify({'error': 'Provider not found'}), 404
|
||
|
||
stats = load_stats()
|
||
p_stats = stats.get('providers', {}).get(provider['name'], {})
|
||
p_status = provider_status.get(provider['name'], {})
|
||
|
||
return jsonify({
|
||
**provider,
|
||
'status': {
|
||
'available': p_status.get('available', True),
|
||
'error_count': p_status.get('error_count', 0),
|
||
'last_error': p_status.get('last_error'),
|
||
'request_count': p_stats.get('request_count', 0),
|
||
'success_count': p_stats.get('success_count', 0),
|
||
}
|
||
})
|
||
|
||
@app.route('/api/providers', methods=['POST'])
|
||
def api_add_provider():
|
||
"""添加新提供商"""
|
||
data = request.get_json()
|
||
|
||
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:
|
||
return jsonify({'success': False, 'error': 'Provider not found'}), 404
|
||
|
||
try:
|
||
# 测试模型列表接口
|
||
url = f"{provider['base_url'].rstrip('/')}/models"
|
||
headers = {"Authorization": f"Bearer {provider['api_key']}"}
|
||
|
||
response = requests.get(url, headers=headers, timeout=10)
|
||
|
||
if response.status_code == 200:
|
||
provider_status[provider['name']] = {
|
||
'available': True,
|
||
'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:
|
||
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({
|
||
'success': False,
|
||
'error': f'HTTP {response.status_code}: {response.text[:200]}'
|
||
})
|
||
|
||
except Exception as e:
|
||
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': str(e),
|
||
}
|
||
return jsonify({'success': False, 'error': str(e)})
|
||
|
||
@app.route('/api/models')
|
||
def api_models():
|
||
"""获取模型列表"""
|
||
providers = get_providers()
|
||
aliases = get_model_aliases()
|
||
|
||
models_list = []
|
||
added = set()
|
||
|
||
# 添加auto
|
||
models_list.append({
|
||
'alias': 'auto',
|
||
'target': 'auto',
|
||
'description': '自动选择可用模型(按优先级)'
|
||
})
|
||
added.add('auto')
|
||
|
||
# 从提供商获取模型
|
||
for provider in sorted(providers, key=lambda x: x['priority']):
|
||
for model in provider['models']:
|
||
if model not in added:
|
||
models_list.append({
|
||
'alias': model,
|
||
'target': model,
|
||
'provider': provider['name'],
|
||
'priority': provider['priority'],
|
||
})
|
||
added.add(model)
|
||
|
||
# 添加别名
|
||
for alias, target in aliases.items():
|
||
if alias != 'auto' and alias not in added:
|
||
# 找到目标模型对应的提供商
|
||
provider_name = None
|
||
for p in providers:
|
||
if target in p['models']:
|
||
provider_name = p['name']
|
||
break
|
||
|
||
models_list.append({
|
||
'alias': alias,
|
||
'target': target,
|
||
'provider': provider_name,
|
||
})
|
||
|
||
return jsonify(models_list)
|
||
|
||
@app.route('/api/logs')
|
||
def api_logs():
|
||
"""获取日志"""
|
||
log_file = LOGS_DIR / 'proxy.log'
|
||
|
||
lines = []
|
||
if log_file.exists():
|
||
content = log_file.read_text(encoding='utf-8')
|
||
lines = content.strip().split('\n')[-100:] # 最近100条
|
||
|
||
return jsonify({
|
||
'logs': lines,
|
||
'total_lines': len(lines)
|
||
})
|
||
|
||
@app.route('/api/config')
|
||
def api_config():
|
||
"""获取配置"""
|
||
providers = get_providers()
|
||
aliases = get_model_aliases()
|
||
|
||
return jsonify({
|
||
'providers': [{
|
||
'id': p.get('id', p['name'].lower().replace(' ', '-')),
|
||
'name': p['name'],
|
||
'priority': p['priority'],
|
||
'base_url': p['base_url'],
|
||
'models': p['models'],
|
||
'timeout': p.get('timeout', 120),
|
||
'enabled': p['enabled'],
|
||
} for p in providers],
|
||
'model_aliases': aliases,
|
||
'retry_config': RETRY_CONFIG,
|
||
'server_config': {
|
||
'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')
|
||
def api_recent_requests():
|
||
"""获取最近请求记录"""
|
||
# 模拟数据
|
||
return jsonify([])
|
||
|
||
if __name__ == '__main__':
|
||
print("=" * 50)
|
||
print("大模型API中转系统 - 后台管理")
|
||
print("=" * 50)
|
||
print(f"访问地址: http://localhost:19008")
|
||
print(f"前台地址: http://localhost:19007")
|
||
print("=" * 50)
|
||
|
||
app.run(host='0.0.0.0', port=19008, debug=True) |