mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-23 15:18:14 +00:00
fix: sanitize tool names in native tool call processing
- Update extract_tool_call_info to return sanitized tool names - Fix delegation tool name matching to use sanitized names - Add sanitization in crew_agent_executor tool call extraction - Add sanitization in experimental agent_executor - Add sanitization in LLM.call function lookup - Update streaming utility to use sanitized names - Update base_agent_executor_mixin delegation check
This commit is contained in:
@@ -10,6 +10,7 @@ from crewai.memory.long_term.long_term_memory_item import LongTermMemoryItem
|
||||
from crewai.utilities.converter import ConverterError
|
||||
from crewai.utilities.evaluators.task_evaluator import TaskEvaluator
|
||||
from crewai.utilities.printer import Printer
|
||||
from crewai.utilities.string_utils import sanitize_tool_name
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -36,7 +37,7 @@ class CrewAgentExecutorMixin:
|
||||
self.crew
|
||||
and self.agent
|
||||
and self.task
|
||||
and "Action: Delegate work to coworker" not in output.text
|
||||
and f"Action: {sanitize_tool_name('Delegate work to coworker')}" not in output.text
|
||||
):
|
||||
try:
|
||||
if (
|
||||
|
||||
@@ -576,12 +576,12 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
||||
if hasattr(tool_call, "function"):
|
||||
# OpenAI-style: has .function.name and .function.arguments
|
||||
call_id = getattr(tool_call, "id", f"call_{id(tool_call)}")
|
||||
func_name = tool_call.function.name
|
||||
func_name = sanitize_tool_name(tool_call.function.name)
|
||||
func_args = tool_call.function.arguments
|
||||
elif hasattr(tool_call, "function_call") and tool_call.function_call:
|
||||
# Gemini-style: has .function_call.name and .function_call.args
|
||||
call_id = f"call_{id(tool_call)}"
|
||||
func_name = tool_call.function_call.name
|
||||
func_name = sanitize_tool_name(tool_call.function_call.name)
|
||||
func_args = (
|
||||
dict(tool_call.function_call.args)
|
||||
if tool_call.function_call.args
|
||||
@@ -590,7 +590,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
||||
elif hasattr(tool_call, "name") and hasattr(tool_call, "input"):
|
||||
# Anthropic format: has .name and .input (ToolUseBlock)
|
||||
call_id = getattr(tool_call, "id", f"call_{id(tool_call)}")
|
||||
func_name = tool_call.name
|
||||
func_name = sanitize_tool_name(tool_call.name)
|
||||
func_args = tool_call.input # Already a dict in Anthropic
|
||||
elif isinstance(tool_call, dict):
|
||||
# Support OpenAI "id", Bedrock "toolUseId", or generate one
|
||||
@@ -600,7 +600,9 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
||||
or f"call_{id(tool_call)}"
|
||||
)
|
||||
func_info = tool_call.get("function", {})
|
||||
func_name = func_info.get("name", "") or tool_call.get("name", "")
|
||||
func_name = sanitize_tool_name(
|
||||
func_info.get("name", "") or tool_call.get("name", "")
|
||||
)
|
||||
func_args = func_info.get("arguments", "{}") or tool_call.get("input", {})
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -56,6 +56,7 @@ from crewai.utilities.agent_utils import (
|
||||
from crewai.utilities.constants import TRAINING_DATA_FILE
|
||||
from crewai.utilities.i18n import I18N, get_i18n
|
||||
from crewai.utilities.printer import Printer
|
||||
from crewai.utilities.string_utils import sanitize_tool_name
|
||||
from crewai.utilities.tool_utils import execute_tool_and_check_finality
|
||||
from crewai.utilities.training_handler import CrewTrainingHandler
|
||||
from crewai.utilities.types import LLMMessage
|
||||
@@ -602,8 +603,6 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
|
||||
)
|
||||
|
||||
# Find original tool by matching sanitized name (needed for cache_function and result_as_answer)
|
||||
from crewai.utilities.string_utils import sanitize_tool_name
|
||||
|
||||
original_tool = None
|
||||
for tool in self.original_tools or []:
|
||||
if sanitize_tool_name(tool.name) == func_name:
|
||||
@@ -761,14 +760,14 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
|
||||
def _extract_tool_name(self, tool_call: Any) -> str:
|
||||
"""Extract tool name from various tool call formats."""
|
||||
if hasattr(tool_call, "function"):
|
||||
return tool_call.function.name
|
||||
return sanitize_tool_name(tool_call.function.name)
|
||||
if hasattr(tool_call, "function_call") and tool_call.function_call:
|
||||
return tool_call.function_call.name
|
||||
return sanitize_tool_name(tool_call.function_call.name)
|
||||
if hasattr(tool_call, "name"):
|
||||
return tool_call.name
|
||||
return sanitize_tool_name(tool_call.name)
|
||||
if isinstance(tool_call, dict):
|
||||
func_info = tool_call.get("function", {})
|
||||
return func_info.get("name", "") or tool_call.get("name", "unknown")
|
||||
return sanitize_tool_name(func_info.get("name", "") or tool_call.get("name", "unknown"))
|
||||
return "unknown"
|
||||
|
||||
@router(execute_native_tool)
|
||||
|
||||
@@ -50,6 +50,7 @@ from crewai.utilities.exceptions.context_window_exceeding_exception import (
|
||||
LLMContextLengthExceededError,
|
||||
)
|
||||
from crewai.utilities.logger_utils import suppress_warnings
|
||||
from crewai.utilities.string_utils import sanitize_tool_name
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -1540,7 +1541,7 @@ class LLM(BaseLLM):
|
||||
|
||||
# --- 2) Extract function name from first tool call
|
||||
tool_call = tool_calls[0]
|
||||
function_name = tool_call.function.name
|
||||
function_name = sanitize_tool_name(tool_call.function.name)
|
||||
function_args = {} # Initialize to empty dict to avoid unbound variable
|
||||
|
||||
# --- 3) Check if function is available
|
||||
|
||||
@@ -296,6 +296,4 @@ class CrewStructuredTool:
|
||||
return self.args_schema.model_json_schema()["properties"]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"CrewStructuredTool(name='{self.name}', description='{self.description}')"
|
||||
)
|
||||
return f"CrewStructuredTool(name='{sanitize_tool_name(self.name)}', description='{self.description}')"
|
||||
|
||||
@@ -821,10 +821,8 @@ def load_agent_from_repository(from_repository: str) -> dict[str, Any]:
|
||||
|
||||
DELEGATION_TOOL_NAMES: Final[frozenset[str]] = frozenset(
|
||||
[
|
||||
"Delegate work to coworker",
|
||||
"Delegate_work_to_coworker",
|
||||
"Ask question to coworker",
|
||||
"Ask_question_to_coworker",
|
||||
sanitize_tool_name("Delegate work to coworker"),
|
||||
sanitize_tool_name("Ask question to coworker"),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -842,7 +840,7 @@ def track_delegation_if_needed(
|
||||
tool_args: Arguments passed to the tool.
|
||||
task: The task being executed (used to track delegations).
|
||||
"""
|
||||
if tool_name in DELEGATION_TOOL_NAMES and task is not None:
|
||||
if sanitize_tool_name(tool_name) in DELEGATION_TOOL_NAMES and task is not None:
|
||||
coworker = tool_args.get("coworker")
|
||||
task.increment_delegations(coworker)
|
||||
|
||||
@@ -861,19 +859,19 @@ def extract_tool_call_info(
|
||||
if hasattr(tool_call, "function"):
|
||||
# OpenAI-style: has .function.name and .function.arguments
|
||||
call_id = getattr(tool_call, "id", f"call_{id(tool_call)}")
|
||||
return call_id, tool_call.function.name, tool_call.function.arguments
|
||||
return call_id, sanitize_tool_name(tool_call.function.name), tool_call.function.arguments
|
||||
if hasattr(tool_call, "function_call") and tool_call.function_call:
|
||||
# Gemini-style: has .function_call.name and .function_call.args
|
||||
call_id = f"call_{id(tool_call)}"
|
||||
return (
|
||||
call_id,
|
||||
tool_call.function_call.name,
|
||||
sanitize_tool_name(tool_call.function_call.name),
|
||||
dict(tool_call.function_call.args) if tool_call.function_call.args else {},
|
||||
)
|
||||
if hasattr(tool_call, "name") and hasattr(tool_call, "input"):
|
||||
# Anthropic format: has .name and .input (ToolUseBlock)
|
||||
call_id = getattr(tool_call, "id", f"call_{id(tool_call)}")
|
||||
return call_id, tool_call.name, tool_call.input
|
||||
return call_id, sanitize_tool_name(tool_call.name), tool_call.input
|
||||
if isinstance(tool_call, dict):
|
||||
# Support OpenAI "id", Bedrock "toolUseId", or generate one
|
||||
call_id = (
|
||||
@@ -882,7 +880,7 @@ def extract_tool_call_info(
|
||||
func_info = tool_call.get("function", {})
|
||||
func_name = func_info.get("name", "") or tool_call.get("name", "")
|
||||
func_args = func_info.get("arguments", "{}") or tool_call.get("input", {})
|
||||
return call_id, func_name, func_args
|
||||
return call_id, sanitize_tool_name(func_name), func_args
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ from crewai.types.streaming import (
|
||||
StreamChunkType,
|
||||
ToolCallChunk,
|
||||
)
|
||||
from crewai.utilities.string_utils import sanitize_tool_name
|
||||
|
||||
|
||||
class TaskInfo(TypedDict):
|
||||
@@ -58,7 +59,7 @@ def _extract_tool_call_info(
|
||||
StreamChunkType.TOOL_CALL,
|
||||
ToolCallChunk(
|
||||
tool_id=event.tool_call.id,
|
||||
tool_name=event.tool_call.function.name,
|
||||
tool_name=sanitize_tool_name(event.tool_call.function.name),
|
||||
arguments=event.tool_call.function.arguments,
|
||||
index=event.tool_call.index,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user