From 3e725dd2ec0fa28c0c6be06001333a8e90a054c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 22 Jun 2026 22:06:30 +0200 Subject: [PATCH] Increased timeout for local AI inferences, and made AI timeout configurable per provider Fixes issue #1396 --- config/packages/ai_lm_studio_platform.yaml | 1 + config/packages/ai_ollama_platform.yaml | 1 + config/packages/ai_open_router_platform.yaml | 1 + config/services.yaml | 22 +++++++++++++++++++ .../Providers/AIWebProvider.php | 4 ++++ src/Settings/AISettings/AISettings.php | 2 ++ src/Settings/AISettings/LMStudioSettings.php | 11 +++++++++- src/Settings/AISettings/OllamaSettings.php | 10 +++++++++ .../AISettings/OpenRouterSettings.php | 10 +++++++++ translations/messages.en.xlf | 12 ++++++++++ 10 files changed, 73 insertions(+), 1 deletion(-) diff --git a/config/packages/ai_lm_studio_platform.yaml b/config/packages/ai_lm_studio_platform.yaml index 0e4287e0..1d832763 100644 --- a/config/packages/ai_lm_studio_platform.yaml +++ b/config/packages/ai_lm_studio_platform.yaml @@ -2,3 +2,4 @@ ai: platform: lmstudio: host_url: '%env(string:settings:ai_lmstudio:hostURL)%' + http_client: 'app.http_client.ai_lmstudio' diff --git a/config/packages/ai_ollama_platform.yaml b/config/packages/ai_ollama_platform.yaml index df551d4a..67ebe190 100644 --- a/config/packages/ai_ollama_platform.yaml +++ b/config/packages/ai_ollama_platform.yaml @@ -3,3 +3,4 @@ ai: ollama: endpoint: '%env(string:settings:ai_ollama:endpoint)%' api_key: '%env(string:settings:ai_ollama:apiKey)%' + http_client: 'app.http_client.ai_ollama' diff --git a/config/packages/ai_open_router_platform.yaml b/config/packages/ai_open_router_platform.yaml index d34de592..53eb20b9 100644 --- a/config/packages/ai_open_router_platform.yaml +++ b/config/packages/ai_open_router_platform.yaml @@ -2,3 +2,4 @@ ai: platform: openrouter: api_key: '%env(string:settings:ai_openrouter:apiKey)%' + http_client: 'app.http_client.ai_openrouter' diff --git a/config/services.yaml b/config/services.yaml index 5021c577..aa476bbb 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -52,6 +52,28 @@ services: alias: 'doctrine.migrations.dependency_factory' + #################################################################################################################### + # AI provider HTTP clients (with configurable timeouts) + #################################################################################################################### + + app.http_client.ai_ollama: + class: Symfony\Contracts\HttpClient\HttpClientInterface + factory: ['@http_client', 'withOptions'] + arguments: + - { timeout: '%env(int:settings:ai_ollama:timeout)%' } + + app.http_client.ai_lmstudio: + class: Symfony\Contracts\HttpClient\HttpClientInterface + factory: ['@http_client', 'withOptions'] + arguments: + - { timeout: '%env(int:settings:ai_lmstudio:timeout)%' } + + app.http_client.ai_openrouter: + class: Symfony\Contracts\HttpClient\HttpClientInterface + factory: ['@http_client', 'withOptions'] + arguments: + - { timeout: '%env(int:settings:ai_openrouter:timeout)%' } + #################################################################################################################### # Email #################################################################################################################### diff --git a/src/Services/InfoProviderSystem/Providers/AIWebProvider.php b/src/Services/InfoProviderSystem/Providers/AIWebProvider.php index 6539e69b..be91041e 100644 --- a/src/Services/InfoProviderSystem/Providers/AIWebProvider.php +++ b/src/Services/InfoProviderSystem/Providers/AIWebProvider.php @@ -282,6 +282,10 @@ final class AIWebProvider implements InfoProviderInterface try { $aiPlatform = $this->AIPlatformRegistry->getPlatform($this->settings->platform ?? throw new \RuntimeException('No AI platform selected') ); + // AI inference can take much longer than PHP's default max_execution_time (typically 30s). + // The HTTP client timeout already enforces the configured limit; disable PHP's constraint here. + set_time_limit(0); + //'openai/gpt-5-mini' $result = $aiPlatform->invoke($this->settings->model ?? throw new \RuntimeException('No model selected'), $input, [ 'response_format' => [ diff --git a/src/Settings/AISettings/AISettings.php b/src/Settings/AISettings/AISettings.php index 9f145c7f..659577b6 100644 --- a/src/Settings/AISettings/AISettings.php +++ b/src/Settings/AISettings/AISettings.php @@ -35,6 +35,8 @@ class AISettings { use SettingsTrait; + public const TIMEOUT_LIMIT = 600; + #[EmbeddedSettings] public ?OpenRouterSettings $openRouter = null; diff --git a/src/Settings/AISettings/LMStudioSettings.php b/src/Settings/AISettings/LMStudioSettings.php index 2bdad06e..d92ce97e 100644 --- a/src/Settings/AISettings/LMStudioSettings.php +++ b/src/Settings/AISettings/LMStudioSettings.php @@ -23,16 +23,17 @@ declare(strict_types=1); namespace App\Settings\AISettings; -use App\Form\Type\APIKeyType; use App\Services\AI\AIPlatformSettingsInterface; use App\Settings\SettingsIcon; use Jbtronics\SettingsBundle\Metadata\EnvVarMode; use Jbtronics\SettingsBundle\Settings\Settings; use Jbtronics\SettingsBundle\Settings\SettingsParameter; use Jbtronics\SettingsBundle\Settings\SettingsTrait; +use Symfony\Component\Form\Extension\Core\Type\NumberType; use Symfony\Component\Form\Extension\Core\Type\UrlType; use Symfony\Component\Translation\StaticMessage; use Symfony\Component\Translation\TranslatableMessage as TM; +use Symfony\Component\Validator\Constraints as Assert; #[Settings(name: 'ai_lmstudio', label: new TM("settings.ai.lmstudio"))] #[SettingsIcon("fa-robot")] @@ -46,6 +47,14 @@ class LMStudioSettings implements AIPlatformSettingsInterface envVar: "AI_LMSTUDIO_HOSTURL", envVarMode: EnvVarMode::OVERWRITE)] public ?string $hostURL = null; + #[SettingsParameter(label: new TM("settings.ai.timeout"), + description: new TM("settings.ai.timeout.help"), + formType: NumberType::class, + formOptions: ["scale" => 0, "attr" => ["min" => 1]], + )] + #[Assert\Range(min: 1, max: AISettings::TIMEOUT_LIMIT)] + public int $timeout = 180; + public function isAIPlatformEnabled(): bool { return $this->hostURL !== null && $this->hostURL !== ""; diff --git a/src/Settings/AISettings/OllamaSettings.php b/src/Settings/AISettings/OllamaSettings.php index dd17d5e2..7ca0e5a0 100644 --- a/src/Settings/AISettings/OllamaSettings.php +++ b/src/Settings/AISettings/OllamaSettings.php @@ -30,9 +30,11 @@ use Jbtronics\SettingsBundle\Metadata\EnvVarMode; use Jbtronics\SettingsBundle\Settings\Settings; use Jbtronics\SettingsBundle\Settings\SettingsParameter; use Jbtronics\SettingsBundle\Settings\SettingsTrait; +use Symfony\Component\Form\Extension\Core\Type\NumberType; use Symfony\Component\Form\Extension\Core\Type\UrlType; use Symfony\Component\Translation\StaticMessage; use Symfony\Component\Translation\TranslatableMessage as TM; +use Symfony\Component\Validator\Constraints as Assert; #[Settings(name: 'ai_ollama', label: new TM("settings.ai.ollama"))] #[SettingsIcon("fa-robot")] @@ -51,6 +53,14 @@ class OllamaSettings implements AIPlatformSettingsInterface envVar: "AI_OLLAMA_API_KEY", envVarMode: EnvVarMode::OVERWRITE)] public ?string $apiKey = null; + #[SettingsParameter(label: new TM("settings.ai.timeout"), + description: new TM("settings.ai.timeout.help"), + formType: NumberType::class, + formOptions: ["scale" => 0, "attr" => ["min" => 1]] + )] + #[Assert\Range(min: 1, max: AISettings::TIMEOUT_LIMIT)] + public int $timeout = 180; + public function isAIPlatformEnabled(): bool { return $this->endpoint !== null && $this->endpoint !== ""; diff --git a/src/Settings/AISettings/OpenRouterSettings.php b/src/Settings/AISettings/OpenRouterSettings.php index e083513a..16665554 100644 --- a/src/Settings/AISettings/OpenRouterSettings.php +++ b/src/Settings/AISettings/OpenRouterSettings.php @@ -30,7 +30,9 @@ use Jbtronics\SettingsBundle\Metadata\EnvVarMode; use Jbtronics\SettingsBundle\Settings\Settings; use Jbtronics\SettingsBundle\Settings\SettingsParameter; use Jbtronics\SettingsBundle\Settings\SettingsTrait; +use Symfony\Component\Form\Extension\Core\Type\NumberType; use Symfony\Component\Translation\TranslatableMessage as TM; +use Symfony\Component\Validator\Constraints as Assert; #[Settings(name: 'ai_openrouter', label: new TM("settings.ai.openrouter"), description: "settings.ai.openrouter.help")] #[SettingsIcon("fa-robot")] @@ -43,6 +45,14 @@ class OpenRouterSettings implements AIPlatformSettingsInterface formOptions: ["help_html" => true], envVar: "AI_OPENROUTER_KEY", envVarMode: EnvVarMode::OVERWRITE)] public ?string $apiKey = null; + #[SettingsParameter(label: new TM("settings.ai.timeout"), + description: new TM("settings.ai.timeout.help"), + formType: NumberType::class, + formOptions: ["scale" => 0, "attr" => ["min" => 1]], + envVar: "int:AI_OPENROUTER_TIMEOUT", envVarMode: EnvVarMode::OVERWRITE)] + #[Assert\Range(min: 1, max: AISettings::TIMEOUT_LIMIT)] + public int $timeout = 90; + public function isAIPlatformEnabled(): bool { return $this->apiKey !== null && $this->apiKey !== ""; diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index af33ee97..d828d3ee 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -13637,6 +13637,18 @@ Buerklin-API Authentication server: API Key + + + settings.ai.timeout + Timeout + + + + + settings.ai.timeout.help + Maximum time in seconds to wait for a response. Local AI inference might take multiple minutes, cloud inference is normally faster. + + browser_plugin.recent_pages.title