From 9de521f53dabd42cb33d247ae96aef0e00efa56f Mon Sep 17 00:00:00 2001 From: Marcus Johansson <me@marcusmailbox.com> Date: Wed, 19 Jun 2024 13:32:48 +0200 Subject: [PATCH] Issue #3455651 by Marcus_Johansson: Catch errors and show them on the Explorers --- .../ai_api_explorer.services.yml | 3 ++ modules/ai_api_explorer/css/api_explorer.css | 5 +++ .../ai_api_explorer/src/ExplorerHelper.php | 32 +++++++++++++++++++ .../src/Form/ChatGenerationForm.php | 20 ++++++++++-- .../src/Form/EmbeddingsGenerationForm.php | 17 ++++++++-- .../src/Form/ModerationGenerationForm.php | 8 +++++ .../src/Form/SpeechToTextGenerationForm.php | 15 ++++++++- .../src/Form/TextToImageGenerationForm.php | 25 +++++++++++---- .../src/Form/TextToSpeechGenerationForm.php | 29 ++++++++++++----- .../ai_external_moderation.settings.yml | 1 - .../src/Plugin/AiProvider/OpenAiProvider.php | 3 +- 11 files changed, 136 insertions(+), 22 deletions(-) create mode 100644 modules/ai_api_explorer/ai_api_explorer.services.yml create mode 100644 modules/ai_api_explorer/src/ExplorerHelper.php diff --git a/modules/ai_api_explorer/ai_api_explorer.services.yml b/modules/ai_api_explorer/ai_api_explorer.services.yml new file mode 100644 index 000000000..5eb363228 --- /dev/null +++ b/modules/ai_api_explorer/ai_api_explorer.services.yml @@ -0,0 +1,3 @@ +services: + ai_api_explorer.helper: + class: '\Drupal\ai_api_explorer\ExplorerHelper' diff --git a/modules/ai_api_explorer/css/api_explorer.css b/modules/ai_api_explorer/css/api_explorer.css index a4efb1d0e..3899bbbb2 100644 --- a/modules/ai_api_explorer/css/api_explorer.css +++ b/modules/ai_api_explorer/css/api_explorer.css @@ -16,6 +16,11 @@ clear: both; } +.ai-error { + color: #cc3d3d; + +} + code.ai-code { display: block; white-space: pre-wrap; diff --git a/modules/ai_api_explorer/src/ExplorerHelper.php b/modules/ai_api_explorer/src/ExplorerHelper.php new file mode 100644 index 000000000..4b2a1cc17 --- /dev/null +++ b/modules/ai_api_explorer/src/ExplorerHelper.php @@ -0,0 +1,32 @@ +<?php + +namespace Drupal\ai_api_explorer; + +/** + * Helper functions for the forms. + * + * @package Drupal\ai_api_explorer + */ +class ExplorerHelper { + + /** + * Function to render an exception in a modal. + * + * @param \Exception $e + * The exception to render. + * + * @return string + * The rendered exception. + */ + public static function renderException(\Exception $e): string { + $message = '<div class="ai-error">'; + $message .= '<h2>' . t('An error occurred') . '</h2>'; + $message .= '<p>' . t('The following error occurred while processing your request:') . '</p>'; + $message .= '<p><em>' . $e->getMessage() . '</em></p>'; + $message .= '<p>' . t('Of the following exception type:') . '</p>'; + $message .= '<p>' . get_class($e) . '</p>'; + $message .= '</div>'; + return $message; + } + +} diff --git a/modules/ai_api_explorer/src/Form/ChatGenerationForm.php b/modules/ai_api_explorer/src/Form/ChatGenerationForm.php index a78354d9e..820f22dc2 100644 --- a/modules/ai_api_explorer/src/Form/ChatGenerationForm.php +++ b/modules/ai_api_explorer/src/Form/ChatGenerationForm.php @@ -25,6 +25,13 @@ class ChatGenerationForm extends FormBase { */ protected $aiProviderHelper; + /** + * The Explorer Helper. + * + * @var \Drupal\ai_api_explorer\ExplorerHelper + */ + protected $explorerHelper; + /** * {@inheritdoc} */ @@ -38,6 +45,7 @@ class ChatGenerationForm extends FormBase { public static function create(ContainerInterface $container) { $instance = parent::create($container); $instance->aiProviderHelper = $container->get('ai.form_helper'); + $instance->explorerHelper = $container->get('ai_api_explorer.helper'); return $instance; } @@ -131,17 +139,23 @@ class ChatGenerationForm extends FormBase { } $input = new ChatInput($messages); - $response = $provider->chat($input, $form_state->getValue('chat_ai_model'), ['chat_generation'])->getNormalized(); + $response = NULL; + try { + $response = $provider->chat($input, $form_state->getValue('chat_ai_model'), ['chat_generation'])->getNormalized(); + } + catch (\Exception $e) { + $response = $this->explorerHelper->renderException($e); + } // Generation code for normalization. $code = $this->normalizeCodeExample($provider, $form_state, $messages); $code .= $this->rawCodeExample($provider, $form_state, $messages); - if (get_class($response) == ChatMessage::class) { + if (is_object($response) && get_class($response) == ChatMessage::class) { $form['response']['#context']['texts'] = '<h4>Role: ' . $response->getRole() . "</h4><p>" . $response->getMessage() . '</p>' . $code; } else { - $form['response']['#context']['texts'] = '<p>' . $this->t('Error: Invalid response from the provider.') . '</p>'; + $form['response']['#context']['texts'] = '<p>' . $response . '</p>'; } return $form['response']; } diff --git a/modules/ai_api_explorer/src/Form/EmbeddingsGenerationForm.php b/modules/ai_api_explorer/src/Form/EmbeddingsGenerationForm.php index be7292535..26b8c60ae 100644 --- a/modules/ai_api_explorer/src/Form/EmbeddingsGenerationForm.php +++ b/modules/ai_api_explorer/src/Form/EmbeddingsGenerationForm.php @@ -28,6 +28,13 @@ class EmbeddingsGenerationForm extends FormBase { */ protected $requestStack; + /** + * The Explorer Helper. + * + * @var \Drupal\ai_api_explorer\ExplorerHelper + */ + protected $explorerHelper; + /** * {@inheritdoc} */ @@ -42,6 +49,7 @@ class EmbeddingsGenerationForm extends FormBase { $instance = parent::create($container); $instance->aiProviderHelper = $container->get('ai.form_helper'); $instance->requestStack = $container->get('request_stack'); + $instance->explorerHelper = $container->get('ai_api_explorer.helper'); return $instance; } @@ -111,8 +119,13 @@ class EmbeddingsGenerationForm extends FormBase { */ public function getResponse(array &$form, FormStateInterface $form_state) { $provider = $this->aiProviderHelper->generateAiProviderFromFormSubmit($form, $form_state, 'embeddings', 'embed'); - $embeddings = $provider->embeddings($form_state->getValue('prompt'), $form_state->getValue('embed_ai_model'), ['ai_api_explorer']); - $response = implode(', ', $embeddings->getNormalized()); + try { + $embeddings = $provider->embeddings($form_state->getValue('prompt'), $form_state->getValue('embed_ai_model'), ['ai_api_explorer']); + $response = implode(', ', $embeddings->getNormalized()); + } + catch (\Exception $e) { + $response = $this->explorerHelper->renderException($e); + } // Generation code. $code = "<details style=\"background: #ccc; padding: 5px;\"><summary>Code Example</summary><code style=\"display: block; white-space: pre-wrap; padding: 20px;\">"; $code .= '$prompt = "' . $form_state->getValue('prompt') . '";<br>'; diff --git a/modules/ai_api_explorer/src/Form/ModerationGenerationForm.php b/modules/ai_api_explorer/src/Form/ModerationGenerationForm.php index cdf832426..c613ac4f7 100644 --- a/modules/ai_api_explorer/src/Form/ModerationGenerationForm.php +++ b/modules/ai_api_explorer/src/Form/ModerationGenerationForm.php @@ -24,6 +24,13 @@ class ModerationGenerationForm extends FormBase { */ protected $aiProviderHelper; + /** + * The Explorer Helper. + * + * @var \Drupal\ai_api_explorer\ExplorerHelper + */ + protected $explorerHelper; + /** * {@inheritdoc} */ @@ -37,6 +44,7 @@ class ModerationGenerationForm extends FormBase { public static function create(ContainerInterface $container) { $instance = parent::create($container); $instance->aiProviderHelper = $container->get('ai.form_helper'); + $instance->explorerHelper = $container->get('ai.explorer_helper'); return $instance; } diff --git a/modules/ai_api_explorer/src/Form/SpeechToTextGenerationForm.php b/modules/ai_api_explorer/src/Form/SpeechToTextGenerationForm.php index a0509961a..22223cfce 100644 --- a/modules/ai_api_explorer/src/Form/SpeechToTextGenerationForm.php +++ b/modules/ai_api_explorer/src/Form/SpeechToTextGenerationForm.php @@ -28,6 +28,13 @@ class SpeechToTextGenerationForm extends FormBase { */ protected $requestStack; + /** + * The Explorer Helper. + * + * @var \Drupal\ai_api_explorer\ExplorerHelper + */ + protected $explorerHelper; + /** * {@inheritdoc} */ @@ -42,6 +49,7 @@ class SpeechToTextGenerationForm extends FormBase { $instance = parent::create($container); $instance->aiProviderHelper = $container->get('ai.form_helper'); $instance->requestStack = $container->get('request_stack'); + $instance->explorerHelper = $container->get('ai.explorer_helper'); return $instance; } @@ -115,7 +123,12 @@ class SpeechToTextGenerationForm extends FormBase { $file = reset($files); $raw_file = file_get_contents($file['file']->getPathname()); - $response = $provider->speechToText($raw_file, $form_state->getValue('stt_ai_model'), ['ai_api_explorer'])->getNormalized(); + try { + $response = $provider->speechToText($raw_file, $form_state->getValue('stt_ai_model'), ['ai_api_explorer'])->getNormalized(); + } + catch (\Exception $e) { + $response = $this->explorerHelper->renderException($e); + } // Generation code. $code = "<details style=\"background: #ccc; padding: 5px;\"><summary>Code Example</summary><code style=\"display: block; white-space: pre-wrap; padding: 20px;\">"; diff --git a/modules/ai_api_explorer/src/Form/TextToImageGenerationForm.php b/modules/ai_api_explorer/src/Form/TextToImageGenerationForm.php index d1f06848a..34af9e838 100644 --- a/modules/ai_api_explorer/src/Form/TextToImageGenerationForm.php +++ b/modules/ai_api_explorer/src/Form/TextToImageGenerationForm.php @@ -42,6 +42,13 @@ class TextToImageGenerationForm extends FormBase { */ protected $entityTypeManager; + /** + * The Explorer Helper. + * + * @var \Drupal\ai_api_explorer\ExplorerHelper + */ + protected $explorerHelper; + /** * {@inheritdoc} */ @@ -58,6 +65,7 @@ class TextToImageGenerationForm extends FormBase { $instance->requestStack = $container->get('request_stack'); $instance->moduleHandler = $container->get('module_handler'); $instance->entityTypeManager = $container->get('entity_type.manager'); + $instance->explorerHelper = $container->get('ai_api_explorer.helper'); return $instance; } @@ -144,13 +152,18 @@ class TextToImageGenerationForm extends FormBase { */ public function getResponse(array &$form, FormStateInterface $form_state) { $provider = $this->aiProviderHelper->generateAiProviderFromFormSubmit($form, $form_state, 'text_to_image', 'image_generator'); - $images = $provider->textToImage($form_state->getValue('prompt'), $form_state->getValue('image_generator_ai_model'), ['ai_api_explorer']); - $response = ''; - foreach ($images->getAsBase64EncodedString() as $image) { - $response .= '<img src="data:image/png;charset=utf-8;base64,' . $image . '" />'; + try { + $images = $provider->textToImage($form_state->getValue('prompt'), $form_state->getValue('image_generator_ai_model'), ['ai_api_explorer']); + $response = ''; + foreach ($images->getAsBase64EncodedString() as $image) { + $response .= '<img src="data:image/png;charset=utf-8;base64,' . $image . '" />'; + } + if ($form_state->getValue('save_as_media')) { + $images->getAsMediaReference($form_state->getValue('save_as_media'), 'image.png'); + } } - if ($form_state->getValue('save_as_media')) { - $images->getAsMediaReference($form_state->getValue('save_as_media'), 'image.png'); + catch (\Exception $e) { + $response = $this->explorerHelper->renderException($e); } // Generation code. diff --git a/modules/ai_api_explorer/src/Form/TextToSpeechGenerationForm.php b/modules/ai_api_explorer/src/Form/TextToSpeechGenerationForm.php index 2995d913e..82da95b42 100644 --- a/modules/ai_api_explorer/src/Form/TextToSpeechGenerationForm.php +++ b/modules/ai_api_explorer/src/Form/TextToSpeechGenerationForm.php @@ -57,6 +57,13 @@ class TextToSpeechGenerationForm extends FormBase { */ protected $entityTypeManager; + /** + * The Explorer Helper. + * + * @var \Drupal\ai_api_explorer\ExplorerHelper + */ + protected $explorerHelper; + /** * {@inheritdoc} */ @@ -75,6 +82,7 @@ class TextToSpeechGenerationForm extends FormBase { $instance->fileSystem = $container->get('file_system'); $instance->moduleHandler = $container->get('module_handler'); $instance->entityTypeManager = $container->get('entity_type.manager'); + $instance->explorerHelper = $container->get('ai_api_explorer.helper'); return $instance; } @@ -161,16 +169,21 @@ class TextToSpeechGenerationForm extends FormBase { */ public function getResponse(array &$form, FormStateInterface $form_state) { $provider = $this->aiProviderHelper->generateAiProviderFromFormSubmit($form, $form_state, 'text_to_speech', 'tts_'); - $audio = $provider->textToSpeech($form_state->getValue('prompt'), $form_state->getValue('tts_ai_model'), ['ai_api_explorer']); + $response = ''; - if ($form_state->getValue('save_as_media')) { - $audio->getAsMediaReference($form_state->getValue('save_as_media'), 'text-to-speech.mp3'); + try { + $audio = $provider->textToSpeech($form_state->getValue('prompt'), $form_state->getValue('tts_ai_model'), ['ai_api_explorer']); + if ($form_state->getValue('save_as_media')) { + $audio->getAsMediaReference($form_state->getValue('save_as_media'), 'text-to-speech.mp3'); + } + $audio_normalized = $audio->getNormalized(); + // Save the binary data to a file. + $file_url = $this->fileSystem->saveData($audio_normalized[0], 'public://text-to-speech-test.mp3', FileExists::Replace); + $response .= '<audio controls><source src="' . $this->fileUrlGenerator->generateAbsoluteString($file_url) . '" type="audio/mpeg"></audio>'; + } + catch (\Exception $e) { + $response = $this->explorerHelper->renderException($e); } - $audio_normalized = $audio->getNormalized(); - // Save the binary data to a file. - $file_url = $this->fileSystem->saveData($audio_normalized[0], 'public://text-to-speech-test.mp3', FileExists::Replace); - $response .= '<audio controls><source src="' . $this->fileUrlGenerator->generateAbsoluteString($file_url) . '" type="audio/mpeg"></audio>'; - // Generation code. $code = "<details style=\"background: #ccc; padding: 5px;\"><summary>Code Example</summary><code style=\"display: block; white-space: pre-wrap; padding: 20px;\">"; $code .= '$prompt = "' . $form_state->getValue('prompt') . '";<br>'; diff --git a/modules/ai_external_moderation/config/install/ai_external_moderation.settings.yml b/modules/ai_external_moderation/config/install/ai_external_moderation.settings.yml index ed552b91b..388259299 100644 --- a/modules/ai_external_moderation/config/install/ai_external_moderation.settings.yml +++ b/modules/ai_external_moderation/config/install/ai_external_moderation.settings.yml @@ -1,2 +1 @@ moderations: - diff --git a/modules/providers/provider_openai/src/Plugin/AiProvider/OpenAiProvider.php b/modules/providers/provider_openai/src/Plugin/AiProvider/OpenAiProvider.php index 81046d357..59cf13366 100644 --- a/modules/providers/provider_openai/src/Plugin/AiProvider/OpenAiProvider.php +++ b/modules/providers/provider_openai/src/Plugin/AiProvider/OpenAiProvider.php @@ -406,16 +406,17 @@ class OpenAiProvider extends AiProviderClientBase implements * @throws \Drupal\ai\Exception\AiUnsafePromptException */ public function moderationEndpoints(string $prompt): void { + $this->getClient(); // If moderation is disabled, we skip this. if (!$this->moderation) { return; } - $this->getClient(); $payload = [ 'model' => 'text-moderation-latest', 'input' => $prompt, ] + $this->configuration; $response = $this->client->moderations()->create($payload)->toArray(); + if (!empty($response['results'][0]['flagged'])) { throw new AiUnsafePromptException('The prompt was flagged by the moderation model.'); } -- GitLab