openAi = \OpenAI::client($apiKey ?? config('services.openai.api_key')); } public function setAiModel(string $model) { $this->model = $model; return $this; } public function setSystemMessage(string $systemMessage): self { $this->systemMessage = $systemMessage; return $this; } public function useStreaming(): self { $this->useStreaming = true; return $this; } public function expectsJson(): self { $this->expectsJson = true; return $this; } public function doesNotExpectJson(): self { $this->expectsJson = false; return $this; } public function setJsonSchema(array $schema): self { $this->completionInput['response_format'] = [ 'type' => 'json_schema', 'json_schema' => [ 'name' => 'response_schema', 'strict' => true, 'schema' => $schema ] ]; return $this; } public function completeChat(array $messages, int $maxTokens = 4096, float $temperature = 0.81, ?bool $exceptJson = null): self { if (! is_null($exceptJson)) { $this->expectsJson = $exceptJson; } $this->computeChatCompletion($messages, $maxTokens, $temperature) ->queryCompletion(); return $this; } public function getBool(): bool { switch (strtolower($this->result)) { case 'true': return true; case 'false': return false; default: throw new \InvalidArgumentException("Expected a boolean value, got {$this->result}"); } } public function getArray(): array { for ($i = 0; $i < $this->retries; $i++) { $payload = Str::of($this->result)->trim(); if ($payload->contains('```json')) { $payload = $payload->after('```json')->before('```'); } elseif ($payload->contains('```')) { $payload = $payload->after('```')->before('```'); } $payload = $payload->toString(); $exception = null; try { $newPayload = (new JsonFixer())->fix($payload); return json_decode($newPayload, true); } catch (\Aws\Exception\InvalidJsonException $e) { $exception = $e; Log::warning('Invalid JSON, retrying:'); Log::warning($payload); $this->queryCompletion(); } } throw $exception; } public function getHtml(): string { $payload = Str::of($this->result)->trim(); if ($payload->contains('```html')) { $payload = $payload->after('```html')->before('```'); } elseif ($payload->contains('```')) { $payload = $payload->after('```')->before('```'); } return $payload->toString(); } public function getString(): string { return trim($this->result); } public function getInputTokens(): int { return $this->inputTokens; } public function getOutputTokens(): int { return $this->outputTokens; } protected function computeChatCompletion(array $messages, int $maxTokens = 4096, float $temperature = 0.81): self { if (isset($this->systemMessage) && $messages[0]['role'] !== 'system') { $messages = array_merge([[ 'role' => 'system', 'content' => $this->systemMessage, ]], $messages); } $completionInput = [ 'model' => $this->model, 'messages' => $messages, 'max_tokens' => $maxTokens, 'temperature' => $temperature, ]; if ($this->expectsJson && !isset($this->completionInput['response_format'])) { $completionInput['response_format'] = [ 'type' => 'json_object', ]; } elseif (isset($this->completionInput['response_format'])) { $completionInput['response_format'] = $this->completionInput['response_format']; } $this->completionInput = $completionInput; return $this; } protected function queryCompletion(): self { if ($this->useStreaming) { return $this->queryStreamedCompletion(); } $attempt = 1; $lastError = null; while ($attempt <= $this->retries) { try { Log::debug('Open AI query: ' . json_encode($this->completionInput)); $response = $this->openAi->chat()->create($this->completionInput); $this->inputTokens = $response->usage->promptTokens; $this->outputTokens = $response->usage->completionTokens; $this->result = $response->choices[0]->message->content; return $this; } catch (ErrorException $errorException) { $lastError = $errorException; Log::warning("Open AI error, retrying: {$errorException->getMessage()}"); $attempt++; } } throw $lastError ?? new \Exception('Failed to complete OpenAI request after multiple attempts'); } protected function queryStreamedCompletion(): self { Log::debug('Open AI query: ' . json_encode($this->completionInput)); $this->result = ''; $response = $this->openAi->chat()->createStreamed($this->completionInput); foreach ($response as $chunk) { $choice = $chunk->choices[0]; if (is_null($choice->delta->role)) { $this->result .= $choice->delta->content; } } return $this; } }