mirror of
https://github.com/usestrix/strix.git
synced 2026-08-16 01:16:40 +02:00
fix(llm): use httpx.Timeout read-inactivity for per-turn model timeout
This commit is contained in:
+10
-7
@@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
import os
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import httpx
|
||||
from agents import set_default_openai_api, set_default_openai_key, set_tracing_disabled
|
||||
from agents.models.multi_provider import MultiProvider
|
||||
from agents.retry import (
|
||||
@@ -21,17 +22,19 @@ if TYPE_CHECKING:
|
||||
from strix.config.settings import Settings
|
||||
|
||||
|
||||
def request_timeout_extra_args(timeout_s: float | None) -> dict[str, float] | None:
|
||||
"""Per-request model timeout (connect + read/inactivity) as ``extra_args``.
|
||||
def request_timeout_extra_args(timeout_s: float | None) -> dict[str, httpx.Timeout] | None:
|
||||
"""Per-request model timeout as ``extra_args``, forwarded to the provider call.
|
||||
|
||||
Restores pre-v1 behavior: a stalled model stream trips this timeout and is
|
||||
retried by ``DEFAULT_MODEL_RETRY`` instead of hanging the agent indefinitely.
|
||||
The value is forwarded to the underlying ``responses.create`` /
|
||||
``chat.completions.create`` / ``litellm.acompletion`` call.
|
||||
Uses ``read`` for inactivity (matching pre-v1's per-chunk ``wait_for``): a stalled
|
||||
stream trips ``read`` and is retried by ``DEFAULT_MODEL_RETRY``, while a healthy
|
||||
long stream that keeps emitting tokens never trips it. An explicit ``httpx.Timeout``
|
||||
(rather than a scalar) keeps this a read-inactivity timeout instead of a
|
||||
total-duration deadline on every httpx-based backend (``responses.create`` /
|
||||
``chat.completions.create`` / ``litellm.acompletion``).
|
||||
"""
|
||||
if not timeout_s or timeout_s <= 0:
|
||||
return None
|
||||
return {"timeout": float(timeout_s)}
|
||||
return {"timeout": httpx.Timeout(timeout_s, connect=min(timeout_s, 30.0))}
|
||||
|
||||
|
||||
def _retry_statusless_provider_errors(context: RetryPolicyContext) -> bool:
|
||||
|
||||
@@ -164,7 +164,8 @@ def test_make_model_settings_sets_request_timeout() -> None:
|
||||
request_timeout=300.0,
|
||||
)
|
||||
|
||||
assert settings.extra_args == {"timeout": 300.0}
|
||||
assert settings.extra_args is not None
|
||||
assert settings.extra_args["timeout"].read == 300.0
|
||||
|
||||
|
||||
def test_make_model_settings_omits_timeout_when_unset() -> None:
|
||||
@@ -182,4 +183,5 @@ def test_make_model_settings_timeout_survives_reasoning_resolve() -> None:
|
||||
request_timeout=120.0,
|
||||
)
|
||||
|
||||
assert settings.extra_args == {"timeout": 120.0}
|
||||
assert settings.extra_args is not None
|
||||
assert settings.extra_args["timeout"].read == 120.0
|
||||
|
||||
+12
-2
@@ -17,8 +17,18 @@ def test_recommended_models_are_accepted(model_name: str) -> None:
|
||||
|
||||
|
||||
def test_request_timeout_extra_args_positive() -> None:
|
||||
assert request_timeout_extra_args(300) == {"timeout": 300.0}
|
||||
assert request_timeout_extra_args(120.5) == {"timeout": 120.5}
|
||||
args = request_timeout_extra_args(300)
|
||||
assert args is not None
|
||||
timeout = args["timeout"]
|
||||
# read (inactivity) carries the configured value; connect is capped so a dead
|
||||
# endpoint fails fast rather than waiting the full read window.
|
||||
assert timeout.read == 300.0
|
||||
assert timeout.connect == 30.0
|
||||
|
||||
short = request_timeout_extra_args(10)
|
||||
assert short is not None
|
||||
assert short["timeout"].read == 10.0
|
||||
assert short["timeout"].connect == 10.0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", [None, 0, -1])
|
||||
|
||||
Reference in New Issue
Block a user