File size: 3,356 Bytes
e2f4e9e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0a45558
e2f4e9e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# src/rotator_library/timeout_config.py
"""
Centralized timeout configuration for HTTP requests.

All values can be overridden via environment variables:
    TIMEOUT_CONNECT - Connection establishment timeout (default: 30s)
    TIMEOUT_WRITE - Request body send timeout (default: 30s)
    TIMEOUT_POOL - Connection pool acquisition timeout (default: 60s)
    TIMEOUT_READ_STREAMING - Read timeout between chunks for streaming (default: 180s / 3 min)
    TIMEOUT_READ_NON_STREAMING - Read timeout for non-streaming responses (default: 600s / 10 min)
"""

import os
import logging
import httpx

lib_logger = logging.getLogger("rotator_library")


class TimeoutConfig:
    """
    Centralized timeout configuration for HTTP requests.

    All values can be overridden via environment variables.
    """

    # Default values (in seconds)
    _CONNECT = 30.0
    _WRITE = 30.0
    _POOL = 60.0
    _READ_STREAMING = 300.0  # 5 minutes between chunks
    _READ_NON_STREAMING = 600.0  # 10 minutes for full response

    @classmethod
    def _get_env_float(cls, key: str, default: float) -> float:
        """Get a float value from environment variable, or return default."""
        value = os.environ.get(key)
        if value is not None:
            try:
                return float(value)
            except ValueError:
                lib_logger.warning(
                    f"Invalid value for {key}: {value}. Using default: {default}"
                )
        return default

    @classmethod
    def connect(cls) -> float:
        """Connection establishment timeout."""
        return cls._get_env_float("TIMEOUT_CONNECT", cls._CONNECT)

    @classmethod
    def write(cls) -> float:
        """Request body send timeout."""
        return cls._get_env_float("TIMEOUT_WRITE", cls._WRITE)

    @classmethod
    def pool(cls) -> float:
        """Connection pool acquisition timeout."""
        return cls._get_env_float("TIMEOUT_POOL", cls._POOL)

    @classmethod
    def read_streaming(cls) -> float:
        """Read timeout between chunks for streaming requests."""
        return cls._get_env_float("TIMEOUT_READ_STREAMING", cls._READ_STREAMING)

    @classmethod
    def read_non_streaming(cls) -> float:
        """Read timeout for non-streaming responses."""
        return cls._get_env_float("TIMEOUT_READ_NON_STREAMING", cls._READ_NON_STREAMING)

    @classmethod
    def streaming(cls) -> httpx.Timeout:
        """
        Timeout configuration for streaming LLM requests.

        Uses a shorter read timeout (default 3 min) since we expect
        periodic chunks. If no data arrives for this duration, the
        connection is considered stalled.
        """
        return httpx.Timeout(
            connect=cls.connect(),
            read=cls.read_streaming(),
            write=cls.write(),
            pool=cls.pool(),
        )

    @classmethod
    def non_streaming(cls) -> httpx.Timeout:
        """
        Timeout configuration for non-streaming LLM requests.

        Uses a longer read timeout (default 10 min) since the server
        may take significant time to generate the complete response
        before sending anything back.
        """
        return httpx.Timeout(
            connect=cls.connect(),
            read=cls.read_non_streaming(),
            write=cls.write(),
            pool=cls.pool(),
        )