"system", "content" => $system], ["role" => "user", "content" => $prompt]]; // CASCADE: try each provider $providers = [ ["url" => "https://api.groq.com/openai/v1/chat/completions", "key" => $keys["groq"] ?? "", "model" => "llama-3.3-70b-versatile", "name" => "groq"], ["url" => "https://api.cerebras.ai/v1/chat/completions", "key" => $keys["cerebras"] ?? "", "model" => "llama3.1-8b", "name" => "cerebras"], ["url" => "https://api.mistral.ai/v1/chat/completions", "key" => $keys["mistral"] ?? "", "model" => "open-mistral-7b", "name" => "mistral"], ["url" => "http://127.0.0.1:11435/api/chat", "key" => "", "model" => "qwen3:4b", "name" => "ollama"] ]; foreach ($providers as $p) { if (!$p["key"] && $p["name"] !== "ollama") continue; if ($p["name"] === "ollama") { // Ollama uses different format $data = json_encode(["model" => $p["model"], "messages" => $msgs, "stream" => false]); $headers = ["Content-Type: application/json"]; } else { $data = json_encode(["model" => $p["model"], "messages" => $msgs, "max_tokens" => 1500]); $headers = ["Content-Type: application/json", "Authorization: Bearer " . $p["key"]]; } $ch = curl_init($p["url"]); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $data, CURLOPT_HTTPHEADER => $headers, CURLOPT_TIMEOUT => 20, CURLOPT_SSL_VERIFYPEER => false ]); $r = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($code === 200) { $d = @json_decode($r, true); $content = $d["choices"][0]["message"]["content"] ?? ($d["message"]["content"] ?? null); if ($content) return $content; } // 429 or error → try next provider } return "LLM cascade: tous les providers occupes"; }