mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-23 07:08:14 +00:00
Merge branch 'lorenze/imp/native-tool-calling' of github.com:crewAIInc/crewAI into lorenze/imp/native-tool-calling
This commit is contained in:
@@ -89,6 +89,7 @@ from crewai.utilities.guardrail_types import GuardrailType
|
||||
from crewai.utilities.llm_utils import create_llm
|
||||
from crewai.utilities.prompts import Prompts, StandardPromptResult, SystemPromptResult
|
||||
from crewai.utilities.pydantic_schema_utils import generate_model_description
|
||||
from crewai.utilities.string_utils import sanitize_tool_name
|
||||
from crewai.utilities.token_counter_callback import TokenCalcHandler
|
||||
from crewai.utilities.training_handler import CrewTrainingHandler
|
||||
|
||||
@@ -1339,10 +1340,10 @@ class Agent(BaseAgent):
|
||||
args_schema = None
|
||||
if hasattr(tool, "inputSchema") and tool.inputSchema:
|
||||
args_schema = self._json_schema_to_pydantic(
|
||||
tool.name, tool.inputSchema
|
||||
sanitize_tool_name(tool.name), tool.inputSchema
|
||||
)
|
||||
|
||||
schemas[tool.name] = {
|
||||
schemas[sanitize_tool_name(tool.name)] = {
|
||||
"description": getattr(tool, "description", ""),
|
||||
"args_schema": args_schema,
|
||||
}
|
||||
@@ -1498,7 +1499,7 @@ class Agent(BaseAgent):
|
||||
"""
|
||||
return "\n".join(
|
||||
[
|
||||
f"Tool name: {tool.name}\nTool description:\n{tool.description}"
|
||||
f"Tool name: {sanitize_tool_name(tool.name)}\nTool description:\n{tool.description}"
|
||||
for tool in tools
|
||||
]
|
||||
)
|
||||
|
||||
@@ -104,6 +104,7 @@ from crewai.utilities.streaming import (
|
||||
signal_end,
|
||||
signal_error,
|
||||
)
|
||||
from crewai.utilities.string_utils import sanitize_tool_name
|
||||
from crewai.utilities.task_output_storage_handler import TaskOutputStorageHandler
|
||||
from crewai.utilities.training_handler import CrewTrainingHandler
|
||||
|
||||
@@ -1241,10 +1242,14 @@ class Crew(FlowTrackable, BaseModel):
|
||||
return existing_tools
|
||||
|
||||
# Create mapping of tool names to new tools
|
||||
new_tool_map = {tool.name: tool for tool in new_tools}
|
||||
new_tool_map = {sanitize_tool_name(tool.name): tool for tool in new_tools}
|
||||
|
||||
# Remove any existing tools that will be replaced
|
||||
tools = [tool for tool in existing_tools if tool.name not in new_tool_map]
|
||||
tools = [
|
||||
tool
|
||||
for tool in existing_tools
|
||||
if sanitize_tool_name(tool.name) not in new_tool_map
|
||||
]
|
||||
|
||||
# Add all new tools
|
||||
tools.extend(new_tools)
|
||||
|
||||
@@ -11,6 +11,7 @@ from crewai.experimental.evaluation.base_evaluator import (
|
||||
)
|
||||
from crewai.experimental.evaluation.json_parser import extract_json_from_llm_response
|
||||
from crewai.task import Task
|
||||
from crewai.utilities.string_utils import sanitize_tool_name
|
||||
from crewai.utilities.types import LLMMessage
|
||||
|
||||
|
||||
@@ -52,7 +53,9 @@ class ToolSelectionEvaluator(BaseEvaluator):
|
||||
available_tools_info = ""
|
||||
if agent.tools:
|
||||
for tool in agent.tools:
|
||||
available_tools_info += f"- {tool.name}: {tool.description}\n"
|
||||
available_tools_info += (
|
||||
f"- {sanitize_tool_name(tool.name)}: {tool.description}\n"
|
||||
)
|
||||
else:
|
||||
available_tools_info = "No tools available"
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ from crewai.mcp.transports.base import BaseTransport
|
||||
from crewai.mcp.transports.http import HTTPTransport
|
||||
from crewai.mcp.transports.sse import SSETransport
|
||||
from crewai.mcp.transports.stdio import StdioTransport
|
||||
from crewai.utilities.string_utils import sanitize_tool_name
|
||||
|
||||
|
||||
# MCP Connection timeout constants (in seconds)
|
||||
@@ -418,7 +419,7 @@ class MCPClient:
|
||||
|
||||
return [
|
||||
{
|
||||
"name": tool.name,
|
||||
"name": sanitize_tool_name(tool.name),
|
||||
"description": getattr(tool, "description", ""),
|
||||
"inputSchema": getattr(tool, "inputSchema", {}),
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ from crewai.telemetry.utils import (
|
||||
close_span,
|
||||
)
|
||||
from crewai.utilities.logger_utils import suppress_warnings
|
||||
from crewai.utilities.string_utils import sanitize_tool_name
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -323,7 +324,8 @@ class Telemetry:
|
||||
),
|
||||
"max_retry_limit": getattr(agent, "max_retry_limit", 3),
|
||||
"tools_names": [
|
||||
tool.name.casefold() for tool in agent.tools or []
|
||||
sanitize_tool_name(tool.name)
|
||||
for tool in agent.tools or []
|
||||
],
|
||||
# Add agent fingerprint data if sharing crew details
|
||||
"fingerprint": (
|
||||
@@ -372,7 +374,8 @@ class Telemetry:
|
||||
else None
|
||||
),
|
||||
"tools_names": [
|
||||
tool.name.casefold() for tool in task.tools or []
|
||||
sanitize_tool_name(tool.name)
|
||||
for tool in task.tools or []
|
||||
],
|
||||
# Add task fingerprint data if sharing crew details
|
||||
"fingerprint": (
|
||||
@@ -425,7 +428,8 @@ class Telemetry:
|
||||
),
|
||||
"max_retry_limit": getattr(agent, "max_retry_limit", 3),
|
||||
"tools_names": [
|
||||
tool.name.casefold() for tool in agent.tools or []
|
||||
sanitize_tool_name(tool.name)
|
||||
for tool in agent.tools or []
|
||||
],
|
||||
}
|
||||
for agent in crew.agents
|
||||
@@ -447,7 +451,8 @@ class Telemetry:
|
||||
),
|
||||
"agent_key": task.agent.key if task.agent else None,
|
||||
"tools_names": [
|
||||
tool.name.casefold() for tool in task.tools or []
|
||||
sanitize_tool_name(tool.name)
|
||||
for tool in task.tools or []
|
||||
],
|
||||
}
|
||||
for task in crew.tasks
|
||||
@@ -832,7 +837,8 @@ class Telemetry:
|
||||
"llm": agent.llm.model,
|
||||
"delegation_enabled?": agent.allow_delegation,
|
||||
"tools_names": [
|
||||
tool.name.casefold() for tool in agent.tools or []
|
||||
sanitize_tool_name(tool.name)
|
||||
for tool in agent.tools or []
|
||||
],
|
||||
}
|
||||
for agent in crew.agents
|
||||
@@ -858,7 +864,8 @@ class Telemetry:
|
||||
else None
|
||||
),
|
||||
"tools_names": [
|
||||
tool.name.casefold() for tool in task.tools or []
|
||||
sanitize_tool_name(tool.name)
|
||||
for tool in task.tools or []
|
||||
],
|
||||
}
|
||||
for task in crew.tasks
|
||||
|
||||
@@ -26,6 +26,7 @@ from typing_extensions import TypeIs
|
||||
from crewai.tools.structured_tool import CrewStructuredTool
|
||||
from crewai.utilities.printer import Printer
|
||||
from crewai.utilities.pydantic_schema_utils import generate_model_description
|
||||
from crewai.utilities.string_utils import sanitize_tool_name
|
||||
|
||||
|
||||
_printer = Printer()
|
||||
@@ -259,10 +260,12 @@ class BaseTool(BaseModel, ABC):
|
||||
else:
|
||||
fields[name] = (param_annotation, param.default)
|
||||
if fields:
|
||||
args_schema = create_model(f"{tool.name}Input", **fields)
|
||||
args_schema = create_model(
|
||||
f"{sanitize_tool_name(tool.name)}_input", **fields
|
||||
)
|
||||
else:
|
||||
args_schema = create_model(
|
||||
f"{tool.name}Input", __base__=PydanticBaseModel
|
||||
f"{sanitize_tool_name(tool.name)}_input", __base__=PydanticBaseModel
|
||||
)
|
||||
|
||||
return cls(
|
||||
@@ -301,7 +304,7 @@ class BaseTool(BaseModel, ABC):
|
||||
schema = generate_model_description(self.args_schema)
|
||||
args_json = json.dumps(schema["json_schema"]["schema"], indent=2)
|
||||
self.description = (
|
||||
f"Tool Name: {self.name}\n"
|
||||
f"Tool Name: {sanitize_tool_name(self.name)}\n"
|
||||
f"Tool Arguments: {args_json}\n"
|
||||
f"Tool Description: {self.description}"
|
||||
)
|
||||
@@ -379,7 +382,7 @@ class Tool(BaseTool, Generic[P, R]):
|
||||
if _is_awaitable(result):
|
||||
return await result
|
||||
raise NotImplementedError(
|
||||
f"{self.name} does not have an async function. "
|
||||
f"{sanitize_tool_name(self.name)} does not have an async function. "
|
||||
"Use run() for sync execution or provide an async function."
|
||||
)
|
||||
|
||||
@@ -421,10 +424,12 @@ class Tool(BaseTool, Generic[P, R]):
|
||||
else:
|
||||
fields[name] = (param_annotation, param.default)
|
||||
if fields:
|
||||
args_schema = create_model(f"{tool.name}Input", **fields)
|
||||
args_schema = create_model(
|
||||
f"{sanitize_tool_name(tool.name)}_input", **fields
|
||||
)
|
||||
else:
|
||||
args_schema = create_model(
|
||||
f"{tool.name}Input", __base__=PydanticBaseModel
|
||||
f"{sanitize_tool_name(tool.name)}_input", __base__=PydanticBaseModel
|
||||
)
|
||||
|
||||
return cls(
|
||||
|
||||
@@ -2,6 +2,7 @@ from pydantic import BaseModel, Field
|
||||
|
||||
from crewai.agents.cache.cache_handler import CacheHandler
|
||||
from crewai.tools.structured_tool import CrewStructuredTool
|
||||
from crewai.utilities.string_utils import sanitize_tool_name
|
||||
|
||||
|
||||
class CacheTools(BaseModel):
|
||||
@@ -13,14 +14,14 @@ class CacheTools(BaseModel):
|
||||
default_factory=CacheHandler,
|
||||
)
|
||||
|
||||
def tool(self):
|
||||
def tool(self) -> CrewStructuredTool:
|
||||
return CrewStructuredTool.from_function(
|
||||
func=self.hit_cache,
|
||||
name=self.name,
|
||||
name=sanitize_tool_name(self.name),
|
||||
description="Reads directly from the cache",
|
||||
)
|
||||
|
||||
def hit_cache(self, key):
|
||||
def hit_cache(self, key: str) -> str | None:
|
||||
split = key.split("tool:")
|
||||
tool = split[1].split("|input:")[0].strip()
|
||||
tool_input = split[1].split("|input:")[1].strip()
|
||||
|
||||
@@ -10,6 +10,7 @@ from typing import TYPE_CHECKING, Any, get_type_hints
|
||||
from pydantic import BaseModel, Field, create_model
|
||||
|
||||
from crewai.utilities.logger import Logger
|
||||
from crewai.utilities.string_utils import sanitize_tool_name
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -229,7 +230,7 @@ class CrewStructuredTool:
|
||||
|
||||
if self.has_reached_max_usage_count():
|
||||
raise ToolUsageLimitExceededError(
|
||||
f"Tool '{self.name}' has reached its maximum usage limit of {self.max_usage_count}. You should not use the {self.name} tool again."
|
||||
f"Tool '{sanitize_tool_name(self.name)}' has reached its maximum usage limit of {self.max_usage_count}. You should not use the {sanitize_tool_name(self.name)} tool again."
|
||||
)
|
||||
|
||||
self._increment_usage_count()
|
||||
@@ -261,7 +262,7 @@ class CrewStructuredTool:
|
||||
|
||||
if self.has_reached_max_usage_count():
|
||||
raise ToolUsageLimitExceededError(
|
||||
f"Tool '{self.name}' has reached its maximum usage limit of {self.max_usage_count}. You should not use the {self.name} tool again."
|
||||
f"Tool '{sanitize_tool_name(self.name)}' has reached its maximum usage limit of {self.max_usage_count}. You should not use the {sanitize_tool_name(self.name)} tool again."
|
||||
)
|
||||
|
||||
self._increment_usage_count()
|
||||
|
||||
@@ -30,6 +30,7 @@ from crewai.utilities.agent_utils import (
|
||||
from crewai.utilities.converter import Converter
|
||||
from crewai.utilities.i18n import I18N, get_i18n
|
||||
from crewai.utilities.printer import Printer
|
||||
from crewai.utilities.string_utils import sanitize_tool_name
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -145,7 +146,8 @@ class ToolUsage:
|
||||
|
||||
if (
|
||||
isinstance(tool, CrewStructuredTool)
|
||||
and tool.name == self._i18n.tools("add_image")["name"] # type: ignore
|
||||
and sanitize_tool_name(tool.name)
|
||||
== sanitize_tool_name(self._i18n.tools("add_image")["name"]) # type: ignore
|
||||
):
|
||||
try:
|
||||
return self._use(tool_string=tool_string, tool=tool, calling=calling)
|
||||
@@ -192,7 +194,8 @@ class ToolUsage:
|
||||
|
||||
if (
|
||||
isinstance(tool, CrewStructuredTool)
|
||||
and tool.name == self._i18n.tools("add_image")["name"] # type: ignore
|
||||
and sanitize_tool_name(tool.name)
|
||||
== sanitize_tool_name(self._i18n.tools("add_image")["name"]) # type: ignore
|
||||
):
|
||||
try:
|
||||
return await self._ause(
|
||||
@@ -233,7 +236,7 @@ class ToolUsage:
|
||||
)
|
||||
self._telemetry.tool_repeated_usage(
|
||||
llm=self.function_calling_llm,
|
||||
tool_name=tool.name,
|
||||
tool_name=sanitize_tool_name(tool.name),
|
||||
attempts=self._run_attempts,
|
||||
)
|
||||
return self._format_result(result=result)
|
||||
@@ -278,7 +281,7 @@ class ToolUsage:
|
||||
input_str = str(calling.arguments)
|
||||
|
||||
result = self.tools_handler.cache.read(
|
||||
tool=calling.tool_name, input=input_str
|
||||
tool=sanitize_tool_name(calling.tool_name), input=input_str
|
||||
) # type: ignore
|
||||
from_cache = result is not None
|
||||
|
||||
@@ -286,12 +289,15 @@ class ToolUsage:
|
||||
(
|
||||
available_tool
|
||||
for available_tool in self.tools
|
||||
if available_tool.name == tool.name
|
||||
if sanitize_tool_name(available_tool.name)
|
||||
== sanitize_tool_name(tool.name)
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
usage_limit_error = self._check_usage_limit(available_tool, tool.name)
|
||||
usage_limit_error = self._check_usage_limit(
|
||||
available_tool, sanitize_tool_name(tool.name)
|
||||
)
|
||||
if usage_limit_error:
|
||||
result = usage_limit_error
|
||||
self._telemetry.tool_usage_error(llm=self.function_calling_llm)
|
||||
@@ -299,9 +305,9 @@ class ToolUsage:
|
||||
# Don't return early - fall through to finally block
|
||||
elif result is None:
|
||||
try:
|
||||
if calling.tool_name in [
|
||||
"Delegate work to coworker",
|
||||
"Ask question to coworker",
|
||||
if sanitize_tool_name(calling.tool_name) in [
|
||||
sanitize_tool_name("Delegate work to coworker"),
|
||||
sanitize_tool_name("Ask question to coworker"),
|
||||
]:
|
||||
coworker = (
|
||||
calling.arguments.get("coworker")
|
||||
@@ -350,13 +356,13 @@ class ToolUsage:
|
||||
|
||||
self._telemetry.tool_usage(
|
||||
llm=self.function_calling_llm,
|
||||
tool_name=tool.name,
|
||||
tool_name=sanitize_tool_name(tool.name),
|
||||
attempts=self._run_attempts,
|
||||
)
|
||||
result = self._format_result(result=result)
|
||||
data = {
|
||||
"result": result,
|
||||
"tool_name": tool.name,
|
||||
"tool_name": sanitize_tool_name(tool.name),
|
||||
"tool_args": calling.arguments,
|
||||
}
|
||||
|
||||
@@ -380,7 +386,7 @@ class ToolUsage:
|
||||
and available_tool.max_usage_count is not None
|
||||
):
|
||||
self._printer.print(
|
||||
content=f"Tool '{available_tool.name}' usage: {available_tool.current_usage_count}/{available_tool.max_usage_count}",
|
||||
content=f"Tool '{sanitize_tool_name(available_tool.name)}' usage: {available_tool.current_usage_count}/{available_tool.max_usage_count}",
|
||||
color="blue",
|
||||
)
|
||||
elif available_tool and hasattr(
|
||||
@@ -392,7 +398,7 @@ class ToolUsage:
|
||||
and available_tool.max_usage_count is not None
|
||||
):
|
||||
self._printer.print(
|
||||
content=f"Tool '{available_tool.name}' usage: {available_tool.current_usage_count}/{available_tool.max_usage_count}",
|
||||
content=f"Tool '{sanitize_tool_name(available_tool.name)}' usage: {available_tool.current_usage_count}/{available_tool.max_usage_count}",
|
||||
color="blue",
|
||||
)
|
||||
|
||||
@@ -403,7 +409,11 @@ class ToolUsage:
|
||||
self._telemetry.tool_usage_error(llm=self.function_calling_llm)
|
||||
error_message = self._i18n.errors(
|
||||
"tool_usage_exception"
|
||||
).format(error=e, tool=tool.name, tool_inputs=tool.description)
|
||||
).format(
|
||||
error=e,
|
||||
tool=sanitize_tool_name(tool.name),
|
||||
tool_inputs=tool.description,
|
||||
)
|
||||
result = ToolUsageError(
|
||||
f"\n{error_message}.\nMoving on then. {self._i18n.slice('format').format(tool_names=self.tools_names)}"
|
||||
).message
|
||||
@@ -450,7 +460,7 @@ class ToolUsage:
|
||||
)
|
||||
self._telemetry.tool_repeated_usage(
|
||||
llm=self.function_calling_llm,
|
||||
tool_name=tool.name,
|
||||
tool_name=sanitize_tool_name(tool.name),
|
||||
attempts=self._run_attempts,
|
||||
)
|
||||
return self._format_result(result=result)
|
||||
@@ -497,7 +507,7 @@ class ToolUsage:
|
||||
input_str = str(calling.arguments)
|
||||
|
||||
result = self.tools_handler.cache.read(
|
||||
tool=calling.tool_name, input=input_str
|
||||
tool=sanitize_tool_name(calling.tool_name), input=input_str
|
||||
) # type: ignore
|
||||
from_cache = result is not None
|
||||
|
||||
@@ -505,12 +515,15 @@ class ToolUsage:
|
||||
(
|
||||
available_tool
|
||||
for available_tool in self.tools
|
||||
if available_tool.name == tool.name
|
||||
if sanitize_tool_name(available_tool.name)
|
||||
== sanitize_tool_name(tool.name)
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
usage_limit_error = self._check_usage_limit(available_tool, tool.name)
|
||||
usage_limit_error = self._check_usage_limit(
|
||||
available_tool, sanitize_tool_name(tool.name)
|
||||
)
|
||||
if usage_limit_error:
|
||||
result = usage_limit_error
|
||||
self._telemetry.tool_usage_error(llm=self.function_calling_llm)
|
||||
@@ -518,9 +531,9 @@ class ToolUsage:
|
||||
# Don't return early - fall through to finally block
|
||||
elif result is None:
|
||||
try:
|
||||
if calling.tool_name in [
|
||||
"Delegate work to coworker",
|
||||
"Ask question to coworker",
|
||||
if sanitize_tool_name(calling.tool_name) in [
|
||||
sanitize_tool_name("Delegate work to coworker"),
|
||||
sanitize_tool_name("Ask question to coworker"),
|
||||
]:
|
||||
coworker = (
|
||||
calling.arguments.get("coworker")
|
||||
@@ -569,13 +582,13 @@ class ToolUsage:
|
||||
|
||||
self._telemetry.tool_usage(
|
||||
llm=self.function_calling_llm,
|
||||
tool_name=tool.name,
|
||||
tool_name=sanitize_tool_name(tool.name),
|
||||
attempts=self._run_attempts,
|
||||
)
|
||||
result = self._format_result(result=result)
|
||||
data = {
|
||||
"result": result,
|
||||
"tool_name": tool.name,
|
||||
"tool_name": sanitize_tool_name(tool.name),
|
||||
"tool_args": calling.arguments,
|
||||
}
|
||||
|
||||
@@ -599,7 +612,7 @@ class ToolUsage:
|
||||
and available_tool.max_usage_count is not None
|
||||
):
|
||||
self._printer.print(
|
||||
content=f"Tool '{available_tool.name}' usage: {available_tool.current_usage_count}/{available_tool.max_usage_count}",
|
||||
content=f"Tool '{sanitize_tool_name(available_tool.name)}' usage: {available_tool.current_usage_count}/{available_tool.max_usage_count}",
|
||||
color="blue",
|
||||
)
|
||||
elif available_tool and hasattr(
|
||||
@@ -611,7 +624,7 @@ class ToolUsage:
|
||||
and available_tool.max_usage_count is not None
|
||||
):
|
||||
self._printer.print(
|
||||
content=f"Tool '{available_tool.name}' usage: {available_tool.current_usage_count}/{available_tool.max_usage_count}",
|
||||
content=f"Tool '{sanitize_tool_name(available_tool.name)}' usage: {available_tool.current_usage_count}/{available_tool.max_usage_count}",
|
||||
color="blue",
|
||||
)
|
||||
|
||||
@@ -622,7 +635,11 @@ class ToolUsage:
|
||||
self._telemetry.tool_usage_error(llm=self.function_calling_llm)
|
||||
error_message = self._i18n.errors(
|
||||
"tool_usage_exception"
|
||||
).format(error=e, tool=tool.name, tool_inputs=tool.description)
|
||||
).format(
|
||||
error=e,
|
||||
tool=sanitize_tool_name(tool.name),
|
||||
tool_inputs=tool.description,
|
||||
)
|
||||
result = ToolUsageError(
|
||||
f"\n{error_message}.\nMoving on then. {self._i18n.slice('format').format(tool_names=self.tools_names)}"
|
||||
).message
|
||||
@@ -680,9 +697,10 @@ class ToolUsage:
|
||||
if not self.tools_handler:
|
||||
return False
|
||||
if last_tool_usage := self.tools_handler.last_used_tool:
|
||||
return (calling.tool_name == last_tool_usage.tool_name) and (
|
||||
calling.arguments == last_tool_usage.arguments
|
||||
)
|
||||
return (
|
||||
sanitize_tool_name(calling.tool_name)
|
||||
== sanitize_tool_name(last_tool_usage.tool_name)
|
||||
) and (calling.arguments == last_tool_usage.arguments)
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
@@ -705,20 +723,19 @@ class ToolUsage:
|
||||
return None
|
||||
|
||||
def _select_tool(self, tool_name: str) -> Any:
|
||||
sanitized_input = sanitize_tool_name(tool_name)
|
||||
order_tools = sorted(
|
||||
self.tools,
|
||||
key=lambda tool: SequenceMatcher(
|
||||
None, tool.name.lower().strip(), tool_name.lower().strip()
|
||||
None, sanitize_tool_name(tool.name), sanitized_input
|
||||
).ratio(),
|
||||
reverse=True,
|
||||
)
|
||||
for tool in order_tools:
|
||||
sanitized_tool = sanitize_tool_name(tool.name)
|
||||
if (
|
||||
tool.name.lower().strip() == tool_name.lower().strip()
|
||||
or SequenceMatcher(
|
||||
None, tool.name.lower().strip(), tool_name.lower().strip()
|
||||
).ratio()
|
||||
> 0.85
|
||||
sanitized_tool == sanitized_input
|
||||
or SequenceMatcher(None, sanitized_tool, sanitized_input).ratio() > 0.85
|
||||
):
|
||||
return tool
|
||||
if self.task:
|
||||
@@ -803,7 +820,7 @@ class ToolUsage:
|
||||
return ToolUsageError(f"{self._i18n.errors('tool_arguments_error')}")
|
||||
|
||||
return ToolCalling(
|
||||
tool_name=tool.name,
|
||||
tool_name=sanitize_tool_name(tool.name),
|
||||
arguments=arguments,
|
||||
)
|
||||
|
||||
@@ -957,7 +974,7 @@ class ToolUsage:
|
||||
event_data = {
|
||||
"run_attempts": self._run_attempts,
|
||||
"delegations": self.task.delegations if self.task else 0,
|
||||
"tool_name": tool.name,
|
||||
"tool_name": sanitize_tool_name(tool.name),
|
||||
"tool_args": tool_calling.arguments,
|
||||
"tool_class": tool.__class__.__name__,
|
||||
"agent_key": (
|
||||
|
||||
@@ -13,6 +13,7 @@ from crewai.events.types.reasoning_events import (
|
||||
)
|
||||
from crewai.llm import LLM
|
||||
from crewai.task import Task
|
||||
from crewai.utilities.string_utils import sanitize_tool_name
|
||||
|
||||
|
||||
class ReasoningPlan(BaseModel):
|
||||
@@ -340,7 +341,9 @@ class AgentReasoning:
|
||||
str: Comma-separated list of tool names.
|
||||
"""
|
||||
try:
|
||||
return ", ".join([tool.name for tool in (self.task.tools or [])])
|
||||
return ", ".join(
|
||||
[sanitize_tool_name(tool.name) for tool in (self.task.tools or [])]
|
||||
)
|
||||
except (AttributeError, TypeError):
|
||||
return "No tools available"
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ from crewai.tools.tool_types import ToolResult
|
||||
from crewai.tools.tool_usage import ToolUsage, ToolUsageError
|
||||
from crewai.utilities.i18n import I18N
|
||||
from crewai.utilities.logger import Logger
|
||||
from crewai.utilities.string_utils import sanitize_tool_name
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -63,7 +64,7 @@ async def aexecute_tool_and_check_finality(
|
||||
treated as a final answer.
|
||||
"""
|
||||
logger = Logger(verbose=crew.verbose if crew else False)
|
||||
tool_name_to_tool_map = {tool.name: tool for tool in tools}
|
||||
tool_name_to_tool_map = {sanitize_tool_name(tool.name): tool for tool in tools}
|
||||
|
||||
if agent_key and agent_role and agent:
|
||||
fingerprint_context = fingerprint_context or {}
|
||||
@@ -90,19 +91,9 @@ async def aexecute_tool_and_check_finality(
|
||||
if isinstance(tool_calling, ToolUsageError):
|
||||
return ToolResult(tool_calling.message, False)
|
||||
|
||||
if tool_calling.tool_name.casefold().strip() in [
|
||||
name.casefold().strip() for name in tool_name_to_tool_map
|
||||
] or tool_calling.tool_name.casefold().replace("_", " ") in [
|
||||
name.casefold().strip() for name in tool_name_to_tool_map
|
||||
]:
|
||||
tool = tool_name_to_tool_map.get(tool_calling.tool_name)
|
||||
if not tool:
|
||||
tool_result = i18n.errors("wrong_tool_name").format(
|
||||
tool=tool_calling.tool_name,
|
||||
tools=", ".join([t.name.casefold() for t in tools]),
|
||||
)
|
||||
return ToolResult(result=tool_result, result_as_answer=False)
|
||||
|
||||
sanitized_tool_name = sanitize_tool_name(tool_calling.tool_name)
|
||||
tool = tool_name_to_tool_map.get(sanitized_tool_name)
|
||||
if tool:
|
||||
tool_input = tool_calling.arguments if tool_calling.arguments else {}
|
||||
hook_context = ToolCallHookContext(
|
||||
tool_name=tool_calling.tool_name,
|
||||
@@ -152,8 +143,8 @@ async def aexecute_tool_and_check_finality(
|
||||
return ToolResult(modified_result, tool.result_as_answer)
|
||||
|
||||
tool_result = i18n.errors("wrong_tool_name").format(
|
||||
tool=tool_calling.tool_name,
|
||||
tools=", ".join([tool.name.casefold() for tool in tools]),
|
||||
tool=sanitized_tool_name,
|
||||
tools=", ".join(tool_name_to_tool_map.keys()),
|
||||
)
|
||||
return ToolResult(result=tool_result, result_as_answer=False)
|
||||
|
||||
@@ -193,7 +184,7 @@ def execute_tool_and_check_finality(
|
||||
ToolResult containing the execution result and whether it should be treated as a final answer
|
||||
"""
|
||||
logger = Logger(verbose=crew.verbose if crew else False)
|
||||
tool_name_to_tool_map = {tool.name: tool for tool in tools}
|
||||
tool_name_to_tool_map = {sanitize_tool_name(tool.name): tool for tool in tools}
|
||||
|
||||
if agent_key and agent_role and agent:
|
||||
fingerprint_context = fingerprint_context or {}
|
||||
@@ -206,7 +197,6 @@ def execute_tool_and_check_finality(
|
||||
except Exception as e:
|
||||
raise ValueError(f"Failed to set fingerprint: {e}") from e
|
||||
|
||||
# Create tool usage instance
|
||||
tool_usage = ToolUsage(
|
||||
tools_handler=tools_handler,
|
||||
tools=tools,
|
||||
@@ -216,26 +206,14 @@ def execute_tool_and_check_finality(
|
||||
action=agent_action,
|
||||
)
|
||||
|
||||
# Parse tool calling
|
||||
tool_calling = tool_usage.parse_tool_calling(agent_action.text)
|
||||
|
||||
if isinstance(tool_calling, ToolUsageError):
|
||||
return ToolResult(tool_calling.message, False)
|
||||
|
||||
# Check if tool name matches
|
||||
if tool_calling.tool_name.casefold().strip() in [
|
||||
name.casefold().strip() for name in tool_name_to_tool_map
|
||||
] or tool_calling.tool_name.casefold().replace("_", " ") in [
|
||||
name.casefold().strip() for name in tool_name_to_tool_map
|
||||
]:
|
||||
tool = tool_name_to_tool_map.get(tool_calling.tool_name)
|
||||
if not tool:
|
||||
tool_result = i18n.errors("wrong_tool_name").format(
|
||||
tool=tool_calling.tool_name,
|
||||
tools=", ".join([t.name.casefold() for t in tools]),
|
||||
)
|
||||
return ToolResult(result=tool_result, result_as_answer=False)
|
||||
|
||||
sanitized_tool_name = sanitize_tool_name(tool_calling.tool_name)
|
||||
tool = tool_name_to_tool_map.get(sanitized_tool_name)
|
||||
if tool:
|
||||
tool_input = tool_calling.arguments if tool_calling.arguments else {}
|
||||
hook_context = ToolCallHookContext(
|
||||
tool_name=tool_calling.tool_name,
|
||||
@@ -285,9 +263,8 @@ def execute_tool_and_check_finality(
|
||||
|
||||
return ToolResult(modified_result, tool.result_as_answer)
|
||||
|
||||
# Handle invalid tool name
|
||||
tool_result = i18n.errors("wrong_tool_name").format(
|
||||
tool=tool_calling.tool_name,
|
||||
tools=", ".join([tool.name.casefold() for tool in tools]),
|
||||
tool=sanitized_tool_name,
|
||||
tools=", ".join(tool_name_to_tool_map.keys()),
|
||||
)
|
||||
return ToolResult(result=tool_result, result_as_answer=False)
|
||||
|
||||
@@ -17,7 +17,7 @@ def test_creating_a_tool_using_annotation():
|
||||
|
||||
# Assert all the right attributes were defined
|
||||
assert my_tool.name == "Name of my tool"
|
||||
assert "Tool Name: Name of my tool" in my_tool.description
|
||||
assert "Tool Name: name_of_my_tool" in my_tool.description
|
||||
assert "Tool Arguments:" in my_tool.description
|
||||
assert '"question"' in my_tool.description
|
||||
assert '"type": "string"' in my_tool.description
|
||||
@@ -32,7 +32,7 @@ def test_creating_a_tool_using_annotation():
|
||||
converted_tool = my_tool.to_structured_tool()
|
||||
assert converted_tool.name == "Name of my tool"
|
||||
|
||||
assert "Tool Name: Name of my tool" in converted_tool.description
|
||||
assert "Tool Name: name_of_my_tool" in converted_tool.description
|
||||
assert "Tool Arguments:" in converted_tool.description
|
||||
assert '"question"' in converted_tool.description
|
||||
assert converted_tool.args_schema.model_json_schema()["properties"] == {
|
||||
@@ -56,7 +56,7 @@ def test_creating_a_tool_using_baseclass():
|
||||
# Assert all the right attributes were defined
|
||||
assert my_tool.name == "Name of my tool"
|
||||
|
||||
assert "Tool Name: Name of my tool" in my_tool.description
|
||||
assert "Tool Name: name_of_my_tool" in my_tool.description
|
||||
assert "Tool Arguments:" in my_tool.description
|
||||
assert '"question"' in my_tool.description
|
||||
assert '"type": "string"' in my_tool.description
|
||||
@@ -69,7 +69,7 @@ def test_creating_a_tool_using_baseclass():
|
||||
converted_tool = my_tool.to_structured_tool()
|
||||
assert converted_tool.name == "Name of my tool"
|
||||
|
||||
assert "Tool Name: Name of my tool" in converted_tool.description
|
||||
assert "Tool Name: name_of_my_tool" in converted_tool.description
|
||||
assert "Tool Arguments:" in converted_tool.description
|
||||
assert '"question"' in converted_tool.description
|
||||
assert converted_tool.args_schema.model_json_schema()["properties"] == {
|
||||
|
||||
@@ -108,7 +108,7 @@ def test_tool_usage_render():
|
||||
rendered = tool_usage._render()
|
||||
|
||||
# Check that the rendered output contains the expected tool information
|
||||
assert "Tool Name: Random Number Generator" in rendered
|
||||
assert "Tool Name: random_number_generator" in rendered
|
||||
assert "Tool Arguments:" in rendered
|
||||
assert (
|
||||
"Tool Description: Generates a random number within a specified range"
|
||||
@@ -488,7 +488,7 @@ def test_tool_selection_error_event_direct():
|
||||
assert event.agent_role == "test_role"
|
||||
assert event.tool_name == "Non Existent Tool"
|
||||
assert event.tool_args == {}
|
||||
assert "Tool Name: Test Tool" in event.tool_class
|
||||
assert "Tool Name: test_tool" in event.tool_class
|
||||
assert "A test tool" in event.tool_class
|
||||
assert "don't exist" in event.error
|
||||
|
||||
@@ -503,7 +503,7 @@ def test_tool_selection_error_event_direct():
|
||||
assert event.agent_role == "test_role"
|
||||
assert event.tool_name == ""
|
||||
assert event.tool_args == {}
|
||||
assert "Test Tool" in event.tool_class
|
||||
assert "test_tool" in event.tool_class
|
||||
assert "forgot the Action name" in event.error
|
||||
|
||||
|
||||
@@ -654,7 +654,7 @@ def test_tool_usage_finished_event_with_result():
|
||||
# Verify event attributes
|
||||
assert event.agent_key == "test_agent_key"
|
||||
assert event.agent_role == "test_agent_role"
|
||||
assert event.tool_name == "Test Tool"
|
||||
assert event.tool_name == "test_tool"
|
||||
assert event.tool_args == {"arg1": "value1"}
|
||||
assert event.tool_class == "TestTool"
|
||||
assert event.run_attempts == 1 # Default value from ToolUsage
|
||||
@@ -734,7 +734,7 @@ def test_tool_usage_finished_event_with_cached_result():
|
||||
# Verify event attributes
|
||||
assert event.agent_key == "test_agent_key"
|
||||
assert event.agent_role == "test_agent_role"
|
||||
assert event.tool_name == "Test Tool"
|
||||
assert event.tool_name == "test_tool"
|
||||
assert event.tool_args == {"arg1": "value1"}
|
||||
assert event.tool_class == "TestTool"
|
||||
assert event.run_attempts == 1 # Default value from ToolUsage
|
||||
|
||||
Reference in New Issue
Block a user