diff --git a/ai_provider_ollama.install b/ai_provider_ollama.install index 3fcbcf8..4ec7647 100644 --- a/ai_provider_ollama.install +++ b/ai_provider_ollama.install @@ -38,3 +38,11 @@ function ai_provider_ollama_install() { $installer->uninstall(['provider_ollama']); } } + +/** + * Implements hook_uninstall(). + */ +function simple_sitemap_engines_uninstall() { + $state = \Drupal::service('state'); + $state->delete('ai_provider_ollama.models'); +} diff --git a/ai_provider_ollama.services.yml b/ai_provider_ollama.services.yml index 8f1c57f..0190a0b 100644 --- a/ai_provider_ollama.services.yml +++ b/ai_provider_ollama.services.yml @@ -1,4 +1,4 @@ services: ai_provider_ollama.control_api: class: Drupal\ai_provider_ollama\OllamaControlApi - arguments: ['@http_client'] + arguments: ['@http_client_factory'] diff --git a/src/OllamaControlApi.php b/src/OllamaControlApi.php index c6e9ae7..93d63b4 100644 --- a/src/OllamaControlApi.php +++ b/src/OllamaControlApi.php @@ -3,7 +3,7 @@ namespace Drupal\ai_provider_ollama; use Drupal\Component\Serialization\Json; -use GuzzleHttp\Client; +use Drupal\Core\Http\ClientFactory; /** * Ollama Control API. @@ -11,25 +11,27 @@ use GuzzleHttp\Client; class OllamaControlApi { /** - * The http client. - */ - protected Client $client; - - /** - * The base host. + * The HTTP client. + * + * @var \Drupal\Core\Http\ClientFactory */ - protected string $baseHost; + protected ClientFactory $httpClient; /** * Constructs a new Ollama AI object. * - * @param \GuzzleHttp\Client $client - * Http client. + * @param \Drupal\Core\Http\ClientFactory $http_client + * The HTTP client. */ - public function __construct(Client $client) { - $this->client = $client; + public function __construct(ClientFactory $http_client) { + $this->httpClient = $http_client; } + /** + * The base host. + */ + protected string $baseHost; + /** * Sets connect data. * @@ -164,7 +166,7 @@ class OllamaControlApi { $new_url = rtrim($this->baseHost, '/') . '/' . $path; $new_url .= count($query_string) ? '?' . http_build_query($query_string) : ''; - $res = $this->client->request($method, $new_url, $options); + $res = $this->httpClient->fromOptions()->request($method, $new_url, $options); return $res->getBody(); } diff --git a/src/Plugin/AiProvider/OllamaProvider.php b/src/Plugin/AiProvider/OllamaProvider.php index f65fe41..be07274 100644 --- a/src/Plugin/AiProvider/OllamaProvider.php +++ b/src/Plugin/AiProvider/OllamaProvider.php @@ -3,6 +3,7 @@ namespace Drupal\ai_provider_ollama\Plugin\AiProvider; use Drupal\Core\Config\ImmutableConfig; +use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\StringTranslation\TranslatableMarkup; @@ -74,6 +75,27 @@ class OllamaProvider extends AiProviderClientBase implements */ protected $messenger; + /** + * Stores the state storage service. + * + * @var \Drupal\Core\State\StateInterface + */ + protected $state; + + /** + * The transliteration helper. + * + * @var \Drupal\Component\Transliteration\TransliterationInterface + */ + protected $transliteration; + + /** + * Ollama Models. + * + * @var array + */ + protected $models = []; + /** * Dependency Injection for the Ollama Control API. */ @@ -83,6 +105,8 @@ class OllamaProvider extends AiProviderClientBase implements $instance->controlApi->setConnectData($instance->getBaseHost()); $instance->currentUser = $container->get('current_user'); $instance->messenger = $container->get('messenger'); + $instance->state = $container->get('state'); + $instance->transliteration = $container->get('transliteration'); return $instance; } @@ -90,6 +114,8 @@ class OllamaProvider extends AiProviderClientBase implements * {@inheritdoc} */ public function getConfiguredModels(?string $operation_type = NULL, array $capabilities = []): array { + + $t = ""; // Graceful failure. try { $response = $this->controlApi->getModels(); @@ -104,23 +130,39 @@ class OllamaProvider extends AiProviderClientBase implements $models = []; if (isset($response['models'])) { foreach ($response['models'] as $model) { + $model_id = $this->getMachineName($model['model']); $root_model = explode(':', $model['model'])[0]; if ($operation_type == 'moderation') { if (in_array($root_model, [ 'shieldgemma', 'llama-guard3', ])) { - $models[$model['model']] = $model['name']; + $models[$model_id] = $model['name']; } } else { - $models[$model['model']] = $model['name']; + $models[$model_id] = $model['name']; } } } + + // Store models. + $this->state->set('ai_provider_ollama.models', $models); + $this->models = $models; + return $models; } + /** + * Get model by id from model list. + */ + protected function getModel($model_id) { + if (empty($this->models)) { + $this->models = $this->state->get('ai_provider_ollama.models') ?? $this->getConfiguredModels(); + } + return $this->models[$model_id] ?? $model_id; + } + /** * {@inheritdoc} */ @@ -226,6 +268,7 @@ class OllamaProvider extends AiProviderClientBase implements */ public function chat(array|string|ChatInput $input, string $model_id, array $tags = []): ChatOutput { $this->loadClient(); + $model_id = $this->getModel($model_id); // Normalize the input if needed. $chat_input = $input; $images_found = FALSE; @@ -256,9 +299,9 @@ class OllamaProvider extends AiProviderClientBase implements } } $payload = [ - 'model' => $model_id, - 'messages' => $chat_input, - ] + $this->configuration; + 'model' => $model_id, + 'messages' => $chat_input, + ] + $this->configuration; if (method_exists($input, 'getChatTools') && $input->getChatTools()) { $tools = $input->getChatTools()->renderToolsArray(); // Ollama does only support string enums. @@ -315,6 +358,7 @@ class OllamaProvider extends AiProviderClientBase implements */ public function embeddings(string|EmbeddingsInput $input, string $model_id, array $tags = []): EmbeddingsOutput { $this->loadClient(); + $model_id = $this->getModel($model_id); // Normalize the input if needed. if ($input instanceof EmbeddingsInput) { $input = $input->getPrompt(); @@ -335,10 +379,12 @@ class OllamaProvider extends AiProviderClientBase implements 'content' => $input instanceof ModerationInput ? $input->getPrompt() : $input, ]; + $model_id = $this->getModel($model_id); + $payload = [ - 'model' => $model_id, - 'messages' => $chat_input, - ] + $this->configuration; + 'model' => $model_id, + 'messages' => $chat_input, + ] + $this->configuration; $response = $this->client->chat()->create($payload)->toArray(); if (!isset($response['choices'][0]['message']['content'])) { @@ -370,6 +416,7 @@ class OllamaProvider extends AiProviderClientBase implements */ public function embeddingsVectorSize(string $model_id): int { $this->loadClient(); + $model_id = $this->getModel($model_id); $data = $this->controlApi->embeddingsVectorSize($model_id); if ($data) { return $data; @@ -397,7 +444,35 @@ class OllamaProvider extends AiProviderClientBase implements */ public function maxEmbeddingsInput($model_id = ''): int { $this->loadClient(); + $model_id = $this->getModel($model_id); return $this->controlApi->embeddingsContextSize($model_id); } + /** + * Generates a machine name from a string. + * + * This is basically the same as what is done in + * \Drupal\Core\Block\BlockBase::getMachineNameSuggestion() and + * \Drupal\system\MachineNameController::transliterate(), but it seems + * that so far there is no common service for handling this. + * Difference: We replacing '.' also. + * + * @param string $string + * String to have translated. + * + * @return string + * The machine name. + * + * @see \Drupal\Core\Block\BlockBase::getMachineNameSuggestion() + * @see \Drupal\system\MachineNameController::transliterate() + */ + protected function getMachineName($string): string { + $transliterated = $this->transliteration->transliterate($string, LanguageInterface::LANGCODE_DEFAULT, '_'); + $transliterated = mb_strtolower($transliterated); + + $transliterated = preg_replace('@[^a-z0-9_]+@', '_', $transliterated); + + return $transliterated; + } + }