Files
html/api/ambre-multiagent-parallel.php
opus 3ec53dd4e1
Some checks failed
WEVAL NonReg / nonreg (push) Has been cancelled
auto-sync-0440
2026-04-22 04:40:03 +02:00

191 lines
7.2 KiB
PHP

<?php
/**
* ambre-multiagent-parallel.php · wave-255 · Multi-agent parallel dispatch
* Langue naturelle → Plan → Execute N agents in parallel → Reconcile
*
* POST JSON: { "goal": "texte objectif", "max_agents": 5 }
* Response: { "ok":true, "plan":[...], "results":[...], "elapsed_ms":N, "reconciled":"..." }
*/
header("Content-Type: application/json; charset=utf-8");
set_time_limit(120);
$raw = file_get_contents("php://input");
$in = json_decode($raw, true) ?: $_POST;
$goal = trim($in["goal"] ?? $in["message"] ?? "");
$max_agents = min(10, max(1, intval($in["max_agents"] ?? 5)));
if (!$goal) {
echo json_encode(["error"=>"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\": \"<reformulation en une phrase>\",\n" .
" \"agents\": [\n" .
" {\"role\":\"researcher\", \"task\":\"<tâche précise>\", \"tool\":\"<pdf_premium|mermaid|web_search|calc|image|code|translate|kb_search|none>\"},\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);