From 54f7d9bd38c3338ea4e85ed007a54df5d3925122 Mon Sep 17 00:00:00 2001 From: FDS Date: Mon, 8 Jun 2026 21:03:54 +0200 Subject: [PATCH 1/2] Fix #55: LLM-Verbindungstest mit Diagnoseausgabe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - LlmService::testConnection() gibt cURL-Fehler, HTTP-Code, Antwortzeit und verfügbare Modelle zurück - probeOllama() schreibt cURL-Fehler ins PHP-Fehlerlog - Einstellungsseite: Button "Verbindung testen" mit Inline-Diagnose und Firewall-Hinweis - Route POST /settings/llm-test --- Changes.md | 7 +++ public/index.php | 1 + src/Controllers/SettingsController.php | 7 +++ src/Services/LlmService.php | 59 ++++++++++++++++++++++++-- src/Views/settings/form.php | 54 ++++++++++++++++++++++- 5 files changed, 123 insertions(+), 5 deletions(-) diff --git a/Changes.md b/Changes.md index 42ba768..7de518d 100644 --- a/Changes.md +++ b/Changes.md @@ -2,6 +2,13 @@ --- +## 2026-06-08 – Fix: KI-Verbindungstest mit Diagnoseinformationen (Issue #55) + +- **`LlmService`:** Neue Methode `testConnection()` gibt detaillierte Diagnose zurück (cURL-Fehlernummer/-meldung, HTTP-Code, Antwortzeit, verfügbare Modelle) +- **`LlmService`:** `probeOllama()` schreibt bei cURL-Fehlern jetzt einen Eintrag ins PHP-Fehlerlog (`docker compose logs web`) +- **Einstellungsseite:** Neuer Button „Verbindung testen" im KI-Abschnitt; zeigt bei Erfolg verfügbare Modelle, bei Fehler cURL-Fehler, URL und Firewall-Hinweis an +- **Route:** `POST /settings/llm-test` → `SettingsController::llmTest()` + ## 2026-06-01 – Fix: EN16931-Validierungsfehler in ZUGFeRD-Rechnungen - **[BR-FX-EN-04]** behoben: `ZugferdService` setzt jetzt BT-72 (Liefer-/Leistungsdatum) via `setDocumentSupplyChainEvent()`; das Datum wird aus dem Feld `leistungszeitraum` geparst (Format `DD.MM.YYYY` → direkt; Format `MM.YYYY` → letzter Tag des Monats; sonst Fallback auf Rechnungsdatum) diff --git a/public/index.php b/public/index.php index 1efd2cb..8a9ef53 100644 --- a/public/index.php +++ b/public/index.php @@ -165,6 +165,7 @@ $router->post('/settings/mail-test', fn($p) => (new SettingsController())->mailT $router->post('/settings/logo', fn($p) => (new SettingsController())->uploadLogo($p)); $router->post('/settings/logo/delete', fn($p) => (new SettingsController())->deleteLogo($p)); $router->post('/settings/generate-app-key', fn($p) => (new SettingsController())->generateAppKey($p)); +$router->post('/settings/llm-test', fn($p) => (new SettingsController())->llmTest($p)); $router->get('/settings/bankimport', fn($p) => (new SettingsController())->bankImport($p)); $router->post('/settings/bankimport/suggest', fn($p) => (new SettingsController())->storeSuggestion($p)); $router->post('/settings/bankimport/create', fn($p) => (new SettingsController())->createBankImportFormat($p)); diff --git a/src/Controllers/SettingsController.php b/src/Controllers/SettingsController.php index 40503aa..aec7af0 100644 --- a/src/Controllers/SettingsController.php +++ b/src/Controllers/SettingsController.php @@ -126,6 +126,13 @@ class SettingsController extends BaseController echo json_encode(['key' => bin2hex(random_bytes(32))]); } + public function llmTest(array $params): void + { + header('Content-Type: application/json'); + $result = (new \Steuerkram\Services\LlmService())->testConnection(); + echo json_encode($result); + } + public function bankImport(array $params): void { $formats = BankSource::all('csv'); diff --git a/src/Services/LlmService.php b/src/Services/LlmService.php index f144826..a41cd29 100644 --- a/src/Services/LlmService.php +++ b/src/Services/LlmService.php @@ -134,9 +134,54 @@ class LlmService } } + /** + * Liefert Diagnose-Informationen für den Verbindungstest auf der Einstellungsseite. + * Gibt ein Array zurück: ['ok' => bool, 'url' => string, 'elapsed' => int, 'error'? => string, 'models'? => string[]] + * + * @return array + */ + public function testConnection(): array + { + if ($this->mode === null) { + return ['ok' => false, 'error' => 'Kein LLM-Provider konfiguriert.']; + } + if ($this->mode === 'openai') { + return ['ok' => true, 'url' => $this->url, 'elapsed' => 0, 'info' => 'OpenAI-Modus – kein Verbindungstest möglich.']; + } + + $url = $this->url . '/api/tags'; + $ch = curl_init($url); + curl_setopt_array($ch, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => 5, + CURLOPT_CONNECTTIMEOUT => 3, + ]); + $start = microtime(true); + $response = curl_exec($ch); + $elapsed = (int)round((microtime(true) - $start) * 1000); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $errno = curl_errno($ch); + $errstr = curl_error($ch); + curl_close($ch); + + if ($errno !== 0) { + error_log("[Steuerkram] LlmService::testConnection() cURL-Fehler #{$errno} auf {$url}: {$errstr}"); + return ['ok' => false, 'url' => $url, 'elapsed' => $elapsed, 'error' => "cURL #{$errno}: {$errstr}"]; + } + if ($code < 200 || $code >= 300) { + return ['ok' => false, 'url' => $url, 'elapsed' => $elapsed, 'error' => "HTTP {$code}"]; + } + + $data = json_decode($response, true); + $models = array_column($data['models'] ?? [], 'name'); + + return ['ok' => true, 'url' => $url, 'elapsed' => $elapsed, 'models' => $models]; + } + private function probeOllama(string $baseUrl): bool { - $ch = curl_init($baseUrl . '/api/tags'); + $url = $baseUrl . '/api/tags'; + $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 2, @@ -144,10 +189,16 @@ class LlmService CURLOPT_NOBODY => false, ]); curl_exec($ch); - $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); - $err = curl_errno($ch); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $errno = curl_errno($ch); + $err = curl_error($ch); curl_close($ch); - return $err === 0 && $code >= 200 && $code < 300; + + if ($errno !== 0) { + error_log("[Steuerkram] LlmService::probeOllama() cURL-Fehler #{$errno} auf {$url}: {$err}"); + } + + return $errno === 0 && $code >= 200 && $code < 300; } private function callOllama(string $prompt): string diff --git a/src/Views/settings/form.php b/src/Views/settings/form.php index facb00f..b558032 100644 --- a/src/Views/settings/form.php +++ b/src/Views/settings/form.php @@ -256,7 +256,7 @@ $reachable = $providerLabel !== null ? $llm->isReachable() : false; $ollamaUrlEnv = getenv('OLLAMA_URL') ?: ($_ENV['OLLAMA_URL'] ?? ''); ?> -

+

✓ Provider erreichbar: @@ -269,6 +269,11 @@ — Kein LLM-Provider konfiguriert (Auto-Discovery localhost:11434 fehlgeschlagen)

+
+ + +
+ @@ -477,6 +482,53 @@ + +