PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 3.4.2
AI Engine – The Chatbot, AI Framework & MCP for WordPress v3.4.2
3.5.8 3.5.7 3.5.6 3.5.5 3.5.4 3.5.3 3.5.2 3.5.1 3.5.0 3.4.9 3.4.8 3.4.7 0.2.1 1.6.91 0.2.2 1.6.92 0.2.3 1.6.93 0.2.4 1.6.94 0.2.5 1.6.95 0.2.6 1.6.96 0.2.7 1.6.97 0.2.8 1.6.98 0.2.9 1.6.99 0.3.0 1.7.0 0.3.1 1.7.1 0.3.2 1.7.2 0.3.3 1.7.3 0.3.4 1.7.4 0.3.5 1.7.5 0.3.6 1.7.6 0.4.0 1.7.7 0.4.1 1.7.8 0.4.2 1.7.9 0.4.3 1.8.0 0.4.4 1.8.1 0.4.5 1.8.2 0.4.6 1.8.3 0.4.7 1.8.4 0.4.8 1.8.5 0.4.9 1.8.6 0.5.0 1.8.7 0.5.1 1.8.8 0.5.2 1.8.9 0.5.3 1.9.0 0.5.4 1.9.1 0.5.5 1.9.2 0.5.6 1.9.3 0.5.7 1.9.4 0.5.8 1.9.5 0.5.9 1.9.6 0.6.0 1.9.7 0.6.1 1.9.8 0.6.2 1.9.81 0.6.3 1.9.82 0.6.4 1.9.83 0.6.5 1.9.84 0.6.6 1.9.85 0.6.7 1.9.86 0.6.8 1.9.87 0.6.9 1.9.88 0.7.0 1.9.89 0.7.1 1.9.90 0.7.2 1.9.91 0.7.3 1.9.92 0.7.4 1.9.93 0.7.5 1.9.94 0.7.6 1.9.95 0.7.7 1.9.96 0.7.8 1.9.97 0.7.9 1.9.98 0.8.0 1.9.99 0.8.1 2.0.0 0.8.2 2.0.1 0.8.3 2.0.2 0.8.4 2.0.3 0.8.5 2.0.4 0.8.6 2.0.5 0.8.7 2.0.6 0.8.8 2.0.7 0.8.9 2.0.8 0.9.0 2.0.9 0.9.2 2.1.0 0.9.3 2.1.1 0.9.4 2.1.2 0.9.5 2.1.3 0.9.6 2.1.4 0.9.7 2.1.5 0.9.8 2.1.6 0.9.81 2.1.7 0.9.82 2.1.8 0.9.83 2.1.9 0.9.84 2.2.0 0.9.85 2.2.1 0.9.86 2.2.2 0.9.87 2.2.3 0.9.88 2.2.4 0.9.89 2.2.5 0.9.9 2.2.51 0.9.91 2.2.52 0.9.92 2.2.53 0.9.93 2.2.54 0.9.94 2.2.56 0.9.95 2.2.57 0.9.96 2.2.6 0.9.97 2.2.60 0.9.98 2.2.61 0.9.99 2.2.62 1.0.0 2.2.63 1.0.01 2.2.70 1.0.1 2.2.80 1.0.2 2.2.81 1.0.3 2.2.90 1.0.4 2.2.91 1.0.5 2.2.92 1.0.6 2.2.93 1.0.7 2.2.94 1.0.8 2.2.95 1.0.9 2.3.0 1.1.0 2.3.1 1.1.1 2.3.2 1.1.2 2.3.3 1.1.3 2.3.4 1.1.4 2.3.5 1.1.5 2.3.6 1.1.6 2.3.7 1.1.7 2.3.8 1.1.8 2.3.9 1.1.9 2.4.0 1.2.0 2.4.1 1.2.1 2.4.2 1.2.2 2.4.3 1.2.21 2.4.4 1.2.3 2.4.5 1.2.30 2.4.6 1.3.0 2.4.7 1.3.1 2.4.8 1.3.2 2.4.9 1.3.3 2.5.0 1.3.31 2.5.1 1.3.32 2.5.2 1.3.33 2.5.3 1.3.34 2.5.4 1.3.35 2.5.5 1.3.36 2.5.6 1.3.37 2.5.7 1.3.38 2.5.8 1.3.39 2.5.9 1.3.40 2.6.0 1.3.41 2.6.1 1.3.42 2.6.2 1.3.43 2.6.3 1.3.44 2.6.5 1.3.45 2.6.6 1.3.46 2.6.7 1.3.47 2.6.8 1.3.48 2.6.9 1.3.49 2.7.0 1.3.50 2.7.1 1.3.51 2.7.2 1.3.52 2.7.3 1.3.53 2.7.4 1.3.54 2.7.5 1.3.56 2.7.6 1.3.57 2.7.7 1.3.58 2.7.8 1.3.59 2.7.9 1.3.60 2.8.0 1.3.61 2.8.1 1.3.62 2.8.2 1.3.63 2.8.3 1.3.64 2.8.4 1.3.65 2.8.5 1.3.66 2.8.6 1.3.67 2.8.7 1.3.68 2.8.8 1.3.69 2.8.9 1.3.70 2.9.0 1.3.71 2.9.1 1.3.72 2.9.2 1.3.73 2.9.3 1.3.74 2.9.4 1.3.75 2.9.5 1.3.76 2.9.6 1.3.77 2.9.7 1.3.78 2.9.8 1.3.79 2.9.9 1.3.80 3.0.0 1.3.81 3.0.1 1.3.82 3.0.2 1.3.83 3.0.3 1.3.84 3.0.4 1.3.85 3.0.5 1.3.86 3.0.6 1.3.87 3.0.7 1.3.88 3.0.8 1.3.89 3.0.9 1.3.90 3.1.0 1.3.91 3.1.1 1.3.92 3.1.2 1.3.93 3.1.3 1.3.94 3.1.4 1.3.95 3.1.5 1.3.96 3.1.6 1.3.97 3.1.7 1.3.98 3.1.8 1.3.99 3.1.9 1.4.0 3.2.0 1.4.1 3.2.1 1.4.2 3.2.2 1.4.3 3.2.3 1.4.4 3.2.4 1.4.5 3.2.5 1.4.6 3.2.6 1.4.7 3.2.7 1.4.8 3.2.8 1.4.9 3.2.9 1.5.0 3.3.0 1.5.1 3.3.1 1.5.2 3.3.2 1.5.3 3.3.3 1.5.4 3.3.4 1.5.5 3.3.5 1.5.6 3.3.6 1.5.7 3.3.7 1.5.8 3.3.8 1.5.9 3.3.9 1.6.0 3.4.0 1.6.1 3.4.1 1.6.2 3.4.2 1.6.3 3.4.3 1.6.5 3.4.4 1.6.51 3.4.5 1.6.52 3.4.6 1.6.53 1.6.54 1.6.55 1.6.56 1.6.57 1.6.58 1.6.59 1.6.60 1.6.61 1.6.62 1.6.63 1.6.64 1.6.65 1.6.66 1.6.67 1.6.68 trunk 1.6.69 0.0.1 1.6.70 0.0.2 1.6.71 0.0.3 1.6.72 0.0.4 1.6.73 0.0.5 1.6.74 0.0.6 1.6.75 0.0.7 1.6.76 0.0.8 1.6.77 0.0.9 1.6.78 0.1.0 1.6.79 0.1.1 1.6.81 0.1.2 1.6.82 0.1.3 1.6.83 0.1.4 1.6.84 0.1.5 1.6.85 0.1.6 1.6.86 0.1.7 1.6.87 0.1.8 1.6.88 0.1.9 1.6.89 0.2.0 1.6.90
ai-engine / classes / engines / mistral.php
ai-engine / classes / engines Last commit date
anthropic.php 3 months ago chatml.php 4 months ago core.php 3 months ago factory.php 8 months ago google.php 3 months ago mistral.php 5 months ago open-router.php 5 months ago openai.php 3 months ago perplexity.php 6 months ago replicate.php 5 months ago
mistral.php
582 lines
1 <?php
2
3 class Meow_MWAI_Engines_Mistral extends Meow_MWAI_Engines_ChatML {
4 public function __construct( $core, $env ) {
5 parent::__construct( $core, $env );
6 }
7
8 protected function set_environment() {
9 $env = $this->env;
10 $this->apiKey = $env['apikey'] ?? null;
11 }
12
13 protected function get_service_name() {
14 return 'Mistral';
15 }
16
17 public function get_models() {
18 // Return dynamically fetched models only
19 return $this->core->get_engine_models( 'mistral' );
20 }
21
22 public static function get_models_static() {
23 return MWAI_MISTRAL_MODELS;
24 }
25
26 protected function build_url( $query, $endpoint = null ) {
27 $endpoint = apply_filters( 'mwai_mistral_endpoint', 'https://api.mistral.ai/v1', $this->env );
28
29 if ( $query instanceof Meow_MWAI_Query_Text || $query instanceof Meow_MWAI_Query_Feedback ) {
30 return $endpoint . '/chat/completions';
31 }
32 else if ( $query instanceof Meow_MWAI_Query_Embed ) {
33 return $endpoint . '/embeddings';
34 }
35 else {
36 throw new Exception( 'Unsupported query type for Mistral.' );
37 }
38 }
39
40 protected function build_headers( $query ) {
41 if ( $query->apiKey ) {
42 $this->apiKey = $query->apiKey;
43 }
44 if ( empty( $this->apiKey ) ) {
45 throw new Exception( 'No Mistral API Key provided. Please check your settings.' );
46 }
47 return [
48 'Content-Type' => 'application/json',
49 'Authorization' => 'Bearer ' . $this->apiKey,
50 'User-Agent' => 'AI Engine',
51 ];
52 }
53
54 protected function build_messages( $query ) {
55 $messages = parent::build_messages( $query );
56
57 // For feedback queries with tool results, ensure proper format for Mistral
58 if ( $query instanceof Meow_MWAI_Query_Feedback ) {
59 foreach ( $messages as &$message ) {
60 // Mistral expects tool messages to have specific format
61 if ( isset( $message['role'] ) && $message['role'] === 'tool' ) {
62 // Ensure content is never empty
63 if ( empty( $message['content'] ) ) {
64 $message['content'] = json_encode( [ 'result' => 'success' ] );
65 }
66 // Ensure content is a string (Mistral requirement)
67 if ( !is_string( $message['content'] ) ) {
68 $message['content'] = json_encode( $message['content'] );
69 }
70 }
71 }
72 }
73
74 return $messages;
75 }
76
77 protected function build_body( $query, $streamCallback = null, $extra = null ) {
78 // Use parent's build_body for standard ChatML format
79 $body = parent::build_body( $query, $streamCallback, $extra );
80
81 // Mistral embedding models don't support the dimensions parameter
82 if ( $query instanceof Meow_MWAI_Query_Embed ) {
83 unset( $body['dimensions'] );
84 return $body;
85 }
86
87 // Mistral uses 'max_tokens' instead of 'max_completion_tokens'
88 if ( isset( $body['max_completion_tokens'] ) ) {
89 $body['max_tokens'] = $body['max_completion_tokens'];
90 unset( $body['max_completion_tokens'] );
91 }
92
93 // TEMPORARILY DISABLED: Function calling for Mistral
94 // Remove tools/functions from the request until feedback loop is properly debugged
95 if ( isset( $body['tools'] ) ) {
96 unset( $body['tools'] );
97 }
98 if ( isset( $body['tool_choice'] ) ) {
99 unset( $body['tool_choice'] );
100 }
101
102 return $body;
103 }
104
105 /**
106 * Generate a human-readable name from model ID
107 * Based on Mistral's official naming conventions
108 */
109 private function generate_human_readable_name( $modelId ) {
110 // Extract version from model ID (e.g., "2508" becomes "25.08")
111 $versionMatch = [];
112 preg_match( '/(\d{4})$/', $modelId, $versionMatch );
113 $version = isset( $versionMatch[1] ) ?
114 substr( $versionMatch[1], 0, 2 ) . '.' . substr( $versionMatch[1], 2 ) : '';
115
116 // Handle special cases for latest versions
117 if ( strpos( $modelId, '-latest' ) !== false ) {
118 $modelId = str_replace( '-latest', '', $modelId );
119 $version = 'Latest';
120 }
121
122 // Build the base name
123 $name = '';
124
125 // Magistral models (reasoning)
126 if ( strpos( $modelId, 'magistral' ) !== false ) {
127 if ( strpos( $modelId, 'medium' ) !== false ) {
128 $name = 'Magistral Medium';
129 }
130 else if ( strpos( $modelId, 'small' ) !== false ) {
131 $name = 'Magistral Small';
132 }
133 // Add version number for Magistral
134 // No version suffix for latest models
135 }
136 // Mistral models
137 else if ( strpos( $modelId, 'mistral' ) !== false ) {
138 if ( strpos( $modelId, 'large' ) !== false ) {
139 $name = 'Mistral Large';
140 }
141 else if ( strpos( $modelId, 'medium' ) !== false ) {
142 $name = 'Mistral Medium';
143 }
144 else if ( strpos( $modelId, 'small' ) !== false ) {
145 $name = 'Mistral Small';
146 }
147 else if ( strpos( $modelId, 'saba' ) !== false ) {
148 $name = 'Mistral Saba';
149 }
150 else if ( strpos( $modelId, 'tiny' ) !== false || strpos( $modelId, 'nemo' ) !== false ) {
151 $name = 'Mistral Nemo';
152 }
153 else if ( strpos( $modelId, 'embed' ) !== false ) {
154 $name = 'Mistral Embed';
155 }
156 }
157 // Pixtral models (vision)
158 else if ( strpos( $modelId, 'pixtral' ) !== false ) {
159 if ( strpos( $modelId, 'large' ) !== false ) {
160 $name = 'Pixtral Large';
161 }
162 else if ( strpos( $modelId, '12b' ) !== false ) {
163 $name = 'Pixtral 12B';
164 }
165 // No (Latest) suffix needed
166 }
167 // Codestral models (code)
168 else if ( strpos( $modelId, 'codestral' ) !== false ) {
169 if ( strpos( $modelId, 'embed' ) !== false ) {
170 $name = 'Codestral Embed';
171 }
172 else {
173 $name = 'Codestral';
174 // No version suffix for Codestral
175 }
176 }
177 // Devstral models (dev tools)
178 else if ( strpos( $modelId, 'devstral' ) !== false ) {
179 if ( strpos( $modelId, 'medium' ) !== false ) {
180 $name = 'Devstral Medium';
181 }
182 else if ( strpos( $modelId, 'small' ) !== false ) {
183 $name = 'Devstral Small';
184 // No version suffix for Devstral
185 }
186 // No (Latest) suffix needed
187 }
188 // Ministral models (edge)
189 else if ( strpos( $modelId, 'ministral' ) !== false ) {
190 if ( strpos( $modelId, '8b' ) !== false ) {
191 $name = 'Ministral 8B';
192 }
193 else if ( strpos( $modelId, '3b' ) !== false ) {
194 $name = 'Ministral 3B';
195 }
196 // No (Latest) suffix needed
197 }
198 // Voxtral models (audio)
199 else if ( strpos( $modelId, 'voxtral' ) !== false ) {
200 if ( strpos( $modelId, 'small' ) !== false ) {
201 $name = 'Voxtral Small';
202 }
203 else if ( strpos( $modelId, 'mini' ) !== false ) {
204 $name = 'Voxtral Mini';
205 }
206 if ( strpos( $modelId, 'transcribe' ) !== false ) {
207 $name .= ' Transcribe';
208 }
209 }
210 // Open models
211 else if ( strpos( $modelId, 'open-' ) === 0 ) {
212 if ( strpos( $modelId, 'mistral-7b' ) !== false ) {
213 $name = 'Mistral 7B (Open)';
214 }
215 else if ( strpos( $modelId, 'mistral-nemo' ) !== false ) {
216 $name = 'Mistral Nemo (Open)';
217 }
218 else if ( strpos( $modelId, 'mixtral-8x7b' ) !== false ) {
219 $name = 'Mixtral 8x7B (Open)';
220 }
221 else if ( strpos( $modelId, 'mixtral-8x22b' ) !== false ) {
222 $name = 'Mixtral 8x22B (Open)';
223 }
224 }
225
226 // Fallback to cleaned model ID if no pattern matches
227 if ( empty( $name ) ) {
228 $name = ucwords( str_replace( ['-', '_'], ' ', $modelId ) );
229 }
230
231 return $name;
232 }
233
234 /**
235 * Retrieve the models from Mistral API
236 * Mistral supports a models endpoint similar to OpenAI
237 */
238 public function retrieve_models() {
239 try {
240 $endpoint = apply_filters( 'mwai_mistral_endpoint', 'https://api.mistral.ai/v1', $this->env );
241 $url = $endpoint . '/models';
242
243 if ( empty( $this->apiKey ) ) {
244 throw new Exception( 'No Mistral API Key provided for model retrieval.' );
245 }
246
247 $options = [
248 'headers' => [
249 'Authorization' => 'Bearer ' . $this->apiKey,
250 'User-Agent' => 'AI Engine'
251 ],
252 'timeout' => 10,
253 'sslverify' => MWAI_SSL_VERIFY
254 ];
255
256 $response = wp_remote_get( $url, $options );
257
258 if ( is_wp_error( $response ) ) {
259 throw new Exception( 'AI Engine: ' . $response->get_error_message() );
260 }
261
262 $body = json_decode( $response['body'], true );
263
264 // Debug: Log the complete models response from Mistral
265 // error_log( "AI Engine: Mistral Models Response:\n" . print_r( $body, true ) );
266
267 if ( !isset( $body['data'] ) || !is_array( $body['data'] ) ) {
268 throw new Exception( 'AI Engine: Invalid response for Mistral models list.' );
269 }
270
271 $models = [];
272 $seenModels = []; // Track models we've already added to avoid duplicates
273
274 foreach ( $body['data'] as $model ) {
275 $modelId = $model['id'] ?? '';
276
277 // Generate human-readable name based on model ID
278 $modelName = $this->generate_human_readable_name( $modelId );
279
280 // Skip if we've already seen this model name (to avoid alias duplicates)
281 if ( isset( $seenModels[$modelName] ) ) {
282 continue;
283 }
284
285 // Skip specialized models that shouldn't appear in general chat lists
286 // These are models for specific tasks like moderation, OCR, transcription
287 $skipPatterns = [
288 'moderation', // Moderation models
289 'ocr', // OCR-specific models
290 'transcribe', // Transcription-specific models
291 ];
292
293 $shouldSkip = false;
294 foreach ( $skipPatterns as $pattern ) {
295 if ( strpos( $modelId, $pattern ) !== false ) {
296 $shouldSkip = true;
297 break;
298 }
299 }
300 if ( $shouldSkip ) {
301 continue;
302 }
303
304 // Skip models that are just aliases (they appear in other model's aliases array)
305 // We'll keep the primary model, not the alias entries
306 $isAlias = false;
307 if ( isset( $model['aliases'] ) && is_array( $model['aliases'] ) && count( $model['aliases'] ) > 0 ) {
308 // If this model ID appears in its own aliases, it's likely an alias entry
309 foreach ( $model['aliases'] as $alias ) {
310 if ( $alias !== $modelId && isset( $seenModels[$alias] ) ) {
311 $isAlias = true;
312 break;
313 }
314 }
315 }
316 if ( $isAlias ) {
317 continue;
318 }
319
320 // Set defaults based on model type
321 $maxCompletionTokens = 32768;
322 $maxContextualTokens = 128000;
323 $features = ['completion'];
324 $tags = ['core', 'chat'];
325
326 // Parse capabilities from the API response
327 $capabilities = $model['capabilities'] ?? [];
328
329 // TEMPORARILY DISABLED: Function calling tags
330 // Not adding 'functions' tag since function calling is disabled for Mistral
331 // if ( in_array( 'function_calling', $capabilities ) ||
332 // ( isset( $model['supports_tool_choice'] ) && $model['supports_tool_choice'] ) ) {
333 // $tags[] = 'functions';
334 // $features[] = 'functions';
335 // }
336
337 // Check for vision capability
338 if ( in_array( 'vision', $capabilities ) ) {
339 $tags[] = 'vision';
340 }
341
342 // Check for embeddings capability
343 $dimensions = null;
344 if ( strpos( $modelId, 'embed' ) !== false ) {
345 // Skip only the dated legacy version
346 if ( $modelId === 'mistral-embed-2312' ) {
347 continue;
348 }
349 $features = ['embedding'];
350 $tags = ['core', 'embedding'];
351 // Set dimensions based on model type
352 // mistral-embed: 1024 dimensions (fixed)
353 // codestral-embed: 3072 dimensions (fixed)
354 if ( strpos( $modelId, 'codestral' ) !== false ) {
355 $dimensions = 3072;
356 }
357 else {
358 $dimensions = 1024;
359 }
360 }
361
362 // Check for audio capability (voxtral models for chat, not transcription)
363 $capabilities = $model['capabilities'] ?? [];
364 if ( isset( $capabilities['audio'] ) && $capabilities['audio'] &&
365 strpos( $modelId, 'transcribe' ) === false ) {
366 $tags[] = 'audio';
367 }
368
369 // Use max_tokens if available
370 if ( isset( $model['max_tokens'] ) ) {
371 $maxCompletionTokens = (int) $model['max_tokens'];
372 }
373
374 // Use context_length if available
375 if ( isset( $model['max_context_length'] ) ) {
376 $maxContextualTokens = (int) $model['max_context_length'];
377 }
378 else if ( isset( $model['context_window'] ) ) {
379 $maxContextualTokens = (int) $model['context_window'];
380 }
381
382 // Determine pricing based on model (prices per million tokens)
383 $priceIn = 0;
384 $priceOut = 0;
385
386 // Updated Mistral pricing (as of 2025)
387 if ( strpos( $modelId, 'magistral' ) !== false ) {
388 // Magistral reasoning models
389 if ( strpos( $modelId, 'medium' ) !== false ) {
390 $priceIn = 4.00;
391 $priceOut = 12.00;
392 }
393 else {
394 $priceIn = 2.00;
395 $priceOut = 6.00;
396 }
397 }
398 else if ( strpos( $modelId, 'mistral-large' ) !== false || strpos( $modelId, 'pixtral-large' ) !== false ) {
399 $priceIn = 3.00;
400 $priceOut = 9.00;
401 }
402 else if ( strpos( $modelId, 'mistral-medium' ) !== false ) {
403 $priceIn = 2.70;
404 $priceOut = 8.10;
405 }
406 else if ( strpos( $modelId, 'mistral-small' ) !== false ) {
407 $priceIn = 1.00;
408 $priceOut = 3.00;
409 }
410 else if ( strpos( $modelId, 'codestral' ) !== false ) {
411 if ( strpos( $modelId, '2501' ) !== false || strpos( $modelId, '2508' ) !== false ) {
412 $priceIn = 0.30;
413 $priceOut = 0.90;
414 }
415 else {
416 $priceIn = 1.00;
417 $priceOut = 3.00;
418 }
419 }
420 else if ( strpos( $modelId, 'devstral' ) !== false ) {
421 $priceIn = 0.50;
422 $priceOut = 1.50;
423 }
424 else if ( strpos( $modelId, 'ministral' ) !== false ) {
425 $priceIn = 0.10;
426 $priceOut = 0.10;
427 }
428 else if ( strpos( $modelId, 'pixtral-12b' ) !== false ) {
429 $priceIn = 0.15;
430 $priceOut = 0.15;
431 }
432 else if ( strpos( $modelId, 'voxtral' ) !== false ) {
433 $priceIn = 0.50;
434 $priceOut = 1.50;
435 }
436 else if ( strpos( $modelId, 'mistral-saba' ) !== false ) {
437 $priceIn = 0.20;
438 $priceOut = 0.60;
439 }
440 else if ( strpos( $modelId, 'open-mistral' ) !== false || strpos( $modelId, 'mistral-tiny' ) !== false ) {
441 $priceIn = 0.15;
442 $priceOut = 0.15;
443 }
444 else if ( strpos( $modelId, 'open-mixtral-8x7b' ) !== false ) {
445 $priceIn = 0.50;
446 $priceOut = 0.50;
447 }
448 else if ( strpos( $modelId, 'open-mixtral-8x22b' ) !== false ) {
449 $priceIn = 0.90;
450 $priceOut = 0.90;
451 }
452 else if ( strpos( $modelId, 'embed' ) !== false ) {
453 $priceIn = 0.10;
454 $priceOut = 0.00;
455 }
456 else {
457 // Default pricing for unknown models
458 $priceIn = 1.00;
459 $priceOut = 3.00;
460 }
461
462 // Only include latest models and key open-source versions
463 // This keeps the list clean and manageable
464 $preferredModels = [
465 // Latest versions (primary models)
466 'mistral-large-latest',
467 'mistral-medium-latest',
468 'mistral-small-latest',
469 'mistral-tiny-latest',
470 'mistral-saba-latest',
471 'pixtral-large-latest',
472 'pixtral-12b-latest',
473 'codestral-latest',
474 'devstral-small-latest',
475 'devstral-medium-latest',
476 'magistral-medium-latest',
477 'magistral-small-latest',
478 'voxtral-small-latest',
479 'voxtral-mini-latest',
480 'ministral-3b-latest',
481 'ministral-8b-latest',
482 // Open-source models (always include)
483 'open-mistral-7b',
484 'open-mistral-nemo',
485 'open-mixtral-8x7b',
486 'open-mixtral-8x22b'
487 ];
488
489 // We're focusing on latest versions, so no versioned models
490 $versionedModels = [];
491
492 // Check if this is a model we want to include
493 $includeModel = in_array( $modelId, $preferredModels ) ||
494 in_array( $modelId, $versionedModels ) ||
495 strpos( $modelId, 'embed' ) !== false; // Always include embedding models
496
497 if ( !$includeModel ) {
498 continue;
499 }
500
501 // Mark this model as seen (after confirming it will be included)
502 $seenModels[$modelName] = true;
503
504 $modelData = [
505 'model' => $modelId,
506 'name' => $modelName,
507 'family' => 'mistral',
508 'features' => $features,
509 'price' => [
510 'in' => $priceIn,
511 'out' => $priceOut,
512 ],
513 'type' => 'token',
514 'unit' => 1 / 1000000,
515 'maxCompletionTokens' => $maxCompletionTokens,
516 'maxContextualTokens' => $maxContextualTokens,
517 'tags' => $tags,
518 ];
519 // Add dimensions for embedding models (fixed, not configurable)
520 if ( $dimensions !== null ) {
521 $modelData['dimensions'] = $dimensions;
522 }
523 $models[] = $modelData;
524 }
525
526 return $models;
527 }
528 catch ( Exception $e ) {
529 Meow_MWAI_Logging::error( 'Mistral: Failed to retrieve models: ' . $e->getMessage() );
530 // Return empty array on error - models must be fetched from API
531 return [];
532 }
533 }
534
535 /**
536 * Connection check for Mistral API
537 * Tests the API key by listing models
538 */
539 public function connection_check() {
540 try {
541 // Use the retrieve_models method to check connection
542 $models = $this->retrieve_models();
543
544 if ( !is_array( $models ) ) {
545 throw new Exception( 'Invalid response format from Mistral' );
546 }
547
548 $modelCount = count( $models );
549 $availableModels = [];
550
551 // Get first 5 models for display
552 $displayModels = array_slice( $models, 0, 5 );
553 foreach ( $displayModels as $model ) {
554 if ( isset( $model['model'] ) ) {
555 $availableModels[] = $model['model'];
556 }
557 }
558
559 return [
560 'success' => true,
561 'service' => 'Mistral',
562 'message' => "Connection successful. Found {$modelCount} models.",
563 'details' => [
564 'endpoint' => 'https://api.mistral.ai/v1/models',
565 'model_count' => $modelCount,
566 'sample_models' => $availableModels
567 ]
568 ];
569 }
570 catch ( Exception $e ) {
571 return [
572 'success' => false,
573 'service' => 'Mistral',
574 'error' => $e->getMessage(),
575 'details' => [
576 'endpoint' => 'https://api.mistral.ai/v1/models'
577 ]
578 ];
579 }
580 }
581 }
582