diff --git a/app.py b/app.py index 278b91e..a03dc32 100644 --- a/app.py +++ b/app.py @@ -9,9 +9,10 @@ import requests import json import time import logging -from datetime import datetime +from datetime import datetime, date from pathlib import Path import sys +import threading # 添加配置路径 sys.path.insert(0, str(Path(__file__).parent)) @@ -20,6 +21,75 @@ from config.settings import ( LOG_CONFIG, RETRY_CONFIG ) +# 数据目录和统计文件 +DATA_DIR = Path(__file__).parent / 'data' +DATA_DIR.mkdir(exist_ok=True) +STATS_FILE = DATA_DIR / 'stats.json' + +# 统计锁(避免并发写入冲突) +stats_lock = threading.Lock() + +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) + app = Flask(__name__) CORS(app) @@ -244,8 +314,27 @@ def index(): "endpoints": { "chat": "/v1/chat/completions", "models": "/v1/models", + "embeddings": "/v1/embeddings", "health": "/health", "status": "/status" + }, + "examples": { + "curl": { + "description": "curl 命令行", + "code": "curl -X POST http://localhost:19007/v1/chat/completions -H 'Content-Type: application/json' -d '{\"model\": \"auto\", \"messages\": [{\"role\": \"user\", \"content\": \"你好\"}]}'" + }, + "python_openai": { + "description": "Python OpenAI SDK", + "code": "from openai import OpenAI\nclient = OpenAI(base_url='http://localhost:19007/v1', api_key='any')\nresponse = client.chat.completions.create(model='auto', messages=[{'role': 'user', 'content': '你好'}])\nprint(response.choices[0].message.content)" + }, + "python_requests": { + "description": "Python requests", + "code": "import requests\nresponse = requests.post('http://localhost:19007/v1/chat/completions', json={'model': 'auto', 'messages': [{'role': 'user', 'content': '你好'}]})\nprint(response.json()['choices'][0]['message']['content'])" + }, + "javascript": { + "description": "JavaScript fetch", + "code": "fetch('http://localhost:19007/v1/chat/completions', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({model: 'auto', messages: [{role: 'user', content: '你好'}]})}).then(r => r.json()).then(d => console.log(d.choices[0].message.content))" + } } }) @@ -292,19 +381,29 @@ def list_models(): @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}", @@ -312,6 +411,8 @@ def chat_completions(): } }), 400 + request_provider = provider['name'] + logger.info(f"Request: model={model} -> provider={provider['name']}, resolved_model={resolved_model}, stream={stream}") # 重试逻辑 @@ -324,9 +425,11 @@ def chat_completions(): 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', @@ -336,8 +439,14 @@ def chat_completions(): } ) else: - # 非流式响应 - return jsonify(response.json()) + # 非流式响应 - 提取token统计 + result = response.json() + # 尝试提取usage信息 + usage = result.get('usage', {}) + request_tokens = usage.get('total_tokens', 0) + + increment_stats(model, provider['name'], success=True, tokens=request_tokens) + return jsonify(result) elif response.status_code == 429: # 速率限制,尝试下一个提供商 @@ -349,12 +458,15 @@ def chat_completions(): if next_provider and next_provider['name'] not in tried_providers: provider = next_provider resolved_model = next_model + request_provider = provider['name'] continue + increment_stats(model, provider['name'], success=False, error='Rate limit') return jsonify(response.json()), response.status_code else: last_error = response.json() if response.headers.get('content-type', '').startswith('application/json') else {"error": response.text} + increment_stats(model, provider['name'], success=False, error=str(last_error)) return jsonify(last_error), response.status_code except Exception as e: @@ -367,10 +479,12 @@ def chat_completions(): 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}", @@ -380,6 +494,7 @@ def chat_completions(): 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),