Files
llm-proxy/app.py
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
Raw Blame History

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