GeminiService.php 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  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' => 512,
  29. ],
  30. ];
  31. try {
  32. $response = $this->client->post($this->buildApiUrl($model), [
  33. 'json' => $payload,
  34. 'query' => ['key' => $apiKey],
  35. ]);
  36. $data = json_decode($response->getBody()->getContents(), true);
  37. return $data['candidates'][0]['content']['parts'][0]['text']
  38. ?? __('chatbot.error_response');
  39. } catch (GuzzleException $e) {
  40. $this->logGeminiError($e, $model);
  41. return __('chatbot.error_response');
  42. }
  43. }
  44. //
  45. private function buildApiUrl(string $model): string
  46. {
  47. return self::API_BASE_URL . '/' . rawurlencode($model) . ':generateContent';
  48. }
  49. private function buildContents(array $history, string $newMessage): array
  50. {
  51. $contents = [];
  52. foreach ($history as $entry) {
  53. $role = $entry['role'] === 'model' ? 'model' : 'user';
  54. $contents[] = [
  55. 'role' => $role,
  56. 'parts' => [['text' => $entry['text']]],
  57. ];
  58. }
  59. $contents[] = [
  60. 'role' => 'user',
  61. 'parts' => [['text' => $newMessage]],
  62. ];
  63. return $contents;
  64. }
  65. private function loadContext(UserTypeEnum $userType): string
  66. {
  67. $restrictionsFile = resource_path('chatbot/restricoes.md');
  68. $restrictions = file_exists($restrictionsFile) ? file_get_contents($restrictionsFile) : '';
  69. $contextFile = match ($userType) {
  70. UserTypeEnum::CLIENT => resource_path('chatbot/context_cliente.txt'),
  71. UserTypeEnum::PROVIDER => resource_path('chatbot/context_prestador.txt'),
  72. default => resource_path('chatbot/context_cliente.txt'),
  73. };
  74. $context = file_exists($contextFile) ? file_get_contents($contextFile) : '';
  75. return $restrictions . "\n\n" . $context;
  76. }
  77. private function logGeminiError(GuzzleException $e, string $model): void
  78. {
  79. $context = [
  80. 'model' => $model,
  81. 'message' => $this->sanitizeApiKey($e->getMessage()),
  82. ];
  83. if ($e instanceof RequestException && $e->hasResponse()) {
  84. $response = $e->getResponse();
  85. $context['status'] = $response->getStatusCode();
  86. $context['body'] = $this->sanitizeApiKey((string) $response->getBody());
  87. }
  88. Log::error('Gemini API error', $context);
  89. }
  90. private function sanitizeApiKey(string $message): string
  91. {
  92. return preg_replace('/([?&]key=)[^&\s"]+/i', '$1[REDACTED]', $message);
  93. }
  94. }