Files
llm-proxy/app.py
T
hubian 34afe96055 feat: v2.0.0 - 合并后台管理到单端口
- 端口从 19007+19008 合并为 19007
- 前台API: http://localhost:19007/v1/chat/completions
- 后台管理: http://localhost:19007/admin
- 新增 templates 目录,整合管理页面模板
- 更新所有路由为 /admin 路径
- 后台API统一到 /api/admin 路径下
2026-04-13 10:59:00 +08:00

1184 lines
39 KiB
Python

"""
大模型API中转系统
v2.0.0 - 合并后台管理到单端口
兼容OpenAI API格式,支持多上游提供商优先级调度
端口: 19007
前台API: http://localhost:19007/v1/chat/completions
后台管理: http://localhost:19007/admin
"""
from flask import Flask, request, jsonify, Response, stream_with_context, render_template
from flask_cors import CORS
import requests
import json
import time
import logging
from datetime import datetime, date
from pathlib import Path
import sys
import threading
import uuid
# 添加配置路径
sys.path.insert(0, str(Path(__file__).parent))
from config.settings import (
get_providers, get_model_aliases, get_auto_profiles, SERVER_CONFIG,
LOG_CONFIG, RETRY_CONFIG,
load_config, save_config, get_provider, add_provider, update_provider,
delete_provider, update_priority, update_model_alias,
get_auto_profile, add_auto_profile, update_auto_profile, delete_auto_profile,
DEFAULT_PROVIDERS, DEFAULT_MODEL_ALIASES, DEFAULT_AUTO_PROFILES
)
app = Flask(__name__, template_folder='templates')
CORS(app)
# 数据目录和统计文件
DATA_DIR = Path(__file__).parent / 'data'
DATA_DIR.mkdir(exist_ok=True)
STATS_FILE = DATA_DIR / 'stats.json'
CHATS_FILE = DATA_DIR / 'chats.json'
LOGS_DIR = Path(__file__).parent / 'logs'
LOGS_DIR.mkdir(exist_ok=True)
# 统计锁(避免并发写入冲突)
stats_lock = threading.Lock()
# 提供商状态缓存
provider_status = {}
# 配置缓存时间(秒)
CONFIG_CACHE_TTL = 5
_last_config_load = 0
_cached_providers = []
_cached_aliases = {}
_cached_auto_profiles = {}
def load_stats():
"""加载统计数据"""
if STATS_FILE.exists():
try:
return json.loads(STATS_FILE.read_text(encoding='utf-8'))
except:
pass
return {
'total_requests': 0,
'total_success': 0,
'total_errors': 0,
'total_tokens': 0,
'requests_today': 0,
'requests_by_model': {},
'providers': {},
'last_updated': None,
'date': 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')
def increment_stats(model, provider_name, success=False, tokens=0, error=None):
"""增加统计计数"""
with stats_lock:
stats = load_stats()
today = date.today().isoformat()
if stats.get('date') != today:
stats['date'] = today
stats['requests_today'] = 0
stats['total_requests'] += 1
stats['requests_today'] += 1
if model not in stats['requests_by_model']:
stats['requests_by_model'][model] = {'count': 0, 'success': 0, 'tokens': 0}
stats['requests_by_model'][model]['count'] += 1
if success:
stats['requests_by_model'][model]['success'] += 1
stats['requests_by_model'][model]['tokens'] += tokens
if provider_name not in stats['providers']:
stats['providers'][provider_name] = {'requests': 0, 'success': 0, 'errors': 0, 'tokens': 0}
stats['providers'][provider_name]['requests'] += 1
if success:
stats['providers'][provider_name]['success'] += 1
stats['providers'][provider_name]['tokens'] += tokens
stats['total_success'] += 1
stats['total_tokens'] += tokens
else:
stats['providers'][provider_name]['errors'] += 1
stats['total_errors'] += 1
save_stats(stats)
def refresh_config():
"""动态刷新配置"""
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
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,
}
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_chats():
"""加载对话数据"""
if CHATS_FILE.exists():
return json.loads(CHATS_FILE.read_text(encoding='utf-8'))
return {'chats': []}
def save_chats(data):
"""保存对话数据"""
CHATS_FILE.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding='utf-8')
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(LOGS_DIR / 'proxy.log', encoding='utf-8'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# 初始化
refresh_config()
# ============ 提供商选择逻辑 ============
def get_provider_for_model(model_name):
"""根据模型名获取提供商"""
refresh_config()
resolved_model = _cached_aliases.get(model_name, model_name)
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'])
for provider in sorted_providers:
if not provider['enabled']:
continue
if not provider_status.get(provider['name'], {}).get('available', True):
continue
if resolved_model in provider['models']:
return provider, resolved_model
for provider in sorted_providers:
if not provider['enabled']:
continue
if not provider_status.get(provider['name'], {}).get('available', True):
continue
for m in provider['models']:
if resolved_model.lower() in m.lower() or m.lower() in resolved_model.lower():
return provider, m
return None, None
def get_available_provider_for_auto(auto_name='auto', exclude_providers=None):
"""获取auto模式下的可用提供商"""
refresh_config()
exclude_providers = exclude_providers or []
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 not provider['enabled']:
continue
if provider['name'] in exclude_providers:
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 not candidates:
if sorted_providers:
return sorted_providers[0], sorted_providers[0]['default_model']
return None, None
return candidates[0], candidates[0]['default_model']
def mark_provider_error(provider_name, error):
"""标记提供商错误"""
if provider_name in provider_status:
provider_status[provider_name]['error_count'] += 1
provider_status[provider_name]['last_error'] = str(error)
provider_status[provider_name]['last_check'] = datetime.now()
if provider_status[provider_name]['error_count'] >= 3:
provider_status[provider_name]['available'] = False
logger.warning(f"Provider {provider_name} marked as unavailable")
def mark_provider_success(provider_name):
"""标记提供商成功"""
if provider_name in provider_status:
provider_status[provider_name]['error_count'] = 0
provider_status[provider_name]['available'] = True
provider_status[provider_name]['last_check'] = datetime.now()
def proxy_request(provider, model, request_data, stream=False):
"""转发请求到上游提供商"""
url = f"{provider['base_url'].rstrip('/')}/chat/completions"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {provider['api_key']}"
}
data = request_data.copy()
data['model'] = model
try:
if stream:
response = requests.post(url, headers=headers, json=data, stream=True, timeout=provider.get('timeout', 120))
return response
else:
response = requests.post(url, headers=headers, json=data, timeout=provider.get('timeout', 120))
return response
except requests.exceptions.Timeout:
mark_provider_error(provider['name'], "Timeout")
raise Exception(f"Provider {provider['name']} timeout")
except requests.exceptions.ConnectionError:
mark_provider_error(provider['name'], "Connection error")
raise Exception(f"Provider {provider['name']} connection error")
except Exception as e:
mark_provider_error(provider['name'], str(e))
raise
def stream_response(response):
"""流式响应生成器"""
try:
for line in response.iter_lines():
if line:
yield line + b'\n'
except Exception as e:
logger.error(f"Stream error: {e}")
yield b'data: {"error": "' + str(e).encode() + b'"}\n\n'
# ============ 前台 API 路由 ============
@app.route('/')
def index():
"""首页"""
return jsonify({
"name": "LLM Proxy",
"version": "2.0.0",
"description": "OpenAI-compatible LLM API Proxy",
"endpoints": {
"chat": "/v1/chat/completions",
"models": "/v1/models",
"embeddings": "/v1/embeddings",
"health": "/health",
"status": "/status",
"admin": "/admin"
}
})
@app.route('/v1/models', methods=['GET'])
def list_models():
"""列出可用模型"""
refresh_config()
models_list = []
added_models = set()
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
for model in provider['models']:
if model not in added_models:
models_list.append({
"id": model,
"object": "model",
"created": int(time.time()),
"owned_by": provider['name'],
})
added_models.add(model)
return jsonify({"object": "list", "data": models_list})
@app.route('/v1/chat/completions', methods=['POST'])
def chat_completions():
"""聊天完成API"""
request_model = None
request_provider = None
request_success = False
request_tokens = 0
try:
data = request.get_json()
if not data:
increment_stats('unknown', 'unknown', success=False, error='Invalid request body')
return jsonify({"error": "Invalid request body"}), 400
model = data.get('model', 'auto')
stream = data.get('stream', False)
request_model = model
provider, resolved_model = get_provider_for_model(model)
if not provider:
increment_stats(model, 'unknown', success=False, error=f'No provider for model: {model}')
return jsonify({"error": {"message": f"No available provider for model: {model}", "type": "invalid_request_error"}}), 400
request_provider = provider['name']
logger.info(f"Request: model={model} -> provider={provider['name']}, resolved_model={resolved_model}, stream={stream}")
last_error = None
tried_providers = []
for attempt in range(RETRY_CONFIG['max_retries']):
try:
response = proxy_request(provider, resolved_model, data, stream)
if response.status_code == 200:
mark_provider_success(provider['name'])
request_success = True
if stream:
increment_stats(model, provider['name'], success=True, tokens=0)
return Response(
stream_with_context(stream_response(response)),
content_type='text/event-stream',
headers={'Cache-Control': 'no-cache', 'Connection': 'keep-alive'}
)
else:
result = response.json()
usage = result.get('usage', {})
request_tokens = usage.get('total_tokens', 0)
increment_stats(model, provider['name'], success=True, tokens=request_tokens)
return jsonify(result)
else:
error_info = response.json() if response.headers.get('content-type', '').startswith('application/json') else {"error": response.text}
last_error = error_info
logger.warning(f"Provider {provider['name']} returned {response.status_code}: {error_info}")
mark_provider_error(provider['name'], f"HTTP {response.status_code}")
tried_providers.append(provider['name'])
next_provider, next_model = get_available_provider_for_auto('auto', exclude_providers=tried_providers)
if next_provider and next_provider['name'] not in tried_providers:
logger.info(f"Switching to next provider: {next_provider['name']}")
provider = next_provider
resolved_model = next_model
request_provider = provider['name']
time.sleep(RETRY_CONFIG['retry_delay'])
continue
increment_stats(model, provider['name'], success=False, error=str(last_error))
return jsonify(error_info), response.status_code
except Exception as e:
last_error = str(e)
logger.error(f"Attempt {attempt + 1} failed: {e}")
tried_providers.append(provider['name'])
next_provider, next_model = get_available_provider_for_auto('auto', exclude_providers=tried_providers)
if next_provider and next_provider['name'] not in tried_providers:
provider = next_provider
resolved_model = next_model
request_provider = provider['name']
time.sleep(RETRY_CONFIG['retry_delay'])
continue
increment_stats(model, request_provider or 'unknown', success=False, error=str(last_error))
return jsonify({"error": {"message": f"All providers failed. Last error: {last_error}", "type": "api_error"}}), 503
except Exception as e:
logger.error(f"Unexpected error: {e}")
increment_stats(request_model or 'unknown', request_provider or 'unknown', success=False, error=str(e))
return jsonify({"error": {"message": str(e), "type": "internal_error"}}), 500
@app.route('/v1/embeddings', methods=['POST'])
def embeddings():
"""嵌入API"""
refresh_config()
try:
data = request.get_json()
if _cached_providers:
provider = _cached_providers[0]
url = f"{provider['base_url'].rstrip('/')}/embeddings"
headers = {"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
else:
return jsonify({"error": "No providers available"}), 503
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/health', methods=['GET'])
def health():
"""健康检查"""
available_count = sum(1 for s in provider_status.values() if s['available'])
total_count = len(provider_status)
return jsonify({
"status": "healthy" if available_count > 0 else "degraded",
"providers": {"available": available_count, "total": total_count},
"timestamp": datetime.now().isoformat()
})
@app.route('/status', methods=['GET'])
def status():
"""详细状态"""
refresh_config()
providers_detail = []
for provider in _cached_providers:
status_info = provider_status.get(provider['name'], {})
providers_detail.append({
"name": provider['name'],
"priority": provider['priority'],
"enabled": provider['enabled'],
"available": status_info.get('available', True),
"error_count": status_info.get('error_count', 0),
"last_error": status_info.get('last_error'),
"models": provider['models'],
})
return jsonify({
"version": "2.0.0",
"uptime": time.time(),
"providers": providers_detail,
"model_aliases": _cached_aliases,
})
# 兼容旧版端点
@app.route('/v1/engines', methods=['GET'])
def list_engines():
return list_models()
@app.route('/v1/engines/<model>/completions', methods=['POST'])
def engine_completions(model):
data = request.get_json()
data['model'] = model
return chat_completions()
# ============ 后台管理页面路由 ============
@app.route('/admin')
def admin_index():
return render_template('index.html')
@app.route('/admin/providers')
def admin_providers():
return render_template('providers.html')
@app.route('/admin/models')
def admin_models():
return render_template('models.html')
@app.route('/admin/logs')
def admin_logs():
return render_template('logs.html')
@app.route('/admin/config')
def admin_config():
return render_template('config.html')
@app.route('/admin/chat')
def admin_chat():
return render_template('chat.html')
@app.route('/admin/auto-profiles')
def admin_auto_profiles():
return render_template('auto-profiles.html')
# ============ 后台管理 API 路由 ============
@app.route('/api/admin/stats')
def api_admin_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/admin/providers')
def api_admin_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('requests', 0),
'success_count': p_stats.get('success', 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/admin/providers/<provider_id>', methods=['GET'])
def api_admin_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('requests', 0),
'success_count': p_stats.get('success', 0),
}
})
@app.route('/api/admin/providers', methods=['POST'])
def api_admin_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/admin/providers/<provider_id>', methods=['PUT'])
def api_admin_update_provider(provider_id):
"""更新提供商"""
data = request.get_json()
if not data:
return jsonify({'error': 'Invalid request body'}), 400
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/admin/providers/<provider_id>', methods=['DELETE'])
def api_admin_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/admin/providers/priority', methods=['POST'])
def api_admin_update_priority():
"""更新优先级顺序"""
data = request.get_json()
if not data or 'order' not in data:
return jsonify({'error': 'Missing order field'}), 400
provider_ids = data['order']
result = update_priority(provider_ids)
return jsonify({'success': True, 'providers': result})
@app.route('/api/admin/providers/<provider_id>/toggle', methods=['POST'])
def api_admin_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/admin/providers/<provider_id>/test', methods=['POST'])
def api_admin_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/admin/models')
def api_admin_models():
"""获取模型列表"""
providers = get_providers()
aliases = get_model_aliases()
models_list = []
added = set()
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/admin/logs')
def api_admin_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:]
return jsonify({'logs': lines, 'total_lines': len(lines)})
@app.route('/api/admin/config')
def api_admin_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']}
})
# ============ Auto配置管理 ============
@app.route('/api/admin/auto-profiles')
def api_admin_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/admin/auto-profiles/<profile_name>', methods=['GET'])
def api_admin_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/admin/auto-profiles', methods=['POST'])
def api_admin_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/admin/auto-profiles/<profile_name>', methods=['PUT'])
def api_admin_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/admin/auto-profiles/<profile_name>', methods=['DELETE'])
def api_admin_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})
# ============ 对话功能 ============
@app.route('/api/admin/chat/models')
def api_admin_chat_models():
"""获取可用模型列表"""
providers = get_providers()
profiles = get_auto_profiles()
models = []
added = set()
for name, profile in profiles.items():
if name not in added:
models.append({'id': name, 'description': profile.get('description', 'Auto-select')})
added.add(name)
for provider in providers:
if not provider['enabled']:
continue
for model in provider['models']:
if model not in added:
models.append({'id': model, 'description': provider['name']})
added.add(model)
return jsonify(models)
@app.route('/api/admin/chat/list')
def api_admin_chat_list():
"""获取对话列表"""
data = load_chats()
chats = []
for chat in data.get('chats', []):
chats.append({
'id': chat['id'],
'title': chat.get('title', '新对话'),
'model': chat.get('model', 'auto'),
'message_count': len(chat.get('messages', [])),
'created_at': chat.get('created_at'),
'updated_at': chat.get('updated_at')
})
chats.sort(key=lambda x: x.get('updated_at', ''), reverse=True)
return jsonify(chats)
@app.route('/api/admin/chat/<chat_id>')
def api_admin_chat_detail(chat_id):
"""获取对话详情"""
data = load_chats()
for chat in data.get('chats', []):
if chat['id'] == chat_id:
return jsonify(chat)
return jsonify({'error': 'Chat not found'}), 404
@app.route('/api/admin/chat/send', methods=['POST'])
def api_admin_chat_send():
"""发送消息"""
req = request.get_json()
user_message = req.get('message', '')
model = req.get('model', 'auto')
chat_id = req.get('chat_id')
if not user_message:
return jsonify({'error': 'Message is required'}), 400
data = load_chats()
chat = None
if chat_id:
for c in data['chats']:
if c['id'] == chat_id:
chat = c
break
if not chat:
chat_id = str(uuid.uuid4())[:8]
chat = {
'id': chat_id,
'title': '新对话',
'model': model,
'messages': [],
'created_at': datetime.now().isoformat(),
'updated_at': datetime.now().isoformat()
}
data['chats'].append(chat)
chat['messages'].append({
'role': 'user',
'content': user_message,
'time': datetime.now().isoformat()
})
try:
proxy_url = f"http://localhost:{SERVER_CONFIG['port']}/v1/chat/completions"
messages = []
for msg in chat['messages'][-20:]:
messages.append({'role': msg['role'], 'content': msg['content']})
response = requests.post(proxy_url, json={
'model': model,
'messages': messages,
'stream': False
}, timeout=120)
if response.status_code == 200:
result = response.json()
assistant_message = result['choices'][0]['message']['content']
used_model = result.get('model', model)
chat['messages'].append({
'role': 'assistant',
'content': assistant_message,
'model': used_model,
'time': datetime.now().isoformat()
})
if len(chat['messages']) == 2:
chat['title'] = user_message[:30] + ('...' if len(user_message) > 30 else '')
chat['updated_at'] = datetime.now().isoformat()
save_chats(data)
return jsonify({
'success': True,
'chat_id': chat_id,
'response': assistant_message,
'model': used_model,
'title': chat['title']
})
else:
error_msg = response.json().get('error', {}).get('message', 'Unknown error')
return jsonify({'error': error_msg}), response.status_code
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/admin/chat/<chat_id>', methods=['DELETE'])
def api_admin_delete_chat(chat_id):
"""删除对话"""
data = load_chats()
data['chats'] = [c for c in data['chats'] if c['id'] != chat_id]
save_chats(data)
return jsonify({'success': True})
@app.route('/api/admin/chat/<chat_id>/clear', methods=['POST'])
def api_admin_clear_chat(chat_id):
"""清空对话消息"""
data = load_chats()
for chat in data['chats']:
if chat['id'] == chat_id:
chat['messages'] = []
chat['updated_at'] = datetime.now().isoformat()
save_chats(data)
return jsonify({'success': True})
return jsonify({'error': 'Chat not found'}), 404
if __name__ == '__main__':
refresh_config()
print("=" * 60)
print("大模型API中转系统 v2.0.0")
print("=" * 60)
print(f"API地址: http://localhost:{SERVER_CONFIG['port']}")
print(f"后台管理: http://localhost:{SERVER_CONFIG['port']}/admin")
print("=" * 60)
print("上游提供商:")
for p in sorted(_cached_providers, key=lambda x: x['priority']):
print(f" [{p['priority']}] {p['name']}: {p['base_url']}")
print(f" 模型: {', '.join(p['models'])}")
print("=" * 60)
app.run(
host=SERVER_CONFIG['host'],
port=SERVER_CONFIG['port'],
debug=SERVER_CONFIG['debug']
)