openai-php / laravel

⚡️ OpenAI PHP for Laravel is a supercharged PHP API client that allows you to interact with OpenAI API
MIT License
2.75k stars 185 forks source link

Switch to Laravel HTTP client for compatibility with Pulse, Telescope, Sentry etc #75

Open binaryfire opened 11 months ago

binaryfire commented 11 months ago

The package currently uses Guzzle directly, which means requests won't appear in core tools like Pulse and Telescope. It would be great if Laravel's HTTP client could be used instead to ensure compatibility with the wider Laravel ecosystem.

It also doesn't work with things like Sentry's Laravel HTTP client integration: https://github.com/getsentry/sentry-laravel/pull/797

As a side note, it would also provide a solution to this issue since Laravel's HTTP client has built in retry functionality.

gehrisandro commented 11 months ago

Hi @binaryfire

I am not sure if this i an easy change because of the underlying client being http client agnostic. Is the Laravel HTTP Client PSR-17 compatible? If so, could you make a PR?

binaryfire commented 11 months ago

Hi @gehrisandro. Unfortunately no, I don't think Laravel's HTTP client is PSR-17 compatible. It's a bespoke wrapper around Guzzle.

andrzejkupczyk commented 10 months ago

You might find the Guzzle Watcher Telescope plugin particularly useful, as Laravel's 'HTTP Client' leverages Guzzle internally. This plugin allows you to view Guzzle's request and response logs directly in the Telescope UI. It’s important to remember, however, that Laravel's 'HTTP Client' primarily functions as a PendingRequest builder, rather than being specifically designed for PSR-17 compatibility.

binaryfire commented 10 months ago

@andrzejkupczyk Thanks for the suggestion but it affects more than just Telescope. Several other core and third party packages integrate with the HTTP client eg. Pulse, Sentry etc.

gehrisandro commented 9 months ago

Hi @binaryfire

If tinkered around a bit and came up with a solution, creating a PSR-18 compatible wrapper around the HTTP facade.

At least for now, the wrapper is in a separate project: https://github.com/gehrisandro/laravel-http-psr18

When using the package, you have to register the OpenAI client in your ServiceProvider:

$this->app->extend(ClientContract::class, static function (): Client {
    return OpenAI::factory()
        ->withApiKey(config('openai.api_key'))
        ->withOrganization(config('openai.organization'))
        ->withHttpClient($httpClient = HttpPsr18::make(Http::withHeader('OpenAI-Beta', 'assistants=v1')))
        ->withStreamHandler(fn(RequestInterface $request) => $httpClient->sendRequest($request))
        ->make();
});

Hope this helps and would like to hear your feedback.

binaryfire commented 9 months ago

Thanks @gehrisandro! I'll let you know how things go as soon as I have some time to test.

michgeek commented 7 months ago

Thanks for the solution. Unfortunately for me, this method works only for the first request. When attempting to rerun a request, it will merge the headers and result in duplicate headers values, see example below :

{
    "content-length": "259",
    "user-agent": "GuzzleHttp/7",
    "openai-beta": "assistants=v1, assistants=v1, assistants=v1",
    "accept": "application/json",
    "content-type": "application/json, application/json, application/json",
    "host": "api.openai.com, api.openai.com",
    "authorization": "********",
    "openai-organization": "*********"
}

This will result in an 400 Bad request from Open AI API.

To fix that, it's better to use a new instance of Http client for each request. Here is a solution that works well. In addition, it doesn't need you to install the HttpPsr18 wrapper.

use Illuminate\Support\Facades\Http;
use OpenAI;
use OpenAI\Client;
use OpenAI\Contracts\ClientContract;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

// AppServiceProvider::register
$this->app->extend(ClientContract::class, static function (): Client {
            $client = new class implements ClientInterface {
                public function sendRequest(RequestInterface $request): ResponseInterface
                {
                    return Http::withHeaders($request->getHeaders())
                        ->asJson()
                        ->acceptJson()
                        ->send($request->getMethod(), (string)$request->getUri(), [
                            'body' => $request->getBody()->getContents(),
                        ])
                        ->toPsrResponse();
                }
            };

            return OpenAI::factory()
                ->withApiKey(config('openai.api_key'))
                ->withOrganization(config('openai.organization'))
                ->withHttpHeader('OpenAI-Beta', 'assistants=v1')
                ->withHttpClient($client)
                ->make();
        });