Files
livekit--agents/tests/test_plugin_anthropic.py
2026-07-13 13:39:38 +08:00

110 lines
3.6 KiB
Python

"""Unit tests for the Anthropic LLM plugin that do not require a real API key or network."""
from __future__ import annotations
import httpx
import pytest
from livekit.agents import APIConnectOptions, llm
from livekit.plugins.anthropic.llm import LLMStream
pytestmark = pytest.mark.plugin("anthropic")
def _make_llm(**kwargs):
from livekit.plugins.anthropic import LLM
return LLM(api_key="sk-ant-test", **kwargs)
class TestHttpxTimeoutDefaults:
def test_default_read_timeout_is_generous(self) -> None:
"""Default read timeout must accommodate adaptive-thinking pauses (≥30 s)."""
llm = _make_llm()
read = llm._client._client.timeout.read
assert read >= 30.0, f"read timeout {read}s is too short for adaptive thinking"
def test_default_connect_timeout_remains_tight(self) -> None:
"""Connect timeout should stay short so genuine connection failures surface fast."""
llm = _make_llm()
connect = llm._client._client.timeout.connect
assert connect <= 10.0, f"connect timeout {connect}s is unexpectedly long"
def test_default_timeout_is_split(self) -> None:
"""Default must be an httpx.Timeout object, not a flat scalar."""
llm = _make_llm()
t = llm._client._client.timeout
assert isinstance(t, httpx.Timeout)
assert t.read != t.connect, "read and connect timeouts should differ in the default"
class TestHttpxTimeoutCustom:
def test_custom_timeout_honored(self) -> None:
"""A caller-supplied httpx.Timeout is passed through to the httpx client."""
custom = httpx.Timeout(3.0, read=120.0)
llm = _make_llm(timeout=custom)
t = llm._client._client.timeout
assert t.read == 120.0
assert t.connect == 3.0
def test_none_timeout_uses_default(self) -> None:
"""Passing timeout=None must fall back to the built-in default."""
llm = _make_llm(timeout=None)
assert llm._client._client.timeout.read >= 30.0
def test_explicit_client_bypasses_timeout_param(self) -> None:
"""When a pre-built client= is supplied, timeout= is ignored (client wins)."""
import anthropic
tight_client = anthropic.AsyncClient(
api_key="sk-ant-test",
http_client=httpx.AsyncClient(timeout=httpx.Timeout(1.0)),
)
# timeout= argument should have no effect here
llm = _make_llm(client=tight_client, timeout=httpx.Timeout(5.0, read=999.0))
assert llm._client._client.timeout.read == 1.0
class _EmptyAnthropicStream:
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc, tb):
return None
def __aiter__(self):
return self
async def __anext__(self):
raise StopAsyncIteration
class TestAnthropicStreamRetry:
@pytest.mark.asyncio
async def test_retry_creates_a_fresh_stream_awaitable(self) -> None:
calls = 0
async def failing_stream():
raise RuntimeError("transient connect failure")
async def empty_stream():
return _EmptyAnthropicStream()
def create_stream():
nonlocal calls
calls += 1
return failing_stream() if calls == 1 else empty_stream()
stream = LLMStream(
_make_llm(),
create_anthropic_stream=create_stream,
chat_ctx=llm.ChatContext.empty(),
tools=[],
conn_options=APIConnectOptions(max_retry=1, retry_interval=0),
)
response = await stream.collect()
assert calls == 2
assert response.usage is not None