Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions lib/crewai/src/crewai/tools/structured_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ def _format_tool_output_for_agent(tool: Any, raw_result: Any) -> str:

result_schema = getattr(tool, "result_schema", None)
if not (isinstance(result_schema, type) and issubclass(result_schema, BaseModel)):
if isinstance(raw_result, (dict, list)):
try:
return json.dumps(raw_result, ensure_ascii=False)
except Exception:
return str(raw_result)
return str(raw_result)

try:
Expand All @@ -70,6 +75,23 @@ def _format_tool_output_for_agent(tool: Any, raw_result: Any) -> str:
validated = result_schema.model_validate(validation_input)
return validated.model_dump_json()
except Exception as exc:
if isinstance(raw_result, (dict, list)):
try:
serialized = json.dumps(raw_result, ensure_ascii=False)
warnings.warn(
(
f"Failed to validate or serialize output from tool "
f"'{getattr(tool, 'name', '<unknown>')}' using result_schema "
f"'{result_schema.__name__}': {exc.__class__.__name__}. "
"Falling back to JSON-serialized raw_result."
),
RuntimeWarning,
stacklevel=2,
)
return serialized
except Exception:
pass

warnings.warn(
(
f"Failed to validate or serialize output from tool "
Expand Down
28 changes: 28 additions & 0 deletions lib/crewai/tests/tools/test_structured_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -519,3 +519,31 @@ def failing_function(should_fail: bool) -> str:
tool.invoke({"should_fail": True})

assert call_count == 1


def test_format_output_for_agent_with_dict_and_list():
"""Test that format_output_for_agent automatically serializes dict and list outputs to JSON."""
def dict_tool() -> dict:
return {"nested": {"key": "value"}}

tool = CrewStructuredTool.from_function(
func=dict_tool,
name="dict_tool",
description="A tool returning a dict",
)

result = tool.format_output_for_agent({"nested": {"key": "value"}})
assert json.loads(result) == {"nested": {"key": "value"}}

def list_tool() -> list:
return [{"nested": {"key": "value"}}]

tool_list = CrewStructuredTool.from_function(
func=list_tool,
name="list_tool",
description="A tool returning a list",
)

result_list = tool_list.format_output_for_agent([{"nested": {"key": "value"}}])
assert json.loads(result_list) == [{"nested": {"key": "value"}}]