Skip to content

fix: serialize nested dict tool output to prevent TypeError (#6267)#6332

Open
C1-BA-B1-F3 wants to merge 2 commits into
crewAIInc:mainfrom
C1-BA-B1-F3:fix/nested-dict-tool-output
Open

fix: serialize nested dict tool output to prevent TypeError (#6267)#6332
C1-BA-B1-F3 wants to merge 2 commits into
crewAIInc:mainfrom
C1-BA-B1-F3:fix/nested-dict-tool-output

Conversation

@C1-BA-B1-F3

@C1-BA-B1-F3 C1-BA-B1-F3 commented Jun 25, 2026

Copy link
Copy Markdown

Summary

When a custom tool returns a nested dictionary, the agent's execution loop crashes with a TypeError (e.g., unhashable type: 'dict') because str() produces a Python repr instead of valid JSON.

This fix adds json.dumps() serialization for dict and list outputs in both:

  • _format_tool_output_for_agent() in structured_tool.py
  • _format_result() in tool_usage.py

Also fixes #6072 by showing task output before prompting for human feedback when verbose=False.

Changes

  • lib/crewai/src/crewai/tools/structured_tool.py: JSON serialize dict/list in _format_tool_output_for_agent
  • lib/crewai/src/crewai/tools/tool_usage.py: JSON serialize dict/list in _format_result
  • lib/crewai/src/crewai/agents/crew_agent_executor.py: Show output before human feedback prompt
  • lib/crewai/tests/tools/test_structured_tool.py: Add tests for dict/list/string tool outputs

Test plan

  • Added unit tests for dict, list, and string tool outputs
  • Verify existing tests pass

Closes #6267
Closes #6072

Summary by CodeRabbit

  • Bug Fixes
    • Improved formatting of tool outputs for dictionaries and lists by emitting JSON when possible, with a safe fallback to plain text.
    • When requesting human input with verbose output disabled, ensured execution logs are shown before human feedback handling begins.
  • Tests
    • Added and expanded coverage for structured tool result formatting, including nested dict/list cases and edge conditions such as unstringifiable keys.

…c#6267)

When a custom tool returns a nested dictionary, the agent's execution
loop crashed with TypeError because str() produces a Python repr instead
of valid JSON. Now dict/list outputs are serialized via json.dumps() in
both _format_tool_output_for_agent and _format_result.

Also fixes crewAIInc#6072 by showing task output before prompting for human
feedback when verbose=False.

Closes crewAIInc#6267
Closes crewAIInc#6072

@corridor-security corridor-security Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary: This PR changes tool output formatting to JSON-serialize dict/list results and displays task output before human feedback when verbose logging is disabled; no exploitable security vulnerabilities were identified.

Risk: Low risk. The changes do not introduce new authentication, authorization, external request, file path, SQL, or command execution surfaces, and the added behavior only affects internal output formatting/display.

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR renders human-input output before the feedback prompt in sync and async paths when verbose output is disabled. It also JSON-serializes dict and list tool results with string fallback, and expands structured-tool tests for nested, fallback, and key-conversion cases.

Changes

Output rendering

Layer / File(s) Summary
Show logs before feedback
lib/crewai/src/crewai/agents/crew_agent_executor.py
invoke and ainvoke call _show_logs(formatted_answer) before _handle_human_feedback(...) or _ahandle_human_feedback(...) when ask_for_human_input is enabled and verbose output is disabled.

Tool result JSON formatting

Layer / File(s) Summary
Serialize dict/list outputs
lib/crewai/src/crewai/tools/structured_tool.py, lib/crewai/src/crewai/tools/tool_usage.py, lib/crewai/tests/tools/test_structured_tool.py
CrewStructuredTool.format_output_for_agent and ToolUsage._format_result now JSON-serialize dict and list outputs with fallback to str(...) on serialization errors; TestDictToolOutput covers nested dicts, lists, strings, integer keys, and unstringifiable keys.

Suggested reviewers

  • greysonlalonde
  • lucasgomide
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.37% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main bug fix: serializing nested tool output to avoid TypeError.
Linked Issues check ✅ Passed The PR implements both linked fixes: JSON serialization for dict/list tool outputs and showing results before human feedback when not verbose.
Out of Scope Changes check ✅ Passed The added tests and async human-feedback path stay within the scope of the two linked bug fixes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
lib/crewai/tests/tools/test_structured_tool.py (1)

527-582: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a fallback serialization test case.

Please add one test where json.dumps(..., default=str) still fails (e.g., unsupported dict key type) and assert fallback to str(...). That will cover the new exception branch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/tests/tools/test_structured_tool.py` around lines 527 - 582, The
new structured tool output tests cover JSON serialization for dicts, lists, and
strings, but they do not exercise the fallback path when json.dumps(...,
default=str) still fails. Add a test in test_structured_tool.py around the
existing CrewStructuredTool.format_output_for_agent cases that returns a dict
with an unsupported key type so serialization raises, then assert the fallback
to str(...) is used for the result. Use the existing test helpers and
CrewStructuredTool.from_function/invoke/format_output_for_agent flow to locate
the right branch.
lib/crewai/src/crewai/tools/tool_usage.py (1)

714-718: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add direct tests for _format_result dict/list serialization path.

This branch changed behavior but isn’t directly covered by the new tests (which only exercise CrewStructuredTool.format_output_for_agent). Please add a focused unit test for _format_result with dict/list inputs (including fallback cases) to lock this path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/src/crewai/tools/tool_usage.py` around lines 714 - 718, Add
focused unit tests directly for the `_format_result` branch in `tool_usage.py`
that handles `dict` and `list` inputs, rather than only testing
`CrewStructuredTool.format_output_for_agent`. Cover both the successful
`json.dumps(..., ensure_ascii=False, default=str)` serialization path and the
fallback `str(result)` path when serialization raises `TypeError` or
`ValueError`, using `_format_result` as the target symbol so the behavior is
locked down independently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/crewai/src/crewai/agents/crew_agent_executor.py`:
- Around line 244-247: The async feedback flow in CrewAgentExecutor still skips
the pre-feedback result display when verbose is false, unlike the sync invoke
path. Mirror the same visibility check and call to _show_logs(formatted_answer)
inside ainvoke before any feedback prompt, using the existing verbose condition
on self.agent.verbose and self.crew.verbose so both invoke and ainvoke behave
consistently.

---

Nitpick comments:
In `@lib/crewai/src/crewai/tools/tool_usage.py`:
- Around line 714-718: Add focused unit tests directly for the `_format_result`
branch in `tool_usage.py` that handles `dict` and `list` inputs, rather than
only testing `CrewStructuredTool.format_output_for_agent`. Cover both the
successful `json.dumps(..., ensure_ascii=False, default=str)` serialization path
and the fallback `str(result)` path when serialization raises `TypeError` or
`ValueError`, using `_format_result` as the target symbol so the behavior is
locked down independently.

In `@lib/crewai/tests/tools/test_structured_tool.py`:
- Around line 527-582: The new structured tool output tests cover JSON
serialization for dicts, lists, and strings, but they do not exercise the
fallback path when json.dumps(..., default=str) still fails. Add a test in
test_structured_tool.py around the existing
CrewStructuredTool.format_output_for_agent cases that returns a dict with an
unsupported key type so serialization raises, then assert the fallback to
str(...) is used for the result. Use the existing test helpers and
CrewStructuredTool.from_function/invoke/format_output_for_agent flow to locate
the right branch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6556fb63-a45c-4ae8-b336-02589106aeab

📥 Commits

Reviewing files that changed from the base of the PR and between 01fc389 and 77be303.

📒 Files selected for processing (4)
  • lib/crewai/src/crewai/agents/crew_agent_executor.py
  • lib/crewai/src/crewai/tools/structured_tool.py
  • lib/crewai/src/crewai/tools/tool_usage.py
  • lib/crewai/tests/tools/test_structured_tool.py

Comment thread lib/crewai/src/crewai/agents/crew_agent_executor.py
- Show task output before human feedback in async path when verbose=False
- Add fallback serialization test for unserializable dict keys
- Add tests for int-key dicts and nested list serialization

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
lib/crewai/tests/tools/test_structured_tool.py (1)

602-604: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the fallback contract directly.

assert "value" in formatted still passes for several non-fallback outputs, so it doesn't actually lock in the str(raw_result) branch this test is named after. Comparing against str(result) or asserting that json.loads(formatted) fails would make this regression-proof.

Suggested test tightening
         result = tool.invoke(input={"query": "test"})
         formatted = tool.format_output_for_agent(result)
         assert isinstance(formatted, str)
-        assert "value" in formatted
+        assert formatted == str(result)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/tests/tools/test_structured_tool.py` around lines 602 - 604, The
structured tool fallback test is too permissive and does not verify the actual
fallback path in format_output_for_agent. Tighten the assertion in
test_structured_tool by comparing the formatted result directly to str(result),
or by explicitly asserting that JSON parsing fails for this output, so the test
uniquely covers the raw string fallback branch instead of any output containing
"value".
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/crewai/tests/tools/test_structured_tool.py`:
- Around line 584-640: The new structured-tool tests cover output serialization,
but ToolUsage._format_result is a separate formatting path that still needs
coverage. Add a companion unit test in test_tool_usage.py that exercises
ToolUsage._format_result with a tool result that is a dict or list and verifies
the returned value is valid JSON. Use the existing ToolUsage test setup/helpers
in test_tool_usage.py to locate the right path and ensure the assertion checks
the serialized output, not just argument handling.

---

Nitpick comments:
In `@lib/crewai/tests/tools/test_structured_tool.py`:
- Around line 602-604: The structured tool fallback test is too permissive and
does not verify the actual fallback path in format_output_for_agent. Tighten the
assertion in test_structured_tool by comparing the formatted result directly to
str(result), or by explicitly asserting that JSON parsing fails for this output,
so the test uniquely covers the raw string fallback branch instead of any output
containing "value".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0caf6bd9-0242-4fcf-ba68-761e682f72d2

📥 Commits

Reviewing files that changed from the base of the PR and between 77be303 and 124c1bf.

📒 Files selected for processing (2)
  • lib/crewai/src/crewai/agents/crew_agent_executor.py
  • lib/crewai/tests/tools/test_structured_tool.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/crewai/src/crewai/agents/crew_agent_executor.py

Comment on lines +584 to +640
def test_dict_with_unserializable_key_falls_back_to_str(self):
"""Dict with non-string keys that json.dumps(default=str) can't handle falls back to str()."""
from crewai.tools.structured_tool import CrewStructuredTool

class UnhashableKey:
"""A key type that json.dumps cannot serialize even with default=str."""
def __str__(self):
raise RuntimeError("cannot stringify")

def bad_key_tool(query: str) -> dict:
return {UnhashableKey(): "value"}

tool = CrewStructuredTool.from_function(
func=bad_key_tool,
name="bad_key_tool",
description="Returns a dict with unhashable keys.",
)
result = tool.invoke(input={"query": "test"})
formatted = tool.format_output_for_agent(result)
assert isinstance(formatted, str)
assert "value" in formatted

def test_dict_with_non_string_keys_serialized(self):
"""Dict with int keys should be JSON-serialized with default=str."""
import json
from crewai.tools.structured_tool import CrewStructuredTool

def int_key_tool(query: str) -> dict:
return {1: "one", 2: "two"}

tool = CrewStructuredTool.from_function(
func=int_key_tool,
name="int_key_tool",
description="Returns a dict with int keys.",
)
result = tool.invoke(input={"query": "test"})
formatted = tool.format_output_for_agent(result)
parsed = json.loads(formatted)
assert parsed["1"] == "one"

def test_nested_list_output_serialized(self):
"""Deeply nested list should be JSON-serialized."""
import json
from crewai.tools.structured_tool import CrewStructuredTool

def nested_list_tool(query: str) -> list:
return [[{"a": 1}], [{"b": 2}]]

tool = CrewStructuredTool.from_function(
func=nested_list_tool,
name="nested_list_tool",
description="Returns nested lists.",
)
result = tool.invoke(input={"query": "test"})
formatted = tool.format_output_for_agent(result)
parsed = json.loads(formatted)
assert parsed[0][0]["a"] == 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "ToolUsage serialization implementation:"
sed -n '709,720p' lib/crewai/src/crewai/tools/tool_usage.py

echo
echo "Tests that appear to cover ToolUsage/_format_result:"
rg -n -C2 --type=py '\b_format_result\s*\(|\bToolUsage\b' lib/crewai/tests

echo
echo "Structured tool serialization tests in this PR:"
rg -n -C2 --type=py '\bformat_output_for_agent\s*\(' lib/crewai/tests/tools/test_structured_tool.py

Repository: crewAIInc/crewAI

Length of output: 14295


Add companion unit test for ToolUsage._format_result

The new tests in test_structured_tool.py confirm CrewStructuredTool.format_output_for_agent handles serialization correctly. However, ToolUsage._format_result in lib/crewai/src/crewai/tools/tool_usage.py is a distinct code path responsible for formatting tool results during agent execution. The existing test_tool_usage.py focuses on input validation and argument parsing, lacking a test that asserts the output is JSON-serialized when a tool returns a dict or list.

Please add a test in lib/crewai/tests/tools/test_tool_usage.py that calls ToolUsage._format_result with a dict or list and asserts the return value is valid JSON.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/tests/tools/test_structured_tool.py` around lines 584 - 640, The
new structured-tool tests cover output serialization, but
ToolUsage._format_result is a separate formatting path that still needs
coverage. Add a companion unit test in test_tool_usage.py that exercises
ToolUsage._format_result with a tool result that is a dict or list and verifies
the returned value is valid JSON. Use the existing ToolUsage test setup/helpers
in test_tool_usage.py to locate the right path and ensure the assertion checks
the serialized output, not just argument handling.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant