mirror of
https://github.com/NCBM/plyngent.git
synced 2026-07-25 08:04:57 +08:00
core/runtime: configurable provider HTTP request timeouts
This commit is contained in:
@@ -51,11 +51,11 @@ lmproto/deepseek/openai_compat/ ← Extends base via inheritance + extra fields
|
|||||||
|
|
||||||
### Config (`config/`)
|
### Config (`config/`)
|
||||||
|
|
||||||
TOML load/store (`ConfigStore`): `[providers]` tagged union presets, `[database]` section. Default path via platformdirs.
|
TOML load/store (`ConfigStore`): `[providers]` tagged union presets, `[database]` section. Default path via platformdirs. Providers may set `timeout` as a float or `{ connect, read }` (`HttpTimeoutConfig`); omitted → product defaults (10s connect / 600s read).
|
||||||
|
|
||||||
### Runtime (`runtime/`)
|
### Runtime (`runtime/`)
|
||||||
|
|
||||||
`create_client(provider)` maps config `Provider` → protocol client. OpenAI → `lmproto.openai.OpenAIClient` (Responses-capable); openai-compatible / deepseek(openai convention) → chat-completions clients; anthropic and deepseek anthropic convention raise `ProviderNotSupportedError`.
|
`create_client(provider)` maps config `Provider` → protocol client. `provider_to_openai_config` normalizes `timeout` via `normalize_http_timeout` into `OpenAIConfig.timeout` for `niquests.AsyncSession`. OpenAI → `lmproto.openai.OpenAIClient` (Responses-capable); openai-compatible / deepseek(openai convention) → chat-completions clients; anthropic and deepseek anthropic convention raise `ProviderNotSupportedError`.
|
||||||
|
|
||||||
### Memory (`memory/`)
|
### Memory (`memory/`)
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,9 @@ Minimal shape:
|
|||||||
preset = "openai-compatible"
|
preset = "openai-compatible"
|
||||||
url = "https://api.openai.com/v1"
|
url = "https://api.openai.com/v1"
|
||||||
access_key_or_token = "sk-..."
|
access_key_or_token = "sk-..."
|
||||||
|
# Optional HTTP timeouts (seconds). Default: connect=10, read=600.
|
||||||
|
# timeout = 120
|
||||||
|
# timeout = { connect = 10, read = 600 }
|
||||||
|
|
||||||
[providers.local.models]
|
[providers.local.models]
|
||||||
"gpt-4o-mini" = { text = true }
|
"gpt-4o-mini" = { text = true }
|
||||||
@@ -138,6 +141,8 @@ confirm_destructive = true
|
|||||||
max_context_tokens = 200000
|
max_context_tokens = 200000
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Per-provider **`timeout`** is passed to the HTTP session for chat/completions, Responses, and `GET /models`. A single number sets one timeout; `{ connect, read }` splits TCP/TLS setup vs idle wait between response bytes (SSE can run longer than `read` while chunks keep arriving). Tool/process timeouts (`run_command`, PTY, policy confirm) are separate.
|
||||||
|
|
||||||
Supported provider presets today: `openai` (default if `preset` is omitted; default models `gpt-5.4` / `gpt-5.4-mini` / `gpt-5.4-nano` when `models` is omitted), `openai-compatible`, `deepseek` (OpenAI convention; default models `deepseek-v4-flash` and `deepseek-v4-pro` if `models` is omitted). Anthropic presets are modeled in config but not wired in the runtime client yet.
|
Supported provider presets today: `openai` (default if `preset` is omitted; default models `gpt-5.4` / `gpt-5.4-mini` / `gpt-5.4-nano` when `models` is omitted), `openai-compatible`, `deepseek` (OpenAI convention; default models `deepseek-v4-flash` and `deepseek-v4-pro` if `models` is omitted). Anthropic presets are modeled in config but not wired in the runtime client yet.
|
||||||
|
|
||||||
If `[database]` is omitted (or SQLite `url` is unset/empty), chat uses a durable file under the user data dir (e.g. `~/.local/share/plyngent/chat.db` on Linux). Set `url = ":memory:"` for a true in-memory SQLite (CLI warns; no file; useful for tests).
|
If `[database]` is omitted (or SQLite `url` is unset/empty), chat uses a durable file under the user data dir (e.g. `~/.local/share/plyngent/chat.db` on Linux). Set `url = ":memory:"` for a true in-memory SQLite (CLI warns; no file; useful for tests).
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ max_context_tokens = 200000
|
|||||||
preset = "openai-compatible"
|
preset = "openai-compatible"
|
||||||
url = "https://api.openai.com/v1"
|
url = "https://api.openai.com/v1"
|
||||||
access_key_or_token = "sk-replace-me"
|
access_key_or_token = "sk-replace-me"
|
||||||
|
# HTTP timeouts for LLM API calls (seconds). Default if omitted: connect=10, read=600.
|
||||||
|
# timeout = 120
|
||||||
|
# timeout = { connect = 10, read = 600 }
|
||||||
|
|
||||||
[providers.openai_compat.models]
|
[providers.openai_compat.models]
|
||||||
"gpt-4o-mini" = { text = true, cost_factor = 1.0 }
|
"gpt-4o-mini" = { text = true, cost_factor = 1.0 }
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from .models import AgentConfig as AgentConfig
|
|||||||
from .models import AnthropicProvider as AnthropicProvider
|
from .models import AnthropicProvider as AnthropicProvider
|
||||||
from .models import DatabaseConfig as DatabaseConfig
|
from .models import DatabaseConfig as DatabaseConfig
|
||||||
from .models import DeepseekProvider as DeepseekProvider
|
from .models import DeepseekProvider as DeepseekProvider
|
||||||
|
from .models import HttpTimeoutConfig as HttpTimeoutConfig
|
||||||
from .models import ModelConfig as ModelConfig
|
from .models import ModelConfig as ModelConfig
|
||||||
from .models import OpenAICompatibleProvider as OpenAICompatibleProvider
|
from .models import OpenAICompatibleProvider as OpenAICompatibleProvider
|
||||||
from .models import OpenAIProvider as OpenAIProvider
|
from .models import OpenAIProvider as OpenAIProvider
|
||||||
|
|||||||
@@ -50,6 +50,23 @@ class ModelConfig(Struct, omit_defaults=True):
|
|||||||
cost_factor: float = 1.0
|
cost_factor: float = 1.0
|
||||||
|
|
||||||
|
|
||||||
|
class HttpTimeoutConfig(Struct, omit_defaults=True):
|
||||||
|
"""Per-provider HTTP timeouts (seconds) for the LLM API client.
|
||||||
|
|
||||||
|
TOML examples::
|
||||||
|
|
||||||
|
timeout = 120
|
||||||
|
timeout = { connect = 10, read = 600 }
|
||||||
|
|
||||||
|
``connect`` bounds TCP/TLS setup; ``read`` bounds idle wait between response
|
||||||
|
bytes (SSE streams can run longer than *read* as long as chunks keep arriving).
|
||||||
|
Omitted fields use product defaults (10s connect / 600s read).
|
||||||
|
"""
|
||||||
|
|
||||||
|
connect: float | None = None
|
||||||
|
read: float | None = None
|
||||||
|
|
||||||
|
|
||||||
class ProviderConfig(Struct, tag_field="preset", omit_defaults=True):
|
class ProviderConfig(Struct, tag_field="preset", omit_defaults=True):
|
||||||
"""Base config for all LLM providers.
|
"""Base config for all LLM providers.
|
||||||
|
|
||||||
@@ -60,6 +77,8 @@ class ProviderConfig(Struct, tag_field="preset", omit_defaults=True):
|
|||||||
access_key_or_token: str
|
access_key_or_token: str
|
||||||
url: str = ""
|
url: str = ""
|
||||||
models: dict[str, ModelConfig] = field(default_factory=dict)
|
models: dict[str, ModelConfig] = field(default_factory=dict)
|
||||||
|
# float = single timeout for the session; table = connect/read split; omit = defaults.
|
||||||
|
timeout: float | HttpTimeoutConfig | None = None
|
||||||
|
|
||||||
|
|
||||||
def _default_openai_models() -> dict[str, ModelConfig]:
|
def _default_openai_models() -> dict[str, ModelConfig]:
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ class BaseOpenAIClient:
|
|||||||
self.session = niquests.AsyncSession(
|
self.session = niquests.AsyncSession(
|
||||||
base_url=config.base_url,
|
base_url=config.base_url,
|
||||||
auth=BearerTokenAuth(config.access_key_or_token),
|
auth=BearerTokenAuth(config.access_key_or_token),
|
||||||
|
timeout=config.timeout,
|
||||||
)
|
)
|
||||||
self.encoder = msgspec.json.Encoder()
|
self.encoder = msgspec.json.Encoder()
|
||||||
self.decoder = msgspec.json.Decoder(ChatCompletionResponse)
|
self.decoder = msgspec.json.Decoder(ChatCompletionResponse)
|
||||||
|
|||||||
@@ -1,7 +1,18 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
# Product defaults when provider omits ``timeout`` (connect vs read idle).
|
||||||
|
DEFAULT_HTTP_CONNECT_TIMEOUT = 10.0
|
||||||
|
DEFAULT_HTTP_READ_TIMEOUT = 600.0
|
||||||
|
|
||||||
|
type HttpTimeout = float | tuple[float, float]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class OpenAIConfig:
|
class OpenAIConfig:
|
||||||
access_key_or_token: str
|
access_key_or_token: str
|
||||||
base_url: str = "https://api.openai.com/v1"
|
base_url: str = "https://api.openai.com/v1"
|
||||||
|
# Passed to ``niquests.AsyncSession(timeout=...)``.
|
||||||
|
# ``float`` = single timeout; ``(connect, read)`` = split; factory always sets a concrete value.
|
||||||
|
timeout: HttpTimeout = (DEFAULT_HTTP_CONNECT_TIMEOUT, DEFAULT_HTTP_READ_TIMEOUT)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from .client_factory import InvalidHttpTimeoutError as InvalidHttpTimeoutError
|
||||||
from .client_factory import ProviderNotSupportedError as ProviderNotSupportedError
|
from .client_factory import ProviderNotSupportedError as ProviderNotSupportedError
|
||||||
from .client_factory import create_client as create_client
|
from .client_factory import create_client as create_client
|
||||||
|
from .client_factory import normalize_http_timeout as normalize_http_timeout
|
||||||
from .client_factory import provider_to_openai_config as provider_to_openai_config
|
from .client_factory import provider_to_openai_config as provider_to_openai_config
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import math
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from plyngent.config.models import (
|
from plyngent.config.models import (
|
||||||
DeepseekProvider,
|
DeepseekProvider,
|
||||||
|
HttpTimeoutConfig,
|
||||||
OpenAICompatibleProvider,
|
OpenAICompatibleProvider,
|
||||||
OpenAIProvider,
|
OpenAIProvider,
|
||||||
Provider,
|
Provider,
|
||||||
@@ -11,6 +13,11 @@ from plyngent.config.models import (
|
|||||||
from plyngent.lmproto.deepseek import DeepseekOpenAIClient
|
from plyngent.lmproto.deepseek import DeepseekOpenAIClient
|
||||||
from plyngent.lmproto.openai import OpenAIClient
|
from plyngent.lmproto.openai import OpenAIClient
|
||||||
from plyngent.lmproto.openai_compatible import OpenAICompatibleClient, OpenAIConfig
|
from plyngent.lmproto.openai_compatible import OpenAICompatibleClient, OpenAIConfig
|
||||||
|
from plyngent.lmproto.openai_compatible.config import (
|
||||||
|
DEFAULT_HTTP_CONNECT_TIMEOUT,
|
||||||
|
DEFAULT_HTTP_READ_TIMEOUT,
|
||||||
|
HttpTimeout,
|
||||||
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
@@ -29,6 +36,44 @@ class ProviderNotSupportedError(NotImplementedError):
|
|||||||
"""Raised when a provider preset cannot be turned into a runtime client."""
|
"""Raised when a provider preset cannot be turned into a runtime client."""
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidHttpTimeoutError(ValueError):
|
||||||
|
"""Raised when a provider ``timeout`` value is not usable for HTTP clients."""
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_http_timeout(timeout: float | HttpTimeoutConfig | None) -> HttpTimeout:
|
||||||
|
"""Normalize TOML/provider timeout into a niquests session timeout.
|
||||||
|
|
||||||
|
* ``None`` → product defaults ``(connect=10, read=600)``
|
||||||
|
* ``float`` / ``int`` → single timeout (niquests applies to the request)
|
||||||
|
* :class:`HttpTimeoutConfig` → ``(connect, read)`` with defaults for omitted fields
|
||||||
|
|
||||||
|
All finite values must be ``> 0``.
|
||||||
|
"""
|
||||||
|
if timeout is None:
|
||||||
|
return (DEFAULT_HTTP_CONNECT_TIMEOUT, DEFAULT_HTTP_READ_TIMEOUT)
|
||||||
|
if isinstance(timeout, bool):
|
||||||
|
# ``bool`` is an ``int`` subclass; reject before the numeric branch.
|
||||||
|
msg = "timeout must be a positive number or { connect, read }, not a boolean"
|
||||||
|
raise InvalidHttpTimeoutError(msg)
|
||||||
|
if isinstance(timeout, int | float):
|
||||||
|
value = float(timeout)
|
||||||
|
if not math.isfinite(value) or value <= 0:
|
||||||
|
msg = f"timeout must be a finite number > 0, got {timeout!r}"
|
||||||
|
raise InvalidHttpTimeoutError(msg)
|
||||||
|
return value
|
||||||
|
|
||||||
|
# Remaining union member: HttpTimeoutConfig
|
||||||
|
connect = DEFAULT_HTTP_CONNECT_TIMEOUT if timeout.connect is None else float(timeout.connect)
|
||||||
|
read = DEFAULT_HTTP_READ_TIMEOUT if timeout.read is None else float(timeout.read)
|
||||||
|
if not math.isfinite(connect) or connect <= 0:
|
||||||
|
msg = f"timeout.connect must be a finite number > 0, got {timeout.connect!r}"
|
||||||
|
raise InvalidHttpTimeoutError(msg)
|
||||||
|
if not math.isfinite(read) or read <= 0:
|
||||||
|
msg = f"timeout.read must be a finite number > 0, got {timeout.read!r}"
|
||||||
|
raise InvalidHttpTimeoutError(msg)
|
||||||
|
return (connect, read)
|
||||||
|
|
||||||
|
|
||||||
def provider_to_openai_config(provider: OpenAIProvider | OpenAICompatibleProvider | DeepseekProvider) -> OpenAIConfig:
|
def provider_to_openai_config(provider: OpenAIProvider | OpenAICompatibleProvider | DeepseekProvider) -> OpenAIConfig:
|
||||||
"""Map a provider config entry to :class:`OpenAIConfig`."""
|
"""Map a provider config entry to :class:`OpenAIConfig`."""
|
||||||
if isinstance(provider, OpenAIProvider):
|
if isinstance(provider, OpenAIProvider):
|
||||||
@@ -40,9 +85,14 @@ def provider_to_openai_config(provider: OpenAIProvider | OpenAICompatibleProvide
|
|||||||
if not base_url:
|
if not base_url:
|
||||||
msg = "openai-compatible provider requires a non-empty url"
|
msg = "openai-compatible provider requires a non-empty url"
|
||||||
raise ProviderNotSupportedError(msg)
|
raise ProviderNotSupportedError(msg)
|
||||||
|
try:
|
||||||
|
http_timeout = normalize_http_timeout(provider.timeout)
|
||||||
|
except InvalidHttpTimeoutError as exc:
|
||||||
|
raise ProviderNotSupportedError(str(exc)) from exc
|
||||||
return OpenAIConfig(
|
return OpenAIConfig(
|
||||||
access_key_or_token=provider.access_key_or_token,
|
access_key_or_token=provider.access_key_or_token,
|
||||||
base_url=base_url,
|
base_url=base_url,
|
||||||
|
timeout=http_timeout,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,46 @@ access_key_or_token = "sk-test"
|
|||||||
provider = config.providers["oai"]
|
provider = config.providers["oai"]
|
||||||
assert isinstance(provider, OpenAIProvider)
|
assert isinstance(provider, OpenAIProvider)
|
||||||
assert set(provider.models) == {"gpt-5.4", "gpt-5.4-mini", "gpt-5.4-nano"}
|
assert set(provider.models) == {"gpt-5.4", "gpt-5.4-mini", "gpt-5.4-nano"}
|
||||||
|
assert provider.timeout is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_provider_timeout_float_from_toml(tmp_path: Path) -> None:
|
||||||
|
path = tmp_path / "timeout-float.toml"
|
||||||
|
_ = path.write_text(
|
||||||
|
"""
|
||||||
|
[providers.local]
|
||||||
|
preset = "openai-compatible"
|
||||||
|
url = "https://example.com/v1"
|
||||||
|
access_key_or_token = "sk-test"
|
||||||
|
timeout = 90
|
||||||
|
models = { "m" = { text = true } }
|
||||||
|
""",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
config = plyngent.config.load(path)
|
||||||
|
provider = config.providers["local"]
|
||||||
|
assert isinstance(provider, OpenAICompatibleProvider)
|
||||||
|
assert provider.timeout == 90
|
||||||
|
|
||||||
|
|
||||||
|
def test_provider_timeout_table_from_toml(tmp_path: Path) -> None:
|
||||||
|
from plyngent.config import HttpTimeoutConfig
|
||||||
|
|
||||||
|
path = tmp_path / "timeout-table.toml"
|
||||||
|
_ = path.write_text(
|
||||||
|
"""
|
||||||
|
[providers.oai]
|
||||||
|
access_key_or_token = "sk-test"
|
||||||
|
timeout = { connect = 5, read = 120 }
|
||||||
|
""",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
config = plyngent.config.load(path)
|
||||||
|
provider = config.providers["oai"]
|
||||||
|
assert isinstance(provider, OpenAIProvider)
|
||||||
|
assert isinstance(provider.timeout, HttpTimeoutConfig)
|
||||||
|
assert provider.timeout.connect == 5
|
||||||
|
assert provider.timeout.read == 120
|
||||||
|
|
||||||
|
|
||||||
def test_deepseek_explicit_models_override_defaults() -> None:
|
def test_deepseek_explicit_models_override_defaults() -> None:
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from plyngent.config.models import (
|
||||||
|
HttpTimeoutConfig,
|
||||||
|
OpenAICompatibleProvider,
|
||||||
|
OpenAIProvider,
|
||||||
|
)
|
||||||
|
from plyngent.lmproto.openai_compatible.config import (
|
||||||
|
DEFAULT_HTTP_CONNECT_TIMEOUT,
|
||||||
|
DEFAULT_HTTP_READ_TIMEOUT,
|
||||||
|
)
|
||||||
|
from plyngent.runtime import (
|
||||||
|
InvalidHttpTimeoutError,
|
||||||
|
ProviderNotSupportedError,
|
||||||
|
create_client,
|
||||||
|
normalize_http_timeout,
|
||||||
|
provider_to_openai_config,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_normalize_none_uses_product_defaults() -> None:
|
||||||
|
assert normalize_http_timeout(None) == (
|
||||||
|
DEFAULT_HTTP_CONNECT_TIMEOUT,
|
||||||
|
DEFAULT_HTTP_READ_TIMEOUT,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_normalize_float() -> None:
|
||||||
|
assert normalize_http_timeout(120) == 120.0
|
||||||
|
assert normalize_http_timeout(0.5) == 0.5
|
||||||
|
|
||||||
|
|
||||||
|
def test_normalize_table_partial_and_full() -> None:
|
||||||
|
assert normalize_http_timeout(HttpTimeoutConfig()) == (
|
||||||
|
DEFAULT_HTTP_CONNECT_TIMEOUT,
|
||||||
|
DEFAULT_HTTP_READ_TIMEOUT,
|
||||||
|
)
|
||||||
|
assert normalize_http_timeout(HttpTimeoutConfig(connect=5.0)) == (
|
||||||
|
5.0,
|
||||||
|
DEFAULT_HTTP_READ_TIMEOUT,
|
||||||
|
)
|
||||||
|
assert normalize_http_timeout(HttpTimeoutConfig(read=30.0)) == (
|
||||||
|
DEFAULT_HTTP_CONNECT_TIMEOUT,
|
||||||
|
30.0,
|
||||||
|
)
|
||||||
|
assert normalize_http_timeout(HttpTimeoutConfig(connect=3.0, read=90.0)) == (3.0, 90.0)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"bad",
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
-1,
|
||||||
|
math.nan,
|
||||||
|
math.inf,
|
||||||
|
True, # bool is int subclass but rejected
|
||||||
|
HttpTimeoutConfig(connect=0),
|
||||||
|
HttpTimeoutConfig(read=-5),
|
||||||
|
HttpTimeoutConfig(connect=math.nan),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_normalize_rejects_invalid(bad: float | HttpTimeoutConfig) -> None:
|
||||||
|
with pytest.raises(InvalidHttpTimeoutError):
|
||||||
|
_ = normalize_http_timeout(bad)
|
||||||
|
|
||||||
|
|
||||||
|
def test_provider_to_openai_config_default_timeout() -> None:
|
||||||
|
provider = OpenAIProvider(access_key_or_token="sk-test")
|
||||||
|
config = provider_to_openai_config(provider)
|
||||||
|
assert config.timeout == (DEFAULT_HTTP_CONNECT_TIMEOUT, DEFAULT_HTTP_READ_TIMEOUT)
|
||||||
|
|
||||||
|
|
||||||
|
def test_provider_to_openai_config_float_timeout() -> None:
|
||||||
|
provider = OpenAICompatibleProvider(
|
||||||
|
access_key_or_token="sk-test",
|
||||||
|
url="https://example.com/v1",
|
||||||
|
timeout=45.0,
|
||||||
|
)
|
||||||
|
config = provider_to_openai_config(provider)
|
||||||
|
assert config.timeout == 45.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_provider_to_openai_config_table_timeout() -> None:
|
||||||
|
provider = OpenAIProvider(
|
||||||
|
access_key_or_token="sk-test",
|
||||||
|
timeout=HttpTimeoutConfig(connect=2.0, read=99.0),
|
||||||
|
)
|
||||||
|
config = provider_to_openai_config(provider)
|
||||||
|
assert config.timeout == (2.0, 99.0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_timeout_via_create_client() -> None:
|
||||||
|
provider = OpenAICompatibleProvider(
|
||||||
|
access_key_or_token="sk-test",
|
||||||
|
url="https://example.com/v1",
|
||||||
|
timeout=0,
|
||||||
|
)
|
||||||
|
with pytest.raises(ProviderNotSupportedError, match="timeout"):
|
||||||
|
_ = create_client(provider)
|
||||||
|
|
||||||
|
|
||||||
|
def test_client_session_receives_timeout() -> None:
|
||||||
|
from plyngent.lmproto.openai_compatible import OpenAICompatibleClient
|
||||||
|
|
||||||
|
provider = OpenAICompatibleProvider(
|
||||||
|
access_key_or_token="sk-test",
|
||||||
|
url="https://example.com/v1",
|
||||||
|
timeout=HttpTimeoutConfig(connect=7.0, read=11.0),
|
||||||
|
)
|
||||||
|
client = create_client(provider)
|
||||||
|
assert isinstance(client, OpenAICompatibleClient)
|
||||||
|
assert client.session.timeout == (7.0, 11.0)
|
||||||
Reference in New Issue
Block a user