Skip to content
Snippets Groups Projects
Commit ea62e395 authored by mindaugasd's avatar mindaugasd
Browse files

Issue #3439038 by mindaugasd: Provide default backend replacing example backend

parent 529c617a
No related merge requests found
Pipeline #139843 passed with warnings
<?php
namespace Drupal\aichat_backend_example\Exception;
class ExampleBackendException extends \Exception {
}
\ No newline at end of file
......@@ -2,268 +2,34 @@
namespace Drupal\aichat_backend_example\Plugin\AIChatBackend;
use Drupal\aichat_backend_example\Message;
use Drupal\aichat_backend_example\Exception\ExampleBackendException;
use Drupal\aichat\MessageInterface;
use Drupal\aichat\Plugin\AIChatBackendBase;
use Drupal\Component\Uuid\UuidInterface;
use Drupal\aichat\Annotation\AIChatBackend;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Annotation\Translation;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\key\KeyRepositoryInterface;
use Drupal\aichat\Plugin\AIChatBackend\DefaultBackend;
use Drupal\aichat_backend_example\Message;
/**
* Example backend for AI Chat no. 1
*
* Demonstrates an easiest way to create a fully functional backend by extending
* DefaultBackend and changing some methods.
*
* This method is good if modifications don't differ too much from default
* backend. But if you want to implement completely different backend, then look
* at another example in ExampleBackend2.php file.
*
* @AIChatBackend(
* id = "example_backend",
* label = @Translation("AI chat example backend"),
* description = @Translation("Eexample backend for AI Chat.")
* label = @Translation("AI chat example backend 1"),
* description = @Translation("Example backend for AI Chat extending DefaultBackend.")
* )
*/
class ExampleBackend extends AIChatBackendBase {
/**
* The UUID service.
*
* @var \Drupal\Component\Uuid\UuidInterface
*/
protected $uuid;
/**
* Keys repository service
*
* @var \Drupal\key\KeyRepositoryInterface
*/
protected $keys;
/**
* {@inheritdoc}
*/
protected $messages;
public function __construct(array $configuration, $plugin_id, $plugin_definition, UuidInterface $uuid, KeyRepositoryInterface $keys) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->uuid = $uuid;
$this->keys = $keys;
$this->messages = [];
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('uuid'),
$container->get('key.repository')
);
}
class ExampleBackend extends DefaultBackend {
/**
* {@inheritdoc}
* Demonstrates, that overriding this method allows to swap message class
* with a custom one.
*/
public function createNewMessageObject(): MessageInterface {
return new Message($this);
}
/**
* {@inheritdoc}
*/
public function defineBackendConfig(): array {
return [
'api_key' => ['default' => ''],
'model' => ['default' => 'gpt-4-turbo-preview'],
'system_message' => ['default' => '']
];
}
/**
* {@inheritdoc}
*/
public function buildBackendConfigForm(array $form, FormStateInterface $form_state): array {
$library_installed = class_exists('OpenAI');
$instructions = '';
$instructions .= $this->t('This backend requires openai-php/client library to be installed on the system to be able to connect with OpenAI API. </br>'.
'You can install openai library by writing composer command: <em>@command</em>',
['@command' => 'composer require openai-php/client']) . '</br>';
$library_info = $library_installed ? $this->t('OK, library looks installed') : $this->t('Not installed');
$instructions .= $this->t('Current library status: <strong>@status</strong>',
['@status' => $library_info]);
$form['library_status'] = [
'#type' => 'markup',
'#markup' => '<p>'.$instructions.'</p>'
];
$form['api_key'] = [
'#type' => 'key_select',
'#title' => $this->t('OpenAI API key'),
'#default_value' => $this->getBackendConfigurationValue('api_key')
];
$form['model'] = [
'#type' => 'select',
'#title' => $this->t('Model'),
'#options' => [
'gpt-4-turbo-preview' => 'gpt-4-turbo-preview',
'gpt-3.5-turbo' => 'gpt-3.5-turbo'
],
'#required' => TRUE,
'#default_value' => $this->getBackendConfigurationValue('model')
];
$form['system_message'] = [
'#type' => 'textarea',
'#title' => $this->t('OpenAI system message'),
'#default_value' => $this->getBackendConfigurationValue('system_message'),
'#description' => $this->t('Here you can provide some context information for AI')
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateBackendConfigForm(array $form, FormStateInterface $form_state): void {
$library_installed = class_exists('OpenAI');
if (! $library_installed) {
$form_state->setError($form['library_status'], $this->t('openai-php/client library is not installed.'));
}
}
/**
* {@inheritdoc}
*/
public function loadMessages(): void {
$messages_array = $this->aichat->getDataValue('messages');
if (empty($messages_array)) return;
foreach ($messages_array as $uuid => $values) {
$message = $this->createNewMessageObject();
$message->setValuesFromArray($values);
$this->messages[$uuid] = $message;
}
}
/**
* {@inheritdoc}
*/
public function saveMessage(MessageInterface $message): void {
$uuid = $message->getId();
if (empty($uuid)) {
$uuid = $this->uuid->generate();
$message->setId($uuid);
}
$this->aichat->setDataSubValue('messages', $uuid, $message->toArray());
$this->aichat->save();
}
/**
* {@inheritdoc}
*/
public function sendMessage(MessageInterface $message): array {
// save user message
$message->save();
// create response message
$response_message = $this->createNewMessageObject();
$response_message->setRole('assistant');
return $this->queryOpenAI($response_message);
}
/**
* {@inheritdoc}
*/
public function repeatMessage(MessageInterface $response_message): array {
return $this->queryOpenAI($response_message);
}
/**
* Helper method to query OpenAI.
*/
public function queryOpenAI(MessageInterface $response_message): array {
$messages_array = $this->getMessageHistoryArray();
try {
$client = \OpenAI::factory()
->withApiKey($this->getApiKey())
->withHttpClient(\Drupal::httpClient())
->make();
$response = $client->chat()->create([
'model' => $this->getBackendConfigurationValue('model'),
'messages' => $messages_array
]);
$response_message->setResponseData($response->toArray());
$response_message->save();
return [];
}
catch (\Exception $exception) {
$response_message->setException($exception);
$response_message->save();
return ['warning' => $this->t('There was a problem sending or receiving answer from artificial intelligence.')];
}
}
/**
* Helper method to get API key.
*/
public function getApiKey(): string {
$key_id = $this->getBackendConfigurationValue('api_key');
$key = $this->keys->getKey($key_id);
if (empty($key)) {
throw new ExampleBackendException("OpenAI key is not setup.");
}
$api_key = $key->getKeyValue();
return trim($api_key);
}
/**
* Helper method to get messages history.
*/
public function getMessageHistoryArray(): array {
$history = $this->getMessages(TRUE);
$history_array = [];
$system_message = $this->getBackendConfigurationValue('system_message');
if (!empty($system_message)) {
$history_array[] = [
'role' => 'system',
'content' => $system_message
];
}
foreach ($history as $history_message) {
$content = $history_message->getText();
if (empty($content)) continue;
$history_array[] = [
'role' => $history_message->getRole(),
'content' => $content
];
}
return $history_array;
}
}
<?php
namespace Drupal\aichat_backend_example\Plugin\AIChatBackend;
use Drupal\aichat_backend_example\Message;
use Drupal\aichat\Annotation\AIChatBackend;
use Drupal\aichat\Exception\AIChatBackendException;
use Drupal\aichat\MessageInterface;
use Drupal\aichat\Plugin\AIChatBackendBase;
use Drupal\Component\Uuid\UuidInterface;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\key\KeyRepositoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Example backend for AI Chat no. 2
*
* A minimal example demonstrating how to create a completely custom backend
* by extending base class AIChatBackendBase.
*
* @AIChatBackend(
* id = "example_backend2",
* label = @Translation("AI chat example backend 2"),
* description = @Translation("Eexample backend for AI Chat extending AIChatBackendBase")
* )
*/
class ExampleBackend2 extends AIChatBackendBase {
/**
* The UUID service.
*
* @var \Drupal\Component\Uuid\UuidInterface
*/
protected $uuid;
/**
* Keys repository service
*
* @var \Drupal\key\KeyRepositoryInterface
*/
protected $keys;
/**
* {@inheritdoc}
*/
protected $messages;
public function __construct(array $configuration, $plugin_id, $plugin_definition, UuidInterface $uuid, KeyRepositoryInterface $keys) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->uuid = $uuid;
$this->keys = $keys;
$this->messages = [];
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('uuid'),
$container->get('key.repository')
);
}
/**
* {@inheritdoc}
*/
public function createNewMessageObject(): MessageInterface {
return new Message($this);
}
/**
* {@inheritdoc}
*/
public function defineBackendConfig(): array {
return [
'api_key' => ['default' => ''],
'model' => ['default' => 'gpt-4-turbo-preview'],
'system_message' => ['default' => '']
];
}
/**
* {@inheritdoc}
*/
public function buildBackendConfigForm(array $form, FormStateInterface $form_state): array {
$library_installed = class_exists('OpenAI');
$instructions = '';
$instructions .= $this->t('This backend requires openai-php/client library to be installed on the system to be able to connect with OpenAI API. </br>'.
'You can install openai library by writing composer command: <em>@command</em>',
['@command' => 'composer require openai-php/client']) . '</br>';
$library_info = $library_installed ? $this->t('OK, library looks installed') : $this->t('Not installed');
$instructions .= $this->t('Current library status: <strong>@status</strong>',
['@status' => $library_info]);
$form['library_status'] = [
'#type' => 'markup',
'#markup' => '<p>'.$instructions.'</p>'
];
$form['api_key'] = [
'#type' => 'key_select',
'#title' => $this->t('OpenAI API key'),
'#default_value' => $this->getBackendConfigurationValue('api_key')
];
$form['model'] = [
'#type' => 'select',
'#title' => $this->t('Model'),
'#options' => [
'gpt-4-turbo-preview' => 'gpt-4-turbo-preview',
'gpt-3.5-turbo' => 'gpt-3.5-turbo'
],
'#required' => TRUE,
'#default_value' => $this->getBackendConfigurationValue('model')
];
$form['system_message'] = [
'#type' => 'textarea',
'#title' => $this->t('OpenAI system message'),
'#default_value' => $this->getBackendConfigurationValue('system_message'),
'#description' => $this->t('Here you can provide some context information for AI')
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateBackendConfigForm(array $form, FormStateInterface $form_state): void {
$library_installed = class_exists('OpenAI');
if (! $library_installed) {
$form_state->setError($form['library_status'], $this->t('openai-php/client library is not installed.'));
}
}
/**
* {@inheritdoc}
*/
public function loadMessages(): void {
$messages_array = $this->aichat->getDataValue('messages');
if (empty($messages_array)) return;
foreach ($messages_array as $uuid => $values) {
$message = $this->createNewMessageObject();
$message->setValuesFromArray($values);
$this->messages[$uuid] = $message;
}
}
/**
* {@inheritdoc}
*/
public function saveMessage(MessageInterface $message): void {
$uuid = $message->getId();
if (empty($uuid)) {
$uuid = $this->uuid->generate();
$message->setId($uuid);
}
$this->aichat->setDataSubValue('messages', $uuid, $message->toArray());
$this->aichat->save();
}
/**
* {@inheritdoc}
*/
public function sendMessage(MessageInterface $message): array {
// save user message
$message->save();
// create response message
$response_message = $this->createNewMessageObject();
$response_message->setRole('assistant');
return $this->queryOpenAI($response_message);
}
/**
* {@inheritdoc}
*/
public function repeatMessage(MessageInterface $response_message): array {
return $this->queryOpenAI($response_message);
}
/**
* Helper method to query OpenAI.
*/
public function queryOpenAI(MessageInterface $response_message): array {
$messages_array = $this->getMessageHistoryArray();
try {
$client = \OpenAI::factory()
->withApiKey($this->getApiKey())
->withHttpClient(\Drupal::httpClient())
->make();
$response = $client->chat()->create([
'model' => $this->getBackendConfigurationValue('model'),
'messages' => $messages_array
]);
$response_message->setResponseData($response->toArray());
$response_message->save();
return [];
}
catch (\Exception $exception) {
$response_message->setException($exception);
$response_message->save();
return ['warning' => $this->t('There was a problem sending or receiving answer from artificial intelligence.')];
}
}
/**
* Helper method to get API key.
*/
public function getApiKey(): string {
$key_id = $this->getBackendConfigurationValue('api_key');
$key = $this->keys->getKey($key_id);
if (empty($key)) {
throw new ExampleBackendException("OpenAI key is not setup.");
}
$api_key = $key->getKeyValue();
return trim($api_key);
}
/**
* Helper method to get messages history.
*/
public function getMessageHistoryArray(): array {
$history = $this->getMessages(TRUE);
$history_array = [];
$system_message = $this->getBackendConfigurationValue('system_message');
if (!empty($system_message)) {
$history_array[] = [
'role' => 'system',
'content' => $system_message
];
}
foreach ($history as $history_message) {
$content = $history_message->getText();
if (empty($content)) continue;
$history_array[] = [
'role' => $history_message->getRole(),
'content' => $content
];
}
return $history_array;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment