Skip to content

Commit 03ac006

Browse files
authored
fix(meta): handle 429 (rate limit) gracefully (#652)
1 parent 9460fd2 commit 03ac006

File tree

3 files changed

+49
-7
lines changed

3 files changed

+49
-7
lines changed

src/Exceptions/RateLimitException.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenAI\Exceptions;
6+
7+
use Exception;
8+
use Psr\Http\Message\ResponseInterface;
9+
10+
final class RateLimitException extends Exception
11+
{
12+
public function __construct(public ResponseInterface $response)
13+
{
14+
parent::__construct('Request rate limit has been exceeded.');
15+
}
16+
}

src/Transporters/HttpTransporter.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use OpenAI\Contracts\TransporterContract;
1111
use OpenAI\Enums\Transporter\ContentType;
1212
use OpenAI\Exceptions\ErrorException;
13+
use OpenAI\Exceptions\RateLimitException;
1314
use OpenAI\Exceptions\TransporterException;
1415
use OpenAI\Exceptions\UnserializableResponse;
1516
use OpenAI\ValueObjects\Transporter\AdaptableResponse;
@@ -51,6 +52,7 @@ public function requestObject(Payload $payload): Response
5152

5253
$contents = (string) $response->getBody();
5354

55+
$this->throwIfRateLimit($response);
5456
$this->throwIfJsonError($response, $contents);
5557

5658
try {
@@ -78,6 +80,7 @@ public function requestStringOrObject(Payload $payload): AdaptableResponse
7880
return AdaptableResponse::from($contents, $response->getHeaders());
7981
}
8082

83+
$this->throwIfRateLimit($response);
8184
$this->throwIfJsonError($response, $contents);
8285

8386
try {
@@ -101,6 +104,7 @@ public function requestContent(Payload $payload): string
101104

102105
$contents = (string) $response->getBody();
103106

107+
$this->throwIfRateLimit($response);
104108
$this->throwIfJsonError($response, $contents);
105109

106110
return $contents;
@@ -115,6 +119,7 @@ public function requestStream(Payload $payload): ResponseInterface
115119

116120
$response = $this->sendRequest(fn () => ($this->streamHandler)($request));
117121

122+
$this->throwIfRateLimit($response);
118123
$this->throwIfJsonError($response, $response);
119124

120125
return $response;
@@ -133,6 +138,15 @@ private function sendRequest(Closure $callable): ResponseInterface
133138
}
134139
}
135140

141+
private function throwIfRateLimit(ResponseInterface $response): void
142+
{
143+
if ($response->getStatusCode() !== 429) {
144+
return;
145+
}
146+
147+
throw new RateLimitException($response);
148+
}
149+
136150
private function throwIfJsonError(ResponseInterface $response, string|ResponseInterface $contents): void
137151
{
138152
if ($response->getStatusCode() < 400) {

tests/Transporters/HttpTransporter.php

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use GuzzleHttp\Psr7\Response;
66
use OpenAI\Enums\Transporter\ContentType;
77
use OpenAI\Exceptions\ErrorException;
8+
use OpenAI\Exceptions\RateLimitException;
89
use OpenAI\Exceptions\TransporterException;
910
use OpenAI\Exceptions\UnserializableResponse;
1011
use OpenAI\Responses\Models\ListResponse;
@@ -189,7 +190,7 @@
189190
});
190191
})->with('request methods');
191192

192-
test('error type may be null', function (string $requestMethod) {
193+
test('error type may be null on 429', function (string $requestMethod) {
193194
$payload = Payload::list('models');
194195

195196
$response = new Response(429, ['Content-Type' => 'application/json; charset=utf-8'], json_encode([
@@ -207,12 +208,23 @@
207208
->andReturn($response);
208209

209210
expect(fn () => $this->http->$requestMethod($payload))
210-
->toThrow(function (ErrorException $e) {
211-
expect($e->getMessage())->toBe('You exceeded your current quota, please check')
212-
->and($e->getErrorMessage())->toBe('You exceeded your current quota, please check')
213-
->and($e->getErrorCode())->toBe('quota_exceeded')
214-
->and($e->getErrorType())->toBeNull();
215-
});
211+
->toThrow(RateLimitException::class);
212+
})->with('request methods');
213+
214+
test('429 may not follow OpenAI structure', function (string $requestMethod) {
215+
$payload = Payload::list('models');
216+
217+
$response = new Response(429, ['Content-Type' => 'application/json; charset=utf-8'], json_encode([
218+
'message' => 'Requests rate limit exceeded',
219+
]));
220+
221+
$this->client
222+
->shouldReceive('sendRequest')
223+
->once()
224+
->andReturn($response);
225+
226+
expect(fn () => $this->http->$requestMethod($payload))
227+
->toThrow(RateLimitException::class);
216228
})->with('request methods');
217229

218230
test('error message may be an array', function (string $requestMethod) {

0 commit comments

Comments
 (0)