Skip to content

Commit d34c916

Browse files
committed
Improve error reporting in Platform layer
1 parent d2caecc commit d34c916

File tree

10 files changed

+78
-15
lines changed

10 files changed

+78
-15
lines changed

examples/bootstrap.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Psr\Log\LoggerInterface;
1414
use Symfony\AI\Agent\Exception\ExceptionInterface as AgentException;
1515
use Symfony\AI\Platform\Exception\ExceptionInterface as PlatformException;
16+
use Symfony\AI\Platform\Exception\ResultException;
1617
use Symfony\AI\Platform\Metadata\Metadata;
1718
use Symfony\AI\Platform\Metadata\TokenUsage;
1819
use Symfony\AI\Platform\Result\DeferredResult;
@@ -190,7 +191,11 @@ function print_stream(DeferredResult $result): void
190191
if ($exception instanceof AgentException || $exception instanceof PlatformException || $exception instanceof StoreException) {
191192
output()->writeln(sprintf('<error>%s</error>', $exception->getMessage()));
192193

193-
if (output()->isVerbose()) {
194+
if ($exception instanceof ResultException && output()->isVerbose()) {
195+
dump($exception->getDetails());
196+
}
197+
198+
if (output()->isVeryVerbose()) {
194199
output()->writeln($exception->getTraceAsString());
195200
}
196201

src/platform/src/Bridge/Anthropic/ResultConverter.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\AI\Platform\Bridge\Anthropic;
1313

1414
use Symfony\AI\Platform\Exception\RateLimitExceededException;
15+
use Symfony\AI\Platform\Exception\ResultException;
1516
use Symfony\AI\Platform\Exception\RuntimeException;
1617
use Symfony\AI\Platform\Model;
1718
use Symfony\AI\Platform\Result\RawHttpResult;
@@ -52,6 +53,10 @@ public function convert(RawHttpResult|RawResultInterface $result, array $options
5253

5354
$data = $result->getData();
5455

56+
if (isset($data['error'])) {
57+
throw new ResultException($data['error']['message'], $data['error']);
58+
}
59+
5560
if (!isset($data['content']) || [] === $data['content']) {
5661
throw new RuntimeException('Response does not contain any content.');
5762
}

src/platform/src/Bridge/OpenAi/Gpt/ResultConverter.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
use Symfony\AI\Platform\Bridge\OpenAi\Gpt;
1515
use Symfony\AI\Platform\Exception\AuthenticationException;
1616
use Symfony\AI\Platform\Exception\BadRequestException;
17-
use Symfony\AI\Platform\Exception\ContentFilterException;
1817
use Symfony\AI\Platform\Exception\RateLimitExceededException;
18+
use Symfony\AI\Platform\Exception\ResultException;
1919
use Symfony\AI\Platform\Exception\RuntimeException;
2020
use Symfony\AI\Platform\Model;
2121
use Symfony\AI\Platform\Result\ChoiceResult;
@@ -47,8 +47,8 @@ public function convert(RawResultInterface|RawHttpResult $result, array $options
4747
$response = $result->getObject();
4848

4949
if (401 === $response->getStatusCode()) {
50-
$errorMessage = json_decode($response->getContent(false), true)['error']['message'];
51-
throw new AuthenticationException($errorMessage);
50+
$data = $response->toArray(false);
51+
throw new AuthenticationException($data['error']['message'], $data['error']);
5252
}
5353

5454
if (400 === $response->getStatusCode()) {
@@ -66,21 +66,21 @@ public function convert(RawResultInterface|RawHttpResult $result, array $options
6666
}
6767

6868
if ($options['stream'] ?? false) {
69-
return new StreamResult($this->convertStream($response));
69+
return new StreamResult($this->convertStream($result->getObject()));
7070
}
7171

7272
$data = $result->getData();
7373

74-
if (isset($data['error']['code']) && 'content_filter' === $data['error']['code']) {
75-
throw new ContentFilterException($data['error']['message']);
74+
if (isset($data['error'])) {
75+
throw new ResultException($data['error']['message'], $data['error']);
7676
}
7777

7878
if (isset($data['error'])) {
7979
throw new RuntimeException(\sprintf('Error "%s"-%s (%s): "%s".', $data['error']['code'], $data['error']['type'], $data['error']['param'], $data['error']['message']));
8080
}
8181

8282
if (!isset($data['choices'])) {
83-
throw new RuntimeException('Response does not contain choices.');
83+
throw new ResultException('Result does not contain choices.');
8484
}
8585

8686
$choices = array_map($this->convertChoice(...), $data['choices']);

src/platform/src/Bridge/Replicate/Client.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\AI\Platform\Bridge\Replicate;
1313

14+
use Symfony\AI\Platform\Exception\ResultException;
1415
use Symfony\Component\Clock\ClockInterface;
1516
use Symfony\Contracts\HttpClient\HttpClientInterface;
1617
use Symfony\Contracts\HttpClient\ResponseInterface;
@@ -40,7 +41,11 @@ public function request(string $model, string $endpoint, array $body): ResponseI
4041
'auth_bearer' => $this->apiKey,
4142
'json' => ['input' => $body],
4243
]);
43-
$data = $response->toArray();
44+
$data = $response->toArray(false);
45+
46+
if (isset($data['detail'])) {
47+
throw new ResultException($data['detail'], $data);
48+
}
4449

4550
while (!\in_array($data['status'], ['succeeded', 'failed', 'canceled'], true)) {
4651
$this->clock->sleep(1); // we need to wait until the prediction is ready

src/platform/src/Bridge/Scaleway/Llm/ResultConverter.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
namespace Symfony\AI\Platform\Bridge\Scaleway\Llm;
1313

1414
use Symfony\AI\Platform\Bridge\Scaleway\Scaleway;
15-
use Symfony\AI\Platform\Exception\ContentFilterException;
15+
use Symfony\AI\Platform\Exception\ResultException;
1616
use Symfony\AI\Platform\Exception\RuntimeException;
1717
use Symfony\AI\Platform\Model;
1818
use Symfony\AI\Platform\Result\ChoiceResult;
@@ -45,8 +45,8 @@ public function convert(RawResultInterface|RawHttpResult $result, array $options
4545
}
4646
$data = $result->getData();
4747

48-
if (isset($data['error']['code']) && 'content_filter' === $data['error']['code']) {
49-
throw new ContentFilterException($data['error']['message']);
48+
if (isset($data['error'])) {
49+
throw new ResultException($data['error']['message'], $data['error']);
5050
}
5151

5252
if (!isset($data['choices'])) {

src/platform/src/Bridge/Voyage/ResultConverter.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\AI\Platform\Bridge\Voyage;
1313

14+
use Symfony\AI\Platform\Exception\ResultException;
1415
use Symfony\AI\Platform\Exception\RuntimeException;
1516
use Symfony\AI\Platform\Model;
1617
use Symfony\AI\Platform\Result\RawResultInterface;
@@ -33,6 +34,10 @@ public function convert(RawResultInterface $result, array $options = []): Result
3334
{
3435
$result = $result->getData();
3536

37+
if (isset($result['detail'])) {
38+
throw new ResultException($result['detail']);
39+
}
40+
3641
if (!isset($result['data'])) {
3742
throw new RuntimeException('Response does not contain embedding data.');
3843
}

src/platform/src/Exception/AuthenticationException.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@
1717
* @author Vitalii Kyktov <vitalii.kyktov@gmail.com>
1818
* @author Dmytro Liashko <dmlyashko@gmail.com>
1919
*/
20-
class AuthenticationException extends RuntimeException
20+
class AuthenticationException extends ResultException
2121
{
2222
}

src/platform/src/Exception/RateLimitExceededException.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
/**
1515
* @author Floran Pagliai <floran.pagliai@gmail.com>
1616
*/
17-
final class RateLimitExceededException extends RuntimeException
17+
final class RateLimitExceededException extends ResultException
1818
{
1919
public function __construct(
2020
private readonly ?int $retryAfter = null,
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\AI\Platform\Exception;
13+
14+
class ResultException extends \Exception implements ExceptionInterface
15+
{
16+
/**
17+
* @param array<string, string> $details
18+
*/
19+
public function __construct(
20+
string $message,
21+
private array $details = [],
22+
?\Throwable $previous = null,
23+
) {
24+
parent::__construct($message, previous: $previous);
25+
}
26+
27+
/**
28+
* @return array<string, string>
29+
*/
30+
public function getDetails(): array
31+
{
32+
return $this->details;
33+
}
34+
}

src/platform/src/Result/RawHttpResult.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111

1212
namespace Symfony\AI\Platform\Result;
1313

14+
use Symfony\AI\Platform\Exception\ResultException;
15+
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
16+
use Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
1417
use Symfony\Contracts\HttpClient\ResponseInterface;
1518

1619
/**
@@ -25,7 +28,13 @@ public function __construct(
2528

2629
public function getData(): array
2730
{
28-
return $this->response->toArray(false);
31+
try {
32+
return $this->response->toArray();
33+
} catch (ClientExceptionInterface $e) {
34+
throw new ResultException(message: \sprintf('API responded with an error: "%s"', $e->getMessage()), details: $this->response->toArray(false), previous: $e);
35+
} catch (ExceptionInterface $e) {
36+
throw new ResultException(\sprintf('Error while calling the API: "%s"', $e->getMessage()), previous: $e);
37+
}
2938
}
3039

3140
public function getObject(): ResponseInterface

0 commit comments

Comments
 (0)