191 lines
7.2 KiB
PHP
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);
|