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