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

@@ -2,7 +2,7 @@
import uuid
from datetime import datetime
from typing import Optional
from sqlalchemy import String, Integer, Boolean, DateTime, ForeignKey, text
from sqlalchemy import String, Integer, Boolean, DateTime, ForeignKey, Text
from sqlalchemy.dialects.postgresql import UUID as PG_UUID
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.sql import func
@@ -11,6 +11,7 @@ from app.db.base_class import Base
class Document(Base):
""" NAS alapú dokumentumtár metaadatai. """
__tablename__ = "documents"
__table_args__ = {"schema": "data"}
id: Mapped[uuid.UUID] = mapped_column(PG_UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
parent_type: Mapped[str] = mapped_column(String(20)) # 'organization' vagy 'asset'
@@ -27,4 +28,27 @@ class Document(Base):
thumbnail_path: Mapped[Optional[str]] = mapped_column(String(255))
uploaded_by: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("identity.users.id"))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
# =========================================================================
# THOUGHT PROCESS & ARCHITECTURE DECISIONS (2026-03-04)
# =========================================================================
# Probléma: Az `ocr_robot.py` (Robot 3) módosítani próbálta a dokumentumok
# állapotát és menteni akarta az AI eredményeket, de a mezők hiányoztak.
#
# Megoldás: Hozzáadtuk a szükséges mezőket a munkafolyamat (Workflow)
# támogatásához.
#
# 1. `status`: A robot a 'pending_ocr' státuszra szűr. Indexeljük,
# mert a WHERE feltételben szerepel, így az adatbázis sokkal gyorsabb lesz.
#
# 2. `ocr_data`: A kinyert adatokat tárolja. Text típust használunk String
# helyett, mert az AI válasza (pl. JSON formátumú adat) hosszú lehet.
#
# 3. `error_log`: Ha az AI hibázik, vagy üres választ ad, itt rögzítjük
# a hiba okát a könnyebb debuggolás érdekében.
# =========================================================================
status: Mapped[str] = mapped_column(String(50), default="uploaded", index=True)
ocr_data: Mapped[Optional[str]] = mapped_column(Text)
error_log: Mapped[Optional[str]] = mapped_column(Text)

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_

View File

@@ -1,4 +1,4 @@
# /opt/docker/dev/service_finder/backend/app/workers/ocr_robot.py
# /opt/docker/dev/service_finder/backend/app/workers/ocr/robot_1_ocr_processor.py
import asyncio
import os
import logging
@@ -71,13 +71,16 @@ class OCRRobot:
with open(temp_path, "rb") as f:
image_bytes = f.read()
# 1. Kép kódolása Base64 formátumba (A Vision modellek ezt várják)
import base64
base64_image = base64.b64encode(image_bytes).decode('utf-8')
# AI felismerés (pl. Llama-Vision vagy GPT-4o)
ocr_result = await AIService.get_clean_vehicle_data(
make="OCR_SCAN",
raw_model=doc.parent_type,
v_type="document",
sources={"image_data": "raw_scan"}
# 2. A MEGFELELŐ AI HÍVÁS (Vision modell hívása az AIService-en keresztül)
doc_type_str = doc.doc_type or "general_document"
ocr_result = await AIService.process_ocr_document(
doc_type=doc_type_str,
base64_image=base64_image
)
if ocr_result: