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 |