diff --git a/lib/crewai/src/crewai/agent/core.py b/lib/crewai/src/crewai/agent/core.py index 37962400af..966c58d027 100644 --- a/lib/crewai/src/crewai/agent/core.py +++ b/lib/crewai/src/crewai/agent/core.py @@ -1436,12 +1436,13 @@ def get_code_execution_tools(self) -> list[Any]: ) return [] - @staticmethod def get_output_converter( - llm: BaseLLM, text: str, model: type[BaseModel], instructions: str + self, llm: BaseLLM, text: str, model: type[BaseModel], instructions: str ) -> Converter: """Create a Converter instance for transforming LLM output to a structured model.""" - return Converter(llm=llm, text=text, model=model, instructions=instructions) + return Converter( + llm=llm, text=text, model=model, instructions=instructions, agent=self + ) def _training_handler(self, task_prompt: str) -> str: """Handle training data for the agent task prompt to improve output on Training.""" @@ -2040,7 +2041,13 @@ def _build_output_from_result( "formatted_task_instructions" ).format(output_format=schema) - converter = Converter( + # Build the fallback converter through get_output_converter + # so it carries the agent: the partial-JSON path can then + # run the (bounded) instructions-based refit instead of + # aborting with the #7984 TypeError. The refit converter + # itself is still agent-less (see convert_with_instructions), + # so no nested LLM call tree can form here either. + converter = self.get_output_converter( llm=cast(BaseLLM, self.llm), text=raw_output, model=response_format, diff --git a/lib/crewai/src/crewai/agents/agent_builder/utilities/base_output_converter.py b/lib/crewai/src/crewai/agents/agent_builder/utilities/base_output_converter.py index b8709ef743..a46f8e73de 100644 --- a/lib/crewai/src/crewai/agents/agent_builder/utilities/base_output_converter.py +++ b/lib/crewai/src/crewai/agents/agent_builder/utilities/base_output_converter.py @@ -32,6 +32,11 @@ class OutputConverter(BaseModel, ABC): description="Max number of attempts to try to get the output formatted.", default=3, ) + agent: Any = Field( + default=None, + description="Optional reference to the calling agent, used when the " + "partial-JSON fallback needs to run another LLM round-trip.", + ) @abstractmethod def to_pydantic(self, current_attempt: int = 1) -> BaseModel: diff --git a/lib/crewai/src/crewai/utilities/converter.py b/lib/crewai/src/crewai/utilities/converter.py index 40a04e1baf..5f269187b6 100644 --- a/lib/crewai/src/crewai/utilities/converter.py +++ b/lib/crewai/src/crewai/utilities/converter.py @@ -67,7 +67,7 @@ def _coerce_response_to_pydantic(self, response: Any) -> BaseModel: result=response, model=self.model, is_json_output=False, - agent=None, + agent=self.agent, ) if isinstance(partial, BaseModel): return partial @@ -405,9 +405,14 @@ def convert_with_instructions( raise ValueError("Agent must have a valid LLM instance for conversion") instructions = get_conversion_instructions(model=model, llm=llm) + # The refit converter must NOT carry the calling agent: an agent-stamped + # converter routes its own partial-JSON fallback back through + # convert_with_instructions, spawning a nested LLM call tree on every + # failed attempt (unbounded provider fan-out). With agent=None, a + # ValidationError stays inside this converter's max_attempts loop. converter = create_converter( - agent=agent, - converter_cls=converter_cls, + agent=None, + converter_cls=converter_cls or Converter, llm=llm, text=result, model=model, @@ -549,9 +554,14 @@ async def async_convert_with_instructions( raise ValueError("Agent must have a valid LLM instance for conversion") instructions = get_conversion_instructions(model=model, llm=llm) + # The refit converter must NOT carry the calling agent: an agent-stamped + # converter routes its own partial-JSON fallback back through + # convert_with_instructions, spawning a nested LLM call tree on every + # failed attempt (unbounded provider fan-out). With agent=None, a + # ValidationError stays inside this converter's max_attempts loop. converter = create_converter( - agent=agent, - converter_cls=converter_cls, + agent=None, + converter_cls=converter_cls or Converter, llm=llm, text=result, model=model,