impmementation de gemini vertex & maistral ai

This commit is contained in:
jeremy bayse
2026-06-22 11:24:06 +02:00
parent b85c2ba5a1
commit 61c3c979c4
10 changed files with 849 additions and 24 deletions
+88 -14
View File
@@ -6,16 +6,23 @@ use App\Models\Attachment;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
use Google\Auth\Credentials\ServiceAccountCredentials;
class GeminiService
{
protected ?string $apiKey;
protected string $model;
protected string $region;
protected ?string $projectId;
protected ?string $credentialsPath;
public function __construct()
{
$this->apiKey = config('services.gemini.key');
$this->model = config('services.gemini.model', 'gemini-1.5-flash');
$this->region = config('services.gemini.region', 'us-central1');
$this->projectId = config('services.gemini.project_id');
$this->credentialsPath = config('services.gemini.credentials_path');
}
/**
@@ -51,10 +58,6 @@ class GeminiService
*/
protected function analyzeContent(string $fileContent, string $mimeType, string $fileName): array
{
if (empty($this->apiKey)) {
throw new \Exception("La clé API Gemini n'est pas configurée dans l'environnement.");
}
// Encodage Base64 pour l'API Gemini
$base64Data = base64_encode($fileContent);
@@ -86,15 +89,86 @@ Règles importantes :
3. Ne réponds QUE par le JSON brut, valide, sans aucun texte explicatif avant ou après, et sans bloc de code markdown.
4. Assure-toi que les montants et quantités sont des nombres.";
$url = "https://generativelanguage.googleapis.com/v1beta/models/{$this->model}:generateContent?key={$this->apiKey}";
// Détection automatique de Vertex AI vs Gemini (Google AI Studio)
$isVertex = false;
$credentials = null;
$resolvedPath = $this->credentialsPath;
if ($resolvedPath && !str_starts_with($resolvedPath, '{')) {
if (!file_exists($resolvedPath) && file_exists(base_path($resolvedPath))) {
$resolvedPath = base_path($resolvedPath);
}
}
if ($resolvedPath && (file_exists($resolvedPath) || str_starts_with($resolvedPath, '{'))) {
$isVertex = true;
$credentials = $resolvedPath;
} elseif ($this->apiKey) {
$resolvedApiKey = $this->apiKey;
if (!str_starts_with($resolvedApiKey, '{')) {
if (!file_exists($resolvedApiKey) && file_exists(base_path($resolvedApiKey))) {
$resolvedApiKey = base_path($resolvedApiKey);
}
}
if (str_starts_with($resolvedApiKey, '{') || (file_exists($resolvedApiKey) && str_ends_with($resolvedApiKey, '.json'))) {
$isVertex = true;
$credentials = $resolvedApiKey;
}
}
Log::info("Début de l'analyse OCR Gemini pour le fichier : {$fileName}");
if ($isVertex) {
Log::info("Début de l'analyse OCR Vertex AI pour le fichier : {$fileName}");
// Extraction du Project ID à partir des identifiants si non fourni explicitement
$projectId = $this->projectId;
if (!$projectId && $credentials) {
try {
$jsonContent = file_exists($credentials) ? file_get_contents($credentials) : $credentials;
$decodedCreds = json_decode($jsonContent, true);
$projectId = $decodedCreds['project_id'] ?? null;
} catch (\Exception $e) {
Log::warning("Impossible d'extraire le project_id de la configuration : " . $e->getMessage());
}
}
$response = Http::withHeaders([
'Content-Type' => 'application/json',
])->post($url, [
if (!$projectId) {
throw new \Exception("Le Project ID Google Cloud est requis pour Vertex AI.");
}
// Génération du token OAuth2 avec le SDK
try {
$scopes = ['https://www.googleapis.com/auth/cloud-platform'];
$sa = new ServiceAccountCredentials($scopes, $credentials);
$token = $sa->fetchAuthToken();
$accessToken = $token['access_token'];
} catch (\Exception $e) {
Log::error("Erreur d'authentification Vertex AI : " . $e->getMessage());
throw new \Exception("Échec de l'authentification Vertex AI : " . $e->getMessage());
}
// Vertex AI Endpoint
$url = "https://{$this->region}-aiplatform.googleapis.com/v1/projects/{$projectId}/locations/{$this->region}/publishers/google/models/{$this->model}:generateContent";
$headers = [
'Authorization' => 'Bearer ' . $accessToken,
'Content-Type' => 'application/json',
];
} else {
if (empty($this->apiKey)) {
throw new \Exception("La clé API Gemini n'est pas configurée dans l'environnement.");
}
Log::info("Début de l'analyse OCR Gemini (Google AI Studio) pour le fichier : {$fileName}");
$url = "https://generativelanguage.googleapis.com/v1beta/models/{$this->model}:generateContent?key={$this->apiKey}";
$headers = [
'Content-Type' => 'application/json',
];
}
$response = Http::withHeaders($headers)->post($url, [
'contents' => [
[
'role' => 'user',
'parts' => [
['text' => $prompt],
[
@@ -112,25 +186,25 @@ Règles importantes :
]);
if ($response->failed()) {
Log::error("Échec de l'appel Gemini API : " . $response->body());
throw new \Exception("Erreur de communication avec l'API Gemini : " . $response->status());
Log::error("Échec de l'appel API : " . $response->body());
throw new \Exception("Erreur de communication avec l'API Google : " . $response->status());
}
$data = $response->json();
$textResult = $data['candidates'][0]['content']['parts'][0]['text'] ?? null;
if (!$textResult) {
Log::error("Gemini n'a renvoyé aucun contenu exploitable.");
Log::error("Google API n'a renvoyé aucun contenu exploitable.");
throw new \Exception("L'analyse du devis n'a retourné aucun résultat.");
}
$decoded = json_decode($textResult, true);
if (json_last_error() !== JSON_ERROR_NONE) {
Log::error("Impossible de parser le JSON retourné par Gemini : " . $textResult);
Log::error("Impossible de parser le JSON retourné par l'API : " . $textResult);
throw new \Exception("Le format des données retournées par l'OCR est invalide.");
}
Log::info("OCR Gemini réussi pour le fichier : {$fileName}");
Log::info("OCR Google réussi pour le fichier : {$fileName}");
return $decoded;
}