Dispatch AiExceptionEvent when a provider throws an exception
>>> [!note] Migrated issue
<!-- Drupal.org comment -->
<!-- Migrated from issue #3542496. -->
Reported by: [mglaman](https://www.drupal.org/user/2416470)
Related to !1502 !846
>>>
<h3 id="summary-problem-motivation">Problem/Motivation</h3>
<p>We're reaching a moment where projects are implementing the AI module and it's features (like XB AI) and third-parties want to customize the interaction. For example, I want to customize the response for exceeding budget/quota is exceeded. The XB AI can catch the exception (<span class="drupalorg-gitlab-issue-link project-issue-status-info project-issue-status-4"><a href="https://www.drupal.org/project/canvas/issues/3542491" title="Status: Postponed">#3542491: Uncaught exceptions when determining solvability of AI request</a></span>) but would need its own API for customizing.</p>
<p>For example:</p>
<pre>Drupal\ai\Exception\AiRequestErrorException: Error invoking model response: Budget has been exceeded! Current cost: 1.133637, Max budget: 1.0 in Drupal\ai\Plugin\ProviderProxy->wrapperCall() (line 241 of modules/contrib/ai/src/Plugin/ProviderProxy.php).</pre><p>If this exception was dispatched through an event, I could customize the error message to be more effective to the end user. And avoid the wonder floating point precision displayed.</p>
<h4 id="summary-steps-reproduce">Steps to reproduce (required for bugs, but not feature requests)</h4>
<p>Use OpenAI module and burn through quota, see direct error message from provider.</p>
<h3 id="summary-proposed-resolution">Proposed resolution</h3>
<p>- Create AiExceptionEvent (see below<br>
- Dispatch in wrapperCall where there is the large try/catch block and throw the exception after dispatch<br>
- Move the logging for each exception type into an event subscriber</p>
<pre>final class AiExceptionEvent extends \Symfony\Contracts\EventDispatcher\Event {<br> <br> private string $message;<br><br> public function __construct(<br> public readonly \Exception $exception,<br> ) {<br> $this->message = $exception->getMessage();<br> }<br><br> /**<br> * @param string $message<br> */<br> public function setMessage(string $message): void {<br> $this->message = $message;<br> }<br><br> /**<br> * @return \Exception<br> */<br> public function getException(): Exception {<br> return new \Drupal\ai\Exception\AiResponseErrorException(<br> $this->message,<br> $this->exception->getCode(),<br> $this->exception<br> );<br> }<br><br>}</pre><p>I guess the problem is that the thrown exception is then always AiResponseErrorException and if callers want the specific exception they always have to call getPrevious. Maybe that is acceptable. Implementations calling the AI APIs programmatically may not care about the specific exceptions solong as they can catch them all easily.</p>
<h3 id="summary-remaining-tasks">Remaining tasks</h3>
<h3>Optional: Other details as applicable (e.g., User interface changes, API changes, Data model changes)</h3>
issue