diff --git a/src/pipecat/services/openai/base_llm.py b/src/pipecat/services/openai/base_llm.py index 8e0a45f0d..0243cc917 100644 --- a/src/pipecat/services/openai/base_llm.py +++ b/src/pipecat/services/openai/base_llm.py @@ -495,6 +495,8 @@ class BaseOpenAILLMService(LLMService): except httpx.TimeoutException as e: await self._call_event_handler("on_completion_timeout") await self.push_error(error_msg="LLM completion timeout", exception=e) + except Exception as e: + await self.push_error(error_msg=f"Error during completion: {e}", exception=e) finally: await self.stop_processing_metrics() await self.push_frame(LLMFullResponseEndFrame()) diff --git a/tests/test_openai_llm_timeout.py b/tests/test_openai_llm_timeout.py index df9483289..37e4523a9 100644 --- a/tests/test_openai_llm_timeout.py +++ b/tests/test_openai_llm_timeout.py @@ -4,7 +4,7 @@ # SPDX-License-Identifier: BSD 2-Clause License # -"""Unit tests for OpenAI LLM timeout handling.""" +"""Unit tests for OpenAI LLM error handling.""" from unittest.mock import AsyncMock, patch @@ -125,3 +125,39 @@ async def test_openai_llm_timeout_still_pushes_end_frame(): # Verify metrics were stopped service.stop_processing_metrics.assert_called_once() + + +@pytest.mark.asyncio +async def test_openai_llm_emits_error_frame_on_exception(): + """Test that OpenAI LLM service emits ErrorFrame when a general exception occurs. + + This enables proper error handling for API errors, rate limits, and other failures. + """ + with patch.object(OpenAILLMService, "create_client"): + service = OpenAILLMService(model="gpt-4") + service._client = AsyncMock() + + pushed_errors = [] + + async def mock_push_error(error_msg, exception=None): + pushed_errors.append({"error_msg": error_msg, "exception": exception}) + + service.push_frame = AsyncMock() + service.push_error = mock_push_error + service._call_event_handler = AsyncMock() + service._process_context = AsyncMock(side_effect=RuntimeError("API Error")) + service.start_processing_metrics = AsyncMock() + service.stop_processing_metrics = AsyncMock() + + context = LLMContext( + messages=[{"role": "user", "content": "Hello"}], + ) + frame = LLMContextFrame(context=context) + + await service.process_frame(frame, FrameDirection.DOWNSTREAM) + + # Verify push_error was called with correct message + assert len(pushed_errors) == 1 + assert "Error during completion" in pushed_errors[0]["error_msg"] + assert "API Error" in pushed_errors[0]["error_msg"] + assert isinstance(pushed_errors[0]["exception"], RuntimeError)