diff --git a/config/openai.php b/config/openai.php index 67806b4..7c0a53b 100644 --- a/config/openai.php +++ b/config/openai.php @@ -25,4 +25,26 @@ */ 'request_timeout' => env('OPENAI_REQUEST_TIMEOUT', 30), + + /* + |-------------------------------------------------------------------------- + | Maximum Retry Attempt + |-------------------------------------------------------------------------- + | + | The retry attempt may be used to specify how many times retry when OpenAI server return error . + | By default, the library will try once. + */ + + 'max_retry_attempt' => env('OPENAI_RETRY_ATTEMPT', 5), + + /* + |-------------------------------------------------------------------------- + | Retry Delay + |-------------------------------------------------------------------------- + | + | Decides how long after a request should be repeated when a request fails. + | By default, resend the request immediately. + */ + + 'retry_delay' => env('OPENAI_RETRY_DELAY'), ]; diff --git a/src/GuzzleTransporter.php b/src/GuzzleTransporter.php new file mode 100644 index 0000000..d62d4d3 --- /dev/null +++ b/src/GuzzleTransporter.php @@ -0,0 +1,73 @@ +push((new GuzzleTransporter)->getRetryMiddleware()); + + return new Client([ + 'timeout' => config('openai.request_timeout', 30), + 'handler' => $stack, + ]); + } + + /** + * Get retry middleware callable + */ + public function getRetryMiddleware(): callable + { + return Middleware::retry($this->getDecider(), $this->getDelayDuration()); + } + + /** + * Get decider logic + */ + public function getDecider(): Closure + { + $maxRetries = config('openai.max_retry_attempt'); + + return function ( + int $retries, + ?RequestInterface $request = null, + ?ResponseInterface $response = null, + ?RuntimeException $e = null + ) use ($maxRetries): bool { + if ($retries > $maxRetries) { + return false; + } + + if ($e instanceof RequestException || $e instanceof ConnectException) { + return true; + } + + return false; + }; + } + + /** + * Get delay duration + */ + public function getDelayDuration(): callable + { + return function () { + return 1000 * config('openai.retry_delay'); + }; + } +} diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index ad04878..32923b5 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -32,7 +32,7 @@ public function register(): void return OpenAI::factory() ->withApiKey($apiKey) ->withOrganization($organization) - ->withHttpClient(new \GuzzleHttp\Client(['timeout' => config('openai.request_timeout', 30)])) + ->withHttpClient(GuzzleTransporter::getClient()) ->make(); }); diff --git a/tests/Arch.php b/tests/Arch.php index e1ca9cc..163bbdb 100644 --- a/tests/Arch.php +++ b/tests/Arch.php @@ -26,3 +26,20 @@ 'config', 'config_path', ]); + +test('guzzle transporter') + ->expect('OpenAI\Laravel\GuzzleTransporter') + ->toOnlyUse([ + 'Closure', + 'GuzzleHttp\Client', + 'GuzzleHttp\Exception\ConnectException', + 'GuzzleHttp\Exception\RequestException', + 'GuzzleHttp\HandlerStack', + 'GuzzleHttp\Middleware', + 'Psr\Http\Message\RequestInterface', + 'Psr\Http\Message\ResponseInterface', + 'RuntimeException', + + // helpers... + 'config', + ]); diff --git a/tests/GuzzleTransporterTest.php b/tests/GuzzleTransporterTest.php new file mode 100644 index 0000000..fe52c31 --- /dev/null +++ b/tests/GuzzleTransporterTest.php @@ -0,0 +1,35 @@ +getRetryMiddleware(); + + expect($retryMiddleware)->toBeCallable(); +}); + +test('getClient returns a Client', function () { + + $instance = GuzzleTransporter::getClient(); + + expect($instance)->toBeInstanceOf(Client::class); +}); + +test('getDelayDuration returns a callable', function () { + $instance = new GuzzleTransporter(); + + $delayCallable = $instance->getDelayDuration(); + + expect($delayCallable)->toBeCallable(); +}); + +test('getDecider returns a callable', function () { + $instance = new GuzzleTransporter(); + + $deciderCallable = $instance->getDecider(); + + expect($deciderCallable)->toBeCallable(); +});