230 lines
9.0 KiB
PHP
Executable File
230 lines
9.0 KiB
PHP
Executable File
<?php
|
|
/**
|
|
* ╔══════════════════════════════════════════════════════════╗
|
|
* ║ WEVIA OPUS — Agentic Tool Use Engine ║
|
|
* ║ Function Calling + Tool Orchestration ║
|
|
* ╚══════════════════════════════════════════════════════════╝
|
|
*/
|
|
|
|
class ToolUseEngine {
|
|
private $tools = [];
|
|
private $history = [];
|
|
|
|
public function __construct() {
|
|
$this->registerDefaultTools();
|
|
}
|
|
|
|
/**
|
|
* Enregistre les outils disponibles
|
|
*/
|
|
private function registerDefaultTools(): void {
|
|
$this->tools = [
|
|
'web_search' => [
|
|
'name' => 'web_search',
|
|
'description' => 'Recherche sur internet via SearXNG',
|
|
'parameters' => ['query' => 'string', 'max_results' => 'int'],
|
|
'handler' => [$this, 'toolWebSearch']
|
|
],
|
|
'execute_code' => [
|
|
'name' => 'execute_code',
|
|
'description' => 'Exécute du code Python/Bash/PHP',
|
|
'parameters' => ['language' => 'string', 'code' => 'string'],
|
|
'handler' => [$this, 'toolExecuteCode']
|
|
],
|
|
'database_query' => [
|
|
'name' => 'database_query',
|
|
'description' => 'Exécute une requête SQL SELECT',
|
|
'parameters' => ['query' => 'string', 'database' => 'string'],
|
|
'handler' => [$this, 'toolDatabaseQuery']
|
|
],
|
|
'file_read' => [
|
|
'name' => 'file_read',
|
|
'description' => 'Lit le contenu d\'un fichier',
|
|
'parameters' => ['path' => 'string'],
|
|
'handler' => [$this, 'toolFileRead']
|
|
],
|
|
'file_write' => [
|
|
'name' => 'file_write',
|
|
'description' => 'Écrit du contenu dans un fichier',
|
|
'parameters' => ['path' => 'string', 'content' => 'string'],
|
|
'handler' => [$this, 'toolFileWrite']
|
|
],
|
|
'calculator' => [
|
|
'name' => 'calculator',
|
|
'description' => 'Calculs mathématiques',
|
|
'parameters' => ['expression' => 'string'],
|
|
'handler' => [$this, 'toolCalculator']
|
|
],
|
|
'system_info' => [
|
|
'name' => 'system_info',
|
|
'description' => 'Information système (CPU, RAM, disk, services)',
|
|
'parameters' => ['metric' => 'string'],
|
|
'handler' => [$this, 'toolSystemInfo']
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Génère la description des outils pour le system prompt
|
|
*/
|
|
public function getToolsPrompt(): string {
|
|
$prompt = "Tu as accès aux outils suivants:\n\n";
|
|
foreach ($this->tools as $tool) {
|
|
$params = json_encode($tool['parameters']);
|
|
$prompt .= "- **{$tool['name']}**: {$tool['description']}\n Paramètres: $params\n\n";
|
|
}
|
|
$prompt .= "Pour utiliser un outil, réponds avec:\n";
|
|
$prompt .= "<tool_call>{\"name\": \"tool_name\", \"params\": {...}}</tool_call>\n";
|
|
return $prompt;
|
|
}
|
|
|
|
/**
|
|
* Parse et exécute les tool calls dans une réponse LLM
|
|
*/
|
|
public function processResponse(string $response): array {
|
|
$calls = [];
|
|
$results = [];
|
|
|
|
preg_match_all('/<tool_call>(.*?)<\/tool_call>/s', $response, $matches);
|
|
|
|
foreach ($matches[1] as $callJson) {
|
|
$call = json_decode($callJson, true);
|
|
if (!$call || !isset($call['name'])) continue;
|
|
|
|
$calls[] = $call;
|
|
|
|
if (isset($this->tools[$call['name']])) {
|
|
$handler = $this->tools[$call['name']]['handler'];
|
|
$params = $call['params'] ?? [];
|
|
|
|
try {
|
|
$result = call_user_func($handler, $params);
|
|
$results[] = ['tool' => $call['name'], 'success' => true, 'result' => $result];
|
|
} catch (\Exception $e) {
|
|
$results[] = ['tool' => $call['name'], 'success' => false, 'error' => $e->getMessage()];
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->history = array_merge($this->history, $results);
|
|
|
|
return ['calls' => $calls, 'results' => $results, 'had_tools' => !empty($calls)];
|
|
}
|
|
|
|
// Tool implementations
|
|
public function toolWebSearch(array $params): string {
|
|
$query = $params['query'] ?? '';
|
|
$max = min($params['max_results'] ?? 5, 10);
|
|
|
|
$ch = curl_init("http://127.0.0.1:8888/search?q=" . urlencode($query) . "&format=json&engines=google,duckduckgo&results=$max");
|
|
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 15]);
|
|
$raw = curl_exec($ch);
|
|
curl_close($ch);
|
|
|
|
$data = json_decode($raw, true);
|
|
$results = [];
|
|
foreach (($data['results'] ?? []) as $r) {
|
|
$results[] = "**{$r['title']}**\n{$r['content']}\nURL: {$r['url']}";
|
|
}
|
|
|
|
return implode("\n---\n", $results) ?: "Aucun résultat trouvé.";
|
|
}
|
|
|
|
public function toolExecuteCode(array $params): string {
|
|
$lang = $params['language'] ?? 'python';
|
|
$code = $params['code'] ?? '';
|
|
$tmp = '/tmp/wevia_exec_' . uniqid();
|
|
|
|
switch ($lang) {
|
|
case 'python':
|
|
file_put_contents("$tmp.py", $code);
|
|
$out = shell_exec("timeout 30 python3 $tmp.py 2>&1");
|
|
@unlink("$tmp.py");
|
|
break;
|
|
case 'bash':
|
|
file_put_contents("$tmp.sh", $code);
|
|
$out = shell_exec("timeout 30 bash $tmp.sh 2>&1");
|
|
@unlink("$tmp.sh");
|
|
break;
|
|
case 'php':
|
|
file_put_contents("$tmp.php", "<?php\n$code");
|
|
$out = shell_exec("timeout 30 php $tmp.php 2>&1");
|
|
@unlink("$tmp.php");
|
|
break;
|
|
default:
|
|
return "Langage non supporté: $lang";
|
|
}
|
|
|
|
return trim($out ?? '(no output)');
|
|
}
|
|
|
|
public function toolDatabaseQuery(array $params): string {
|
|
$query = $params['query'] ?? '';
|
|
$db = $params['database'] ?? 'wevia_db';
|
|
|
|
// Security: SELECT only
|
|
if (!preg_match('/^\s*SELECT/i', $query)) {
|
|
return "ERROR: Seules les requêtes SELECT sont autorisées.";
|
|
}
|
|
|
|
try {
|
|
$pdo = new PDO("pgsql:host=127.0.0.1;dbname=$db", "postgres", "");
|
|
$stmt = $pdo->query($query);
|
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
return json_encode($rows, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
|
} catch (\Exception $e) {
|
|
return "SQL ERROR: " . $e->getMessage();
|
|
}
|
|
}
|
|
|
|
public function toolFileRead(array $params): string {
|
|
$path = $params['path'] ?? '';
|
|
if (!file_exists($path)) return "Fichier non trouvé: $path";
|
|
$content = file_get_contents($path);
|
|
if (strlen($content) > 10000) {
|
|
return substr($content, 0, 5000) . "\n...[TRUNCATED]...\n" . substr($content, -2000);
|
|
}
|
|
return $content;
|
|
}
|
|
|
|
public function toolFileWrite(array $params): string {
|
|
$path = $params['path'] ?? '';
|
|
$content = $params['content'] ?? '';
|
|
$dir = dirname($path);
|
|
if (!is_dir($dir)) @mkdir($dir, 0755, true);
|
|
file_put_contents($path, $content);
|
|
return "Fichier écrit: $path (" . strlen($content) . " bytes)";
|
|
}
|
|
|
|
public function toolCalculator(array $params): string {
|
|
$expr = $params['expression'] ?? '';
|
|
$safe = preg_replace('/[^0-9+\-*\/().%\s]/', '', $expr);
|
|
try {
|
|
$result = eval("return $safe;");
|
|
return "$expr = $result";
|
|
} catch (\Throwable $e) {
|
|
return "Erreur calcul: " . $e->getMessage();
|
|
}
|
|
}
|
|
|
|
public function toolSystemInfo(array $params): string {
|
|
$metric = $params['metric'] ?? 'all';
|
|
$info = [];
|
|
|
|
if (in_array($metric, ['all', 'cpu'])) {
|
|
$info[] = "CPU: " . trim(shell_exec("nproc")) . " cores, load: " . trim(shell_exec("uptime | awk -F'load average:' '{print \$2}'"));
|
|
}
|
|
if (in_array($metric, ['all', 'ram'])) {
|
|
$info[] = "RAM: " . trim(shell_exec("free -h | grep Mem | awk '{print \$3\"/\"\$2}'"));
|
|
}
|
|
if (in_array($metric, ['all', 'disk'])) {
|
|
$info[] = "Disk: " . trim(shell_exec("df -h / | tail -1 | awk '{print \$3\"/\"\$2\" (\"\$5\" used)\"}'"));
|
|
}
|
|
if (in_array($metric, ['all', 'gpu'])) {
|
|
$info[] = "GPU: " . trim(shell_exec("nvidia-smi --query-gpu=name,memory.used,memory.total --format=csv,noheader 2>/dev/null") ?: "N/A");
|
|
}
|
|
|
|
return implode("\n", $info);
|
|
}
|
|
}
|