GeminiService.php 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. <?php
  2. namespace App\Services;
  3. use App\Enums\UserTypeEnum;
  4. use GuzzleHttp\Client;
  5. use GuzzleHttp\Exception\GuzzleException;
  6. use GuzzleHttp\Exception\RequestException;
  7. use Illuminate\Support\Facades\Log;
  8. class GeminiService
  9. {
  10. private const API_BASE_URL = 'https://generativelanguage.googleapis.com/v1beta/models';
  11. private Client $client;
  12. public function __construct()
  13. {
  14. $this->client = new Client(['timeout' => 30]);
  15. }
  16. public function sendMessage(string $message, UserTypeEnum $userType, array $history = []): string
  17. {
  18. $apiKey = config('services.gemini.api_key');
  19. $model = config('services.gemini.model');
  20. $contents = $this->buildContents($history, $message);
  21. $payload = [
  22. 'systemInstruction' => [
  23. 'parts' => [['text' => $this->loadContext($userType)]],
  24. ],
  25. 'contents' => $contents,
  26. 'generationConfig' => [
  27. 'temperature' => 0.4,
  28. 'maxOutputTokens' => 2048,
  29. 'thinkingConfig' => ['thinkingBudget' => 0],
  30. ],
  31. ];
  32. try {
  33. $response = $this->client->post($this->buildApiUrl($model), [
  34. 'json' => $payload,
  35. 'query' => ['key' => $apiKey],
  36. ]);
  37. $data = json_decode($response->getBody()->getContents(), true);
  38. $this->logTruncatedResponse($data, $model);
  39. return data_get($data, 'candidates.0.content.parts.0.text', __('chatbot.error_response'));
  40. } catch (GuzzleException $e) {
  41. $this->logGeminiError($e, $model);
  42. return __('chatbot.error_response');
  43. }
  44. }
  45. //
  46. private function buildApiUrl(string $model): string
  47. {
  48. return self::API_BASE_URL . '/' . rawurlencode($model) . ':generateContent';
  49. }
  50. private function buildContents(array $history, string $newMessage): array
  51. {
  52. $contents = [];
  53. foreach ($history as $entry) {
  54. $role = data_get($entry, 'role') === 'model' ? 'model' : 'user';
  55. $contents[] = [
  56. 'role' => $role,
  57. 'parts' => [['text' => data_get($entry, 'text')]],
  58. ];
  59. }
  60. $contents[] = [
  61. 'role' => 'user',
  62. 'parts' => [['text' => $newMessage]],
  63. ];
  64. return $contents;
  65. }
  66. private function loadContext(UserTypeEnum $userType): string
  67. {
  68. $restrictionsFile = resource_path('chatbot/restricoes.md');
  69. $restrictions = file_exists($restrictionsFile) ? file_get_contents($restrictionsFile) : '';
  70. $contextFile = match ($userType) {
  71. UserTypeEnum::CLIENT => resource_path('chatbot/context_cliente.txt'),
  72. UserTypeEnum::PROVIDER => resource_path('chatbot/context_prestador.txt'),
  73. default => resource_path('chatbot/context_cliente.txt'),
  74. };
  75. $context = file_exists($contextFile) ? file_get_contents($contextFile) : '';
  76. return $restrictions . "\n\n" . $context;
  77. }
  78. private function logTruncatedResponse(?array $data, string $model): void
  79. {
  80. if (data_get($data, 'candidates.0.finishReason') !== 'MAX_TOKENS') {
  81. return;
  82. }
  83. Log::warning('Resposta do Gemini truncada por limite de tokens', [
  84. 'model' => $model,
  85. 'usageMetadata' => data_get($data, 'usageMetadata'),
  86. ]);
  87. }
  88. private function logGeminiError(GuzzleException $e, string $model): void
  89. {
  90. $context = [
  91. 'model' => $model,
  92. 'message' => $this->sanitizeApiKey($e->getMessage()),
  93. ];
  94. if ($e instanceof RequestException && $e->hasResponse()) {
  95. $response = $e->getResponse();
  96. $context['status'] = $response->getStatusCode();
  97. $context['body'] = $this->sanitizeApiKey((string) $response->getBody());
  98. }
  99. Log::error('Erro na API do Gemini', $context);
  100. }
  101. private function sanitizeApiKey(string $message): string
  102. {
  103. return preg_replace('/([?&]key=)[^&\s"]+/i', '$1[REDACTED]', $message);
  104. }
  105. }