From 06c68082bb7120e85425ce7c876fd043a78270ff Mon Sep 17 00:00:00 2001 From: OpenHands Date: Thu, 3 Apr 2025 15:36:16 -0400 Subject: [PATCH] Fix issue #7658: [Bug]: BadRequestError from ContentPolicyViolationError (#7660) Co-authored-by: Engel Nyst --- frontend/src/i18n/translation.json | 15 +++++++++ openhands/controller/agent_controller.py | 9 +++++- tests/unit/test_agent_controller.py | 41 +++++++++++++++++++++++- 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/frontend/src/i18n/translation.json b/frontend/src/i18n/translation.json index 844c7a3587..3bc996d85a 100644 --- a/frontend/src/i18n/translation.json +++ b/frontend/src/i18n/translation.json @@ -4494,6 +4494,21 @@ "tr": "OpenHands kredileriniz tükendi", "de": "Ihre OpenHands-Guthaben sind aufgebraucht" }, + "STATUS$ERROR_LLM_CONTENT_POLICY_VIOLATION": { + "en": "Content policy violation. The output was blocked by content filtering policy.", + "ja": "コンテンツポリシー違反。出力はコンテンツフィルタリングポリシーによってブロックされました。", + "zh-CN": "内容政策违规。输出被内容过滤策略阻止。", + "zh-TW": "內容政策違規。輸出被內容過濾政策阻止。", + "ko-KR": "콘텐츠 정책 위반. 출력이 콘텐츠 필터링 정책에 의해 차단되었습니다.", + "de": "Verstoß gegen Inhaltsrichtlinien. Die Ausgabe wurde durch die Inhaltsfilterrichtlinie blockiert.", + "no": "Brudd på innholdspolicy. Utdata ble blokkert av innholdsfiltrering.", + "it": "Violazione della policy sui contenuti. L'output è stato bloccato dalla policy di filtraggio dei contenuti.", + "pt": "Violação da política de conteúdo. A saída foi bloqueada pela política de filtragem de conteúdo.", + "es": "Violación de la política de contenido. La salida fue bloqueada por la política de filtrado de contenido.", + "ar": "انتهاك سياسة المحتوى. تم حظر المخرجات بواسطة سياسة تصفية المحتوى.", + "fr": "Violation de la politique de contenu. La sortie a été bloquée par la politique de filtrage de contenu.", + "tr": "İçerik politikası ihlali. Çıktı, içerik filtreleme politikası tarafından engellendi." + }, "STATUS$ERROR_RUNTIME_DISCONNECTED": { "en": "There was an error while connecting to the runtime. Please refresh the page.", "zh-CN": "运行时已断开连接", diff --git a/openhands/controller/agent_controller.py b/openhands/controller/agent_controller.py index f7edc46b82..320c49ebe5 100644 --- a/openhands/controller/agent_controller.py +++ b/openhands/controller/agent_controller.py @@ -10,6 +10,7 @@ from litellm.exceptions import ( # noqa APIError, AuthenticationError, BadRequestError, + ContentPolicyViolationError, ContextWindowExceededError, InternalServerError, NotFoundError, @@ -250,7 +251,12 @@ class AgentController: self.state.last_error = err_id elif isinstance(e, BadRequestError) and 'ExceededBudget' in str(e): err_id = 'STATUS$ERROR_LLM_OUT_OF_CREDITS' - # Set error reason for budget exceeded + self.state.last_error = err_id + elif isinstance(e, ContentPolicyViolationError) or ( + isinstance(e, BadRequestError) + and 'ContentPolicyViolationError' in str(e) + ): + err_id = 'STATUS$ERROR_LLM_CONTENT_POLICY_VIOLATION' self.state.last_error = err_id elif isinstance(e, RateLimitError): await self.set_agent_state_to(AgentState.RATE_LIMITED) @@ -283,6 +289,7 @@ class AgentController: or isinstance(e, InternalServerError) or isinstance(e, AuthenticationError) or isinstance(e, RateLimitError) + or isinstance(e, ContentPolicyViolationError) or isinstance(e, LLMContextWindowExceedError) ): reported = e diff --git a/tests/unit/test_agent_controller.py b/tests/unit/test_agent_controller.py index 429c51cfdc..de1a8a634f 100644 --- a/tests/unit/test_agent_controller.py +++ b/tests/unit/test_agent_controller.py @@ -3,7 +3,7 @@ from unittest.mock import ANY, AsyncMock, MagicMock, patch from uuid import uuid4 import pytest -from litellm import ContextWindowExceededError +from litellm import ContentPolicyViolationError, ContextWindowExceededError from openhands.controller.agent import Agent from openhands.controller.agent_controller import AgentController @@ -166,6 +166,45 @@ async def test_react_to_exception(mock_agent, mock_event_stream, mock_status_cal await controller.close() +@pytest.mark.asyncio +async def test_react_to_content_policy_violation( + mock_agent, mock_event_stream, mock_status_callback +): + """Test that the controller properly handles content policy violations from the LLM.""" + controller = AgentController( + agent=mock_agent, + event_stream=mock_event_stream, + status_callback=mock_status_callback, + max_iterations=10, + sid='test', + confirmation_mode=False, + headless_mode=True, + ) + + controller.state.agent_state = AgentState.RUNNING + + # Create and handle the content policy violation error + error = ContentPolicyViolationError( + message='Output blocked by content filtering policy', + model='gpt-4', + llm_provider='openai', + ) + await controller._react_to_exception(error) + + # Verify the status callback was called with correct parameters + mock_status_callback.assert_called_once_with( + 'error', + 'STATUS$ERROR_LLM_CONTENT_POLICY_VIOLATION', + 'STATUS$ERROR_LLM_CONTENT_POLICY_VIOLATION', + ) + + # Verify the state was updated correctly + assert controller.state.last_error == 'STATUS$ERROR_LLM_CONTENT_POLICY_VIOLATION' + assert controller.state.agent_state == AgentState.ERROR + + await controller.close() + + @pytest.mark.asyncio async def test_run_controller_with_fatal_error(test_event_stream, mock_memory): config = AppConfig()