diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..5d59bb0 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,17 @@ + + +### What: + +- [ ] Bug Fix +- [ ] New Feature +- [ ] Docs + +### Description: + + + +### Related: + + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..ca79ca5 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly diff --git a/.github/workflows/formats.yml b/.github/workflows/formats.yml index 5974f60..50b36af 100644 --- a/.github/workflows/formats.yml +++ b/.github/workflows/formats.yml @@ -11,17 +11,17 @@ jobs: matrix: os: [ubuntu-latest] php: [8.2] - dependency-version: [prefer-lowest, prefer-stable] + dependency-version: [prefer-stable] name: Formats P${{ matrix.php }} - ${{ matrix.os }} - ${{ matrix.dependency-version }} steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v5 - name: Cache dependencies - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: ~/.composer/cache/files key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} @@ -31,7 +31,6 @@ jobs: with: php-version: ${{ matrix.php }} extensions: dom, mbstring, zip - tools: prestissimo coverage: pcov - name: Install Composer dependencies diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 15f7f29..4937678 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,21 +9,22 @@ jobs: fail-fast: true matrix: os: [ubuntu-latest] - php: [8.1, 8.2, 8.3] - laravel: [11.*, 10.*, 9.*] + php: [8.2, 8.3, 8.4] + laravel: [11, 12] + pest: [3, 4] dependency-version: [prefer-lowest, prefer-stable] exclude: - - php: 8.1 - laravel: 11.* + - php: 8.2 + pest: 4 - name: Tests P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.os }} - ${{ matrix.dependency-version }} + name: Tests P${{ matrix.php }} - P${{ matrix.pest }} - L${{ matrix.laravel }} - ${{ matrix.os }} - ${{ matrix.dependency-version }} steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v5 - name: Cache dependencies - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: ~/.composer/cache/files key: dependencies-php-${{ matrix.php }}-L${{ matrix.laravel }}-${{ matrix.dependency-version }}-composer-${{ hashFiles('composer.json') }} @@ -35,11 +36,17 @@ jobs: extensions: dom, mbstring, zip coverage: none + - name: Require Pest Version + run: > + composer require + "pestphp/pest:^${{ matrix.pest }}" + --no-interaction --no-update --with-all-dependencies + - name: Require Laravel Version run: > composer require - "laravel/framework:${{ matrix.laravel }}" - --no-interaction --no-update + "laravel/framework:^${{ matrix.laravel }}" + --no-interaction --no-update --with-all-dependencies - name: Install Composer dependencies run: composer update --${{ matrix.dependency-version }} --no-interaction --prefer-dist diff --git a/.gitignore b/.gitignore index 8e954e3..6cbd5a4 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ /vendor/ *.swp *.swo +.idea/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 64cd014..687f8b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,48 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## v0.16.0 (2025-08-26) +### Changed + - Changed underlying `openai/client` package version from 0.15.0 to 0.16.0 + - HTTP 429 now throw a `RateLimitException` instead of a generic `ErrorException` + +## v0.15.0 (2025-08-04) +### Added +- Add facade for `containers`. ([#161](https://github.com/openai-php/laravel/pull/161)) + +### Changed +- Changed underlying `openai/client` package version from 0.14.0 to 0.15.0 + +## v0.14.0 (2025-06-24) +### Added +- Add facade for `realtime`. ([#156](https://github.com/openai-php/laravel/pull/156)) + +### Changed +- Changed underlying `openai/client` package version from 0.13.0 to 0.14.0 + +## v0.13.0 (2025-05-14) +### Added +- Add facade for `responses` ([#147](https://github.com/openai-php/laravel/pull/147)) + +### Changed +- Changed underlying `openai/client` package version from 0.12.0 to 0.13.0 +- Restored Laravel 11 support. + +## v0.12.0 (2025-05-04) +### Added +- Add facade for `fineTuning` ([#125](https://github.com/openai-php/laravel/pull/125)) +- Make base url configurable ([#144](https://github.com/openai-php/laravel/pull/144)) + +### Changed +- Changed underlying `openai/client` package version from 0.11.0 to 0.12.0 + +### Removed +- Removed support for PHP 8.1 + +## v0.11.0 (2025-02-24) +### Added +- Add support for Laravel 12 ([#137](https://github.com/openai-php/laravel/pull/137)) + ## v0.10.1 (2024-06-06) ### Changed - Changed underlying `openai/client` package version from 0.9.1 to v0.10.1 diff --git a/README.md b/README.md index 601927f..d452d93 100644 --- a/README.md +++ b/README.md @@ -8,20 +8,17 @@

