Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e96cf6527a |
123
app.py
123
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),
|
||||
|
||||
Reference in New Issue
Block a user