From 01d4d2336b9f2d7465ec4a163e0f63ca6132c34e Mon Sep 17 00:00:00 2001 From: MervinPraison <454862+MervinPraison@users.noreply.github.com> Date: Sat, 24 Oct 2026 07:31:51 +0000 Subject: [PATCH 1/2] fix: bound unset LLM request timeout to 600s instead of litellm's 6000s (fixes #5968) Co-Authored-By: Claude Opus 5.5 --- .../praisonaiagents/llm/llm.py | 12 +++++-- .../unit/llm/test_default_request_timeout.py | 32 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 src/praisonai-agents/tests/unit/llm/test_default_request_timeout.py diff --git a/src/praisonai-agents/praisonaiagents/llm/llm.py b/src/praisonai-agents/praisonaiagents/llm/llm.py index 2f70f8aa7e..e1cb982a44 100644 --- a/src/praisonai-agents/praisonaiagents/llm/llm.py +++ b/src/praisonai-agents/praisonaiagents/llm/llm.py @@ -43,6 +43,14 @@ def mark_success(self, profile: "AuthProfile") -> None: ... # Bounded shrink-and-retry steps after a provider context-overflow rejection. _CONTEXT_OVERFLOW_SHRINK_STEPS = 3 +# Default per-request timeout (seconds) when ``timeout`` is not passed. Without +# it litellm falls back to its 6000s ceiling, so a provider that accepts the +# connection but never answers blocks the turn for ~100 minutes per attempt. +# 600s matches the OpenAI SDK default used by the bare-model-name path and is +# a read-gap bound for streams. Pass ``timeout=None`` (or 0) to opt out. +_DEFAULT_LLM_TIMEOUT_S = 600 +_TIMEOUT_UNSET = object() + # ``litellm.success_callback``/``_async_success_callback``/``callbacks`` are # process-global lists shared by every LLM instance. Guard the read-modify-write @@ -493,7 +501,7 @@ def _log_llm_config(self, method_name: str, **config): def __init__( self, model: str, - timeout: Optional[int] = None, + timeout: Optional[int] = _TIMEOUT_UNSET, temperature: Optional[float] = None, top_p: Optional[float] = None, n: Optional[int] = None, @@ -535,7 +543,7 @@ def __init__( ) self.model = model - self.timeout = timeout + self.timeout = _DEFAULT_LLM_TIMEOUT_S if timeout is _TIMEOUT_UNSET else timeout self.temperature = temperature self.top_p = top_p self.n = n diff --git a/src/praisonai-agents/tests/unit/llm/test_default_request_timeout.py b/src/praisonai-agents/tests/unit/llm/test_default_request_timeout.py new file mode 100644 index 0000000000..b625462e12 --- /dev/null +++ b/src/praisonai-agents/tests/unit/llm/test_default_request_timeout.py @@ -0,0 +1,32 @@ +"""An unset LLM timeout is bounded; an explicit None/0 opts out (Issue #5968).""" + +import pytest + +from praisonaiagents.llm.llm import LLM, _DEFAULT_LLM_TIMEOUT_S + + +def _params(llm): + return ( + llm._build_completion_params(), + llm._build_responses_params([{'role': 'user', 'content': 'hi'}]), + ) + + +def test_unset_timeout_uses_finite_default(): + llm = LLM(model='gpt-4o-mini') + assert llm.timeout == _DEFAULT_LLM_TIMEOUT_S + for params in _params(llm): + assert params['timeout'] == _DEFAULT_LLM_TIMEOUT_S + + +@pytest.mark.parametrize('value', [None, 0]) +def test_explicit_opt_out_omits_timeout(value): + llm = LLM(model='gpt-4o-mini', timeout=value) + for params in _params(llm): + assert 'timeout' not in params + + +def test_explicit_timeout_is_forwarded(): + llm = LLM(model='gpt-4o-mini', timeout=30) + for params in _params(llm): + assert params['timeout'] == 30 From 46ebf067e4ed35adc75f010125e162c661d3be5e Mon Sep 17 00:00:00 2001 From: MervinPraison <454862+MervinPraison@users.noreply.github.com> Date: Sat, 24 Oct 2026 07:47:32 +0000 Subject: [PATCH 2/2] fix: waive LLM.__init__.timeout signature parity (TS inherits OpenAI SDK's 600s default) Co-Authored-By: Claude Opus 5.5 --- src/praisonai/praisonai/_dev/parity/signatures/waivers.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/praisonai/praisonai/_dev/parity/signatures/waivers.yaml b/src/praisonai/praisonai/_dev/parity/signatures/waivers.yaml index f55d3c0e69..029f123c03 100644 --- a/src/praisonai/praisonai/_dev/parity/signatures/waivers.yaml +++ b/src/praisonai/praisonai/_dev/parity/signatures/waivers.yaml @@ -105,6 +105,12 @@ waivers: `transfer_to_` exactly (it previously said `handoff_to_`, which could contain a space and be rejected by the provider). Only the moment of derivation differs. + LLM.__init__.timeout: + owner: praisonai-ts + reason: Same effective bound, applied by a different layer. Python's sentinel resolves an unset timeout + to 600s in the body (litellm would otherwise wait 6000s); TS leaves `timeout` undefined and the OpenAI + Node SDK applies its own 600s (10 minute) default. An explicit value is forwarded on both sides. + issue: '#5968' PraisonAIError.__init__.run_id: owner: praisonai-ts reason: 'Both sides mint a UUID at construction -- Python''s `self.run_id = run_id or str(uuid.uuid4())`