------ -**OpenAI PHP** for Laravel is a community-maintained PHP API client that allows you to interact with the [Open AI API](https://beta.openai.com/docs/api-reference/introduction). If you or your business relies on this package, it's important to support the developers who have contributed their time and effort to create and maintain this valuable tool: +**OpenAI PHP** for Laravel is a community-maintained PHP API client that allows you to interact with the [Open AI API](https://platform.openai.com/docs/api-reference/introduction). If you or your business relies on this package, it's important to support the developers who have contributed their time and effort to create and maintain this valuable tool: - Nuno Maduro: **[github.com/sponsors/nunomaduro](https://github.com/sponsors/nunomaduro)** - Sandro Gehri: **[github.com/sponsors/gehrisandro](https://github.com/sponsors/gehrisandro)** > **Note:** This repository contains the integration code of the **OpenAI PHP** for Laravel. If you want to use the **OpenAI PHP** client in a framework-agnostic way, take a look at the [openai-php/client](https://github.com/openai-php/client) repository. -> **Looking for Assistants v2 support?** -> -> Check out the [0.10.x](https://github.com/openai-php/laravel/releases/tag/v0.10.0-beta.1) release (beta) ## Get Started -> **Requires [PHP 8.1+](https://php.net/releases/)** +> **Requires [PHP 8.2+](https://www.php.net/releases/)** First, install OpenAI via the [Composer](https://getcomposer.org/) package manager: @@ -49,14 +46,12 @@ Finally, you may use the `OpenAI` facade to access the OpenAI API: ```php use OpenAI\Laravel\Facades\OpenAI; -$result = OpenAI::chat()->create([ - 'model' => 'gpt-3.5-turbo', - 'messages' => [ - ['role' => 'user', 'content' => 'Hello!'], - ], +$response = OpenAI::responses()->create([ + 'model' => 'gpt-5', + 'input' => 'Hello!', ]); -echo $result->choices[0]->message->content; // Hello! How can I assist you today? +echo $response->outputText; // Hello! How can I assist you today? ``` ## Configuration @@ -74,6 +69,23 @@ OPENAI_API_KEY= OPENAI_ORGANIZATION= ``` +### OpenAI Project + +For implementations that require a project ID, you can specify +the OpenAI project ID in your environment variables. + +```env +OPENAI_PROJECT=proj_... +``` + +### OpenAI API Base URL + +The base URL for the OpenAI API. By default, this is set to `api.openai.com/v1`. + +```env +OPENAI_BASE_URL= +``` + ### Request Timeout The timeout may be used to specify the maximum number of seconds to wait @@ -97,7 +109,7 @@ All responses are having a `fake()` method that allows you to easily create a re ```php use OpenAI\Laravel\Facades\OpenAI; -use OpenAI\Responses\Completions\CreateResponse; +use OpenAI\Responses\Responses\CreateResponse; OpenAI::fake([ CreateResponse::fake([ @@ -109,21 +121,21 @@ OpenAI::fake([ ]), ]); -$completion = OpenAI::completions()->create([ - 'model' => 'gpt-3.5-turbo-instruct', - 'prompt' => 'PHP is ', +$response = OpenAI::responses()->create([ + 'model' => 'gpt-5', + 'input' => 'PHP is ', ]); -expect($completion['choices'][0]['text'])->toBe('awesome!'); +expect($response->outputText)->toBe('awesome!'); ``` After the requests have been sent there are various methods to ensure that the expected requests were sent: ```php // assert completion create request was sent -OpenAI::assertSent(Completions::class, function (string $method, array $parameters): bool { +OpenAI::assertSent(Responses::class, function (string $method, array $parameters): bool { return $method === 'create' && - $parameters['model'] === 'gpt-3.5-turbo-instruct' && + $parameters['model'] === 'gpt-5' && $parameters['prompt'] === 'PHP is '; }); ``` diff --git a/composer.json b/composer.json index ea90647..40ba5ce 100644 --- a/composer.json +++ b/composer.json @@ -10,17 +10,17 @@ } ], "require": { - "php": "^8.1.0", - "guzzlehttp/guzzle": "^7.8.1", - "laravel/framework": "^9.46.0|^10.34.2|^11.0", - "openai-php/client": "^v0.10.1" + "php": "^8.2.0", + "guzzlehttp/guzzle": "^7.9.3", + "laravel/framework": "^11.29|^12.12", + "openai-php/client": "^0.16.1" }, "require-dev": { - "laravel/pint": "^1.13.6", - "pestphp/pest": "^2.27.0", - "pestphp/pest-plugin-arch": "^2.4.1", - "phpstan/phpstan": "^1.10.47", - "symfony/var-dumper": "^6.4.0|^7.0.1" + "laravel/pint": "^1.22.0", + "pestphp/pest": "^3.8.2|^4.0.0", + "pestphp/pest-plugin-arch": "^3.1.1|^4.0.0", + "phpstan/phpstan": "^2.1", + "symfony/var-dumper": "^7.2.6" }, "autoload": { "psr-4": { @@ -51,7 +51,6 @@ }, "scripts": { "lint": "pint -v", - "refactor": "rector --debug", "test:lint": "pint --test -v", "test:types": "phpstan analyse --ansi", "test:unit": "pest --colors=always", diff --git a/config/openai.php b/config/openai.php index 67806b4..ebf66eb 100644 --- a/config/openai.php +++ b/config/openai.php @@ -15,6 +15,27 @@ 'api_key' => env('OPENAI_API_KEY'), 'organization' => env('OPENAI_ORGANIZATION'), + /* + |-------------------------------------------------------------------------- + | OpenAI API Project + |-------------------------------------------------------------------------- + | + | Here you may specify your OpenAI API project. This is used optionally in + | situations where you are using a legacy user API key and need association + | with a project. This is not required for the newer API keys. + */ + 'project' => env('OPENAI_PROJECT'), + + /* + |-------------------------------------------------------------------------- + | OpenAI Base URL + |-------------------------------------------------------------------------- + | + | Here you may specify your OpenAI API base URL used to make requests. This + | is needed if using a custom API endpoint. Defaults to: api.openai.com/v1 + */ + 'base_uri' => env('OPENAI_BASE_URL'), + /* |-------------------------------------------------------------------------- | Request Timeout diff --git a/src/Commands/InstallCommand.php b/src/Commands/InstallCommand.php index fb26c62..7bd4a06 100644 --- a/src/Commands/InstallCommand.php +++ b/src/Commands/InstallCommand.php @@ -78,6 +78,15 @@ private function copyConfig(): void private function addEnvKeys(string $envFile): void { + if (! is_writable(base_path($envFile))) { + View::render('components.two-column-detail', [ + 'left' => $envFile, + 'right' => 'File is not writable.', + ]); + + return; + } + $fileContent = file_get_contents(base_path($envFile)); if ($fileContent === false) { diff --git a/src/Facades/OpenAI.php b/src/Facades/OpenAI.php index 19be0c5..cdc2921 100644 --- a/src/Facades/OpenAI.php +++ b/src/Facades/OpenAI.php @@ -15,13 +15,17 @@ * @method static \OpenAI\Resources\Batches batches() * @method static \OpenAI\Resources\Chat chat() * @method static \OpenAI\Resources\Completions completions() + * @method static \OpenAI\Resources\Containers containers() * @method static \OpenAI\Resources\Embeddings embeddings() * @method static \OpenAI\Resources\Edits edits() * @method static \OpenAI\Resources\Files files() * @method static \OpenAI\Resources\FineTunes fineTunes() + * @method static \OpenAI\Resources\FineTuning fineTuning() * @method static \OpenAI\Resources\Images images() * @method static \OpenAI\Resources\Models models() * @method static \OpenAI\Resources\Moderations moderations() + * @method static \OpenAI\Resources\Realtime realtime() + * @method static \OpenAI\Resources\Responses responses() * @method static \OpenAI\Resources\Threads threads() * @method static \OpenAI\Resources\VectorStores vectorStores() */ diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index a6e6808..410fd98 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -25,17 +25,28 @@ public function register(): void $this->app->singleton(ClientContract::class, static function (): Client { $apiKey = config('openai.api_key'); $organization = config('openai.organization'); + $project = config('openai.project'); + $baseUri = config('openai.base_uri'); if (! is_string($apiKey) || ($organization !== null && ! is_string($organization))) { throw ApiKeyIsMissing::create(); } - return OpenAI::factory() + $client = OpenAI::factory() ->withApiKey($apiKey) ->withOrganization($organization) ->withHttpHeader('OpenAI-Beta', 'assistants=v2') - ->withHttpClient(new \GuzzleHttp\Client(['timeout' => config('openai.request_timeout', 30)])) - ->make(); + ->withHttpClient(new \GuzzleHttp\Client(['timeout' => config('openai.request_timeout', 30)])); + + if (is_string($project)) { + $client->withProject($project); + } + + if (is_string($baseUri)) { + $client->withBaseUri($baseUri); + } + + return $client->make(); }); $this->app->alias(ClientContract::class, 'openai'); diff --git a/src/Testing/OpenAIFake.php b/src/Testing/OpenAIFake.php index a4e2eb2..9ac5b3b 100644 --- a/src/Testing/OpenAIFake.php +++ b/src/Testing/OpenAIFake.php @@ -4,6 +4,4 @@ use OpenAI\Testing\ClientFake; -class OpenAIFake extends ClientFake -{ -} +class OpenAIFake extends ClientFake {} diff --git a/tests/ServiceProvider.php b/tests/ServiceProvider.php index cae4dd5..122058a 100644 --- a/tests/ServiceProvider.php +++ b/tests/ServiceProvider.php @@ -42,6 +42,8 @@ $app->bind('config', fn () => new Repository([])); (new ServiceProvider($app))->register(); + + $app->get(Client::class); })->throws( ApiKeyIsMissing::class, 'The OpenAI API Key is missing. Please publish the [openai.php] configuration file and set the [api_key].',