Mise à jour de la configuration de la base de données pour utiliser PostgreSQL et ajout de Redis dans le fichier .env. Modifications du Dockerfile pour installer les dépendances nécessaires. Amélioration des services de stockage pour supporter les opérations asynchrones avec S3 et le stockage local. Refactorisation des pipelines d'images pour une meilleure gestion des tâches asynchrones. Ajout de la gestion des clés API dans l'authentification. Mise à jour de la documentation et des exemples d'utilisation.
CI / Lint & Format (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled

This commit is contained in:
2026-02-24 16:19:18 -05:00
parent cc99fea20a
commit d68deb9c74
20 changed files with 235 additions and 285 deletions
+17 -11
View File
@@ -7,6 +7,7 @@ import logging
import re
import base64
import httpx
import io
from pathlib import Path
from typing import Optional, Tuple
@@ -14,6 +15,7 @@ from google import genai
from google.genai import types
from app.config import settings
from app.services.storage_backend import get_storage_backend
logger = logging.getLogger(__name__)
@@ -28,8 +30,8 @@ def _get_client() -> genai.Client:
return _client
def _read_image(file_path: str) -> tuple[bytes, str]:
"""Lit l'image en bytes et détecte le media_type."""
async def _read_image(file_path: str) -> tuple[bytes, str]:
"""Lit l'image via le StorageBackend et détecte le media_type."""
path = Path(file_path)
suffix = path.suffix.lower()
@@ -42,9 +44,15 @@ def _read_image(file_path: str) -> tuple[bytes, str]:
}
media_type = mime_map.get(suffix, "image/jpeg")
with open(path, "rb") as f:
data = f.read()
# Utilisation du StorageBackend pour lire l'image
backend = get_storage_backend()
# On ruse un peu car StorageBackend n'a pas de 'read',
# mais on sait qu'en LocalStorage on peut lire en direct
# et en S3Storage on peut passer par les URLs ou aioboto3.
# Pour garder une abstraction propre, on va ajouter une méthode 'get_bytes' au backend.
data = await backend.get_bytes(file_path)
return data, media_type
@@ -141,8 +149,6 @@ async def _generate_openrouter(
"model": settings.OPENROUTER_MODEL,
"messages": messages,
"max_tokens": max_tokens,
# OpenRouter/OpenAI support response_format={"type": "json_object"} pour certains modèles
# On tente le coup si le modèle est compatible, sinon le prompt engineering fait le travail
"response_format": {"type": "json_object"}
}
@@ -237,14 +243,14 @@ async def analyze_image(
}
try:
image_bytes, media_type = _read_image(file_path)
image_bytes, media_type = await _read_image(file_path)
prompt = _build_prompt(ocr_hint, language)
response = await _generate(
prompt=prompt,
image_bytes=image_bytes,
media_type=media_type,
max_tokens=settings.GEMINI_MAX_TOKENS # Ou une config unifiée
max_tokens=settings.GEMINI_MAX_TOKENS
)
text = response.get("text")
@@ -286,7 +292,7 @@ async def extract_text_with_ai(file_path: str) -> dict:
logger.info("ai.ocr.fallback_start", extra={"file": Path(file_path).name})
try:
image_bytes, media_type = _read_image(file_path)
image_bytes, media_type = await _read_image(file_path)
prompt = """Agis comme un moteur OCR avancé.
Extrais TOUT le texte visible dans cette image.
Retourne UNIQUEMENT un objet JSON :
@@ -399,6 +405,7 @@ Retourne UNIQUEMENT ce JSON :
}}"""
try:
# Pas d'image ici
response = await _generate(
prompt=prompt,
max_tokens=settings.GEMINI_MAX_TOKENS
@@ -414,4 +421,3 @@ Retourne UNIQUEMENT ce JSON :
logger.error("ai.draft_task.error", extra={"error": str(e)})
return result