Files
wevia-brain/modules/v2/tool-use-v2.php
2026-04-12 23:01:36 +02:00

271 lines
14 KiB
PHP
Executable File

<?php
/**
* WEVIA OPUS — Tool Use Engine v2
* 15 outils intégrés avec validation, retry, et logging
*/
class ToolRegistry {
private static array $tools = [
'web_search' => [
'description' => 'Recherche sur le web via SearXNG pour des informations récentes ou vérification de faits.',
'parameters' => ['query' => 'string (la recherche)', 'max_results' => 'int (défaut: 5)'],
'example' => '<tool_call>{"tool":"web_search","params":{"query":"PowerMTA latest version 2025","max_results":3}}</tool_call>',
'requires' => ['network'],
'timeout' => 10
],
'execute_python' => [
'description' => 'Exécute du code Python pour calculs, traitement de données, visualisation. Bibliothèques disponibles: numpy, pandas, matplotlib, requests.',
'parameters' => ['code' => 'string (code Python)'],
'example' => '<tool_call>{"tool":"execute_python","params":{"code":"import pandas as pd\ndf = pd.read_csv(\'/tmp/data.csv\')\nprint(df.describe())"}}</tool_call>',
'requires' => ['python3'],
'timeout' => 30,
'sandbox' => true
],
'execute_bash' => [
'description' => 'Exécute une commande bash pour administration système, fichiers, réseau.',
'parameters' => ['command' => 'string (commande bash)'],
'example' => '<tool_call>{"tool":"execute_bash","params":{"command":"df -h && free -m && uptime"}}</tool_call>',
'requires' => ['bash'],
'timeout' => 15,
'dangerous_patterns' => ['rm -rf /', 'mkfs', 'dd if=/dev/zero', '> /dev/sda', 'chmod 777 /']
],
'database_query' => [
'description' => 'Exécute une requête PostgreSQL SELECT (lecture seule) sur les bases WEVADS ou WEVIA.',
'parameters' => ['query' => 'string (SQL SELECT)', 'database' => 'string (adx_system|adx_clients|wevia_db)'],
'example' => '<tool_call>{"tool":"database_query","params":{"query":"SELECT COUNT(*) FROM admin.warmup_accounts WHERE status=\'active\'","database":"adx_system"}}</tool_call>',
'requires' => ['postgresql'],
'timeout' => 10,
'read_only' => true
],
'file_read' => [
'description' => 'Lit le contenu d\'un fichier texte.',
'parameters' => ['path' => 'string (chemin absolu)', 'lines' => 'int (nombre de lignes, défaut: toutes)'],
'example' => '<tool_call>{"tool":"file_read","params":{"path":"/etc/pmta/config","lines":50}}</tool_call>',
'requires' => [],
'timeout' => 5,
'blocked_paths' => ['/etc/shadow', '/etc/passwd', '*.key', '*.pem']
],
'file_write' => [
'description' => 'Écrit du contenu dans un fichier (avec backup automatique).',
'parameters' => ['path' => 'string', 'content' => 'string', 'backup' => 'bool (défaut: true)'],
'example' => '<tool_call>{"tool":"file_write","params":{"path":"/tmp/test.txt","content":"Hello World"}}</tool_call>',
'requires' => [],
'timeout' => 5
],
'http_request' => [
'description' => 'Fait une requête HTTP GET/POST vers une URL.',
'parameters' => ['url' => 'string', 'method' => 'string (GET|POST)', 'headers' => 'object', 'body' => 'string'],
'example' => '<tool_call>{"tool":"http_request","params":{"url":"http://127.0.0.1:5821/api/status","method":"GET"}}</tool_call>',
'requires' => ['network'],
'timeout' => 15
],
'ollama_generate' => [
'description' => 'Génère du texte avec un modèle Ollama spécifique (utile pour comparer modèles ou utiliser un spécialiste).',
'parameters' => ['model' => 'string', 'prompt' => 'string', 'system' => 'string'],
'example' => '<tool_call>{"tool":"ollama_generate","params":{"model":"deepseek-r1:32b","prompt":"Explique les CTE récursifs en SQL","system":"Tu es un expert PostgreSQL."}}</tool_call>',
'requires' => ['ollama'],
'timeout' => 60
],
'embedding_generate' => [
'description' => 'Génère un embedding vectoriel pour du texte (768 dimensions, nomic-embed-text).',
'parameters' => ['text' => 'string'],
'example' => '<tool_call>{"tool":"embedding_generate","params":{"text":"Comment configurer DKIM dans PowerMTA?"}}</tool_call>',
'requires' => ['ollama'],
'timeout' => 10
],
'rag_search' => [
'description' => 'Recherche sémantique dans la base de connaissances WEVIA (pgvector).',
'parameters' => ['query' => 'string', 'top_k' => 'int (défaut: 5)', 'threshold' => 'float (défaut: 0.5)'],
'example' => '<tool_call>{"tool":"rag_search","params":{"query":"warming schedule IP PowerMTA","top_k":3}}</tool_call>',
'requires' => ['postgresql', 'ollama'],
'timeout' => 15
],
'calculator' => [
'description' => 'Calcul mathématique précis (évite les erreurs d\'arrondi des LLMs).',
'parameters' => ['expression' => 'string (expression mathématique PHP)'],
'example' => '<tool_call>{"tool":"calculator","params":{"expression":"1783 * 43.2 / 365"}}</tool_call>',
'requires' => [],
'timeout' => 2
],
'system_info' => [
'description' => 'Informations système du serveur (CPU, RAM, disk, GPU, uptime, services).',
'parameters' => ['server' => 'string (s88|s89|s46|s151)'],
'example' => '<tool_call>{"tool":"system_info","params":{"server":"s88"}}</tool_call>',
'requires' => ['bash'],
'timeout' => 10
],
'dns_lookup' => [
'description' => 'Requête DNS pour vérifier SPF, DKIM, DMARC, MX, A records.',
'parameters' => ['domain' => 'string', 'type' => 'string (A|MX|TXT|CNAME|NS|AAAA)'],
'example' => '<tool_call>{"tool":"dns_lookup","params":{"domain":"weval-consulting.com","type":"TXT"}}</tool_call>',
'requires' => ['network'],
'timeout' => 5
],
'json_transform' => [
'description' => 'Transforme, filtre, agrège des données JSON.',
'parameters' => ['data' => 'array|string', 'operation' => 'string (filter|sort|group|count|sum|unique)'],
'example' => '<tool_call>{"tool":"json_transform","params":{"data":[1,2,3,4,5],"operation":"sum"}}</tool_call>',
'requires' => [],
'timeout' => 5
],
'sentinel_exec' => [
'description' => 'Exécute une commande sur un serveur distant via l\'API Sentinel.',
'parameters' => ['target' => 'string (s88|s46|s151)', 'command' => 'string'],
'example' => '<tool_call>{"tool":"sentinel_exec","params":{"target":"s88","command":"nvidia-smi --query-gpu=memory.used,memory.total --format=csv"}}</tool_call>',
'requires' => ['network', 'sentinel'],
'timeout' => 30
]
];
/**
* Génère le prompt décrivant tous les outils disponibles
*/
public static function getToolsPrompt(): string {
$prompt = "## Outils disponibles\n\n";
$prompt .= "Tu peux utiliser les outils suivants en insérant un bloc <tool_call> dans ta réponse.\n";
$prompt .= "Format: <tool_call>{\"tool\":\"nom\",\"params\":{...}}</tool_call>\n\n";
foreach (self::$tools as $name => $tool) {
$prompt .= "### $name\n";
$prompt .= $tool['description'] . "\n";
$prompt .= "Paramètres: " . json_encode($tool['parameters'], JSON_UNESCAPED_UNICODE) . "\n";
$prompt .= "Exemple: " . $tool['example'] . "\n\n";
}
$prompt .= "### Règles d'utilisation des outils\n";
$prompt .= "1. N'utilise un outil QUE si c'est nécessaire pour répondre correctement\n";
$prompt .= "2. Préfère tes connaissances internes pour les faits généraux\n";
$prompt .= "3. Utilise database_query pour les stats WEVADS en temps réel\n";
$prompt .= "4. Utilise rag_search pour les questions sur WEVAL, WEVIA, SAP, email marketing\n";
$prompt .= "5. JAMAIS de requêtes destructives (DELETE, DROP, UPDATE sans WHERE)\n";
$prompt .= "6. Toujours vérifier les résultats avant de les présenter\n";
return $prompt;
}
/**
* Parse et exécute les tool calls dans une réponse LLM
*/
public static function processResponse(string $response): array {
$results = [];
if (preg_match_all('/<tool_call>(.*?)<\/tool_call>/s', $response, $matches)) {
foreach ($matches[1] as $call_json) {
$call = json_decode(trim($call_json), true);
if (!$call || !isset($call['tool'])) continue;
$tool_name = $call['tool'];
$params = $call['params'] ?? [];
if (!isset(self::$tools[$tool_name])) {
$results[] = ['tool' => $tool_name, 'error' => "Outil inconnu: $tool_name"];
continue;
}
$tool_config = self::$tools[$tool_name];
// Vérification sécurité pour bash
if ($tool_name === 'execute_bash' && isset($params['command'])) {
foreach ($tool_config['dangerous_patterns'] ?? [] as $pattern) {
if (stripos($params['command'], $pattern) !== false) {
$results[] = ['tool' => $tool_name, 'error' => "BLOQUÉ: Commande dangereuse détectée ($pattern)"];
continue 2;
}
}
}
// Vérification read-only pour database
if ($tool_name === 'database_query' && isset($params['query'])) {
$q_upper = strtoupper(trim($params['query']));
if (!str_starts_with($q_upper, 'SELECT') && !str_starts_with($q_upper, 'WITH') && !str_starts_with($q_upper, 'EXPLAIN')) {
$results[] = ['tool' => $tool_name, 'error' => "BLOQUÉ: Seules les requêtes SELECT/WITH/EXPLAIN sont autorisées"];
continue;
}
}
// Exécuter l'outil
try {
$start = microtime(true);
$result = self::executeTool($tool_name, $params, $tool_config);
$duration = microtime(true) - $start;
$results[] = [
'tool' => $tool_name,
'result' => $result,
'duration_ms' => round($duration * 1000),
'success' => true
];
} catch (\Exception $e) {
$results[] = [
'tool' => $tool_name,
'error' => $e->getMessage(),
'success' => false
];
}
}
}
return $results;
}
private static function executeTool(string $name, array $params, array $config): mixed {
$timeout = $config['timeout'] ?? 10;
switch ($name) {
case 'calculator':
$expr = preg_replace('/[^0-9+\-*\/().,%\s]/', '', $params['expression'] ?? '0');
return eval("return $expr;");
case 'system_info':
$server = $params['server'] ?? 's88';
$cmd = "echo '=== SYSTEM ===' && uname -a && echo '=== CPU ===' && nproc && echo '=== RAM ===' && free -m | head -2 && echo '=== DISK ===' && df -h / | tail -1 && echo '=== UPTIME ===' && uptime";
return shell_exec($cmd);
case 'file_read':
$path = $params['path'] ?? '';
if (!file_exists($path)) return "Fichier non trouvé: $path";
$lines = $params['lines'] ?? null;
if ($lines) {
return implode("\n", array_slice(file($path), 0, $lines));
}
return file_get_contents($path);
case 'dns_lookup':
$domain = escapeshellarg($params['domain'] ?? '');
$type = escapeshellarg($params['type'] ?? 'A');
return shell_exec("dig $type $domain +short 2>&1");
case 'http_request':
$url = $params['url'] ?? '';
$method = strtoupper($params['method'] ?? 'GET');
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
if ($method === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $params['body'] ?? '');
}
$result = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return ['status' => $code, 'body' => substr($result, 0, 5000)];
case 'json_transform':
$data = $params['data'] ?? [];
$op = $params['operation'] ?? 'count';
if (is_string($data)) $data = json_decode($data, true);
return match($op) {
'count' => count($data),
'sum' => array_sum($data),
'unique' => array_values(array_unique($data)),
'sort' => (function() use ($data) { sort($data); return $data; })(),
default => $data
};
default:
return "Tool handler not implemented for: $name (placeholder — wire to real backend)";
}
}
}