getConfig(); if ($config->isStream()) { yield from $this->streamInternal($prompt); return; } // Fallback: Blocking generate → Generator-kompatibel ausgeben yield $this->generateInternal($prompt); } /** * Public Blocking API */ public function generate(string $prompt): string { return $this->generateInternal($prompt); } /** * Internal streaming transport */ private function streamInternal(string $prompt): Generator { $payload = $this->buildPayload($prompt, true); $buffer = ''; $done = false; $ch = curl_init($this->apiUrl); if ($ch === false) { throw new RuntimeException('Failed to initialize cURL'); } curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => $payload, CURLOPT_RETURNTRANSFER => false, CURLOPT_TIMEOUT => $this->timeoutSeconds, CURLOPT_WRITEFUNCTION => function ($curl, string $data) use (&$buffer): int { $buffer .= $data; return strlen($data); }, ]); $mh = curl_multi_init(); if ($mh === false) { curl_close($ch); throw new RuntimeException('Failed to initialize cURL multi handle'); } curl_multi_add_handle($mh, $ch); try { do { do { $status = curl_multi_exec($mh, $running); } while ($status === CURLM_CALL_MULTI_PERFORM); while (($pos = strpos($buffer, "\n")) !== false) { $line = trim(substr($buffer, 0, $pos)); $buffer = substr($buffer, $pos + 1); if ($line === '') { continue; } try { $json = json_decode($line, true, flags: JSON_THROW_ON_ERROR); } catch (Throwable) { continue; } if (isset($json['response'])) { yield $json['response']; } if (!empty($json['done'])) { $done = true; } } if ($running) { curl_multi_select($mh, 0.2); } } while ($running && !$done); if (curl_errno($ch)) { throw new RuntimeException('LLM connection error: ' . curl_error($ch)); } } finally { curl_multi_remove_handle($mh, $ch); curl_multi_close($mh); curl_close($ch); } } /** * Internal blocking transport */ private function generateInternal(string $prompt): string { $payload = $this->buildPayload($prompt, false); $ch = curl_init($this->apiUrl); if ($ch === false) { throw new RuntimeException('Failed to initialize cURL'); } curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_POSTFIELDS => $payload, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => $this->timeoutSeconds, ]); $response = curl_exec($ch); if ($response === false) { throw new RuntimeException('LLM error: ' . curl_error($ch)); } curl_close($ch); $json = json_decode($response, true, flags: JSON_THROW_ON_ERROR); return $json['response'] ?? ''; } /** * Central Payload Builder (DRY) */ private function buildPayload(string $prompt, bool $stream): string { return json_encode([ 'model' => $this->model, 'prompt' => $prompt, 'stream' => $stream, 'options' => $this->buildOptions() ], JSON_THROW_ON_ERROR); } /** * Central Options Builder (DRY) */ private function buildOptions(): array { $config = $this->getConfig(); return [ 'temperature' => $config->getTemperature(), 'top_k' => $config->getTopK(), 'top_p' => $config->getTopP(), 'repeat_penalty' => $config->getRepeatPenalty(), 'num_ctx' => $config->getNumCtx(), ]; } /** * Config caching per request */ private function getConfig(): ModelGenerationConfig { if ($this->cachedConfig === null) { $this->cachedConfig = $this->configProvider->getActiveForModel($this->model); } return $this->cachedConfig; } }