mirror of
https://github.com/crewAIInc/crewAI.git
synced 2025-12-15 11:58:31 +00:00
fix: format tool arguments as valid JSON in system prompts
Fixes #4064 The tool arguments in system prompts were being displayed using Python's string representation (single quotes, None) instead of proper JSON (double quotes, null). This could confuse weaker LLMs when they try to make tool calls. Changes: - Use json.dumps() to format args_schema in _generate_description() - Update existing tests to expect JSON format - Add new test to verify JSON validity of tool arguments Co-Authored-By: João <joao@crewai.com>
This commit is contained in:
@@ -4,6 +4,7 @@ from abc import ABC, abstractmethod
|
||||
import asyncio
|
||||
from collections.abc import Awaitable, Callable
|
||||
from inspect import signature
|
||||
import json
|
||||
from typing import (
|
||||
Any,
|
||||
Generic,
|
||||
@@ -282,7 +283,8 @@ class BaseTool(BaseModel, ABC):
|
||||
for name, field in self.args_schema.model_fields.items()
|
||||
}
|
||||
|
||||
self.description = f"Tool Name: {self.name}\nTool Arguments: {args_schema}\nTool Description: {self.description}"
|
||||
args_schema_json = json.dumps(args_schema)
|
||||
self.description = f"Tool Name: {self.name}\nTool Arguments: {args_schema_json}\nTool Description: {self.description}"
|
||||
|
||||
@staticmethod
|
||||
def _get_arg_annotations(annotation: type[Any] | None) -> str:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Callable
|
||||
from unittest.mock import patch
|
||||
|
||||
@@ -19,7 +20,7 @@ def test_creating_a_tool_using_annotation():
|
||||
assert my_tool.name == "Name of my tool"
|
||||
assert (
|
||||
my_tool.description
|
||||
== "Tool Name: Name of my tool\nTool Arguments: {'question': {'description': None, 'type': 'str'}}\nTool Description: Clear description for what this tool is useful for, your agent will need this information to use it."
|
||||
== 'Tool Name: Name of my tool\nTool Arguments: {"question": {"description": null, "type": "str"}}\nTool Description: Clear description for what this tool is useful for, your agent will need this information to use it.'
|
||||
)
|
||||
assert my_tool.args_schema.model_json_schema()["properties"] == {
|
||||
"question": {"title": "Question", "type": "string"}
|
||||
@@ -33,7 +34,7 @@ def test_creating_a_tool_using_annotation():
|
||||
|
||||
assert (
|
||||
converted_tool.description
|
||||
== "Tool Name: Name of my tool\nTool Arguments: {'question': {'description': None, 'type': 'str'}}\nTool Description: Clear description for what this tool is useful for, your agent will need this information to use it."
|
||||
== 'Tool Name: Name of my tool\nTool Arguments: {"question": {"description": null, "type": "str"}}\nTool Description: Clear description for what this tool is useful for, your agent will need this information to use it.'
|
||||
)
|
||||
assert converted_tool.args_schema.model_json_schema()["properties"] == {
|
||||
"question": {"title": "Question", "type": "string"}
|
||||
@@ -58,7 +59,7 @@ def test_creating_a_tool_using_baseclass():
|
||||
|
||||
assert (
|
||||
my_tool.description
|
||||
== "Tool Name: Name of my tool\nTool Arguments: {'question': {'description': None, 'type': 'str'}}\nTool Description: Clear description for what this tool is useful for, your agent will need this information to use it."
|
||||
== 'Tool Name: Name of my tool\nTool Arguments: {"question": {"description": null, "type": "str"}}\nTool Description: Clear description for what this tool is useful for, your agent will need this information to use it.'
|
||||
)
|
||||
assert my_tool.args_schema.model_json_schema()["properties"] == {
|
||||
"question": {"title": "Question", "type": "string"}
|
||||
@@ -70,7 +71,7 @@ def test_creating_a_tool_using_baseclass():
|
||||
|
||||
assert (
|
||||
converted_tool.description
|
||||
== "Tool Name: Name of my tool\nTool Arguments: {'question': {'description': None, 'type': 'str'}}\nTool Description: Clear description for what this tool is useful for, your agent will need this information to use it."
|
||||
== 'Tool Name: Name of my tool\nTool Arguments: {"question": {"description": null, "type": "str"}}\nTool Description: Clear description for what this tool is useful for, your agent will need this information to use it.'
|
||||
)
|
||||
assert converted_tool.args_schema.model_json_schema()["properties"] == {
|
||||
"question": {"title": "Question", "type": "string"}
|
||||
@@ -230,3 +231,55 @@ def test_max_usage_count_is_respected():
|
||||
crew.kickoff()
|
||||
assert tool.max_usage_count == 5
|
||||
assert tool.current_usage_count == 5
|
||||
|
||||
|
||||
def test_tool_description_uses_valid_json_for_arguments():
|
||||
"""Test that tool arguments in description are formatted as valid JSON.
|
||||
|
||||
This test verifies the fix for GitHub issue #4064 where tool arguments
|
||||
were displayed using Python's string representation (single quotes, None)
|
||||
instead of proper JSON (double quotes, null).
|
||||
"""
|
||||
|
||||
class FileWriterTool(BaseTool):
|
||||
name: str = "File Writer Tool"
|
||||
description: str = "A tool to write content to a specified file."
|
||||
|
||||
def _run(
|
||||
self,
|
||||
filename: str,
|
||||
directory: str | None,
|
||||
overwrite: bool | str,
|
||||
content: str,
|
||||
) -> str:
|
||||
return "ok"
|
||||
|
||||
tool = FileWriterTool()
|
||||
desc = tool.description
|
||||
|
||||
# Ensure the basic sections are present
|
||||
assert desc.startswith("Tool Name: File Writer Tool")
|
||||
assert "Tool Arguments:" in desc
|
||||
assert "Tool Description: A tool to write content to a specified file." in desc
|
||||
|
||||
# Extract the JSON substring between "Tool Arguments: " and "\nTool Description:"
|
||||
start = desc.index("Tool Arguments: ") + len("Tool Arguments: ")
|
||||
end = desc.index("\nTool Description:")
|
||||
args_json = desc[start:end]
|
||||
|
||||
# This must be valid JSON (previously would fail due to single quotes and None)
|
||||
parsed = json.loads(args_json)
|
||||
|
||||
# Verify the expected structure
|
||||
assert "filename" in parsed
|
||||
assert parsed["filename"]["type"] == "str"
|
||||
assert parsed["filename"]["description"] is None
|
||||
|
||||
assert "directory" in parsed
|
||||
assert parsed["directory"]["description"] is None
|
||||
# Type should contain Union or similar for str | None
|
||||
assert "str" in parsed["directory"]["type"]
|
||||
|
||||
assert "overwrite" in parsed
|
||||
assert "content" in parsed
|
||||
assert parsed["content"]["type"] == "str"
|
||||
|
||||
Reference in New Issue
Block a user