Skip to content
Open
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
126 changes: 126 additions & 0 deletions tests/llm/test_llm_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,132 @@ def test_retry_parse_raises():
llm_utils.retry(mock_chat, llm_utils.Discussion(), 3, parser_raises)


class FakeRateLimitError(Exception):
pass


class FakeAPIError(Exception):
def __init__(self, message="api error", status_code=None):
super().__init__(message)
self.status_code = status_code


def fake_get_status_code(exception):
return getattr(exception, "status_code", None)


@pytest.fixture
def recorded_sleeps(monkeypatch):
sleeps = []
monkeypatch.setattr(llm_utils.time, "sleep", lambda delay: sleeps.append(delay))
return sleeps


def call_with_retries(client_function, **kwargs):
params = dict(
client_function=client_function,
api_params={},
is_response_valid_fn=lambda response: response == "valid",
rate_limit_exceptions=(FakeRateLimitError,),
api_error_exceptions=(FakeAPIError,),
get_status_code_fn=fake_get_status_code,
max_retries=4,
initial_retry_delay_seconds=2,
max_retry_delay_seconds=10,
)
params.update(kwargs)
return llm_utils.generic_call_api_with_retries(**params)


def test_generic_call_api_succeeds_without_sleeping(recorded_sleeps):
client = Mock(return_value="valid")

assert call_with_retries(client) == "valid"
assert client.call_count == 1
assert recorded_sleeps == []


def test_generic_call_api_retries_rate_limit_error(recorded_sleeps):
client = Mock(side_effect=[FakeRateLimitError("slow down"), "valid"])

assert call_with_retries(client) == "valid"
assert client.call_count == 2
assert recorded_sleeps == [4] # initial_delay * 2 ** attempt, attempt == 1


@pytest.mark.parametrize("status_code", [429, 500, 503])
def test_generic_call_api_retries_retriable_status_codes(recorded_sleeps, status_code):
client = Mock(side_effect=[FakeAPIError(status_code=status_code), "valid"])

assert call_with_retries(client) == "valid"
assert client.call_count == 2
assert recorded_sleeps == [4]


@pytest.mark.parametrize("status_code", [400, 401, 404, None])
def test_generic_call_api_reraises_non_retriable_status_codes(recorded_sleeps, status_code):
client = Mock(side_effect=FakeAPIError(status_code=status_code))

with pytest.raises(FakeAPIError):
call_with_retries(client)

assert client.call_count == 1
assert recorded_sleeps == []


def test_generic_call_api_reraises_unexpected_exception(recorded_sleeps):
client = Mock(side_effect=ValueError("boom"))

with pytest.raises(ValueError):
call_with_retries(client)

assert client.call_count == 1
assert recorded_sleeps == []


def test_generic_call_api_retries_invalid_response(recorded_sleeps):
client = Mock(side_effect=["invalid", "invalid", "valid"])

assert call_with_retries(client) == "valid"
assert client.call_count == 3
# invalid content uses attempt - 1 as the exponent
assert recorded_sleeps == [2, 4]


def test_generic_call_api_raises_runtime_error_when_retries_exhausted(recorded_sleeps):
client = Mock(return_value="invalid")

with pytest.raises(RuntimeError, match="failed after 4 retries"):
call_with_retries(client)

assert client.call_count == 4
# no sleep on the last attempt, which gives up instead of retrying
assert recorded_sleeps == [2, 4, 8]


def test_generic_call_api_raises_runtime_error_after_repeated_rate_limits(recorded_sleeps):
client = Mock(side_effect=FakeRateLimitError("slow down"))

with pytest.raises(RuntimeError):
call_with_retries(client)

assert client.call_count == 4
assert recorded_sleeps == [4, 8, 10] # capped by max_retry_delay_seconds


def test_generic_call_api_reraises_when_status_code_fn_fails(recorded_sleeps):
client = Mock(side_effect=FakeAPIError(status_code=429))

def failing_status_fn(exception):
raise AttributeError("no status code")

with pytest.raises(FakeAPIError):
call_with_retries(client, get_status_code_fn=failing_status_fn)

assert client.call_count == 1
assert recorded_sleeps == []


def test_extract_code_blocks():
text = """\
This is some text.
Expand Down
Loading