Skip to content
Snippets Groups Projects
Commit 6565dd68 authored by Marcus Johansson's avatar Marcus Johansson
Browse files

Issue #3453596 by Marcus_Johansson, wouters_f: MistralAI LLM Provider

parent 42bde8d7
No related branches found
No related tags found
No related merge requests found
Pipeline #204758 passed with warnings
Showing with 341 additions and 1 deletion
provider_groq.settings:
type: mapping
label: 'LM Studio Settings'
label: 'Groq Settings'
mapping:
api_key:
type: string
......
api_key: ''
provider_mistral.settings:
type: mapping
label: 'Mistral AI Settings'
mapping:
api_key:
type: string
label: 'API Key'
required: true
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: 'Mistral 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: 0.7
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: 1
required: false
constraints:
min: 0
max: 1
step: 0.1
name: Mistral AI Provider
description: This enables the use of Mistral AI for the AI module.
package: AI Providers
type: module
core_version_requirement: ^10.3 || ^11
configure: provider_mistral.settings_form
dependencies:
- ai:ai
- key:key
provider_mistral.settings_menu:
title: "Mistral AI Configuration"
description: "Setup Mistral AI"
route_name: provider_mistral.settings_form
parent: ai.admin_providers
provider_mistral.settings_form:
path: '/admin/config/ai/providers/mistral'
defaults:
_form: '\Drupal\provider_mistral\Form\MistralConfigForm'
_title: 'Setup Mistral AI Authentication'
requirements:
_permission: 'administer ai providers'
<?php
namespace Drupal\provider_mistral\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Configure Mistral API access.
*/
class MistralConfigForm extends ConfigFormBase {
/**
* Config settings.
*/
const CONFIG_NAME = 'provider_mistral.settings';
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'mistral_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['api_key'] = [
'#type' => 'key_select',
'#title' => $this->t('Mistral API Key'),
'#description' => $this->t('The API Key. Can be found on <a href="https://console.mistral.ai/api-keys/">https://console.mistral.ai/api-keys/</a>.'),
'#default_value' => $config->get('api_key'),
];
return parent::buildForm($form, $form_state);
}
/**
* {@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'))
->save();
parent::submitForm($form, $form_state);
}
}
<?php
namespace Drupal\provider_mistral\Plugin\AiProvider;
use Drupal\ai\Attribute\AiProvider;
use Drupal\ai\Base\AiProviderClientBase;
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\OperationType\Embeddings\EmbeddingsInput;
use Drupal\ai\OperationType\Embeddings\EmbeddingsInterface;
use Drupal\ai\OperationType\Embeddings\EmbeddingsOutput;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use OpenAI\Client;
use Symfony\Component\Yaml\Yaml;
/**
* Plugin implementation of the 'mistral' provider.
*/
#[AiProvider(
id: 'mistral',
label: new TranslatableMarkup('Mistral AI'),
)]
class MistralProvider extends AiProviderClientBase implements
ContainerFactoryPluginInterface,
ChatInterface,
EmbeddingsInterface {
/**
* The OpenAI Client for API calls.
*
* @var \OpenAI\Client|null
*/
protected $client;
/**
* API Key.
*
* @var string
*/
protected string $apiKey = '';
/**
* {@inheritdoc}
*/
public function getConfiguredModels(string $operation_type = NULL): array {
$response = $this->getClient()->models()->list()->toArray();
$models = [];
if ($operation_type == 'chat') {
if (isset($response['data'])) {
foreach ($response['data'] as $model) {
$models[$model['id']] = $model['id'];
}
}
}
elseif ($operation_type == 'embeddings') {
$models['mistral-embed'] = 'mistral-embed';
}
return $models;
}
/**
* {@inheritdoc}
*/
public function isUsable(string $operation_type = NULL): bool {
// If its not configured, it is not usable.
if (!$this->getConfig()->get('api_key')) {
return FALSE;
}
// If its one of the bundles that Mistral supports its usable.
if ($operation_type) {
return in_array($operation_type, $this->getSupportedOperationTypes());
}
return TRUE;
}
/**
* {@inheritdoc}
*/
public function getSupportedOperationTypes(): array {
return [
'chat',
'embeddings',
];
}
/**
* {@inheritdoc}
*/
public function getConfig(): ImmutableConfig {
return $this->configFactory->get('provider_mistral.settings');
}
/**
* {@inheritdoc}
*/
public function getApiDefinition(): array {
// Load the configuration.
$definition = Yaml::parseFile($this->moduleHandler->getModule('provider_mistral')->getPath() . '/definitions/api_defaults.yml');
return $definition;
}
/**
* {@inheritdoc}
*/
public function getModelSettings(string $model_id): array {
return [];
}
/**
* {@inheritdoc}
*/
public function setAuthentication(mixed $authentication): void {
// Set the new API key and reset the client.
$this->apiKey = $authentication;
$this->client = NULL;
}
/**
* Gets the raw client.
*
* This is the client for inference.
*
* @return \OpenAI\Client
* The OpenAI client.
*/
public function getClient(): Client {
$this->loadClient();
return $this->client;
}
/**
* Loads the Mistral Client with authentication if not initialized.
*/
protected function loadClient(): void {
if (!$this->client) {
if (!$this->apiKey) {
$this->setAuthentication($this->loadApiKey());
}
$this->client = \OpenAI::factory()
->withApiKey($this->apiKey)
->withBaseUri('https://api.mistral.ai/v1')
->withHttpClient($this->httpClient)
->make();
}
}
/**
* {@inheritdoc}
*/
public function chat(array|ChatInput $input, string $model_id, array $tags = []): ChatOutput {
$this->loadClient();
// Normalize the input if needed.
$chat_input = $input;
if ($input instanceof ChatInput) {
$chat_input = [];
foreach ($input->getMessages() as $message) {
$chat_input[] = [
'role' => $message->getRole(),
'content' => $message->getMessage(),
];
}
}
$payload = [
'model' => $model_id,
'messages' => $chat_input,
] + $this->configuration;
$response = $this->client->chat()->create($payload)->toArray();
$message = new ChatMessage($response['choices'][0]['message']['role'], $response['choices'][0]['message']['content']);
return new ChatOutput($message, $response, []);
}
/**
* {@inheritdoc}
*/
public function embeddings(string|EmbeddingsInput $input, string $model_id, array $tags = []): EmbeddingsOutput {
$this->loadClient();
// Normalize the input if needed.
if ($input instanceof EmbeddingsInput) {
$input = $input->getPrompt();
}
// Send the request.
$payload = [
'model' => $model_id,
'input' => $input,
] + $this->configuration;
$response = $this->client->embeddings()->create($payload)->toArray();
return new EmbeddingsOutput($response['data'][0]['embedding'], $response, []);
}
/**
* Load API key from key module.
*
* @return string
* The API key.
*/
protected function loadApiKey(): string {
return $this->keyRepository->getKey($this->getConfig()->get('api_key'))->getKeyValue();
}
}
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