client = new Client(['timeout' => 30]); } public function sendMessage(string $message, UserTypeEnum $userType, array $history = []): string { $apiKey = config('services.gemini.api_key'); $model = config('services.gemini.model'); $contents = $this->buildContents($history, $message); $payload = [ 'systemInstruction' => [ 'parts' => [['text' => $this->loadContext($userType)]], ], 'contents' => $contents, 'generationConfig' => [ 'temperature' => 0.4, 'maxOutputTokens' => 512, ], ]; try { $response = $this->client->post($this->buildApiUrl($model), [ 'json' => $payload, 'query' => ['key' => $apiKey], ]); $data = json_decode($response->getBody()->getContents(), true); return $data['candidates'][0]['content']['parts'][0]['text'] ?? __('chatbot.error_response'); } catch (GuzzleException $e) { $this->logGeminiError($e, $model); return __('chatbot.error_response'); } } private function buildApiUrl(string $model): string { return self::API_BASE_URL . '/' . rawurlencode($model) . ':generateContent'; } private function logGeminiError(GuzzleException $e, string $model): void { $context = [ 'model' => $model, 'message' => $this->sanitizeApiKey($e->getMessage()), ]; if ($e instanceof RequestException && $e->hasResponse()) { $response = $e->getResponse(); $context['status'] = $response->getStatusCode(); $context['body'] = $this->sanitizeApiKey((string) $response->getBody()); } Log::error('Gemini API error', $context); } private function sanitizeApiKey(string $message): string { return preg_replace('/([?&]key=)[^&\s"]+/i', '$1[REDACTED]', $message); } private function buildContents(array $history, string $newMessage): array { $contents = []; foreach ($history as $entry) { $role = $entry['role'] === 'model' ? 'model' : 'user'; $contents[] = [ 'role' => $role, 'parts' => [['text' => $entry['text']]], ]; } $contents[] = [ 'role' => 'user', 'parts' => [['text' => $newMessage]], ]; return $contents; } private function loadContext(UserTypeEnum $userType): string { $restrictionsFile = resource_path('chatbot/restricoes.md'); $restrictions = file_exists($restrictionsFile) ? file_get_contents($restrictionsFile) : ''; $contextFile = match ($userType) { UserTypeEnum::CLIENT => resource_path('chatbot/context_cliente.txt'), UserTypeEnum::PROVIDER => resource_path('chatbot/context_prestador.txt'), default => resource_path('chatbot/context_cliente.txt'), }; $context = file_exists($contextFile) ? file_get_contents($contextFile) : ''; return $restrictions . "\n\n" . $context; } }