From 42eb355a68f7965312acd96b6601506123503ed5 Mon Sep 17 00:00:00 2001 From: Graham Neubig Date: Tue, 29 Apr 2025 10:05:56 -0400 Subject: [PATCH] Fix OpenRouter context window exceeded error detection (#8150) Co-authored-by: openhands --- openhands/controller/agent_controller.py | 2 + tests/unit/test_agent_controller.py | 74 +++++++++++++++++++++++- 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/openhands/controller/agent_controller.py b/openhands/controller/agent_controller.py index 919996cfff..f45762ca4e 100644 --- a/openhands/controller/agent_controller.py +++ b/openhands/controller/agent_controller.py @@ -842,6 +842,8 @@ class AgentController: 'contextwindowexceedederror' in error_str or 'prompt is too long' in error_str or 'input length and `max_tokens` exceed context limit' in error_str + or 'please reduce the length of either one' + in error_str # For OpenRouter context window errors or isinstance(e, ContextWindowExceededError) ): if self.agent.config.enable_history_truncation: diff --git a/tests/unit/test_agent_controller.py b/tests/unit/test_agent_controller.py index 91fb3eada7..193e45ff39 100644 --- a/tests/unit/test_agent_controller.py +++ b/tests/unit/test_agent_controller.py @@ -3,7 +3,11 @@ from unittest.mock import ANY, AsyncMock, MagicMock, patch from uuid import uuid4 import pytest -from litellm import ContentPolicyViolationError, ContextWindowExceededError +from litellm import ( + BadRequestError, + ContentPolicyViolationError, + ContextWindowExceededError, +) from openhands.controller.agent import Agent from openhands.controller.agent_controller import AgentController @@ -1486,3 +1490,71 @@ def test_system_message_in_event_stream(mock_agent, test_event_stream): assert isinstance(events[0], SystemMessageAction) assert events[0].content == 'Test system message' assert events[0].tools == ['test_tool'] + + +@pytest.mark.asyncio +async def test_openrouter_context_window_exceeded_error( + mock_agent, test_event_stream, mock_status_callback +): + """Test that OpenRouter context window exceeded errors are properly detected and handled.""" + max_iterations = 5 + error_after = 2 + + class StepState: + def __init__(self): + self.has_errored = False + self.index = 0 + self.views = [] + + def step(self, state: State): + self.views.append(state.view) + + # Wait until the right step to throw the error, and make sure we + # only throw it once. + if self.index < error_after or self.has_errored: + self.index += 1 + return MessageAction(content=f'Test message {self.index}') + + # Create a BadRequestError with the OpenRouter context window exceeded message pattern + error = BadRequestError( + message='litellm.BadRequestError: OpenrouterException - This endpoint\'s maximum context length is 40960 tokens. However, you requested about 42988 tokens (38892 of text input, 4096 in the output). Please reduce the length of either one, or use the "middle-out" transform to compress your prompt automatically.', + model='openrouter/qwen/qwen3-30b-a3b', + llm_provider='openrouter', + ) + self.has_errored = True + raise error + + step_state = StepState() + mock_agent.step = step_state.step + mock_agent.config = AgentConfig(enable_history_truncation=True) + + controller = AgentController( + agent=mock_agent, + event_stream=test_event_stream, + max_iterations=max_iterations, + sid='test', + confirmation_mode=False, + headless_mode=True, + status_callback=mock_status_callback, + ) + + # Set the agent state to RUNNING + controller.state.agent_state = AgentState.RUNNING + + # Run the controller until it hits the error + for _ in range(error_after + 2): # +2 to ensure we go past the error + await controller._step() + if step_state.has_errored: + break + + # Verify that the error was handled as a context window exceeded error + # by checking that _handle_long_context_error was called (which adds a CondensationAction) + events = list(test_event_stream.get_events()) + condensation_actions = [e for e in events if isinstance(e, CondensationAction)] + + # There should be at least one CondensationAction if the error was handled correctly + assert ( + len(condensation_actions) > 0 + ), 'OpenRouter context window exceeded error was not handled correctly' + + await controller.close()