From ec9daf3bccfe11ff35e394b0b39c7ee33f4b7c07 Mon Sep 17 00:00:00 2001 From: Engel Nyst Date: Fri, 19 Dec 2025 07:31:07 +0100 Subject: [PATCH] Fix tool call validation error handling for Groq LLM provider (#10927) Co-authored-by: openhands Co-authored-by: Engel Nyst --- openhands/controller/agent_controller.py | 17 +++++++ .../unit/controller/test_agent_controller.py | 49 +++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/openhands/controller/agent_controller.py b/openhands/controller/agent_controller.py index 958e5cb348..0753f0a0a1 100644 --- a/openhands/controller/agent_controller.py +++ b/openhands/controller/agent_controller.py @@ -944,6 +944,23 @@ class AgentController: return else: raise LLMContextWindowExceedError() + # Check if this is a tool call validation error that should be recoverable + elif ( + isinstance(e, BadRequestError) + and 'tool call validation failed' in error_str + and ( + 'missing properties' in error_str + or 'missing required' in error_str + ) + ): + # Handle tool call validation errors from Groq as recoverable errors + self.event_stream.add_event( + ErrorObservation( + content=f'Tool call validation failed: {str(e)}. Please check the tool parameters and try again.', + ), + EventSource.AGENT, + ) + return else: raise e diff --git a/tests/unit/controller/test_agent_controller.py b/tests/unit/controller/test_agent_controller.py index da12ee8f9e..88b04b5738 100644 --- a/tests/unit/controller/test_agent_controller.py +++ b/tests/unit/controller/test_agent_controller.py @@ -299,6 +299,55 @@ async def test_react_to_content_policy_violation( await controller.close() +@pytest.mark.asyncio +async def test_tool_call_validation_error_handling( + mock_agent_with_stats, + test_event_stream, +): + """Test that tool call validation errors from Groq are handled as recoverable errors.""" + mock_agent, conversation_stats, llm_registry = mock_agent_with_stats + + controller = AgentController( + agent=mock_agent, + event_stream=test_event_stream, + conversation_stats=conversation_stats, + iteration_delta=10, + sid='test', + confirmation_mode=False, + headless_mode=True, + ) + + controller.state.agent_state = AgentState.RUNNING + + # Mock the agent.step method to raise a BadRequestError with tool validation failure + def mock_step(state): + raise BadRequestError( + message='litellm.BadRequestError: GroqException - {"error":{"message":"tool call validation failed: parameters for tool str_replace_editor did not match schema: errors: [missing properties: \'path\']","type":"invalid_request_error","code":"tool_use_failed"}}', + model='groq/llama3-8b-8192', + llm_provider='groq', + ) + + mock_agent.step = mock_step + + # Call _step which should handle the tool validation error + await controller._step() + + # Verify that the agent state is still RUNNING (not ERROR) + assert controller.state.agent_state == AgentState.RUNNING + + # Verify that an ErrorObservation was added to the event stream + events = list(test_event_stream.get_events()) + error_observations = [e for e in events if isinstance(e, ErrorObservation)] + assert len(error_observations) == 1 + + error_obs = error_observations[0] + assert 'tool call validation failed' in error_obs.content + assert 'missing properties' in error_obs.content + assert 'path' in error_obs.content + + await controller.close() + + @pytest.mark.asyncio async def test_run_controller_with_fatal_error( test_event_stream, mock_memory, mock_agent_with_stats