diff --git a/CHANGELOG.md b/CHANGELOG.md index aee2129..15bdf94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +### Fixed + +- **Blocking sleep in LLM condition** -- Replaced `time.sleep()` with + `await asyncio.sleep()` in `LLMAssisted` retry backoff to avoid + freezing the event loop during LLM retries. + ### Added - **Description-diff alignment** -- `DescriptionDiffAlignmentCondition` uses diff --git a/src/rules/conditions/llm_assisted.py b/src/rules/conditions/llm_assisted.py index 9dbbc00..8ab127c 100644 --- a/src/rules/conditions/llm_assisted.py +++ b/src/rules/conditions/llm_assisted.py @@ -5,6 +5,7 @@ opt-in and clearly documented as having LLM latency in the evaluation path. """ +import asyncio import logging import time from typing import Any @@ -218,7 +219,7 @@ async def evaluate(self, context: Any) -> list[Violation]: ) if attempt < max_attempts: - time.sleep(wait_time) + await asyncio.sleep(wait_time) else: # All attempts failed - gracefully degrade logger.error("All LLM retry attempts exhausted; skipping alignment check.") diff --git a/tests/unit/rules/conditions/test_llm_assisted.py b/tests/unit/rules/conditions/test_llm_assisted.py index 70aa4ac..4ee6f90 100644 --- a/tests/unit/rules/conditions/test_llm_assisted.py +++ b/tests/unit/rules/conditions/test_llm_assisted.py @@ -196,7 +196,7 @@ async def test_graceful_degradation_on_llm_failure(self, mock_get_chat_model, co assert violations == [] @pytest.mark.asyncio - @patch("time.sleep", return_value=None) # Mock sleep to speed up test + @patch("asyncio.sleep", new_callable=AsyncMock) # Mock async sleep to speed up test @patch("src.integrations.providers.get_chat_model") async def test_retry_logic_with_exponential_backoff(self, mock_get_chat_model, mock_sleep, condition): """When structured invoke fails, retries with exponential backoff.""" @@ -213,7 +213,7 @@ async def test_retry_logic_with_exponential_backoff(self, mock_get_chat_model, m # Should have retried 3 times total assert mock_structured.ainvoke.await_count == 3 # Should have slept twice (2s, 4s) - assert mock_sleep.call_count == 2 + assert mock_sleep.await_count == 2 @pytest.mark.asyncio @patch("src.integrations.providers.get_chat_model")