Files
musadaq-saas/app/Core/AI.php
2026-05-08 00:43:22 +03:00

109 lines
3.7 KiB
PHP

<?php
namespace App\Core;
use App\Services\InvoiceExtractionService;
/**
* Gemini AI Integration for Invoice Extraction
* Optimized for Jordan UBL 2.1 Compliance
*/
class AI
{
private static string $baseUrl = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent";
private static int $maxRetries = 3;
/**
* Extract Data from Invoice Image or PDF (Base64)
*/
public static function extractInvoiceData(string $base64Data, string $mimeType): ?array
{
$apiKey = env('GEMINI_API_KEY');
if (!$apiKey) {
error_log('AI Error: GEMINI_API_KEY is missing');
return null;
}
$service = new InvoiceExtractionService();
$prompt = $service->buildExtractionPrompt();
$payload = [
"contents" => [
[
"parts" => [
["text" => $prompt . " If the image is not an invoice, is blank, or is completely unreadable, return ONLY: {\"error\": \"invalid_invoice\"}. DO NOT guess or invent data."],
[
"inline_data" => [
"mime_type" => $mimeType,
"data" => $base64Data
]
]
]
]
],
"generationConfig" => [
"response_mime_type" => "application/json"
]
];
// Retry with exponential backoff for 503/429 errors
for ($attempt = 1; $attempt <= self::$maxRetries; $attempt++) {
$ch = curl_init(self::$baseUrl . "?key=" . $apiKey);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if ($curlError) {
error_log("AI Error: cURL failed (attempt $attempt): $curlError");
if ($attempt < self::$maxRetries) {
$wait = pow(2, $attempt) + rand(1, 3);
echo " Retrying in {$wait}s (cURL error)...\n";
sleep($wait);
continue;
}
return null;
}
if ($httpCode === 200) {
break; // Success
}
// Retry on 503 (overloaded) or 429 (rate limit)
if (in_array($httpCode, [503, 429]) && $attempt < self::$maxRetries) {
$wait = pow(2, $attempt) + rand(1, 3);
echo " Gemini $httpCode — retrying in {$wait}s (attempt $attempt/" . self::$maxRetries . ")...\n";
sleep($wait);
continue;
}
error_log("AI Error: Gemini API returned code $httpCode. Response: " . $response);
return null;
}
if ($httpCode !== 200) {
error_log("AI Error: All retries exhausted. Last code: $httpCode");
return null;
}
$result = json_decode($response, true);
$textResponse = $result['candidates'][0]['content']['parts'][0]['text'] ?? null;
if (!$textResponse) return null;
$data = json_decode($textResponse, true);
if (isset($data['error']) && $data['error'] === 'invalid_invoice') {
return null;
}
return $data;
}
}