"goal required"]); exit; } $t0 = microtime(true); // Step 1 · PLAN via LLM (one call, JSON structured) $plan_sys = "Tu es un planificateur multi-agent. Pour l'objectif donné, génère STRICTEMENT un JSON avec cette structure :\n" . "{\n" . " \"objective\": \"\",\n" . " \"agents\": [\n" . " {\"role\":\"researcher\", \"task\":\"\", \"tool\":\"\"},\n" . " ...\n" . " ]\n" . "}\n" . "Maximum $max_agents agents. Chaque agent a un role distinct et une tâche autonome. NE réponds QUE le JSON."; $plan_raw = @file_get_contents("http://127.0.0.1:4000/v1/chat/completions", false, stream_context_create([ "http" => [ "method" => "POST", "header" => "Content-Type: application/json\r\n", "content" => json_encode([ "model" => "fast", "messages" => [ ["role"=>"system","content"=>$plan_sys], ["role"=>"user","content"=>"Objectif: " . $goal] ], "max_tokens" => 800, "temperature" => 0.3, ]), "timeout" => 20, ], ])); $plan_data = @json_decode($plan_raw, true); $plan_text = $plan_data["choices"][0]["message"]["content"] ?? ""; $plan_text = preg_replace('/^```(?:json)?\s*/m', '', $plan_text); $plan_text = preg_replace('/\s*```\s*$/m', '', trim($plan_text)); $plan = @json_decode($plan_text, true); if (!$plan || !isset($plan["agents"]) || !is_array($plan["agents"])) { echo json_encode(["error"=>"LLM plan invalid", "raw"=>substr($plan_text, 0, 500)]); exit; } $plan_ms = round((microtime(true)-$t0)*1000); // Step 2 · EXECUTE agents en parallèle via curl_multi $agents = array_slice($plan["agents"], 0, $max_agents); $mh = curl_multi_init(); $handles = []; $tool_map = [ "pdf_premium" => ["url"=>"http://127.0.0.1/api/ambre-tool-pdf-premium.php", "body_key"=>"topic"], "mermaid" => ["url"=>"http://127.0.0.1/api/ambre-tool-mermaid.php", "body_key"=>"topic"], "web_search" => ["url"=>"http://127.0.0.1/api/ambre-tool-web-search.php", "body_key"=>"query"], "calc" => ["url"=>"http://127.0.0.1/api/ambre-tool-calc.php", "body_key"=>"expression"], "kb_search" => ["url"=>"http://127.0.0.1/api/ambre-mermaid-learn.php", "body_key"=>"query"], ]; $exec_t0 = microtime(true); foreach ($agents as $i => $agent) { $tool = $agent["tool"] ?? "none"; $task = $agent["task"] ?? ""; if ($tool === "none" || !isset($tool_map[$tool])) { // No tool → LLM direct reasoning $ch = curl_init("http://127.0.0.1:4000/v1/chat/completions"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_HTTPHEADER => ["Content-Type: application/json"], CURLOPT_POSTFIELDS => json_encode([ "model"=>"fast", "messages"=>[["role"=>"user","content"=>$task]], "max_tokens"=>400, ]), CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30, CURLOPT_CONNECTTIMEOUT => 3, ]); } else { $cfg = $tool_map[$tool]; $body = [$cfg["body_key"] => $task]; if ($tool === "kb_search") $body["action"] = "search"; $ch = curl_init($cfg["url"]); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_HTTPHEADER => ["Content-Type: application/json"], CURLOPT_POSTFIELDS => json_encode($body), CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 45, CURLOPT_CONNECTTIMEOUT => 3, ]); } curl_multi_add_handle($mh, $ch); $handles[$i] = ["ch"=>$ch, "agent"=>$agent, "t0"=>microtime(true)]; } // Run all in parallel $running = null; do { curl_multi_exec($mh, $running); curl_multi_select($mh, 0.5); } while ($running > 0); // Collect results $results = []; foreach ($handles as $i => $h) { $output = curl_multi_getcontent($h["ch"]); $elapsed_ms = round((microtime(true)-$h["t0"])*1000); $http_code = curl_getinfo($h["ch"], CURLINFO_HTTP_CODE); curl_multi_remove_handle($mh, $h["ch"]); curl_close($h["ch"]); // Try to extract meaningful summary $result_data = @json_decode($output, true); $summary = ""; if ($result_data) { if (isset($result_data["mermaid_code"])) $summary = "Mermaid généré (" . strlen($result_data["mermaid_code"]) . "B)"; elseif (isset($result_data["url"])) $summary = "PDF: " . $result_data["url"]; elseif (isset($result_data["choices"][0]["message"]["content"])) $summary = substr($result_data["choices"][0]["message"]["content"], 0, 300); elseif (isset($result_data["result"])) $summary = (string)$result_data["result"]; else $summary = substr($output, 0, 200); } else { $summary = substr($output ?: "empty", 0, 200); } $results[] = [ "agent" => $h["agent"]["role"] ?? "agent_$i", "task" => $h["agent"]["task"] ?? "", "tool" => $h["agent"]["tool"] ?? "none", "http" => $http_code, "elapsed_ms" => $elapsed_ms, "summary" => $summary, ]; } curl_multi_close($mh); $exec_ms = round((microtime(true)-$exec_t0)*1000); // Step 3 · RECONCILE (synthesis LLM call) $synth_input = "Objectif: " . $goal . "\n\nRésultats des " . count($results) . " agents:\n"; foreach ($results as $r) { $synth_input .= "- " . $r["agent"] . " (" . $r["tool"] . "): " . substr($r["summary"], 0, 300) . "\n"; } $synth_input .= "\nSynthétise la réponse finale en français clair et actionnable. Max 200 mots."; $synth_raw = @file_get_contents("http://127.0.0.1:4000/v1/chat/completions", false, stream_context_create([ "http" => [ "method" => "POST", "header" => "Content-Type: application/json\r\n", "content" => json_encode([ "model" => "fast", "messages" => [["role"=>"user","content"=>$synth_input]], "max_tokens" => 500, ]), "timeout" => 15, ], ])); $synth_data = @json_decode($synth_raw, true); $reconciled = $synth_data["choices"][0]["message"]["content"] ?? "Synthèse échouée"; echo json_encode([ "ok" => true, "goal" => $goal, "plan" => $plan, "plan_ms" => $plan_ms, "results" => $results, "exec_ms" => $exec_ms, "parallel_speedup" => round(array_sum(array_column($results, "elapsed_ms")) / max($exec_ms, 1), 2), "reconciled" => trim($reconciled), "total_ms" => round((microtime(true)-$t0)*1000), "agents_count" => count($results), "provider" => "WEVIA MultiAgent Parallel Engine · wave-255", ], JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE);