初始化提交
This commit is contained in:
174
NBATransfer-backend/services/v1_service.py
Normal file
174
NBATransfer-backend/services/v1_service.py
Normal file
@@ -0,0 +1,174 @@
|
||||
from flask import current_app
|
||||
from models import db, User, ApiCall
|
||||
from datetime import datetime
|
||||
import requests
|
||||
import json
|
||||
import logging
|
||||
from modelapiservice import get_model_service
|
||||
from services.api_proxy_service import ApiProxyService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class V1Service:
|
||||
@staticmethod
|
||||
def chat_completions(user, data):
|
||||
"""
|
||||
OpenAI 兼容的 Chat Completions 接口逻辑
|
||||
"""
|
||||
model = data.get('model')
|
||||
messages = data.get('messages', [])
|
||||
stream = data.get('stream', False)
|
||||
|
||||
if not model:
|
||||
return {'error': {'message': '缺少 model 参数', 'type': 'invalid_request_error', 'code': 400}}, 400
|
||||
|
||||
if not messages:
|
||||
return {'error': {'message': '缺少 messages 参数', 'type': 'invalid_request_error', 'code': 400}}, 400
|
||||
|
||||
# 获取模型服务并检查余额
|
||||
try:
|
||||
service = get_model_service(model)
|
||||
except Exception as e:
|
||||
return {'error': {'message': f'不支持的模型: {model}', 'type': 'invalid_request_error', 'code': 400}}, 400
|
||||
|
||||
is_sufficient, estimated_cost, error_msg = service.check_balance(user.balance)
|
||||
if not is_sufficient:
|
||||
return {
|
||||
'error': {
|
||||
'message': error_msg,
|
||||
'type': 'insufficient_quota',
|
||||
'code': 402
|
||||
}
|
||||
}, 402
|
||||
|
||||
prompt = messages[-1].get('content', '') if messages else 'Empty prompt'
|
||||
if len(prompt) > 500:
|
||||
prompt = prompt[:500] + '...'
|
||||
|
||||
api_call = ApiCall(
|
||||
user_id=user.id,
|
||||
api_type='chat_completion',
|
||||
prompt=prompt,
|
||||
parameters=json.dumps({
|
||||
'model': model,
|
||||
'stream': stream
|
||||
}),
|
||||
status='processing',
|
||||
cost=estimated_cost, # 暂时记录预估/固定费用
|
||||
request_time=datetime.utcnow()
|
||||
)
|
||||
|
||||
try:
|
||||
db.session.add(api_call)
|
||||
db.session.flush()
|
||||
|
||||
# 准备请求
|
||||
api_url, api_key = service.get_api_config()
|
||||
if not api_url or not api_key:
|
||||
raise ValueError(f'模型 {model} API 配置未完成')
|
||||
|
||||
headers = {
|
||||
'Authorization': f'Bearer {api_key}',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
payload = service.prepare_payload(data)
|
||||
target_url = f'{api_url}/chat/completions'
|
||||
|
||||
logger.info(f'API 转发: {target_url}, User: {user.id}, Model: {model}')
|
||||
|
||||
response = requests.post(
|
||||
target_url,
|
||||
headers=headers,
|
||||
json=payload,
|
||||
stream=stream,
|
||||
timeout=300
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
error_msg = f'Upstream Error: {response.status_code}'
|
||||
try:
|
||||
error_detail = response.json()
|
||||
error_msg += f' - {error_detail}'
|
||||
except:
|
||||
error_msg += f' - {response.text[:200]}'
|
||||
|
||||
api_call.status = 'failed'
|
||||
api_call.error_message = error_msg
|
||||
db.session.commit()
|
||||
|
||||
return {
|
||||
'error': {
|
||||
'message': error_msg,
|
||||
'type': 'upstream_error',
|
||||
'code': 502
|
||||
}
|
||||
}, 502
|
||||
|
||||
# 处理响应
|
||||
if stream:
|
||||
# 流式响应处理
|
||||
def generate():
|
||||
final_usage = None
|
||||
try:
|
||||
for chunk in response.iter_content(chunk_size=1024):
|
||||
if chunk:
|
||||
if hasattr(service, 'parse_stream_usage'):
|
||||
try:
|
||||
text_chunk = chunk.decode('utf-8', errors='ignore')
|
||||
usage = service.parse_stream_usage(text_chunk)
|
||||
if usage:
|
||||
final_usage = usage
|
||||
except:
|
||||
pass
|
||||
yield chunk
|
||||
|
||||
# 计算最终费用
|
||||
actual_cost = service.calculate_cost(final_usage, stream=True)
|
||||
if actual_cost == 0 and estimated_cost > 0:
|
||||
actual_cost = estimated_cost
|
||||
|
||||
# 扣费
|
||||
with current_app.app_context():
|
||||
ApiProxyService.deduct_balance(user.id, api_call.id, actual_cost, model)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f'Stream error: {e}')
|
||||
|
||||
return generate(), 200
|
||||
else:
|
||||
# 普通响应
|
||||
result = response.json()
|
||||
api_call.status = 'success'
|
||||
api_call.response_time = datetime.utcnow()
|
||||
|
||||
# 计算费用
|
||||
usage = result.get('usage')
|
||||
final_cost = service.calculate_cost(usage, stream=False)
|
||||
if final_cost == 0 and estimated_cost > 0:
|
||||
final_cost = estimated_cost
|
||||
|
||||
# 简化响应格式
|
||||
simplified_result = {
|
||||
'model': model,
|
||||
'content': '',
|
||||
'cost': final_cost
|
||||
}
|
||||
|
||||
if 'choices' in result and len(result['choices']) > 0:
|
||||
content = result['choices'][0].get('message', {}).get('content', '')
|
||||
simplified_result['content'] = content
|
||||
# 同时更新 api_call 记录
|
||||
api_call.result_url = content[:500]
|
||||
|
||||
ApiProxyService.deduct_balance(user.id, api_call.id, final_cost, model)
|
||||
|
||||
return simplified_result, 200
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f'API Error: {e}', exc_info=True)
|
||||
if api_call.id:
|
||||
api_call.status = 'failed'
|
||||
api_call.error_message = str(e)
|
||||
db.session.commit()
|
||||
return {'error': {'message': 'Internal Server Error', 'type': 'server_error', 'code': 500}}, 500
|
||||
Reference in New Issue
Block a user