PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 3.3.2
AI Engine – The Chatbot, AI Framework & MCP for WordPress v3.3.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 6 months ago chatml.php 6 months ago core.php 7 months ago factory.php 8 months ago google.php 6 months ago mistral.php 6 months ago open-router.php 6 months ago openai.php 6 months ago perplexity.php 6 months ago replicate.php 6 months ago
mistral.php
564 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 uses 'max_tokens' instead of 'max_completion_tokens'
82 if ( isset( $body['max_completion_tokens'] ) ) {
83 $body['max_tokens'] = $body['max_completion_tokens'];
84 unset( $body['max_completion_tokens'] );
85 }
86
87 // TEMPORARILY DISABLED: Function calling for Mistral
88 // Remove tools/functions from the request until feedback loop is properly debugged
89 if ( isset( $body['tools'] ) ) {
90 unset( $body['tools'] );
91 }
92 if ( isset( $body['tool_choice'] ) ) {
93 unset( $body['tool_choice'] );
94 }
95
96 return $body;
97 }
98
99 /**
100 * Generate a human-readable name from model ID
101 * Based on Mistral's official naming conventions
102 */
103 private function generate_human_readable_name( $modelId ) {
104 // Extract version from model ID (e.g., "2508" becomes "25.08")
105 $versionMatch = [];
106 preg_match( '/(\d{4})$/', $modelId, $versionMatch );
107 $version = isset( $versionMatch[1] ) ?
108 substr( $versionMatch[1], 0, 2 ) . '.' . substr( $versionMatch[1], 2 ) : '';
109
110 // Handle special cases for latest versions
111 if ( strpos( $modelId, '-latest' ) !== false ) {
112 $modelId = str_replace( '-latest', '', $modelId );
113 $version = 'Latest';
114 }
115
116 // Build the base name
117 $name = '';
118
119 // Magistral models (reasoning)
120 if ( strpos( $modelId, 'magistral' ) !== false ) {
121 if ( strpos( $modelId, 'medium' ) !== false ) {
122 $name = 'Magistral Medium';
123 }
124 else if ( strpos( $modelId, 'small' ) !== false ) {
125 $name = 'Magistral Small';
126 }
127 // Add version number for Magistral
128 // No version suffix for latest models
129 }
130 // Mistral models
131 else if ( strpos( $modelId, 'mistral' ) !== false ) {
132 if ( strpos( $modelId, 'large' ) !== false ) {
133 $name = 'Mistral Large';
134 }
135 else if ( strpos( $modelId, 'medium' ) !== false ) {
136 $name = 'Mistral Medium';
137 }
138 else if ( strpos( $modelId, 'small' ) !== false ) {
139 $name = 'Mistral Small';
140 }
141 else if ( strpos( $modelId, 'saba' ) !== false ) {
142 $name = 'Mistral Saba';
143 }
144 else if ( strpos( $modelId, 'tiny' ) !== false || strpos( $modelId, 'nemo' ) !== false ) {
145 $name = 'Mistral Nemo';
146 }
147 else if ( strpos( $modelId, 'embed' ) !== false ) {
148 $name = 'Mistral Embed';
149 }
150 }
151 // Pixtral models (vision)
152 else if ( strpos( $modelId, 'pixtral' ) !== false ) {
153 if ( strpos( $modelId, 'large' ) !== false ) {
154 $name = 'Pixtral Large';
155 }
156 else if ( strpos( $modelId, '12b' ) !== false ) {
157 $name = 'Pixtral 12B';
158 }
159 // No (Latest) suffix needed
160 }
161 // Codestral models (code)
162 else if ( strpos( $modelId, 'codestral' ) !== false ) {
163 if ( strpos( $modelId, 'embed' ) !== false ) {
164 $name = 'Codestral Embed';
165 }
166 else {
167 $name = 'Codestral';
168 // No version suffix for Codestral
169 }
170 }
171 // Devstral models (dev tools)
172 else if ( strpos( $modelId, 'devstral' ) !== false ) {
173 if ( strpos( $modelId, 'medium' ) !== false ) {
174 $name = 'Devstral Medium';
175 }
176 else if ( strpos( $modelId, 'small' ) !== false ) {
177 $name = 'Devstral Small';
178 // No version suffix for Devstral
179 }
180 // No (Latest) suffix needed
181 }
182 // Ministral models (edge)
183 else if ( strpos( $modelId, 'ministral' ) !== false ) {
184 if ( strpos( $modelId, '8b' ) !== false ) {
185 $name = 'Ministral 8B';
186 }
187 else if ( strpos( $modelId, '3b' ) !== false ) {
188 $name = 'Ministral 3B';
189 }
190 // No (Latest) suffix needed
191 }
192 // Voxtral models (audio)
193 else if ( strpos( $modelId, 'voxtral' ) !== false ) {
194 if ( strpos( $modelId, 'small' ) !== false ) {
195 $name = 'Voxtral Small';
196 }
197 else if ( strpos( $modelId, 'mini' ) !== false ) {
198 $name = 'Voxtral Mini';
199 }
200 if ( strpos( $modelId, 'transcribe' ) !== false ) {
201 $name .= ' Transcribe';
202 }
203 }
204 // Open models
205 else if ( strpos( $modelId, 'open-' ) === 0 ) {
206 if ( strpos( $modelId, 'mistral-7b' ) !== false ) {
207 $name = 'Mistral 7B (Open)';
208 }
209 else if ( strpos( $modelId, 'mistral-nemo' ) !== false ) {
210 $name = 'Mistral Nemo (Open)';
211 }
212 else if ( strpos( $modelId, 'mixtral-8x7b' ) !== false ) {
213 $name = 'Mixtral 8x7B (Open)';
214 }
215 else if ( strpos( $modelId, 'mixtral-8x22b' ) !== false ) {
216 $name = 'Mixtral 8x22B (Open)';
217 }
218 }
219
220 // Fallback to cleaned model ID if no pattern matches
221 if ( empty( $name ) ) {
222 $name = ucwords( str_replace( ['-', '_'], ' ', $modelId ) );
223 }
224
225 return $name;
226 }
227
228 /**
229 * Retrieve the models from Mistral API
230 * Mistral supports a models endpoint similar to OpenAI
231 */
232 public function retrieve_models() {
233 try {
234 $endpoint = apply_filters( 'mwai_mistral_endpoint', 'https://api.mistral.ai/v1', $this->env );
235 $url = $endpoint . '/models';
236
237 if ( empty( $this->apiKey ) ) {
238 throw new Exception( 'No Mistral API Key provided for model retrieval.' );
239 }
240
241 $options = [
242 'headers' => [
243 'Authorization' => 'Bearer ' . $this->apiKey,
244 'User-Agent' => 'AI Engine'
245 ],
246 'timeout' => 10,
247 'sslverify' => MWAI_SSL_VERIFY
248 ];
249
250 $response = wp_remote_get( $url, $options );
251
252 if ( is_wp_error( $response ) ) {
253 throw new Exception( 'AI Engine: ' . $response->get_error_message() );
254 }
255
256 $body = json_decode( $response['body'], true );
257
258 // Debug: Log the complete models response from Mistral
259 // error_log( "AI Engine: Mistral Models Response:\n" . print_r( $body, true ) );
260
261 if ( !isset( $body['data'] ) || !is_array( $body['data'] ) ) {
262 throw new Exception( 'AI Engine: Invalid response for Mistral models list.' );
263 }
264
265 $models = [];
266 $seenModels = []; // Track models we've already added to avoid duplicates
267
268 foreach ( $body['data'] as $model ) {
269 $modelId = $model['id'] ?? '';
270
271 // Generate human-readable name based on model ID
272 $modelName = $this->generate_human_readable_name( $modelId );
273
274 // Skip if we've already seen this model name (to avoid alias duplicates)
275 if ( isset( $seenModels[$modelName] ) ) {
276 continue;
277 }
278
279 // Skip specialized models that shouldn't appear in general chat lists
280 // These are models for specific tasks like moderation, OCR, transcription
281 $skipPatterns = [
282 'moderation', // Moderation models
283 'ocr', // OCR-specific models
284 'transcribe', // Transcription-specific models
285 'mistral-embed', // Legacy embed model (we'll include newer ones)
286 'codestral-embed' // Code-specific embed model
287 ];
288
289 $shouldSkip = false;
290 foreach ( $skipPatterns as $pattern ) {
291 if ( strpos( $modelId, $pattern ) !== false ) {
292 $shouldSkip = true;
293 break;
294 }
295 }
296 if ( $shouldSkip ) {
297 continue;
298 }
299
300 // Skip models that are just aliases (they appear in other model's aliases array)
301 // We'll keep the primary model, not the alias entries
302 $isAlias = false;
303 if ( isset( $model['aliases'] ) && is_array( $model['aliases'] ) && count( $model['aliases'] ) > 0 ) {
304 // If this model ID appears in its own aliases, it's likely an alias entry
305 foreach ( $model['aliases'] as $alias ) {
306 if ( $alias !== $modelId && isset( $seenModels[$alias] ) ) {
307 $isAlias = true;
308 break;
309 }
310 }
311 }
312 if ( $isAlias ) {
313 continue;
314 }
315
316 // Set defaults based on model type
317 $maxCompletionTokens = 32768;
318 $maxContextualTokens = 128000;
319 $features = ['completion'];
320 $tags = ['core', 'chat'];
321
322 // Parse capabilities from the API response
323 $capabilities = $model['capabilities'] ?? [];
324
325 // TEMPORARILY DISABLED: Function calling tags
326 // Not adding 'functions' tag since function calling is disabled for Mistral
327 // if ( in_array( 'function_calling', $capabilities ) ||
328 // ( isset( $model['supports_tool_choice'] ) && $model['supports_tool_choice'] ) ) {
329 // $tags[] = 'functions';
330 // $features[] = 'functions';
331 // }
332
333 // Check for vision capability
334 if ( in_array( 'vision', $capabilities ) ) {
335 $tags[] = 'vision';
336 }
337
338 // Check for embeddings capability
339 // Skip older embedding models in favor of newer ones
340 if ( strpos( $modelId, 'embed' ) !== false ) {
341 // Only include the latest embed models
342 if ( $modelId === 'mistral-embed-2312' || $modelId === 'mistral-embed' ) {
343 continue; // Skip legacy embed models
344 }
345 $features = ['embedding'];
346 $tags = ['core', 'embedding'];
347 }
348
349 // Check for audio capability (voxtral models for chat, not transcription)
350 $capabilities = $model['capabilities'] ?? [];
351 if ( isset( $capabilities['audio'] ) && $capabilities['audio'] &&
352 strpos( $modelId, 'transcribe' ) === false ) {
353 $tags[] = 'audio';
354 }
355
356 // Use max_tokens if available
357 if ( isset( $model['max_tokens'] ) ) {
358 $maxCompletionTokens = (int) $model['max_tokens'];
359 }
360
361 // Use context_length if available
362 if ( isset( $model['max_context_length'] ) ) {
363 $maxContextualTokens = (int) $model['max_context_length'];
364 }
365 else if ( isset( $model['context_window'] ) ) {
366 $maxContextualTokens = (int) $model['context_window'];
367 }
368
369 // Determine pricing based on model (prices per million tokens)
370 $priceIn = 0;
371 $priceOut = 0;
372
373 // Updated Mistral pricing (as of 2025)
374 if ( strpos( $modelId, 'magistral' ) !== false ) {
375 // Magistral reasoning models
376 if ( strpos( $modelId, 'medium' ) !== false ) {
377 $priceIn = 4.00;
378 $priceOut = 12.00;
379 }
380 else {
381 $priceIn = 2.00;
382 $priceOut = 6.00;
383 }
384 }
385 else if ( strpos( $modelId, 'mistral-large' ) !== false || strpos( $modelId, 'pixtral-large' ) !== false ) {
386 $priceIn = 3.00;
387 $priceOut = 9.00;
388 }
389 else if ( strpos( $modelId, 'mistral-medium' ) !== false ) {
390 $priceIn = 2.70;
391 $priceOut = 8.10;
392 }
393 else if ( strpos( $modelId, 'mistral-small' ) !== false ) {
394 $priceIn = 1.00;
395 $priceOut = 3.00;
396 }
397 else if ( strpos( $modelId, 'codestral' ) !== false ) {
398 if ( strpos( $modelId, '2501' ) !== false || strpos( $modelId, '2508' ) !== false ) {
399 $priceIn = 0.30;
400 $priceOut = 0.90;
401 }
402 else {
403 $priceIn = 1.00;
404 $priceOut = 3.00;
405 }
406 }
407 else if ( strpos( $modelId, 'devstral' ) !== false ) {
408 $priceIn = 0.50;
409 $priceOut = 1.50;
410 }
411 else if ( strpos( $modelId, 'ministral' ) !== false ) {
412 $priceIn = 0.10;
413 $priceOut = 0.10;
414 }
415 else if ( strpos( $modelId, 'pixtral-12b' ) !== false ) {
416 $priceIn = 0.15;
417 $priceOut = 0.15;
418 }
419 else if ( strpos( $modelId, 'voxtral' ) !== false ) {
420 $priceIn = 0.50;
421 $priceOut = 1.50;
422 }
423 else if ( strpos( $modelId, 'mistral-saba' ) !== false ) {
424 $priceIn = 0.20;
425 $priceOut = 0.60;
426 }
427 else if ( strpos( $modelId, 'open-mistral' ) !== false || strpos( $modelId, 'mistral-tiny' ) !== false ) {
428 $priceIn = 0.15;
429 $priceOut = 0.15;
430 }
431 else if ( strpos( $modelId, 'open-mixtral-8x7b' ) !== false ) {
432 $priceIn = 0.50;
433 $priceOut = 0.50;
434 }
435 else if ( strpos( $modelId, 'open-mixtral-8x22b' ) !== false ) {
436 $priceIn = 0.90;
437 $priceOut = 0.90;
438 }
439 else if ( strpos( $modelId, 'embed' ) !== false ) {
440 $priceIn = 0.10;
441 $priceOut = 0.00;
442 }
443 else {
444 // Default pricing for unknown models
445 $priceIn = 1.00;
446 $priceOut = 3.00;
447 }
448
449 // Only include latest models and key open-source versions
450 // This keeps the list clean and manageable
451 $preferredModels = [
452 // Latest versions (primary models)
453 'mistral-large-latest',
454 'mistral-medium-latest',
455 'mistral-small-latest',
456 'mistral-tiny-latest',
457 'mistral-saba-latest',
458 'pixtral-large-latest',
459 'pixtral-12b-latest',
460 'codestral-latest',
461 'devstral-small-latest',
462 'devstral-medium-latest',
463 'magistral-medium-latest',
464 'magistral-small-latest',
465 'voxtral-small-latest',
466 'voxtral-mini-latest',
467 'ministral-3b-latest',
468 'ministral-8b-latest',
469 // Open-source models (always include)
470 'open-mistral-7b',
471 'open-mistral-nemo',
472 'open-mixtral-8x7b',
473 'open-mixtral-8x22b'
474 ];
475
476 // We're focusing on latest versions, so no versioned models
477 $versionedModels = [];
478
479 // Check if this is a model we want to include
480 $includeModel = in_array( $modelId, $preferredModels ) ||
481 in_array( $modelId, $versionedModels ) ||
482 strpos( $modelId, 'embed' ) !== false; // Always include embedding models
483
484 if ( !$includeModel ) {
485 continue;
486 }
487
488 // Mark this model as seen (after confirming it will be included)
489 $seenModels[$modelName] = true;
490
491 $models[] = [
492 'model' => $modelId,
493 'name' => $modelName,
494 'family' => 'mistral',
495 'features' => $features,
496 'price' => [
497 'in' => $priceIn,
498 'out' => $priceOut,
499 ],
500 'type' => 'token',
501 'unit' => 1 / 1000000,
502 'maxCompletionTokens' => $maxCompletionTokens,
503 'maxContextualTokens' => $maxContextualTokens,
504 'tags' => $tags,
505 ];
506 }
507
508 return $models;
509 }
510 catch ( Exception $e ) {
511 Meow_MWAI_Logging::error( 'Mistral: Failed to retrieve models: ' . $e->getMessage() );
512 // Return empty array on error - models must be fetched from API
513 return [];
514 }
515 }
516
517 /**
518 * Connection check for Mistral API
519 * Tests the API key by listing models
520 */
521 public function connection_check() {
522 try {
523 // Use the retrieve_models method to check connection
524 $models = $this->retrieve_models();
525
526 if ( !is_array( $models ) ) {
527 throw new Exception( 'Invalid response format from Mistral' );
528 }
529
530 $modelCount = count( $models );
531 $availableModels = [];
532
533 // Get first 5 models for display
534 $displayModels = array_slice( $models, 0, 5 );
535 foreach ( $displayModels as $model ) {
536 if ( isset( $model['model'] ) ) {
537 $availableModels[] = $model['model'];
538 }
539 }
540
541 return [
542 'success' => true,
543 'service' => 'Mistral',
544 'message' => "Connection successful. Found {$modelCount} models.",
545 'details' => [
546 'endpoint' => 'https://api.mistral.ai/v1/models',
547 'model_count' => $modelCount,
548 'sample_models' => $availableModels
549 ]
550 ];
551 }
552 catch ( Exception $e ) {
553 return [
554 'success' => false,
555 'service' => 'Mistral',
556 'error' => $e->getMessage(),
557 'details' => [
558 'endpoint' => 'https://api.mistral.ai/v1/models'
559 ]
560 ];
561 }
562 }
563 }
564