feat: costo OCR en dashboard, logs correctos y fix de thinking tokens
- OcrExtractionService: registra cada llamada en ai_usage_logs (servicio=ocr, costo=5 COP/foto) y ahora tambien loggea cuando SI funciona, no solo cuando falla (antes era imposible confirmar por log que el OCR corrio). - GeminiVisionService: el log decia siempre "[Gemini Vision]" sin importar si la llamada fue de imagen o de texto post-OCR, haciendo imposible distinguir cual camino se uso realmente. Ahora usa el servicio real. - GeminiVisionService: agrega thinkingConfig.thinkingBudget=0 (igual que GeminiIntentService) para que el modelo no gaste tokens narrando su razonamiento en texto plano antes del JSON final -- se estaba colando ese razonamiento en la respuesta y comiendose el presupuesto de tokens. - GeminiIntentService: resultado ya no queda forzado a 'ok'; ahora filtra bloques 'thought' igual que vision, y guarda tokens/raw en el detalle para poder diagnosticar sin acceso al servidor. - ShowAiLogs: card y filtro de costo OCR en el dashboard de consumo. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
7b21cd9773
commit
5e0c0367d9
@@ -2,12 +2,15 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\AiUsageLog;
|
||||
use App\Models\ChatConfig;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class OcrExtractionService
|
||||
{
|
||||
private const COSTO_POR_FOTO = 5; // pesos COP, tarifa plana del servicio OCR
|
||||
|
||||
private string $url;
|
||||
private string $token;
|
||||
|
||||
@@ -30,12 +33,14 @@ class OcrExtractionService
|
||||
}
|
||||
|
||||
/** Envia la imagen al servicio OCR externo y devuelve el texto crudo, o null si falla. */
|
||||
public function extraerTexto(string $base64, string $mimeType): ?string
|
||||
public function extraerTexto(string $base64, string $mimeType, ?int $usuarioId = null, string $canal = 'web'): ?string
|
||||
{
|
||||
if (! $this->url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$inicio = microtime(true);
|
||||
|
||||
try {
|
||||
$response = Http::withToken($this->token)
|
||||
->timeout(20)
|
||||
@@ -44,23 +49,40 @@ class OcrExtractionService
|
||||
'mime_type' => $mimeType,
|
||||
]);
|
||||
|
||||
if (! $response->successful()) {
|
||||
Log::warning('[OCR] HTTP ' . $response->status() . ': ' . substr($response->body(), 0, 300));
|
||||
$tiempoMs = (int) ((microtime(true) - $inicio) * 1000);
|
||||
$data = $response->json();
|
||||
|
||||
if (! $response->successful() || ! ($data['success'] ?? false) || empty($data['text'])) {
|
||||
$error = $data['error'] ?? ('HTTP ' . $response->status());
|
||||
Log::warning('[OCR] ' . $error . ': ' . substr($response->body(), 0, 300));
|
||||
$this->registrarUso($usuarioId, $canal, $tiempoMs, 'error', ['error' => $error]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = $response->json();
|
||||
|
||||
if (! ($data['success'] ?? false) || empty($data['text'])) {
|
||||
Log::warning('[OCR] respuesta sin texto util: ' . substr($response->body(), 0, 300));
|
||||
return null;
|
||||
}
|
||||
Log::info("[OCR] ok ({$tiempoMs}ms): " . substr($data['text'], 0, 200));
|
||||
$this->registrarUso($usuarioId, $canal, $tiempoMs, 'ok', ['caracteres' => mb_strlen($data['text'])]);
|
||||
|
||||
return $data['text'];
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$tiempoMs = (int) ((microtime(true) - $inicio) * 1000);
|
||||
Log::warning('[OCR] excepcion: ' . $e->getMessage());
|
||||
$this->registrarUso($usuarioId, $canal, $tiempoMs, 'error', ['error' => $e->getMessage()]);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Cada foto enviada al OCR cuesta lo mismo, se lea bien o no. */
|
||||
private function registrarUso(?int $usuarioId, string $canal, int $tiempoMs, string $resultado, array $detalle): void
|
||||
{
|
||||
AiUsageLog::registrar([
|
||||
'servicio' => 'ocr',
|
||||
'canal' => $canal,
|
||||
'usuario_id' => $usuarioId,
|
||||
'tiempo_ms' => $tiempoMs,
|
||||
'costo' => self::COSTO_POR_FOTO,
|
||||
'resultado' => $resultado,
|
||||
'detalle' => $detalle,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user