diff --git a/lib/crewai/src/crewai/utilities/agent_utils.py b/lib/crewai/src/crewai/utilities/agent_utils.py index 2c8122b99..bc64bc454 100644 --- a/lib/crewai/src/crewai/utilities/agent_utils.py +++ b/lib/crewai/src/crewai/utilities/agent_utils.py @@ -157,7 +157,7 @@ def handle_max_iterations_exceeded( # Perform one more LLM call to get the final answer answer = llm.call( - messages, # type: ignore[arg-type] + messages, callbacks=callbacks, ) @@ -197,9 +197,14 @@ def format_answer(answer: str) -> AgentAction | AgentFinish: Returns: Either an AgentAction or AgentFinish + + Raises: + OutputParserError: When the LLM response format is invalid, allowing retry logic """ try: return parse(answer) + except OutputParserError: + raise except Exception: return AgentFinish( thought="Failed to parse LLM response", @@ -249,10 +254,10 @@ def get_llm_response( """ try: answer = llm.call( - messages, # type: ignore[arg-type] + messages, callbacks=callbacks, from_task=from_task, - from_agent=from_agent, + from_agent=from_agent, # type: ignore[arg-type] response_model=response_model, ) except Exception as e: @@ -294,8 +299,8 @@ def handle_agent_action_core( formatted_answer: AgentAction, tool_result: ToolResult, messages: list[LLMMessage] | None = None, - step_callback: Callable | None = None, - show_logs: Callable | None = None, + step_callback: Callable[[Any], Any] | None = None, + show_logs: Callable[[Any], Any] | None = None, ) -> AgentAction | AgentFinish: """Core logic for handling agent actions and tool results. @@ -481,7 +486,7 @@ def summarize_messages( ), ] summary = llm.call( - messages, # type: ignore[arg-type] + messages, callbacks=callbacks, ) summarized_contents.append({"content": str(summary)}) diff --git a/lib/crewai/tests/utilities/test_agent_utils.py b/lib/crewai/tests/utilities/test_agent_utils.py new file mode 100644 index 000000000..ee843586c --- /dev/null +++ b/lib/crewai/tests/utilities/test_agent_utils.py @@ -0,0 +1,69 @@ +"""Tests for agent_utils module.""" + +import pytest + +from crewai.agents.parser import AgentFinish, OutputParserError +from crewai.utilities.agent_utils import format_answer + + +class TestFormatAnswer: + """Tests for the format_answer function.""" + + def test_format_answer_with_valid_final_answer(self) -> None: + """Test that format_answer correctly parses a valid final answer.""" + answer = """Thought: I have completed the task. +Final Answer: The result is 42.""" + + result = format_answer(answer) + + assert isinstance(result, AgentFinish) + assert result.output == "The result is 42." + + def test_format_answer_reraises_output_parser_error(self) -> None: + """Test that format_answer re-raises OutputParserError for retry logic.""" + # Malformed output missing colons after "Thought", "Action", and "Action Input" + malformed_answer = """Thought +The user wants to verify something. +Action +Video Analysis Tool +Action Input: +{"query": "Is there something?"}""" + + with pytest.raises(OutputParserError) as exc_info: + format_answer(malformed_answer) + + # Verify that the error message contains helpful information + assert exc_info.value.error is not None + + def test_format_answer_with_missing_action_colon(self) -> None: + """Test that format_answer raises OutputParserError when Action colon is missing.""" + malformed_answer = """Thought: I need to search for information. +Action +Search Tool +Action Input: {"query": "test"}""" + + with pytest.raises(OutputParserError): + format_answer(malformed_answer) + + def test_format_answer_with_missing_action_input_colon(self) -> None: + """Test that format_answer raises OutputParserError when Action Input colon is missing.""" + malformed_answer = """Thought: I need to search for information. +Action: Search Tool +Action Input +{"query": "test"}""" + + with pytest.raises(OutputParserError): + format_answer(malformed_answer) + + def test_format_answer_with_valid_action(self) -> None: + """Test that format_answer correctly parses a valid action format.""" + valid_action = """Thought: I need to search for information. +Action: Search Tool +Action Input: {"query": "test"}""" + + # This should parse successfully without raising an exception + result = format_answer(valid_action) + + # The result should be an AgentAction (not AgentFinish) + assert result is not None + assert not isinstance(result, AgentFinish)