from ..repositories.ai_model_repository import AiModelRepository from ..models import AiModel from typing import Optional, List import requests from django.conf import settings from django.core.cache import cache from django.core.files.uploadedfile import UploadedFile class ConnectionError(Exception): """Custom Exception สำหรับการเชื่อมต่อล้มเหลว""" pass class AiModelService: def __init__(self, repository: AiModelRepository): self.repo = repository def get_all_models(self) -> List[AiModel]: return self.repo.get_all() def create_model(self, validated_data: dict) -> AiModel: # Business Logic: ตรวจสอบความซ้ำซ้อน, การตั้งค่าเริ่มต้น new_model = AiModel(**validated_data) return self.repo.save(new_model) def test_connection(self, pk: int) -> dict: """Logic สำหรับเรียก HTTP Ping ไปยัง AI Service ภายนอก""" model = self.repo.get_by_id(pk) if not model: raise ValueError("Model not found") # ใช้ Root URL ของ Service สำหรับ Ping test_url = model.base_url.rstrip('/') + '/' try: # ใช้ requests เพื่อเรียก HTTP (ใช้ httpx ถ้าต้องการ Async) response = requests.get(test_url, timeout=5) response.raise_for_status() # Raise error for 4xx or 5xx return { "status": "success", "message": f"Successfully pinged {test_url}", "response_status": response.status_code, "response_data": response.json() } except requests.exceptions.RequestException as e: # แปลง HTTP Error เป็น Custom Exception ของ Business Logic raise ConnectionError(f"Connection failed to {test_url}: {e}") def set_status(self, pk: int, new_status: str) -> Optional[AiModel]: # Business Logic: การเปลี่ยนสถานะต้องผ่าน Service model = self.repo.get_by_id(pk) if model: # Logic: มีเงื่อนไขว่าต้องผ่าน TESTING ก่อนไป ACTIVE if model.status == 'TESTING' and new_status == 'ACTIVE': # Logic: Trigger Hot Swap ใน FastAPI Service ก่อนเปลี่ยนสถานะ (ถ้าต้องการ) pass model.status = new_status return self.repo.save(model) return None # ----------------------------------------------- # Logic สำหรับ Proxy AI Inference (พร้อม Caching) # ----------------------------------------------- def run_inference(self, pk: int, file_data: UploadedFile, user_id: int) -> dict: model = self.repo.get_by_id(pk) if not model: raise ValueError(f"Model ID {pk} not found.") if model.status != 'ACTIVE': raise PermissionError(f"Model '{model.name}' is not currently ACTIVE.") # --- Redis Caching Logic --- # 1. สร้าง Cache Key จาก Model ID และ Hash/Metadata ของไฟล์ # การใช้ชื่อไฟล์และขนาดไฟล์เป็น hash อย่างง่ายก็เพียงพอ # ใน Production ควรใช้ Hash (e.g., hashlib.sha256) ของเนื้อหาไฟล์ทั้งหมด file_hash_key = f"{file_data.name}_{file_data.size}_{file_data.content_type}" cache_key = f'inference_result:{pk}:{file_hash_key}' # 2. ตรวจสอบ Cache cached_result = cache.get(cache_key) if cached_result: print(f"Cache HIT for Model {pk} with file {file_data.name}") # คืนผลลัพธ์จาก Redis ทันที return cached_result print(f"Cache MISS for Model {pk} - Proxying request...") # --- Proxy Execution Logic --- # 1. สร้าง Full Inference URL full_url = model.full_inference_url() # 2. จัดการ Headers (แนบ Internal Auth Key ถ้าจำเป็น) headers = {} if model.auth_required: # ใช้ชื่อตัวแปรสภาพแวดล้อมที่คาดว่าจะถูกกำหนดไว้ internal_key = getattr(settings, 'AI_INTERNAL_AUTH_KEY', 'default_secret') headers['X-Internal-Auth'] = internal_key # 3. จัดการ Payload (ไฟล์) # requests.post จะจัดการ Content-Type: multipart/form-data ให้อัตโนมัติ files = { 'file': (file_data.name, file_data.file, file_data.content_type) } # 4. ส่ง Request ไปยัง AI Service ภายนอก try: response = requests.post( full_url, files=files, headers=headers, timeout=600 # 10 นาที ) response.raise_for_status() # 5. ประมวลผลผลลัพธ์ result = response.json() # 6. บันทึกผลลัพธ์ลง Redis Cache # ตั้ง TTL (Time To Live) 1 ชั่วโมง cache.set(cache_key, result, timeout=3600) # 7. บันทึก Audit Log (Logic ในอนาคต) # self.repo.log_inference_request(pk, user_id, full_url, response.status_code) return result except requests.exceptions.HTTPError as e: detail = f"AI Service returned error {response.status_code}: {response.text}" raise ConnectionError(detail) except requests.exceptions.RequestException as e: raise ConnectionError(f"Network error or timeout connecting to {full_url}: {e}")