Skip to content

Report an AI provider error instead of ending the request with a 500 - #1539

Open
killecaptron wants to merge 1 commit into
Part-DB:masterfrom
killecaptron:ai-provider-error-handling
Open

Report an AI provider error instead of ending the request with a 500#1539
killecaptron wants to merge 1 commit into
Part-DB:masterfrom
killecaptron:ai-provider-error-handling

Conversation

@killecaptron

Copy link
Copy Markdown
Contributor

Creating a part from an URL with the AI extractor enabled ends in an Internal Server Error whenever the AI
provider rejects the request. An unknown model name, an exhausted quota and a schema the provider does not
accept all look the same from the outside:

Symfony\AI\Platform\Exception\BadRequestException: "Provider returned error"
at ResultConverter.php line 63

The handling for this is already there, it is just bypassed. AIWebProvider::callLLM() wraps the invocation in
a try/catch which turns any failure into a RuntimeException with a readable message, but the platform hands
out a deferred result: invoke() only prepares the request, and it is carried out - and its answer converted -
when the result is read. That read sits one line below the catch, so the very error the catch was written for
escapes it.

Reading the result inside the try fixes that. The search page then reports the failure as it always did, since
it already catches RuntimeException, logs it and shows a message.

The "create from URL" page needed the same catch: it only handled HttpClient exceptions, so even the wrapped
RuntimeException would have ended as a 500 there. It now behaves like the search page, with a flash message
and a log entry - and it is the page where a user is most likely to meet a misconfigured AI provider, since it
is the one which uses it.

Reporting the failure is only half of it though, because the message can be useless on its own. "Provider
returned error" is the wording a gateway like OpenRouter uses when the model provider behind it refused, and
the actual reason travels in a field of the response body which the platform's converter drops. The raw
response is still available at this point, so the status code and the beginning of the body are added to the
message.

That last part is not hypothetical. On my instance the AI extractor failed with exactly the unspecific message
above, and the cause was impossible to guess from it. With the response included, the log said:

LLM invocation failed: Provider returned error (provider answered HTTP 400: {"error":{"message":
"Provider returned error","code":400,"metadata":{"raw":"{\"error\":{\"message\":\"Invalid schema for
response_format 'part_detail': In context=('properties','parameters','items'), 'additionalProperties'
is required to be supplied and to be false.\",\"code\":\"invalid_json_schema\"}}",
"provider_name":"Azure", ...

which turned out to be a bug in Part-DB itself rather than a misconfiguration.

The regression tests build a platform whose result fails when it is read, which is how a provider error really
arrives, and check that the failure is reported rather than escaping, and that the answer of the provider is
part of the message.

Creating a part from an URL with the AI extractor enabled ends in an
Internal Server Error whenever the AI provider rejects the request - an
unknown model name, an exhausted quota or an invalid key all look the
same from the outside:

  Symfony\AI\Platform\Exception\BadRequestException: "Provider returned
  error" at ResultConverter.php line 63

The handling for this is already there, it is just bypassed. callLLM()
wraps the invocation in a try/catch which turns any failure into a
RuntimeException with a readable message, but the platform hands out a
deferred result: invoke() only prepares the request, and it is carried
out (and its answer converted) when the result is read. That read sits
one line below the catch, so the very error the catch was written for
escapes it.

Reading the result inside the try fixes that. The search page then
reports the failure as it always did, since it already catches
RuntimeException, logs it and shows a message.

The "create from URL" page needed the same catch: it only handled
HttpClient exceptions, so even the wrapped RuntimeException would have
ended as a 500 there. It now behaves like the search page - flash
message plus a log entry - which is also where a user is most likely to
meet a misconfigured AI provider, since that page is the one which uses
it.

Reporting the failure is only half of it though, because the message can
be useless on its own. "Provider returned error" is the wording a
gateway like OpenRouter uses when the model provider behind it refused,
and the actual reason travels in a field of the response body which the
platform's converter drops. The raw response is still available at this
point, so the status code and the beginning of the body are added to the
message: without them an administrator cannot tell an exhausted quota
from a rejected request.

The regression tests build a platform whose result fails when it is
read, which is how a provider error really arrives, and check that the
failure is reported rather than escaping, and that the answer of the
provider is part of the message.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 65.00000% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 62.71%. Comparing base (0f0ee60) to head (41584f3).

Files with missing lines Patch % Lines
...ces/InfoProviderSystem/Providers/AIWebProvider.php 76.47% 4 Missing ⚠️
src/Controller/InfoProviderController.php 0.00% 3 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1539      +/-   ##
============================================
+ Coverage     62.39%   62.71%   +0.32%     
- Complexity     9879     9885       +6     
============================================
  Files           736      736              
  Lines         31779    31797      +18     
============================================
+ Hits          19829    19942     +113     
+ Misses        11950    11855      -95     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant