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

Issue #3439019 by mindaugasd: Connect with AI prompt engineering module

parent 9d6eaecd
No related branches found
No related tags found
No related merge requests found
Pipeline #140117 passed with warnings
......@@ -9,7 +9,10 @@ use Drupal\aichat\MessageInterface;
use Drupal\aichat\Plugin\AIChatBackendBase;
use Drupal\Component\Uuid\UuidInterface;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Entity\EntityFieldManager;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\key\KeyRepositoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
......@@ -23,6 +26,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
*/
class DefaultBackend extends AIChatBackendBase {
use DependencySerializationTrait;
/**
* The UUID service.
*
......@@ -37,15 +42,32 @@ class DefaultBackend extends AIChatBackendBase {
*/
protected $keys;
/**
* The entity field manager.
*
* @var \Drupal\Core\Entity\EntityFieldManager
*/
protected $entityFieldManager;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* {@inheritdoc}
*/
protected $messages;
public function __construct(array $configuration, $plugin_id, $plugin_definition, UuidInterface $uuid, KeyRepositoryInterface $keys) {
public function __construct(array $configuration, $plugin_id, $plugin_definition, UuidInterface $uuid, KeyRepositoryInterface $keys,
EntityFieldManager $entity_field_manager, ModuleHandlerInterface $module_handler) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->uuid = $uuid;
$this->keys = $keys;
$this->entityFieldManager = $entity_field_manager;
$this->moduleHandler = $module_handler;
$this->messages = [];
}
......@@ -58,7 +80,9 @@ class DefaultBackend extends AIChatBackendBase {
$plugin_id,
$plugin_definition,
$container->get('uuid'),
$container->get('key.repository')
$container->get('key.repository'),
$container->get('entity_field.manager'),
$container->get('module_handler')
);
}
......@@ -76,7 +100,11 @@ class DefaultBackend extends AIChatBackendBase {
return [
'api_key' => ['default' => ''],
'model' => ['default' => 'gpt-4-turbo-preview'],
'system_message' => ['default' => '']
'system_prompt_type' => ['default' => 'text'],
'system_prompt_text' => ['default' => ''],
'system_prompt_fieldname_textfield' => ['default' => ''],
'system_prompt_fieldname_reference_aiprompt' => ['default' => ''],
'system_message' => ['default' => ''] // @TODO: deprecated
];
}
......@@ -88,7 +116,7 @@ class DefaultBackend extends AIChatBackendBase {
$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>'.
$instructions .= $this->t('This setup 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>';
......@@ -97,18 +125,22 @@ class DefaultBackend extends AIChatBackendBase {
$instructions .= $this->t('Current library status: <strong>@status</strong>',
['@status' => $library_info]);
$form['library_status'] = [
$form['setup_model'] = [
'#type' => 'fieldset',
'#title' => $this->t('AI model setup')
];
$form['setup_model']['library_status'] = [
'#type' => 'markup',
'#markup' => '<p>'.$instructions.'</p>'
];
$form['api_key'] = [
$form['setup_model']['api_key'] = [
'#type' => 'key_select',
'#title' => $this->t('OpenAI API key'),
'#default_value' => $this->getBackendConfigurationValue('api_key')
];
$form['model'] = [
$form['setup_model']['model'] = [
'#type' => 'select',
'#title' => $this->t('Model'),
'#options' => [
......@@ -119,16 +151,188 @@ class DefaultBackend extends AIChatBackendBase {
'#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')
];
$this->addSystemPromptFormElements($form, $form_state);
return $form;
}
/**
* Build part of the form.
*/
public function addSystemPromptFormElements(array &$form, FormStateInterface $form_state): void {
$values = $this->getPromptConfigValues($form_state);
$form['setup_prompt'] = [
'#type' => 'fieldset',
'#title' => $this->t('System prompt setup')
];
$instructions = '';
$instructions .= $this->t("System prompt (or system message) enables define a character of the assistant or provide context information for the conversation. </br>".
"AIChat have many different ways to configure it: </br><ul>\n".
"<li><strong>Simple text</strong> - system message will be the same across all conversations of the same type (easiest to setup, but not configurable for each conversation, so not recommended)</li>\n".
"<li><strong>Text field</strong> - system message is configurable in a standard Drupal text field for each separate conversation.</li>\n".
"<li>Reference field to the prompt provided by <strong>AI prompt engineering module</strong> - highest level of configurability provided by the module finetuned for prompt engineering</li>\n".
"</ul>");
$form['setup_prompt']['prompt_instructions'] = [
'#type' => 'markup',
'#markup' => '<p>'.$instructions.'</p>'
];
$prompt_type = $values['system_prompt_type'];
$form['setup_prompt']['system_prompt_type'] = [
'#type' => 'select',
'#title' => $this->t('Prompt source'),
'#options' => [
'text' => 'Simple text',
'textfield' => 'Text field',
'reference_aiprompt' => 'AI prompt engineering module',
],
'#required' => TRUE,
'#default_value' => $prompt_type,
'#ajax' => [
'callback' => [$this, 'promptTypeAjaxCallback'],
'wrapper' => 'setup-prompt-ajax-container',
]
];
$form['setup_prompt']['ajax_container'] = [
'#type' => 'container',
'#attributes' => ['id' => 'setup-prompt-ajax-container']
];
$instructions = '';
if (in_array($prompt_type, ['text'])) {
$form['setup_prompt']['ajax_container']['system_prompt_text'] = [
'#type' => 'textarea',
'#title' => $this->t('System message'),
'#default_value' => $values['system_prompt_text'],
'#weight' => 5,
'#description' => $this->t('Here you can provide some context information for AI')
];
if (!empty($values['system_message_deprecated'])) { // @TODO: remove
$form['setup_prompt']['ajax_container']['system_message'] = [
'#type' => 'textarea',
'#title' => $this->t('System message (deprecated)'),
'#default_value' => $values['system_message_deprecated'],
'#weight' => 6,
'#description' => $this->t('Old system message field. No longer in use')
];
}
}
if (in_array($prompt_type, ['textfield', 'reference_aiprompt'])) {
$aichat_bundle = FALSE;
if (!empty($this->aichat_type)) {
$aichat_bundle = $this->aichat_type->id();
$field_definitions = $this->entityFieldManager->getFieldDefinitions('aichat', $aichat_bundle);
}
else {
$instructions = $this->t('It seems that entity type is not saved yet. Please save entity type and then create a new field.');
}
$field_options = [];
if ($aichat_bundle && in_array($prompt_type, ['textfield'])) {
if (!empty($field_definitions)) {
foreach ($field_definitions as $field_name => $field_definition) {
if (!in_array($field_definition->getType(), ['string', 'string_long', 'text', 'text_long', 'text_with_summary'])) continue;
if (in_array($field_name, ['title', 'data'])) continue;
$field_options[$field_name] = $field_name.' - '.$field_definition->getLabel();
}
}
$form['setup_prompt']['ajax_container']['system_prompt_fieldname_textfield'] = [
'#type' => 'select',
'#title' => $this->t('Select textfield field'),
'#options' => $field_options,
'#required' => TRUE,
'#disabled' => empty($field_options),
'#description' => empty($field_options) ? $this->t('No fields are available to select.') : '',
'#default_value' => $values['system_prompt_fieldname_textfield'],
'#weight' => 5
];
if (empty($field_options)) {
$instructions = '<bold>'.$this->t('Please create at least one text field for this conversation type, and then you will be able to select it here.').'</bold>';
}
}
if ($aichat_bundle && in_array($prompt_type, ['reference_aiprompt'])) {
if (!empty($field_definitions)) {
foreach ($field_definitions as $field_name => $field_definition) {
if ($field_definition->getType() == 'entity_reference') {
$target_type = $field_definition->getSetting('target_type');
if (in_array($target_type, ['aiprompt', 'aiprompt_config'])) {
$field_options[$field_name] = $field_name.' - '.$field_definition->getLabel();
}
}
}
}
$form['setup_prompt']['ajax_container']['system_prompt_fieldname_reference_aiprompt'] = [
'#type' => 'select',
'#title' => $this->t('Select AI Prompt reference field'),
'#options' => $field_options,
'#required' => TRUE,
'#disabled' => empty($field_options),
'#description' => empty($field_options) ? $this->t('No fields are available to select.') : '',
'#default_value' => $values['system_prompt_fieldname_reference_aiprompt'],
'#weight' => 5
];
if (empty($field_options)) {
$module_installed = $this->moduleHandler->moduleExists("aiprompt_content") ||
$this->moduleHandler->moduleExists("aiprompt_config");
if (!$module_installed) {
$instructions = $this->t('Please install AI prompt engineering module, and its content or config prompt storage sub-modules.');
}
else {
$instructions = $this->t('Please create at least one AI Prompt reference field for this conversation type, and then you will be able to select it here.');
}
}
}
}
if (!empty($instructions)) {
$form['setup_prompt']['ajax_container']['prompt_instructions'] = [
'#type' => 'markup',
'#markup' => '<p>'.$instructions.'</p>',
'#weight' => 4
];
}
}
/**
* Ajax callback function for the system prompt type selection.
*/
public function promptTypeAjaxCallback(array &$form, FormStateInterface $form_state) {
return $form['backend_configuration']['setup_prompt']['ajax_container'];
}
/**
* Get default values for the form.
*/
private function getPromptConfigValues(FormStateInterface $form_state): array {
$values = [];
// for future customization
$key = 'system_prompt_type';
$values[$key] = $form_state->getValue($key) ?? $this->getBackendConfigurationValue($key);
$key = 'system_prompt_text';
$values[$key] = $form_state->getValue($key) ?? $this->getBackendConfigurationValue($key);
$key = 'system_message';
$values[$key.'_deprecated'] = $form_state->getValue($key) ?? $this->getBackendConfigurationValue($key); // @TODO: remove
$key = 'system_prompt_fieldname_textfield';
$values[$key] = $form_state->getValue($key) ?? $this->getBackendConfigurationValue($key);
$key = 'system_prompt_fieldname_reference_aiprompt';
$values[$key] = $form_state->getValue($key) ?? $this->getBackendConfigurationValue($key);
return $values;
}
/**
* {@inheritdoc}
*/
......@@ -243,7 +447,7 @@ class DefaultBackend extends AIChatBackendBase {
$history = $this->getMessages(TRUE);
$history_array = [];
$system_message = $this->getBackendConfigurationValue('system_message');
$system_message = $this->getSystemMessage();
if (!empty($system_message)) {
$history_array[] = [
......@@ -266,4 +470,42 @@ class DefaultBackend extends AIChatBackendBase {
return $history_array;
}
/**
* Method to get system message.
*/
public function getSystemMessage(): string {
$prompt_type = $this->getBackendConfigurationValue('system_prompt_type');
switch ($prompt_type) {
case 'text':
return $this->getBackendConfigurationValue('system_prompt_text');
case 'textfield':
$fieldname = $this->getBackendConfigurationValue('system_prompt_fieldname_textfield');
if (!empty($this->aichat->$fieldname)) {
$value = $this->aichat->$fieldname->value;
$format = $this->aichat->$fieldname->format ?? NULL;
if (!empty($format)) {
return check_markup($value, $format);
}
return $value;
}
break;
case 'reference_aiprompt':
$fieldname = $this->getBackendConfigurationValue('system_prompt_fieldname_reference_aiprompt');
if (!empty($this->aichat->$fieldname)) {
$prompt_entity = $this->aichat->$fieldname->entity;
}
if (!empty($prompt_entity)) {
return $prompt_entity->toString();
}
break;
}
return '';
}
}
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