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.
This commit is contained in:
+17
-11
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user