Skip to content
Snippets Groups Projects
Commit cd84887c authored by Scott Euser's avatar Scott Euser Committed by Marcus Johansson
Browse files

Resolve #3483356 "Move to contrib"

parent b0fa9a0b
No related branches found
No related tags found
1 merge request!224Resolve #3483356 "Move to contrib"
Pipeline #323983 passed
Showing
with 183 additions and 653 deletions
......@@ -36,8 +36,3 @@ phpstan:
before_script:
- composer require --dev drush/drush
.phpunit-base:
before_script:
- npm install -g @mockoon/cli
- export MOCKOON_BASEHOST=http://localhost:3010 && mockoon-cli start --data tests/assets/mockoon/openai.json --port 3010 & > /dev/null 2>&1
......@@ -36,6 +36,29 @@ function ai_requirements($phase) {
];
}
}
elseif ($phase === 'update') {
/** @var \Drupal\Core\Extension\ModuleExtensionList $list */
$list = \Drupal::service('extension.list.module');
/** @var \Drupal\Core\Extension\ModuleHandlerInterface $handler */
$handler = \Drupal::service('module_handler');
// Find any installed providers that do not yet have their corresponding new
// module composer required.
foreach (_ai_providers_mapping() as $submodule => $external_module) {
if ($handler->moduleExists($submodule)) {
$requirements['ai_requirement_' . $submodule] = [
'title' => t('External module "@external_module" should exist and be ready to replace the currently installed "@submodule".', [
'@external_module' => $external_module,
'@submodule' => $submodule,
]),
'value' => $list->exists($external_module) ? t('Yes') : t('Please run "composer require drupal/@external_module"', [
'@external_module' => $external_module,
]),
'severity' => $list->exists($external_module) ? REQUIREMENT_OK : REQUIREMENT_ERROR,
];
}
}
}
return $requirements;
}
......@@ -53,3 +76,75 @@ function ai_update_10300() {
\Drupal::configFactory()->getEditable('ai_models.settings')->delete();
}
}
/**
* Checks to ensure a smooth migration from AI submodules to external modules.
*/
function ai_update_10301() {
/** @var \Drupal\Core\Extension\ModuleExtensionList $list */
$list = \Drupal::service('extension.list.module');
/** @var \Drupal\Core\Extension\ModuleInstallerInterface $installer */
$installer = \Drupal::service('module_installer');
/** @var \Drupal\Core\Extension\ModuleHandlerInterface $handler */
$handler = \Drupal::service('module_handler');
// Find any providers that do not yet have their corresponding new module
// composer required.
$missing = [];
foreach (_ai_providers_mapping() as $submodule => $external_module) {
// phpcs:disable
// Scenarios are:
// 1. Old submodule is not enabled -> we have nothing to do.
// 2. Old submodule is enabled:
// 1. New module exists but is not yet installed -> Install it.
// 2. New module does not exist and is therefore not installed -> Tell
// the user to composer require it and retry the update hook.
// 3. New module exists and is installed (yet the old is still enabled,
// see (1) -> Since the new module should uninstall the old, this
// should never happen. Throw an error.
// phpcs:enable
// If the new module is already enabled, it should have uninstalled the
// old module. 'Module Exists' method checks if the module is actually
// enabled too.
if (!$handler->moduleExists($submodule)) {
continue;
}
// If the new module exists but is not yet installed, enable it. The 'list'
// service exists method only checks that the module is in the file system,
// but does not care if its enabled or not.
if ($list->exists($external_module) && !$handler->moduleExists($external_module)) {
// No try-catch, we want a failure to throw an error.
$installer->install([$external_module]);
continue;
}
// If the new module does not exist but the old submodule is enabled,
// we need to tell the user to composer require it. We will then fail this
// update hook so that we can try it again later after they have carried out
// the composer install.
if (!$list->exists($external_module)) {
$missing[$submodule] = 'drupal/' . $external_module;
continue;
}
// New module is installed but so does is the old submodule (since we
// already checked that above), but as noted.
if ($handler->moduleExists($external_module)) {
$message = t("Only '@external_module' should be installed, not '@submodule'.", [
'@external_module' => $external_module,
'@submodule' => $submodule,
]);
die((string) $message);
}
}
if ($missing) {
$message = t('A number of modules you have installed have been moved from the AI Core to a separate modules. Please run `composer require @missing` then run this update hook again.', [
'@missing' => implode(' ', $missing),
]);
die((string) $message);
}
return t('AI module moving to contributed modules has been completed.');
}
......@@ -38,3 +38,85 @@ function ai_is_admin_route(RouteMatchInterface $route_match) {
return FALSE;
}
/**
* Implements hook_module_preuninstall().
*/
function ai_module_preuninstall($module) {
$providers = _ai_providers_mapping();
// Check if the module is one of the old submodules.
if (array_key_exists($module, $providers)) {
$submodule = $module;
$external_module = $providers[$module];
// External module is already installed as expected. No action needed, so
// we can bail out.
/** @var \Drupal\Core\Extension\ModuleHandlerInterface $handler */
$handler = \Drupal::service('module_handler');
if ($handler->moduleExists($external_module)) {
return;
}
// External module exists but is not enabled, enable it. This will trigger
// the hook install if that module so it copies over the configuration
// before the submodule that is being uninstalled completes its uninstall.
/** @var \Drupal\Core\Extension\ModuleExtensionList $list */
$list = \Drupal::service('extension.list.module');
if ($list->exists($external_module)) {
/** @var \Drupal\Core\Extension\ModuleInstallerInterface $installer */
$installer = \Drupal::service('module_installer');
$installer->install([$external_module]);
return;
}
// Check if there is configuration for the submodule.
$config_factory = \Drupal::configFactory();
$settings = $config_factory->get($submodule . '.settings');
if (!empty($settings->get('api_key'))) {
// Determine the path to configure the submodule.
if (str_starts_with($submodule, 'vdb_')) {
$path = 'admin/config/ai/vdb_providers/' . str_replace('vdb_provider_', '', $submodule);
}
else {
$path = 'admin/config/ai/providers/' . str_replace('provider_', '', $submodule);
}
// Let the user make a conscious choice:
// - Remove the key from the provider settings to allow uninstall.
// - Ensure that the replacement external module is enabled first.
// This ensures we prevent accidental loss of the configuration.
$message = t("You are attempting to uninstall '@submodule' yet the replacement '@external_module' has not yet been installed. If you intentionally want to uninstall this module without having the replacement in place, unset the the API Key at '@path'. You can find the new module under @link or download it by running `composer require drupal/@external_module`.", [
'@submodule' => $submodule,
'@external_module' => $external_module,
'@link' => 'https://www.drupal.org/project/' . $external_module,
'@path' => $path,
]);
die((string) $message);
}
}
}
/**
* Mapping from the provider submodules to the new external modules.
*
* @return string[]
* The submodule providers mapped to the external module providers.
*
* @internal
*/
function _ai_providers_mapping(): array {
// @todo verify the destination ai providers are correct.
return [
'provider_anthropic' => 'ai_provider_anthropic',
'provider_groq' => 'ai_provider_groq',
'provider_huggingface' => 'ai_provider_huggingface',
'provider_lmstudio' => 'ai_provider_lmstudio',
'provider_mistral' => 'ai_provider_mistral',
'provider_ollama' => 'ai_provider_ollama',
'provider_openai' => 'ai_provider_openai',
'vdb_provider_pinecone' => 'ai_vdb_provider_pinecone',
'vdb_provider_milvus' => 'ai_vdb_provider_milvus',
];
}
......@@ -3,14 +3,10 @@
"description": "AI module for Drupal",
"type": "drupal-module",
"require": {
"openai-php/client": ">=v0.10.1",
"wpai-inc/anthropic-sdk-php": ">=0.1.0",
"drupal/key": "^1.18",
"league/commonmark": "^2.5",
"league/html-to-markdown": "^5.1",
"yethee/tiktoken": "^0.5.1",
"hkulekci/qdrant": "^0.5.5",
"scotteuser/pinecone-php": "^1.0.2"
"yethee/tiktoken": "^0.5.1"
},
"require-dev": {
"drupal/eca": "^2.0",
......
# Pinecone Vector Database Provider
## Overview
This Drupal module provides integration with Pinecone, a managed vector database
service. It includes features for inserting, deleting, and managing vector data.
This module is intended to support the 'Serverless' architecture only. The VDB
provider methods around Collections therefore map to Pinecone's namespace
architecture.
If you intend to use Pinecone's Pod infrastructure, that should be provided via
a third-party module implementing a new VDB Provider.
## Requirements
- Pinecone serverless account (starter is fine) and API key.
## Installation
1. Enable the module.
2. Configure the API connection to Pine via Admin > Configuration > Vector
Database Providers > Pinecone.
3. Create an Index in the Pinecone UI.
4. Create a new Search API Server, select Pinecone as the backend, and select
that Index.
5. Set up AI Search as desired (see AI Search documentation).
## Contributing to the Pinecone PHP library dependency.
See https://github.com/probots-io/pinecone-php
\ No newline at end of file
......@@ -39,7 +39,6 @@ nav:
- Ollama: modules/providers/provider_ollama/index.md
- VDB Providers:
- Milvus: modules/vdb_providers/vdb_provider_milvus/index.md
- Pinecone: modules/vdb_providers/vdb_provider_pinecone/index.md
- Providers: providers/matris.md
docs_dir: docs
site_dir: site
......@@ -20,7 +20,7 @@ class AiAssistantFormTest extends WebDriverTestBase {
*/
protected static $modules = [
'ai',
'provider_openai',
'ai_test',
'key',
'ai_assistant_api',
'file',
......@@ -36,35 +36,6 @@ class AiAssistantFormTest extends WebDriverTestBase {
*/
protected $defaultTheme = 'claro';
/**
* Setup the test.
*/
protected function setUp(): void {
parent::setUp();
// Create an OpenAI mockup key.
/** @var \Drupal\key\Entity\Key */
$key = \Drupal::entityTypeManager()
->getStorage('key')
->create([
'id' => 'mockup_openai',
'label' => 'Mockup OpenAI',
'key_provider' => 'config',
]);
$key->setKeyValue('abc123');
$key->save();
// DDEV or local.
$host = getenv('DDEV_PROJECT') ? 'http://mockoon:3010/v1' : 'http://localhost:3010/v1';
// Setup OpenAI as the provider.
\Drupal::configFactory()
->getEditable('provider_openai.settings')
->set('host', $host)
->set('api_key', 'mockup_openai')
->save();
}
/**
* Test the Ajax form interaction.
*/
......@@ -80,7 +51,7 @@ class AiAssistantFormTest extends WebDriverTestBase {
$this->assertSession()->fieldExists('llm_ai_provider');
// Choose the OpenAI provider from the select field.
$this->getSession()->getPage()->selectFieldOption('llm_ai_provider', 'openai');
$this->getSession()->getPage()->selectFieldOption('llm_ai_provider', 'echoai');
// Wait for the Ajax request to complete.
$this->assertSession()->assertWaitOnAjaxRequest();
......@@ -89,7 +60,7 @@ class AiAssistantFormTest extends WebDriverTestBase {
$this->assertSession()->fieldExists('llm_ai_model');
// Choose GPT 3.5 from the select field.
$this->getSession()->getPage()->selectFieldOption('llm_ai_model', 'gpt-3.5-turbo');
$this->getSession()->getPage()->selectFieldOption('llm_ai_model', 'gpt-test');
// Wait for the Ajax request to complete.
$this->assertSession()->assertWaitOnAjaxRequest();
......
......@@ -6,4 +6,3 @@ core_version_requirement: ^10.2 || ^11
dependencies:
- ai:ai
- provider_openai:provider_openai
api_key: ''
openai_moderation: true
version: '20240229'
provider_anthropic.settings:
type: config_object
label: 'Anthropic Settings'
mapping:
api_key:
type: string
label: 'The API key for Anthropic via Key module'
openai_moderation:
type: boolean
label: 'Enable moderation requests before each request.'
version:
type: string
label: 'The version of Anthropic API to use.'
chat:
input:
description: 'Input provided to the model.'
type: 'array'
default:
- { role: "system", content: "You are a helpful assistant." }
- { role: "user", content: "Introduce yourself!" }
required: true
authentication:
description: 'Anthropic API Key.'
type: 'string'
default: ''
required: true
configuration:
max_tokens:
label: 'Max Tokens'
description: 'The maximum number of tokens that can be generated in the chat completion.'
type: 'integer'
default: 1024
required: false
temperature:
label: 'Temperature'
description: 'Sampling temperature 0-1. Higher values mean more random output.'
type: 'float'
default: ''
required: false
constraints:
min: 0
max: 1
step: 0.1
top_p:
label: 'Top P'
description: 'An alternative to sampling with temperature, called nucleus sampling.'
type: 'float'
default:
required: false
constraints:
min: 0
max: 1
step: 0.1
top_k:
label: 'Top K'
description: 'Used to remove "long tail" low probability responses. '
type: 'float'
default:
required: false
constraints:
min: 0
max: 1
step: 0.1
(function (Drupal) {
// Load on document ready, vanilla js.
document.addEventListener('DOMContentLoaded', function () {
// Listener to the checkbox on id edit-moderation-checkbox.
document.getElementById('edit-moderation-checkbox')
.addEventListener('change', function() {
// If it is checked, set the button to disabled for 10 seconds.
if (this.checked) {
let timer = 10000;
document.getElementById('edit-submit').disabled = true;
// Change the value of the button to "Verifying...".
document.getElementById('edit-submit').value = Drupal.t('Please read the text of the checkbox and wait... 10 seconds');
let newInterval = setInterval(function() {
timer -= 1000;
// Change the value of the button to "Verifying...".
document.getElementById('edit-submit').value = Drupal.t('Please read the text of the checkbox and wait... ' + (timer/1000) + ' seconds');
if (timer <= 0) {
// Clear the interval and enable the button.
clearInterval(newInterval);
document.getElementById('edit-submit').disabled = false;
document.getElementById('edit-submit').value = Drupal.t('Save Configuration');
}
}, 1000);
}
});
});
})(Drupal);
......@@ -3,7 +3,8 @@ description: This enables the use of Anthropic for the AI module.
package: AI Providers
type: module
core_version_requirement: ^10.3 || ^11
configure: provider_anthropic.settings_form
lifecycle: deprecated
lifecycle_link: "https://www.drupal.org/project/ai/issues/3483356"
dependencies:
- ai:ai
......
<?php
/**
* @file
* Install, update, and uninstall functions for the provider_anthropic module.
*/
/**
* Implements hook_update_N().
*/
function provider_anthropic_update_10301() {
// Update the version id, so that Sonnet 3.5 is possible.
$config = \Drupal::configFactory()->getEditable('provider_anthropic.settings');
$config->set('version', '20240620');
$config->save();
}
/**
* Implements hook_update_N().
*/
function provider_anthropic_update_10302() {
// If the moderation is set, we need to update the version.
$config = \Drupal::configFactory()->get('provider_anthropic.settings');
if ($config->get('moderation', '1')) {
$config = \Drupal::configFactory()->getEditable('ai_external_moderation.settings');
$moderations = $config->get('moderations');
foreach ($moderations as $key => $value) {
if ($key === 'anthropic__chat') {
$moderations[] = [
'provider' => 'anthropic',
'tags' => '',
'model_title' => '',
'models' => [
'openai__text-moderation-latest',
],
];
unset($moderations[$key]);
}
}
$config->set('moderations', $moderations);
$config->save();
}
}
verification:
js:
js/verification.js: {}
provider_anthropic.settings_menu:
title: "Anthropic Authentication"
description: "Setup Anthropic"
route_name: provider_anthropic.settings_form
parent: ai.admin_providers
provider_anthropic.settings_form:
path: '/admin/config/ai/providers/anthropic'
defaults:
_form: '\Drupal\provider_anthropic\Form\AnthropicConfigForm'
_title: 'Setup Anthropic Authentication'
requirements:
_permission: 'administer ai providers'
<?php
namespace Drupal\provider_anthropic\Form;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\ai\AiProviderPluginManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Configure Anthropic API access.
*/
class AnthropicConfigForm extends ConfigFormBase {
/**
* Config settings.
*/
const CONFIG_NAME = 'provider_anthropic.settings';
/**
* The AI provider manager.
*
* @var \Drupal\ai\AiProviderPluginManager
*/
protected $aiProviderManager;
/**
* Module Handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Constructs a new AnthropicConfigForm object.
*/
final public function __construct(AiProviderPluginManager $ai_provider_manager, ModuleHandlerInterface $module_handler) {
$this->aiProviderManager = $ai_provider_manager;
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
final public static function create(ContainerInterface $container) {
return new static(
$container->get('ai.provider'),
$container->get('module_handler')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'anthropic_settings';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return [
static::CONFIG_NAME,
];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config(static::CONFIG_NAME);
$form['#attached']['library'][] = 'provider_anthropic/verification';
$form['api_key'] = [
'#type' => 'key_select',
'#title' => $this->t('Anthropic API Key'),
'#description' => $this->t('The API Key. Can be found on <a href="https://console.anthropic.com/settings/keys">https://console.anthropic.com/settings/keys</a>.'),
'#default_value' => $config->get('api_key'),
];
// Check if the OpenAI provider is enabled and usabled.
$disabled = TRUE;
$description = $this->t('Enable OpenAI moderation for any Anthropic chat query.');
if ($this->moduleHandler->moduleExists('provider_openai') && $this->aiProviderManager->createInstance('openai')->isUsable() && $this->moduleHandler->moduleExists('ai_external_moderation')) {
$disabled = FALSE;
}
else {
$description .= ' ' . $this->t('<strong>AI External Moderation module and/or OpenAI provider is not enabled or usable. Please enable the OpenAI provider and the AI External Moderation module to use this feature.</strong>');
}
$form['version'] = [
'#type' => 'textfield',
'#title' => $this->t('Anthropic Version'),
'#description' => $this->t('The version of the Anthropic API to use. This could need to be changed if the API gets updated with a better model. See https://docs.anthropic.com/en/docs/models-overview.'),
'#default_value' => $config->get('version'),
'#required' => TRUE,
];
$form['openai_moderation'] = [
'#type' => 'checkbox',
'#title' => $this->t('Enable OpenAI Moderation'),
'#description' => $description,
'#default_value' => $config->get('openai_moderation'),
'#disabled' => $disabled,
];
$form['moderation_checkbox'] = [
'#type' => 'checkbox',
'#title' => $this->t('No Moderation Needed'),
'#description' => $this->t('I hereby understand that Anthropic is being run without moderation, which might lead to me sending a prompt that will be seen as malicious to Anthropic, THAT WILL GET ME BANNED.'),
];
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
if ($form_state->getValue('openai_moderation') == 0 && !$form_state->getValue('moderation_checkbox')) {
$form_state->setErrorByName('moderation_checkbox', $this->t('You need to verify that you understand the consequences of disabling moderation.'));
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Retrieve the configuration.
$this->config(static::CONFIG_NAME)
->set('api_key', $form_state->getValue('api_key'))
->set('openai_moderation', $form_state->getValue('openai_moderation'))
->set('version', $form_state->getValue('version'))
->save();
// Get the configuration of the AI External Moderation.
$config = $this->configFactory->getEditable('ai_external_moderation.settings');
$moderations = $config->get('moderations');
if ($form_state->getValue('openai_moderation')) {
foreach ($moderations as $key => $value) {
if ($value['provider'] === 'anthropic') {
break;
}
}
$moderations[] = [
'provider' => 'anthropic',
'tags' => '',
'model_title' => '',
'models' => [
'openai__text-moderation-latest',
],
];
}
else {
foreach ($moderations as $key => $value) {
if ($value['provider'] === 'anthropic' && isset($value['models'][0]) && $value['models'][0] === 'openai__text-moderation-latest') {
unset($moderations[$key]);
}
}
}
$config->set('moderations', $moderations);
$config->save();
// Set some defaults.
$this->aiProviderManager->defaultIfNone('chat', 'anthropic', 'claude-3-sonnet-20240229');
$this->aiProviderManager->defaultIfNone('chat_with_image_json', 'anthropic', 'claude-3-sonnet-20240229');
$this->aiProviderManager->defaultIfNone('chat_with_complex_json', 'anthropic', 'claude-3-5-sonnet-20240620');
parent::submitForm($form, $form_state);
}
}
<?php
namespace Drupal\provider_anthropic\Plugin\AiProvider;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\ai\Attribute\AiProvider;
use Drupal\ai\Base\AiProviderClientBase;
use Drupal\ai\Enum\AiModelCapability;
use Drupal\ai\Exception\AiQuotaException;
use Drupal\ai\Exception\AiResponseErrorException;
use Drupal\ai\OperationType\Chat\ChatInput;
use Drupal\ai\OperationType\Chat\ChatInterface;
use Drupal\ai\OperationType\Chat\ChatMessage;
use Drupal\ai\OperationType\Chat\ChatOutput;
use Drupal\ai\Traits\OperationType\ChatTrait;
use Symfony\Component\Yaml\Yaml;
use WpAi\Anthropic\AnthropicAPI;
/**
* Plugin implementation of the 'anthropic' provider.
*/
#[AiProvider(
id: 'anthropic',
label: new TranslatableMarkup('Anthropic'),
)]
class AnthropicProvider extends AiProviderClientBase implements
ChatInterface {
use ChatTrait;
/**
* The Anthropic Client.
*
* @var \WpAi\Anthropic\AnthropicAPI|null
*/
protected $client;
/**
* API Key.
*
* @var string
*/
protected string $apiKey = '';
/**
* Run moderation call, before a normal call.
*
* @var bool
*/
protected bool $moderation = TRUE;
/**
* {@inheritdoc}
*/
public function getConfiguredModels(?string $operation_type = NULL, array $capabilities = []): array {
// No complex JSON support.
if (in_array(AiModelCapability::ChatJsonOutput, $capabilities)) {
return [
'claude-3-5-sonnet-20240620' => 'Claude 3.5 Sonnet',
];
}
// Anthropic hard codes :/.
if ($operation_type == 'chat') {
return [
'claude-3-5-sonnet-20240620' => 'Claude 3.5 Sonnet',
'claude-3-opus-20240229' => 'Claude 3 Opus',
'claude-3-sonnet-20240229' => 'Claude 3 Sonnet',
'claude-3-haiku-20240307' => 'Claude 3 Haiku',
];
}
return [];
}
/**
* {@inheritdoc}
*/
public function isUsable(?string $operation_type = NULL, array $capabilities = []): bool {
// If its not configured, it is not usable.
if (!$this->getConfig()->get('api_key')) {
return FALSE;
}
// If its one of the bundles that Anthropic supports its usable.
if ($operation_type) {
return in_array($operation_type, $this->getSupportedOperationTypes());
}
return TRUE;
}
/**
* {@inheritdoc}
*/
public function getSupportedOperationTypes(): array {
return [
'chat',
];
}
/**
* {@inheritdoc}
*/
public function getConfig(): ImmutableConfig {
return $this->configFactory->get('provider_anthropic.settings');
}
/**
* {@inheritdoc}
*/
public function getApiDefinition(): array {
// Load the configuration.
$definition = Yaml::parseFile($this->moduleHandler->getModule('provider_anthropic')->getPath() . '/definitions/api_defaults.yml');
return $definition;
}
/**
* {@inheritdoc}
*/
public function getModelSettings(string $model_id, array $generalConfig = []): array {
return $generalConfig;
}
/**
* {@inheritdoc}
*/
public function setAuthentication(mixed $authentication): void {
// Set the new API key and reset the client.
$this->apiKey = $authentication;
$this->client = NULL;
}
/**
* {@inheritdoc}
*/
public function chat(array|string|ChatInput $input, string $model_id, array $tags = []): ChatOutput {
$this->loadClient();
// Normalize the input if needed.
$chat_input = $input;
$system_prompt = '';
if ($input instanceof ChatInput) {
$chat_input = [];
foreach ($input->getMessages() as $message) {
// System prompts are a variable.
if ($message->getRole() == 'system') {
$system_prompt = $message->getText();
continue;
}
if (count($message->getImages())) {
foreach ($message->getImages() as $image) {
$content[] = [
'type' => 'image',
'source' => [
'type' => 'base64',
'media_type' => $image->getMimeType(),
'data' => $image->getAsBase64EncodedString(''),
],
];
}
}
$content[] = [
'type' => 'text',
'text' => $message->getText(),
];
$chat_input[] = [
'role' => $message->getRole(),
'content' => $content,
];
}
}
$payload = [
'model' => $model_id,
'messages' => $chat_input,
] + $this->configuration;
if (!isset($payload['system']) && $system_prompt) {
$payload['system'] = $system_prompt;
}
// Unset Max Tokens.
$max_tokens = $payload['max_tokens'] ?? 1024;
unset($payload['max_tokens']);
$headers = [];
if ($this->chatSystemRole) {
$payload['system'] = $this->chatSystemRole;
}
try {
/** @var \WpAi\Anthropic\Responses\Response */
$response_object = $this->client->messages()->maxTokens($max_tokens)->create($payload, $headers);
$response = $response_object->content;
}
catch (\Exception $e) {
// Try to figure out credit issues.
if (strpos($e->getMessage(), 'credit balance is too low ') !== FALSE) {
throw new AiQuotaException($e->getMessage());
}
else {
throw $e;
}
}
if (!isset($response[0]['text'])) {
throw new AiResponseErrorException('Invalid response from Anthropic');
}
$message = new ChatMessage('', $response[0]['text']);
return new ChatOutput($message, $response, []);
}
/**
* Enables moderation response, for all next coming responses.
*/
public function enableModeration(): void {
$this->moderation = TRUE;
}
/**
* Disables moderation response, for all next coming responses.
*/
public function disableModeration(): void {
$this->moderation = FALSE;
}
/**
* Gets the raw client.
*
* @param string $api_key
* If the API key should be hot swapped.
*
* @return \WpAi\Anthropic\AnthropicAPI
* The Anthropic client.
*/
public function getClient(string $api_key = ''): AnthropicAPI {
if ($api_key) {
$this->setAuthentication($api_key);
}
$this->loadClient();
return $this->client;
}
/**
* Loads the Anthropic Client with authentication if not initialized.
*/
protected function loadClient(): void {
if (!$this->client) {
if (!$this->apiKey) {
$this->setAuthentication($this->loadApiKey());
}
$this->client = new AnthropicAPI($this->apiKey);
}
}
}
api_key: ''
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