mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-05-01 23:32:39 +00:00
Fix prompt formatting biases affecting JSON output
Co-Authored-By: Joe Moura <joao@crewai.com>
This commit is contained in:
@@ -135,6 +135,7 @@ class BaseTool(BaseModel, ABC):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _generate_description(self):
|
def _generate_description(self):
|
||||||
|
import json
|
||||||
args_schema = {
|
args_schema = {
|
||||||
name: {
|
name: {
|
||||||
"description": field.description,
|
"description": field.description,
|
||||||
@@ -143,7 +144,7 @@ class BaseTool(BaseModel, ABC):
|
|||||||
for name, field in self.args_schema.model_fields.items()
|
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}"
|
self.description = f"Tool Name: {self.name}\nTool Arguments: {json.dumps(args_schema)}\nTool Description: {self.description}"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_arg_annotations(annotation: type[Any] | None) -> str:
|
def _get_arg_annotations(annotation: type[Any] | None) -> str:
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"task": "\nCurrent Task: {input}\n\nBegin! This is VERY important to you, use the tools available and give your best Final Answer, your job depends on it!\n\nThought:",
|
"task": "\nCurrent Task: {input}\n\nBegin! This is VERY important to you, use the tools available and give your best Final Answer, your job depends on it!\n\nThought:",
|
||||||
"memory": "\n\n# Useful context: \n{memory}",
|
"memory": "\n\n# Useful context: \n{memory}",
|
||||||
"role_playing": "You are {role}. {backstory}\nYour personal goal is: {goal}",
|
"role_playing": "You are {role}. {backstory}\nYour personal goal is: {goal}",
|
||||||
"tools": "\nYou ONLY have access to the following tools, and should NEVER make up tools that are not listed here:\n\n{tools}\n\nUse the following format:\n\nThought: you should always think about what to do\nAction: the action to take, only one name of [{tool_names}], just the name, exactly as it's written.\nAction Input: the input to the action, just a simple python dictionary, enclosed in curly braces, using \" to wrap keys and values.\nObservation: the result of the action\n\nOnce all necessary information is gathered:\n\nThought: I now know the final answer\nFinal Answer: the final answer to the original input question\n",
|
"tools": "\nYou ONLY have access to the following tools, and should NEVER make up tools that are not listed here:\n\n{tools}\n\nUse the following format:\n\nThought: you should always think about what to do\nAction: the action to take, only one name of [{tool_names}], just the name, exactly as it's written.\nAction Input: the input to the action, just a simple python dictionary, enclosed in curly braces, using double quotes (\") to wrap keys and values.\nObservation: the result of the action\n\nOnce all necessary information is gathered:\n\nThought: I now know the final answer\nFinal Answer: the final answer to the original input question\n",
|
||||||
"no_tools": "\nTo give my best complete final answer to the task use the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!",
|
"no_tools": "\nTo give my best complete final answer to the task use the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!",
|
||||||
"format": "I MUST either use a tool (use one at time) OR give my best final answer not both at the same time. To Use the following format:\n\nThought: you should always think about what to do\nAction: the action to take, should be one of [{tool_names}]\nAction Input: the input to the action, dictionary enclosed in curly braces\nObservation: the result of the action\n... (this Thought/Action/Action Input/Result can repeat N times)\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described\n\n",
|
"format": "I MUST either use a tool (use one at time) OR give my best final answer not both at the same time. To Use the following format:\n\nThought: you should always think about what to do\nAction: the action to take, should be one of [{tool_names}]\nAction Input: the input to the action, dictionary enclosed in curly braces\nObservation: the result of the action\n... (this Thought/Action/Action Input/Result can repeat N times)\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described\n\n",
|
||||||
"final_answer_format": "If you don't need to use any more tools, you must give your best complete final answer, make sure it satisfies the expected criteria, use the EXACT format below:\n\nThought: I now can give a great answer\nFinal Answer: my best complete final answer to the task.\n\n",
|
"final_answer_format": "If you don't need to use any more tools, you must give your best complete final answer, make sure it satisfies the expected criteria, use the EXACT format below:\n\nThought: I now can give a great answer\nFinal Answer: my best complete final answer to the task.\n\n",
|
||||||
|
|||||||
78
tests/tools/test_json_formatting.py
Normal file
78
tests/tools/test_json_formatting.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import json
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from crewai.tools import BaseTool
|
||||||
|
from crewai.tools.tool_usage import ToolUsage
|
||||||
|
|
||||||
|
|
||||||
|
class TestJsonInput(BaseModel):
|
||||||
|
test_param: str = Field(
|
||||||
|
..., description="A test parameter"
|
||||||
|
)
|
||||||
|
another_param: int = Field(
|
||||||
|
..., description="Another test parameter"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestJsonTool(BaseTool):
|
||||||
|
name: str = "Test JSON Tool"
|
||||||
|
description: str = "A tool for testing JSON formatting"
|
||||||
|
args_schema: type[BaseModel] = TestJsonInput
|
||||||
|
|
||||||
|
def _run(self, test_param: str, another_param: int) -> str:
|
||||||
|
return f"Received {test_param} and {another_param}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_tool_description_json_formatting():
|
||||||
|
"""Test that the tool description uses proper JSON formatting with double quotes."""
|
||||||
|
tool = TestJsonTool()
|
||||||
|
|
||||||
|
assert "Tool Arguments:" in tool.description
|
||||||
|
|
||||||
|
description_parts = tool.description.split("Tool Arguments: ")
|
||||||
|
json_str = description_parts[1].split("\nTool Description:")[0]
|
||||||
|
|
||||||
|
parsed_json = json.loads(json_str)
|
||||||
|
|
||||||
|
assert "test_param" in parsed_json
|
||||||
|
assert "another_param" in parsed_json
|
||||||
|
|
||||||
|
assert '"test_param"' in json_str
|
||||||
|
assert '"another_param"' in json_str
|
||||||
|
assert "'" not in json_str # No single quotes should be present
|
||||||
|
|
||||||
|
|
||||||
|
def test_tool_usage_json_formatting():
|
||||||
|
"""Test that the tool usage renders with proper JSON formatting."""
|
||||||
|
tool = TestJsonTool()
|
||||||
|
|
||||||
|
tool_usage = ToolUsage(
|
||||||
|
tools_handler=MagicMock(),
|
||||||
|
tools=[tool],
|
||||||
|
original_tools=[tool],
|
||||||
|
tools_description="Tool for testing JSON formatting",
|
||||||
|
tools_names="test_json_tool",
|
||||||
|
task=MagicMock(),
|
||||||
|
function_calling_llm=MagicMock(),
|
||||||
|
agent=MagicMock(),
|
||||||
|
action=MagicMock(),
|
||||||
|
)
|
||||||
|
|
||||||
|
rendered = tool_usage._render()
|
||||||
|
|
||||||
|
rendered_parts = rendered.split("Tool Arguments: ")
|
||||||
|
if len(rendered_parts) > 1:
|
||||||
|
json_str = rendered_parts[1].split("\nTool Description:")[0]
|
||||||
|
|
||||||
|
try:
|
||||||
|
parsed_json = json.loads(json_str)
|
||||||
|
assert True # If we get here, JSON parsing succeeded
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
assert False, "The rendered tool arguments are not valid JSON"
|
||||||
|
|
||||||
|
assert '"test_param"' in json_str
|
||||||
|
assert '"another_param"' in json_str
|
||||||
|
assert "'" not in json_str # No single quotes should be present
|
||||||
@@ -102,15 +102,15 @@ def test_tool_usage_render():
|
|||||||
|
|
||||||
rendered = tool_usage._render()
|
rendered = tool_usage._render()
|
||||||
|
|
||||||
# Updated checks to match the actual output
|
# Updated checks to match the actual output with JSON formatting
|
||||||
assert "Tool Name: Random Number Generator" in rendered
|
assert "Tool Name: Random Number Generator" in rendered
|
||||||
assert "Tool Arguments:" in rendered
|
assert "Tool Arguments:" in rendered
|
||||||
assert (
|
assert (
|
||||||
"'min_value': {'description': 'The minimum value of the range (inclusive)', 'type': 'int'}"
|
'"min_value": {"description": "The minimum value of the range (inclusive)", "type": "int"}'
|
||||||
in rendered
|
in rendered
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
"'max_value': {'description': 'The maximum value of the range (inclusive)', 'type': 'int'}"
|
'"max_value": {"description": "The maximum value of the range (inclusive)", "type": "int"}'
|
||||||
in rendered
|
in rendered
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
@@ -118,6 +118,6 @@ def test_tool_usage_render():
|
|||||||
in rendered
|
in rendered
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
"Tool Name: Random Number Generator\nTool Arguments: {'min_value': {'description': 'The minimum value of the range (inclusive)', 'type': 'int'}, 'max_value': {'description': 'The maximum value of the range (inclusive)', 'type': 'int'}}\nTool Description: Generates a random number within a specified range"
|
'Tool Name: Random Number Generator\nTool Arguments: {"min_value": {"description": "The minimum value of the range (inclusive)", "type": "int"}, "max_value": {"description": "The maximum value of the range (inclusive)", "type": "int"}}\nTool Description: Generates a random number within a specified range'
|
||||||
in rendered
|
in rendered
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user