feat: 添加请求统计功能 + API使用示例

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

123
app.py
View File

@@ -9,9 +9,10 @@ import requests
import json import json
import time import time
import logging import logging
from datetime import datetime from datetime import datetime, date
from pathlib import Path from pathlib import Path
import sys import sys
import threading
# 添加配置路径 # 添加配置路径
sys.path.insert(0, str(Path(__file__).parent)) sys.path.insert(0, str(Path(__file__).parent))
@@ -20,6 +21,75 @@ from config.settings import (
LOG_CONFIG, RETRY_CONFIG 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__) app = Flask(__name__)
CORS(app) CORS(app)
@@ -244,8 +314,27 @@ def index():
"endpoints": { "endpoints": {
"chat": "/v1/chat/completions", "chat": "/v1/chat/completions",
"models": "/v1/models", "models": "/v1/models",
"embeddings": "/v1/embeddings",
"health": "/health", "health": "/health",
"status": "/status" "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']) @app.route('/v1/chat/completions', methods=['POST'])
def chat_completions(): def chat_completions():
"""聊天完成API""" """聊天完成API"""
# 用于统计的变量
request_model = None
request_provider = None
request_success = False
request_tokens = 0
try: try:
data = request.get_json() data = request.get_json()
if not data: if not data:
increment_stats('unknown', 'unknown', success=False, error='Invalid request body')
return jsonify({"error": "Invalid request body"}), 400 return jsonify({"error": "Invalid request body"}), 400
model = data.get('model', 'auto') model = data.get('model', 'auto')
stream = data.get('stream', False) stream = data.get('stream', False)
request_model = model
# 获取提供商 # 获取提供商
provider, resolved_model = get_provider_for_model(model) provider, resolved_model = get_provider_for_model(model)
if not provider: if not provider:
increment_stats(model, 'unknown', success=False, error=f'No provider for model: {model}')
return jsonify({ return jsonify({
"error": { "error": {
"message": f"No available provider for model: {model}", "message": f"No available provider for model: {model}",
@@ -312,6 +411,8 @@ def chat_completions():
} }
}), 400 }), 400
request_provider = provider['name']
logger.info(f"Request: model={model} -> provider={provider['name']}, resolved_model={resolved_model}, stream={stream}") 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: if response.status_code == 200:
mark_provider_success(provider['name']) mark_provider_success(provider['name'])
request_success = True
if stream: if stream:
# 流式响应 # 流式响应 - 统计流式请求
increment_stats(model, provider['name'], success=True, tokens=0)
return Response( return Response(
stream_with_context(stream_response(response)), stream_with_context(stream_response(response)),
content_type='text/event-stream', content_type='text/event-stream',
@@ -336,8 +439,14 @@ def chat_completions():
} }
) )
else: else:
# 非流式响应 # 非流式响应 - 提取token统计
return jsonify(response.json()) 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: elif response.status_code == 429:
# 速率限制,尝试下一个提供商 # 速率限制,尝试下一个提供商
@@ -349,12 +458,15 @@ def chat_completions():
if next_provider and next_provider['name'] not in tried_providers: if next_provider and next_provider['name'] not in tried_providers:
provider = next_provider provider = next_provider
resolved_model = next_model resolved_model = next_model
request_provider = provider['name']
continue continue
increment_stats(model, provider['name'], success=False, error='Rate limit')
return jsonify(response.json()), response.status_code return jsonify(response.json()), response.status_code
else: else:
last_error = response.json() if response.headers.get('content-type', '').startswith('application/json') else {"error": response.text} 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 return jsonify(last_error), response.status_code
except Exception as e: except Exception as e:
@@ -367,10 +479,12 @@ def chat_completions():
if next_provider and next_provider['name'] not in tried_providers: if next_provider and next_provider['name'] not in tried_providers:
provider = next_provider provider = next_provider
resolved_model = next_model resolved_model = next_model
request_provider = provider['name']
time.sleep(RETRY_CONFIG['retry_delay']) time.sleep(RETRY_CONFIG['retry_delay'])
continue continue
# 所有重试都失败 # 所有重试都失败
increment_stats(model, request_provider or 'unknown', success=False, error=str(last_error))
return jsonify({ return jsonify({
"error": { "error": {
"message": f"All providers failed. Last error: {last_error}", "message": f"All providers failed. Last error: {last_error}",
@@ -380,6 +494,7 @@ def chat_completions():
except Exception as e: except Exception as e:
logger.error(f"Unexpected error: {e}") logger.error(f"Unexpected error: {e}")
increment_stats(request_model or 'unknown', request_provider or 'unknown', success=False, error=str(e))
return jsonify({ return jsonify({
"error": { "error": {
"message": str(e), "message": str(e),