1 Commits

Author SHA1 Message Date
e96cf6527a feat: 添加请求统计功能 + API使用示例
- 主服务增加请求统计(请求次数、成功/失败、token数)
- 按模型和提供商分别统计
- 每日请求计数自动重置
- 首页增加多种API调用示例(curl、Python OpenAI SDK、Python requests、JavaScript fetch)
2026-04-10 01:34:32 +08:00

123
app.py
View File

@@ -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),