Files
html/api/wevia-dynamic-resolver.php
2026-04-17 16:51:29 +02:00

91 lines
5.2 KiB
PHP

<?php
// Hardened 17avr2026 16h50 — doctrines 57/58/59 (NL-first + priority + security)
// Fix: "quelle mission rapporte le plus" matchait 'ports' via fuzzy "plus"
// Solution: stopwords + seuil relevé + disambiguation suggestion
function wevia_resolve($msg) {
$registry = __DIR__ . '/wevia-tool-registry.json';
if (!file_exists($registry)) return null;
$data = json_decode(file_get_contents($registry), true);
if (!is_array($data)) return null;
$tools = $data['tools'] ?? $data;
$msg_lower = mb_strtolower(trim($msg));
// WEVIA_STOPWORDS — mots trop génériques qui ne doivent pas trigger fuzzy-match seul
$stopwords = ['le','la','les','un','une','des','de','du','et','ou','plus','moins','mieux','avec','sans','pour','par','sur','sous','dans','quel','quelle','quels','quelles','combien','comment','pourquoi','quand','qui','que','quoi','mon','ma','mes','ton','ta','tes','son','sa','ses','notre','votre','leur','ce','cette','ces','si','mais','donc','car','or','ni','est','sont','ai','as','a','avons','avez','ont','the','of','and','or','to','from','for','with','what','how','why','when','who','where','my','your','their','is','are','have','has','had'];
$best = null;
$best_score = 0;
$second_best_score = 0;
foreach ($tools as $tool) {
if (empty($tool['kw'])) continue;
$score = 0;
try {
if (@preg_match('/' . $tool['kw'] . '/i', $msg_lower)) {
$score = 10;
} else {
$keywords = preg_split('/[|.*()]+/', $tool['kw']);
$matched_keywords = 0;
foreach ($keywords as $kw) {
$kw = trim($kw);
// HARDENING: skip stopwords in fuzzy-match
if (!$kw || in_array($kw, $stopwords, true) || mb_strlen($kw) < 3) continue;
if (@preg_match("/\\b" . preg_quote($kw, "/") . "\\b/iu", $msg_lower)) {
$score += 2;
$matched_keywords++;
}
}
// HARDENING: require at least 2 non-stopword keywords to fuzzy-match
if ($matched_keywords < 2) $score = 0;
}
} catch (\Throwable $e) { continue; }
if ($score > 0 && mb_strlen($msg_lower) > 60 && preg_match("/reconcil|diagnostic|bilan|test_global/", $tool["id"] ?? "")) $score += 1;
if ($score > $best_score || ($score == $best_score && !empty($tool["cmd"]) && empty($best["cmd"]))) {
$second_best_score = $best_score;
$best_score = $score;
$best = $tool;
} elseif ($score > $second_best_score) {
$second_best_score = $score;
}
}
// HARDENING: seuil relevé de 3 à 6 (exige regex exact score=10 OU 3 keywords matches score=6)
// Évite fuzzy-match abusif type "plus" → ports.sh
if (!$best || $best_score < 6) {
// Retourner suggestion au lieu de null si score "moyen" (3-5)
if ($best && $best_score >= 3) {
return [
'provider' => 'dynamic-resolver',
'content' => "Je n'ai pas bien compris votre demande. Vous vouliez peut-être :\n" . ($best['id'] ?? 'unknown') . " (demandez : \"" . ($best['kw'] ?? '') . "\")\n\nSi ce n'est pas ça, essayez :\n\"candidats dashboard\" — stats recrutement\n\"facturation mission\" — missions & TJM\n\"etat du systeme\" — vue globale\n\"andons actifs\" — alertes en cours",
'tool' => 'disambiguation'
];
}
return null;
}
// HARDENING: si 2 tools ont scores très proches (diff <= 1) ET regex non-exact, demander clarification
if ($best_score < 10 && $second_best_score >= $best_score - 1 && $second_best_score >= 4) {
return [
'provider' => 'dynamic-resolver',
'content' => "Votre demande est ambiguë (plusieurs interprétations possibles). Précisez svp :\n • Pour la situation business → \"etat du systeme\", \"candidats dashboard\", \"facturation mission\"\n • Pour l'infra → \"etat global\", \"andons actifs\", \"verify l99\"",
'tool' => 'ambiguous'
];
}
$result = '';
if (!empty($best['cmd'])) {
$result = shell_exec('sudo timeout 15 bash -c ' . escapeshellarg($best['cmd']) . ' 2>&1') ?? '';
if (trim($result) === '') $result = '[timeout 15s] Commande lente. Essayez plus ciblé.';
} elseif (strpos($best['api'] ?? '', 'GET:') === 0) {
$ctx = stream_context_create(['http' => ['timeout' => 4]]);
$result = @file_get_contents('http://127.0.0.1' . substr($best['api'], 4), false, $ctx) ?? '';
} elseif (strpos($best['api'] ?? '', 'POST:') === 0) {
$ctx = stream_context_create(['http' => ['method' => 'POST', 'timeout' => 4]]);
$result = @file_get_contents('http://127.0.0.1' . substr($best['api'], 5), false, $ctx) ?? '';
}
if (!$result) return ["provider"=>"dynamic-resolver","content"=>"[" . ($best["id"] ?? "tool") . "] pas de reponse (timeout ou service down)","tool"=>$best["id"]??"unknown"];
return ['provider' => 'dynamic-resolver', 'content' => trim($result), 'tool' => $best['id'] ?? 'unknown'];
}
function wevia_dynamic_resolve($msg) { return wevia_resolve($msg); }