Files
service-finder/backend/app/services/ai_service1.1.0.py

141 lines
5.5 KiB
Python
Executable File

import os
import json
import logging
import asyncio
import re
import base64
import httpx
from typing import Dict, Any, Optional, List
from sqlalchemy import select
from app.db.session import SessionLocal
from app.models import SystemParameter
logger = logging.getLogger("AI-Service")
class AIService:
"""
AI Service v1.3.5 - Private High-Performance Edition
- Engine: Local Ollama (GPU Accelerated)
- Features: DVLA Integration, 50-char Normalization, Private OCR
"""
# A Docker belső hálózatán a szerviznév 'ollama'
OLLAMA_BASE_URL = "http://ollama:11434/api/generate"
TEXT_MODEL = "vehicle-pro"
VISION_MODEL = "llava:7b"
DVLA_API_KEY = os.getenv("DVLA_API_KEY")
@classmethod
async def get_config_delay(cls) -> float:
"""Késleltetés lekérése az adatbázisból."""
try:
async with SessionLocal() as db:
stmt = select(SystemParameter).where(SystemParameter.key == "AI_REQUEST_DELAY")
res = await db.execute(stmt)
param = res.scalar_one_or_none()
return float(param.value) if param else 0.1
except Exception:
return 0.1
@classmethod
async def get_clean_vehicle_data(cls, make: str, raw_model: str, v_type: str, sources: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Robot 2: Adat-összefésülés és normalizálás."""
# Várjunk egy kicsit a GPU kímélése érdekében
await asyncio.sleep(await cls.get_config_delay())
prompt = f"""
FELADAT: Normalizáld a jármű adatait több forrás alapján.
GYÁRTÓ: {make}
NYERS MODELLNÉV: {raw_model}
FORRÁSOK NYERS ADATAI: {json.dumps(sources, ensure_ascii=False)}
SZIGORÚ SZABÁLYOK:
1. 'marketing_name': MAXIMUM 50 KARAKTER!
2. 'synonyms': Gyűjtsd ide az összes többi névváltozatot.
3. 'technical_code': Keresd meg a gyári kódokat.
VÁLASZ FORMÁTUM (Csak tiszta JSON):
{{
"marketing_name": "string (max 50)",
"synonyms": ["string"],
"technical_code": "string",
"ccm": int,
"kw": int,
"euro_class": int,
"year_from": int,
"year_to": int vagy null,
"maintenance": {{
"oil_type": "string",
"oil_qty": float,
"spark_plug": "string"
}},
"is_duplicate_potential": bool
}}
"""
payload = {
"model": cls.TEXT_MODEL,
"prompt": prompt,
"stream": False,
"format": "json",
"options": {"temperature": 0.1}
}
try:
async with httpx.AsyncClient(timeout=90.0) as client:
logger.info(f"📡 AI kérés küldése: {make} {raw_model}...")
response = await client.post(cls.OLLAMA_BASE_URL, json=payload)
response.raise_for_status()
res_json = response.json()
clean_data = json.loads(res_json.get("response", "{}"))
if clean_data.get("marketing_name"):
clean_data["marketing_name"] = clean_data["marketing_name"][:50].strip()
return clean_data
except Exception as e:
logger.error(f"❌ AI hiba ({make} {raw_model}): {e}")
return None
@classmethod
async def get_dvla_data(cls, vrm: str) -> Optional[Dict[str, Any]]:
"""Brit rendszám alapú adatok lekérése."""
if not cls.DVLA_API_KEY: return None
url = "https://driver-vehicle-licensing.api.gov.uk/vehicle-enquiry/v1/vehicles"
headers = {"x-api-key": cls.DVLA_API_KEY, "Content-Type": "application/json"}
try:
async with httpx.AsyncClient() as client:
resp = await client.post(url, json={"registrationNumber": vrm}, headers=headers)
return resp.json() if resp.status_code == 200 else None
except Exception as e:
logger.error(f"❌ DVLA API hiba: {e}")
return None
@classmethod
async def analyze_document_image(cls, image_data: bytes, doc_type: str) -> Optional[Dict[str, Any]]:
"""Robot 3: Helyi OCR és dokumentum elemzés (Llava:7b)."""
await asyncio.sleep(await cls.get_config_delay())
prompts = {
"identity": "Extract ID card data (name, id_number, expiry) as JSON.",
"vehicle_reg": "Extract vehicle registration (plate, VIN, power_kw, engine_ccm) as JSON.",
"invoice": "Extract invoice details (vendor, total_amount, date) as JSON.",
"odometer": "Identify the number on the odometer and return as JSON: {'value': int}."
}
img_b64 = base64.b64encode(image_data).decode('utf-8')
payload = {
"model": cls.VISION_MODEL,
"prompt": prompts.get(doc_type, "Perform OCR and return JSON"),
"images": [img_b64],
"stream": False,
"format": "json"
}
try:
async with httpx.AsyncClient(timeout=120.0) as client:
response = await client.post(cls.OLLAMA_BASE_URL, json=payload)
res_data = response.json()
clean_json = res_data.get("response", "{}")
match = re.search(r'\{.*\}', clean_json, re.DOTALL)
return json.loads(match.group()) if match else json.loads(clean_json)
except Exception as e:
logger.error(f"❌ Helyi OCR hiba: {e}")
return None