apiKey = ChatConfig::get('gemini_api_key', ''); $this->model = ChatConfig::get('gemini_model', 'gemini-2.0-flash'); } public function extraerPago(string $base64, string $mimeType, ?int $usuarioId = null, string $canal = 'telegram'): ?array { if (! $this->apiKey) { Log::warning('[Gemini Vision] No hay API key configurada.'); return null; } $contents = [[ 'parts' => [ ['text' => $this->buildPrompt()], ['inline_data' => ['mime_type' => $mimeType, 'data' => $base64]], ], ]]; $body = [ 'contents' => $contents, 'generationConfig' => [ 'temperature' => 0.1, 'maxOutputTokens' => 512, ], ]; $inicio = microtime(true); $response = null; $lastError = ''; foreach (['v1', 'v1beta'] as $ver) { $url = "https://generativelanguage.googleapis.com/{$ver}/models/{$this->model}:generateContent"; try { $resp = Http::withHeaders(['Content-Type' => 'application/json']) ->timeout(40) ->post("{$url}?key={$this->apiKey}", $body); if ($resp->successful()) { $response = $resp; break; } $lastError = "[{$ver}] " . ($resp->json('error.message') ?? substr($resp->body(), 0, 200)); } catch (\Throwable $e) { $lastError = "[{$ver}] " . $e->getMessage(); } } $tiempoMs = (int) ((microtime(true) - $inicio) * 1000); if (! $response) { Log::warning('[Gemini Vision] API failed: ' . $lastError); AiUsageLog::registrar([ 'servicio' => 'gemini_vision', 'canal' => $canal, 'usuario_id' => $usuarioId, 'tiempo_ms' => $tiempoMs, 'resultado' => 'error', 'detalle' => ['error' => $lastError, 'modelo' => $this->model], ]); // Return error info so caller can show it return ['__error' => $lastError]; } $inputTokens = $response->json('usageMetadata.promptTokenCount', 0); $outputTokens = $response->json('usageMetadata.candidatesTokenCount', 0); // Skip thought parts (gemini-3.x thinking blocks), only keep actual response text $parts = $response->json('candidates.0.content.parts', []); $text = implode("\n", array_column( array_filter($parts, fn($p) => isset($p['text']) && empty($p['thought'])), 'text' )); Log::info('[Gemini Vision] raw response: ' . substr($text, 0, 500)); $resultado = $this->parseRespuesta($text); AiUsageLog::registrar([ 'servicio' => 'gemini_vision', 'canal' => $canal, 'usuario_id' => $usuarioId, 'input_tokens' => $inputTokens, 'output_tokens' => $outputTokens, 'tiempo_ms' => $tiempoMs, 'resultado' => $resultado ? 'ok' : 'error', 'detalle' => $resultado ? ['banco' => $resultado['banco'], 'valor' => $resultado['valor']] : ['raw' => substr($text, 0, 400), 'modelo' => $this->model], ]); // If parse failed, return debug info so caller can show it if (! $resultado) { return ['__parse_error' => substr($text, 0, 300)]; } return $resultado; } private function buildPrompt(): string { return << $json['banco'] ?? null, 'valor' => $json['valor'] ?? null, 'referencia' => $json['referencia'] ?? null, 'fecha' => $json['fecha'] ?? null, 'hora' => $json['hora'] ?? null, 'remitente' => $json['remitente'] ?? null, 'llave' => $json['llave'] ?? null, ]; } }