Make WordPress Core


Ignore:
Timestamp:
03/17/2026 02:28:40 PM (7 months ago)
Author:
flixos90
Message:

AI: Improve AI Client error handling.

Prior to this changeset, the WordPress AI Client would handle every exception thrown in the PHP AI Client in the same way, losing meaningful nuance about different kinds of errors along the way.

Now, the WP_Error objects returned by the WordPress AI Client come with more helpful error codes, and they include semantic HTTP status codes as well so that they can be used in relevant contexts out of the box, like e.g. the REST API.

Props flixos90, gziolo.
Fixes #64867.
See #64591.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/ai-client/class-wp-ai-client-prompt-builder.php

    r61942 r62037  
    99
    1010use WordPress\AiClient\Builders\PromptBuilder;
     11use WordPress\AiClient\Common\Exception\InvalidArgumentException;
     12use WordPress\AiClient\Common\Exception\TokenLimitReachedException;
    1113use WordPress\AiClient\Files\DTO\File;
    1214use WordPress\AiClient\Files\Enums\FileTypeEnum;
    … …  
    1618use WordPress\AiClient\Messages\Enums\ModalityEnum;
    1719use WordPress\AiClient\Providers\Http\DTO\RequestOptions;
     20use WordPress\AiClient\Providers\Http\Exception\ClientException;
     21use WordPress\AiClient\Providers\Http\Exception\NetworkException;
     22use WordPress\AiClient\Providers\Http\Exception\ServerException;
    1823use WordPress\AiClient\Providers\Models\Contracts\ModelInterface;
    1924use WordPress\AiClient\Providers\Models\DTO\ModelConfig;
    … …  
    180185                } catch ( Exception $e ) {
    181186                        $this->builder = new PromptBuilder( $registry );
    182                         $this->error   = new WP_Error(
    183                                 'prompt_builder_error',
    184                                 $e->getMessage(),
    185                                 array(
    186                                         'exception_class' => get_class( $e ),
    187                                 )
    188                         );
     187                        $this->error   = $this->exception_to_wp_error( $e );
    189188                }
    190189
    … …  
    312311                                        __( 'Prompt execution was prevented by a filter.' ),
    313312                                        array(
    314                                                 'exception_class' => 'WP_AI_Client_Prompt_Prevented',
     313                                                'status' => 503,
    315314                                        )
    316315                                );
    … …  
    334333                        return $result;
    335334                } catch ( Exception $e ) {
    336                         $this->error = new WP_Error(
    337                                 'prompt_builder_error',
    338                                 $e->getMessage(),
    339                                 array(
    340                                         'exception_class' => get_class( $e ),
    341                                 )
    342                         );
     335                        $this->error = $this->exception_to_wp_error( $e );
    343336
    344337                        if ( self::is_generating_method( $name ) ) {
    … …  
    347340                        return $this;
    348341                }
     342        }
     343
     344        /**
     345         * Converts an exception into a WP_Error with a structured error code and message.
     346         *
     347         * This method maps different exception types to specific WP_Error codes and HTTP status codes.
     348         * The presence of the status codes means these WP_Error objects can be easily used in REST API responses
     349         * or other contexts where HTTP semantics are relevant.
     350         *
     351         * @since 7.0.0
     352         *
     353         * @param Exception $e The exception to convert.
     354         * @return WP_Error The resulting WP_Error object.
     355         */
     356        private function exception_to_wp_error( Exception $e ): WP_Error {
     357                if ( $e instanceof NetworkException ) {
     358                        $error_code  = 'prompt_network_error';
     359                        $status_code = 503;
     360                } elseif ( $e instanceof ClientException ) {
     361                        // `ClientException` uses HTTP status codes as exception codes, so we can rely on them.
     362                        $error_code  = 'prompt_client_error';
     363                        $status_code = $e->getCode() ? $e->getCode() : 400;
     364                } elseif ( $e instanceof ServerException ) {
     365                        // `ServerException` uses HTTP status codes as exception codes, so we can rely on them.
     366                        $error_code  = 'prompt_upstream_server_error';
     367                        $status_code = $e->getCode() ? $e->getCode() : 500;
     368                } elseif ( $e instanceof TokenLimitReachedException ) {
     369                        $error_code  = 'prompt_token_limit_reached';
     370                        $status_code = 400;
     371                } elseif ( $e instanceof InvalidArgumentException ) {
     372                        $error_code  = 'prompt_invalid_argument';
     373                        $status_code = 400;
     374                } else {
     375                        $error_code  = 'prompt_builder_error';
     376                        $status_code = 500;
     377                }
     378
     379                return new WP_Error(
     380                        $error_code,
     381                        $e->getMessage(),
     382                        array(
     383                                'status'          => $status_code,
     384                                'exception_class' => get_class( $e ),
     385                        )
     386                );
    349387        }
    350388
Note: See TracChangeset for help on using the changeset viewer.