Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/praisonai-agents/praisonaiagents/llm/llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions src/praisonai/praisonai/_dev/parity/signatures/waivers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ waivers:
`transfer_to_<lowercased, spaces-to-underscores name>` exactly (it previously said `handoff_to_<raw
name>`, 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())`
Expand Down
Loading