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