mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-02 21:58:11 +00:00
fix: address review comments - unused import, FC+no-tools routing
- Remove unused asyncio import from test file - Route FC-capable LLMs with no tools + response_model to _invoke_loop_native_no_tools (preserves structured output) - FC-capable LLMs with no tools and no response_model still fall through to ReAct path (no regression) - Add tests for both FC+no-tools routing scenarios Co-Authored-By: João <joao@crewai.com>
This commit is contained in:
@@ -311,16 +311,21 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
||||
Final answer from the agent.
|
||||
"""
|
||||
# Check if model supports native function calling
|
||||
use_native_tools = (
|
||||
supports_fc = (
|
||||
hasattr(self.llm, "supports_function_calling")
|
||||
and callable(getattr(self.llm, "supports_function_calling", None))
|
||||
and self.llm.supports_function_calling()
|
||||
and self.original_tools
|
||||
)
|
||||
|
||||
if use_native_tools:
|
||||
if supports_fc and self.original_tools:
|
||||
return self._invoke_loop_native_tools()
|
||||
|
||||
# FC-capable LLM with no tools but with response_model: use simple
|
||||
# native call path which correctly passes response_model for structured
|
||||
# output instead of dropping it in the ReAct path.
|
||||
if supports_fc and not self.original_tools and self.response_model:
|
||||
return self._invoke_loop_native_no_tools()
|
||||
|
||||
# Fall back to ReAct text-based pattern
|
||||
return self._invoke_loop_react()
|
||||
|
||||
@@ -1132,16 +1137,21 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
||||
Final answer from the agent.
|
||||
"""
|
||||
# Check if model supports native function calling
|
||||
use_native_tools = (
|
||||
supports_fc = (
|
||||
hasattr(self.llm, "supports_function_calling")
|
||||
and callable(getattr(self.llm, "supports_function_calling", None))
|
||||
and self.llm.supports_function_calling()
|
||||
and self.original_tools
|
||||
)
|
||||
|
||||
if use_native_tools:
|
||||
if supports_fc and self.original_tools:
|
||||
return await self._ainvoke_loop_native_tools()
|
||||
|
||||
# FC-capable LLM with no tools but with response_model: use simple
|
||||
# native call path which correctly passes response_model for structured
|
||||
# output instead of dropping it in the ReAct path.
|
||||
if supports_fc and not self.original_tools and self.response_model:
|
||||
return await self._ainvoke_loop_native_no_tools()
|
||||
|
||||
# Fall back to ReAct text-based pattern
|
||||
return await self._ainvoke_loop_react()
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ conversion to pydantic/json should happen in task._export_output() after the ReA
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
@@ -180,6 +179,52 @@ class TestReActFlowDoesNotPassResponseModel:
|
||||
mock_native.assert_called_once()
|
||||
mock_react.assert_not_called()
|
||||
|
||||
def test_invoke_loop_routes_to_native_no_tools_when_fc_no_tools_with_response_model(
|
||||
self,
|
||||
) -> None:
|
||||
"""When LLM supports FC, has no tools, but HAS a response_model,
|
||||
route to _invoke_loop_native_no_tools (which correctly passes
|
||||
response_model) instead of falling through to the ReAct path."""
|
||||
llm = _make_llm(supports_fc=True)
|
||||
executor = _make_executor(llm, response_model=PersonInfo)
|
||||
# No tools
|
||||
executor.original_tools = []
|
||||
|
||||
with patch.object(
|
||||
executor,
|
||||
"_invoke_loop_native_no_tools",
|
||||
return_value=AgentFinish(thought="done", output="test", text="Final Answer: test"),
|
||||
) as mock_native_no_tools:
|
||||
with patch.object(executor, "_invoke_loop_react") as mock_react:
|
||||
with patch.object(executor, "_invoke_loop_native_tools") as mock_native:
|
||||
executor._invoke_loop()
|
||||
|
||||
mock_native_no_tools.assert_called_once()
|
||||
mock_react.assert_not_called()
|
||||
mock_native.assert_not_called()
|
||||
|
||||
def test_invoke_loop_routes_to_react_when_fc_no_tools_no_response_model(
|
||||
self,
|
||||
) -> None:
|
||||
"""When LLM supports FC, has no tools, and NO response_model,
|
||||
fall through to ReAct path (no structured output to preserve)."""
|
||||
llm = _make_llm(supports_fc=True)
|
||||
executor = _make_executor(llm, response_model=None)
|
||||
executor.original_tools = []
|
||||
|
||||
with patch.object(
|
||||
executor,
|
||||
"_invoke_loop_react",
|
||||
return_value=AgentFinish(thought="done", output="test", text="Final Answer: test"),
|
||||
) as mock_react:
|
||||
with patch.object(executor, "_invoke_loop_native_no_tools") as mock_native_no_tools:
|
||||
with patch.object(executor, "_invoke_loop_native_tools") as mock_native:
|
||||
executor._invoke_loop()
|
||||
|
||||
mock_react.assert_called_once()
|
||||
mock_native_no_tools.assert_not_called()
|
||||
mock_native.assert_not_called()
|
||||
|
||||
def test_react_flow_still_works_with_tool_usage(self) -> None:
|
||||
"""Verify the ReAct loop still processes Action/Observation cycles
|
||||
correctly even when output_pydantic is set."""
|
||||
|
||||
Reference in New Issue
Block a user