chore: Roo Code szabályok, módok és MCP szerverek konfigurálása a Masterbook alapján

This commit is contained in:
Kincses
2026-03-05 00:13:40 +01:00
parent 250f4f4b8f
commit 696db55fd8
16 changed files with 266 additions and 66 deletions

View File

@@ -9,35 +9,42 @@ from sqlalchemy import select
from app.db.session import AsyncSessionLocal
from app.models.system import SystemParameter
from app.services.config_service import config # 2.2-es központi config
from app.services.config_service import config
logger = logging.getLogger("AI-Service-2.2")
logger = logging.getLogger("AI-Service-2.2-Gateway")
class AIService:
"""
Sentinel Master AI Service 2.2.
Felelős az LLM hívásokért, prompt sablonok kezeléséért és az OCR feldolgozásért.
Minden paraméter (modell, url, prompt, hőmérséklet) adminból vezérelt.
Sentinel Master AI Service 2.2 - Multi-Agent & Fallback Gateway.
Felelős az LLM hívásokért. Ha a helyi GPU (Ollama) túlterhelt,
automatikusan áttér a felhős (Groq/Gemini) megoldásokra.
"""
@classmethod
async def _execute_ai_call(cls, db, prompt: str, model_key: str = "text", images: Optional[List[str]] = None) -> Optional[Dict[str, Any]]:
"""
Központi AI végrehajtó. Kezeli a modellt, a várakozást és a JSON parzolást.
Központi AI végrehajtó intelligens teherelosztással (Load Balancing).
"""
# 1. BEÁLLÍTÁSOK LEKÉRÉSE
base_url = await config.get_setting(db, "ai_ollama_url", default="http://ollama:11434/api/generate")
delay = await config.get_setting(db, "AI_REQUEST_DELAY", default=0.1)
temp = await config.get_setting(db, "ai_temperature", default=0.1)
# A helyi timeout-ot levesszük 25 mp-re. Ha az RTX 3090 eddig nem végez,
# akkor biztosan tele van a várólistája, és azonnal átváltunk felhőbe.
local_timeout = await config.get_setting(db, "ai_timeout_local", default=25.0)
# Fallback engedélyezése az .env fájlból
enable_fallback = os.getenv("ENABLE_AI_FALLBACK", "false").lower() == "true"
# Helyi modellek definiálása
default_model = "llama3.2-vision:latest" if model_key == "vision" else "qwen2.5-coder:14b"
model_name = await config.get_setting(db, f"ai_model_{model_key}", default=default_model)
await asyncio.sleep(float(delay))
# 2. ELSŐDLEGES PRÓBA: Helyi Ollama szerver (Ingyenes, VRAM alapú)
try:
# 1. ADMIN KONFIGURÁCIÓ LEKÉRÉSE
base_url = await config.get_setting(db, "ai_ollama_url", default="http://ollama:11434/api/generate")
delay = await config.get_setting(db, "AI_REQUEST_DELAY", default=0.1)
# Modell választás (text vagy vision)
model_name = await config.get_setting(db, f"ai_model_{model_key}", default="qwen2.5-coder:32b")
temp = await config.get_setting(db, "ai_temperature", default=0.1)
timeout_val = await config.get_setting(db, "ai_timeout", default=120.0)
await asyncio.sleep(float(delay))
# 2. PAYLOAD ÖSSZEÁLLÍTÁSA
payload = {
"model": model_name,
"prompt": prompt,
@@ -45,60 +52,128 @@ class AIService:
"format": "json",
"options": {"temperature": float(temp)}
}
if images: # Llava/Vision támogatás
if images:
payload["images"] = images
# 3. HTTP HÍVÁS
async with httpx.AsyncClient(timeout=float(timeout_val)) as client:
logger.info(f"🧠 Helyi AI ({model_name}) hívása indult...")
async with httpx.AsyncClient(timeout=float(local_timeout)) as client:
response = await client.post(base_url, json=payload)
response.raise_for_status()
raw_res = response.json().get("response", "{}")
return json.loads(raw_res)
except (httpx.ReadTimeout, httpx.ConnectError) as e:
logger.warning(f"⚠️ Helyi GPU túlterhelt vagy lassú (Timeout/ConnectError). Váltás Fallback módba!")
except json.JSONDecodeError as je:
logger.error(f"❌ AI JSON hiba (parszolási hiba): {je}")
return None
logger.error(f" Helyi AI JSON parszolási hiba: {je}. Váltás Fallback módba!")
except Exception as e:
logger.error(f"AI hívás kritikus hiba: {e}")
logger.error(f"Helyi AI váratlan hiba: {e}. Váltás Fallback módba!")
# 3. MÁSODLAGOS PRÓBA: Hibrid Fallback (Csak ha engedélyezve van és a helyi elbukott)
if enable_fallback:
return await cls._fallback_ai_call(prompt, model_key, images, float(temp))
else:
logger.error("❌ A Fallback ki van kapcsolva (.env), a feladat feldolgozása sikertelen.")
return None
@classmethod
async def get_gold_data_from_research(cls, make: str, model: str, raw_context: str) -> Optional[Dict[str, Any]]:
"""
Robot 3 (Alchemist) dúsító folyamata.
Kutatási adatokból csinál tiszta technikai adatlapot.
"""
async with AsyncSessionLocal() as db:
template = await config.get_setting(db, "ai_prompt_gold_data",
default="Extract technical car data for {make} {model} from: {context}")
async def _fallback_ai_call(cls, prompt: str, model_key: str, images: Optional[List[str]], temp: float) -> Optional[Dict[str, Any]]:
""" Külső API hívások (Groq és Gemini) vészhelyzet esetére. """
if model_key == "vision" and images:
# ---------------------------------------------------------
# VISION FALLBACK: GOOGLE GEMINI 1.5 FLASH (Képelemzés/OCR)
# ---------------------------------------------------------
gemini_api_key = os.getenv("GEMINI_API_KEY")
if not gemini_api_key:
logger.error("❌ Hiányzik a GEMINI_API_KEY! Képtelen Fallback módban OCR-t végezni.")
return None
logger.info("☁️ Google Gemini 1.5 API (Vision) hívása...")
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key={gemini_api_key}"
payload = {
"contents": [{
"parts": [
{"text": prompt + "\nKérlek, SZIGORÚAN csak érvényes JSON objektummal válaszolj, Markdown formázás (```json) nélkül!"},
{"inline_data": {
"mime_type": "image/jpeg", # A korábbi kódban JPEG-be konvertáljuk
"data": images[0]
}}
]
}],
"generationConfig": {
"temperature": temp,
"response_mime_type": "application/json" # Gemini specifikus JSON kényszerítés
}
}
try:
async with httpx.AsyncClient(timeout=30.0) as client:
resp = await client.post(url, json=payload)
resp.raise_for_status()
data = resp.json()
text_res = data["candidates"][0]["content"]["parts"][0]["text"]
return json.loads(text_res)
except Exception as e:
logger.error(f"❌ Gemini Fallback kritikus hiba: {e}")
return None
else:
# ---------------------------------------------------------
# TEXT FALLBACK: GROQ CLOUD (Szövegelemzés / Web Scraping)
# ---------------------------------------------------------
groq_api_key = os.getenv("GROQ_API_KEY")
if not groq_api_key:
logger.error("❌ Hiányzik a GROQ_API_KEY! Képtelen Fallback módban szöveget elemezni.")
return None
logger.info("☁️ Groq Cloud (Llama 3 8B) hívása...")
url = "[https://api.groq.com/openai/v1/chat/completions](https://api.groq.com/openai/v1/chat/completions)"
headers = {
"Authorization": f"Bearer {groq_api_key}",
"Content-Type": "application/json"
}
payload = {
"model": "llama3-8b-8192", # Villámgyors szöveges modell
"messages": [{"role": "user", "content": prompt}],
"temperature": temp,
"response_format": {"type": "json_object"} # Groq specifikus JSON kényszerítés
}
try:
async with httpx.AsyncClient(timeout=20.0) as client:
resp = await client.post(url, headers=headers, json=payload)
resp.raise_for_status()
data = resp.json()
text_res = data["choices"][0]["message"]["content"]
return json.loads(text_res)
except Exception as e:
logger.error(f"❌ Groq Fallback kritikus hiba: {e}")
return None
# --- A TÖBBI METÓDUS VÁLTOZATLAN MARAD ---
@classmethod
async def get_gold_data_from_research(cls, make: str, model: str, raw_context: str) -> Optional[Dict[str, Any]]:
async with AsyncSessionLocal() as db:
template = await config.get_setting(db, "ai_prompt_gold_data", default="Extract technical car data for {make} {model} from: {context}")
full_prompt = template.format(make=make, model=model, context=raw_context)
return await cls._execute_ai_call(db, full_prompt, model_key="text")
@classmethod
async def get_clean_vehicle_data(cls, make: str, raw_model: str, sources: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""
Név normalizálás és szinonima gyűjtés.
"""
async with AsyncSessionLocal() as db:
template = await config.get_setting(db, "ai_prompt_normalization",
default="Normalize car model names: {make} {model}. Sources: {sources}")
template = await config.get_setting(db, "ai_prompt_normalization", default="Normalize car model names: {make} {model}. Sources: {sources}")
full_prompt = template.format(make=make, model=raw_model, sources=json.dumps(sources))
return await cls._execute_ai_call(db, full_prompt, model_key="text")
@classmethod
async def process_ocr_document(cls, doc_type: str, base64_image: str) -> Optional[Dict[str, Any]]:
"""
Robot 1 (OCR) látó folyamata.
Képet (base64) küld a Vision modellnek (pl. Llava).
"""
async with AsyncSessionLocal() as db:
# Külön prompt sablon minden dokumentum típushoz (számla, forgalmi, adásvételi)
template = await config.get_setting(db, f"ai_prompt_ocr_{doc_type}",
default="Analyze this {doc_type} image and return structured JSON data.")
template = await config.get_setting(db, f"ai_prompt_ocr_{doc_type}", default="Analyze this {doc_type} image and return structured JSON data.")
full_prompt = template.format(doc_type=doc_type)
# Itt mondjuk meg a diszpécsernek, hogy ez egy Vision feladat!
return await cls._execute_ai_call(db, full_prompt, model_key="vision", images=[base64_image])

View File

@@ -2,6 +2,7 @@
from __future__ import annotations
import logging
import uuid
from typing import List, Optional, Dict, Any, TYPE_CHECKING
from datetime import datetime
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, and_