Allowed Python commands */ private const ALLOWED_COMMANDS = ['generate', 'critique', 'revise']; public function __construct( ?string $pipelinePath = null, ?string $pythonPath = null ) { $this->pipelinePath = $pipelinePath ?? '/var/www/scripts/pipeline'; $this->pythonPath = $pythonPath ?? $this->pipelinePath . '/venv/bin/python'; } /** * Generate content for an order */ public function generate(int $orderId, string $model, string $collection, int $contextLimit): ContentGenerationResult { $result = $this->callPython('generate', $orderId, [$model, $collection, $contextLimit]); return ContentGenerationResult::fromPythonResult($result); } /** * Run critique on a version */ public function critique(int $versionId, string $model): ContentGenerationResult { $result = $this->callPython('critique', $versionId, [$model]); return ContentGenerationResult::fromPythonResult($result); } /** * Create revision of a version */ public function revise(int $versionId, string $model): ContentGenerationResult { $result = $this->callPython('revise', $versionId, [$model]); return ContentGenerationResult::fromPythonResult($result); } /** * Call Python script with command and arguments * * @param string $command Command to execute (generate, critique, revise) * @param int $entityId Order or version ID * @param array $args Additional arguments * @return array Result from Python script */ private function callPython(string $command, int $entityId, array $args = []): array { // Validate command against whitelist if (!in_array($command, self::ALLOWED_COMMANDS, true)) { return ['error' => 'Ungültiger Command: ' . $command]; } $scriptPath = $this->pipelinePath . '/web_generate.py'; // Build command array for proc_open (safer than shell_exec) $cmdArray = [ $this->pythonPath, $scriptPath, $command, (string) $entityId, ...$args, ]; $descriptors = [ 0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w'], ]; $process = proc_open($cmdArray, $descriptors, $pipes); // nosemgrep: exec-use if (!is_resource($process)) { return ['error' => 'Script konnte nicht gestartet werden']; } fclose($pipes[0]); $stdout = stream_get_contents($pipes[1]); $stderr = stream_get_contents($pipes[2]); fclose($pipes[1]); fclose($pipes[2]); $exitCode = proc_close($process); $output = $stdout . $stderr; if ($exitCode !== 0 && $output === '') { return ['error' => 'Script fehlgeschlagen (Exit: ' . $exitCode . ')']; } if (preg_match('/\{[\s\S]*\}/', $output, $matches)) { $result = json_decode($matches[0], true); if (json_last_error() === JSON_ERROR_NONE) { return $result; } } return ['error' => 'Ungültige Antwort: ' . substr($output, 0, 500)]; } }