diff --git a/README.md b/README.md index f4a0b54..baa3ed5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![Grok PHP Client](assets/images/grok-client.png) -**A lightweight, framework-agnostic PHP client for interacting with Grok AI APIs.** +**A lightweight, framework-agnostic PHP client for interacting with Grok AI APIs.** Supports **PHP 8.2+**, built with **OOP best practices**, and **fully type-safe**. [![Latest Version](https://img.shields.io/packagist/v/grok-php/client)](https://packagist.org/packages/grok-php/client) @@ -22,6 +22,7 @@ Supports **PHP 8.2+**, built with **OOP best practices**, and **fully type-safe* - [Advanced Configuration](#advanced-configuration) - [Available Grok AI Models](#available-grok-ai-models) - [Streaming Responses](#streaming-responses) +- [Response format](#response-format) - [Error Handling](#error-handling) - [Testing](#testing) - [Security](#security) @@ -34,10 +35,10 @@ Supports **PHP 8.2+**, built with **OOP best practices**, and **fully type-safe* ![Grok PHP Client Demo](assets/images/demo.gif) -- **Easy Integration** – Seamlessly connects with Grok AI APIs. -- **Modern PHP Features** – Utilizes PHP 8.2+ features like enums and traits. -- **Framework Agnostic** – Works with any PHP project, CLI scripts, or web applications. -- **Streaming Support** – Built-in support for real-time responses. +- **Easy Integration** – Seamlessly connects with Grok AI APIs. +- **Modern PHP Features** – Utilizes PHP 8.2+ features like enums and traits. +- **Framework Agnostic** – Works with any PHP project, CLI scripts, or web applications. +- **Streaming Support** – Built-in support for real-time responses. - **Lightweight & Efficient** – Optimized with PSR-4 autoloading and minimal dependencies. --- @@ -139,7 +140,7 @@ $messages = [ // Custom API settings $options = new ChatOptions( model: Model::GROK_2_LATEST, - temperature: 1.2, + temperature: 1.2, stream: false ); @@ -151,8 +152,8 @@ echo "AI Says: " . $response['choices'][0]['message']['content']; ## **Available Grok AI Models** -Grok AI offers multiple models optimized for different use cases. -These models are available in the `Model` enum inside our package: +Grok AI offers multiple models optimized for different use cases. +These models are available in the `Model` enum inside our package: 📄 `src/Enums/Model.php` | Model Enum | API Model Name | Description | @@ -172,7 +173,7 @@ These models are available in the `Model` enum inside our package: ## **Streaming Responses** -The Grok API supports streaming responses for real-time interaction. +The Grok API supports streaming responses for real-time interaction. Enable it by setting `stream: true`: ```php @@ -183,9 +184,20 @@ Streaming can be useful for chatbots, real-time applications, and CLI assistants --- +## **Response format** + +The Grok API supports setting a response format, also refered to structured outputs, for the `grok-2-1212` model. + +```php +$options = new ChatOptions(model: Model::GROK_2_1212, temperature: 0.7, stream: false, responseFormat: ['type' => 'json_object']); +$response = $client->chat($messages, $options); +``` + +--- + ## **Error Handling** -This package includes built-in error handling with a dedicated exception class. +This package includes built-in error handling with a dedicated exception class. Common errors and their messages: | Error Type | HTTP Code | Message | @@ -221,7 +233,7 @@ vendor/bin/phpunit ## **Security** -If you discover a security vulnerability, please report it via email: +If you discover a security vulnerability, please report it via email: 📩 [thefeqy@gmail.com](mailto:thefeqy@gmail.com) --- diff --git a/src/Clients/GrokClient.php b/src/Clients/GrokClient.php index b3620cb..17dac05 100644 --- a/src/Clients/GrokClient.php +++ b/src/Clients/GrokClient.php @@ -69,6 +69,7 @@ public function chat(array $messages, ChatOptions $options): array 'messages' => $messages, 'temperature' => $options->temperature, 'stream' => $options->stream, + 'response_format' => $options->responseFormat, ]); } diff --git a/src/Config/ChatOptions.php b/src/Config/ChatOptions.php index 4c031f8..37cd54d 100644 --- a/src/Config/ChatOptions.php +++ b/src/Config/ChatOptions.php @@ -16,10 +16,13 @@ class ChatOptions public bool $stream; + public ?array $responseFormat; + public function __construct( ?Model $model = null, ?float $temperature = null, - ?bool $stream = null + ?bool $stream = null, + ?array $responseFormat = null ) { $this->model = $model ?: Model::tryFrom(DefaultConfig::MODEL->value) ?: Model::GROK_2; @@ -27,5 +30,7 @@ public function __construct( $this->temperature = $temperature ?? (float) DefaultConfig::TEMPERATURE->value; $this->stream = $stream ?? filter_var(DefaultConfig::STREAMING->value, FILTER_VALIDATE_BOOLEAN); + + $this->responseFormat = $responseFormat ? (array) $responseFormat : null; } } diff --git a/src/Testing/ClientFake.php b/src/Testing/ClientFake.php index 99069b9..7dd4dec 100644 --- a/src/Testing/ClientFake.php +++ b/src/Testing/ClientFake.php @@ -12,7 +12,7 @@ class ClientFake * * @throws JsonException */ - public static function fakeSuccessResponse(): Response + public static function fakeSuccessResponse(?array $data = null): Response { return new Response(200, ['Content-Type' => 'application/json'], json_encode([ 'id' => '7c51076a-e4cc-4855-8dbe-66c26818e35f', @@ -24,7 +24,7 @@ public static function fakeSuccessResponse(): Response 'index' => 0, 'message' => [ 'role' => 'assistant', - 'content' => json_encode([ + 'content' => json_encode($data ?? [ 'framework_name' => 'Laravel', 'release_date' => '2011', 'programming_language' => 'PHP', diff --git a/tests/Feature/GrokClientTest.php b/tests/Feature/GrokClientTest.php index 3684347..cc975cc 100644 --- a/tests/Feature/GrokClientTest.php +++ b/tests/Feature/GrokClientTest.php @@ -58,4 +58,30 @@ public function test_chat_request_returns_response(): void $this->assertSame('2011', $decodedContent['release_date']); $this->assertSame('PHP', $decodedContent['programming_language']); } + + public function test_chat_request_returns_response_in_specific_format(): void + { + $this->client->setHttpClient(new Client(['handler' => HandlerStack::create(new MockHandler([ + ClientFake::fakeSuccessResponse([ + 'name' => 'Taylor', + 'city' => 'Little Rock' + ]), + ]))])); + + $messages = [ + ['role' => 'system', 'content' => 'You are a helpful assistant.'], + ['role' => 'user', 'content' => 'Return a json object with a random name (string) and random city (string).'], + ]; + + $options = new ChatOptions(model: Model::GROK_2_1212, temperature: 0.7, stream: false, responseFormat: ['type' => 'json_object']); + $response = $this->client->chat($messages, $options); + $content = $response['choices'][0]['message']['content']; + $decodedContent = json_decode($response['choices'][0]['message']['content'], true, 512, JSON_THROW_ON_ERROR); + + $this->assertJson($content); + $this->assertArrayHasKey('name', $decodedContent); + $this->assertArrayHasKey('city', $decodedContent); + $this->assertSame('Taylor', $decodedContent['name']); + $this->assertSame('Little Rock', $decodedContent['city']); + } }