Skip to content

fix(tools): stop rewriting the authored tool description at construction#6508

Open
joaomdmoura wants to merge 2 commits into
mainfrom
joao/epd-179-basetoolmodel_post_init-silently-overwrites-the-authored
Open

fix(tools): stop rewriting the authored tool description at construction#6508
joaomdmoura wants to merge 2 commits into
mainfrom
joao/epd-179-basetoolmodel_post_init-silently-overwrites-the-authored

Conversation

@joaomdmoura

@joaomdmoura joaomdmoura commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes EPD-179: BaseTool.model_post_init silently replaced the authored description with the LLM-facing composite (Tool Name: …\nTool Arguments: {…}\nTool Description: <authored>). Undocumented and surprising: equality assertions on authored text break, and the extra schema tokens are invisible to token-careful authors. The framework itself already treated this as a liability — the native function-calling path strips the prefix back out (agent_utils.py).

Fix

The authored description now survives construction as written; the composite is composed on demand where prompts are built:

  • New shared helper format_description_for_llm(name, args_schema, description) in structured_tool.py, exposed as a formatted_description property on both BaseTool and CrewStructuredTool. Always fresh — it tracks later edits to name/args_schema/description.
  • All prompt paths that relied on the baked-in composite now render through it: render_text_description_and_args (ReAct system prompt), ToolUsage._render (function-calling selector), and tool-usage error messages. The text the LLM sees is byte-identical to before.
  • The helper is idempotent: it strips any pre-existing composite block before composing, so tools deserialized from checkpoints written by older versions — and adapters that still bake the composite into the field (e.g. the crewai-tools MCP adapter) — don't get double-wrapped.
  • BaseTool._generate_description() remains as a documented no-op hook: model_post_init still calls it because subclasses override it (crewai-tools has ~25 self._generate_description() call sites following "mutate description, then refresh" — those calls are now harmless and their authored text is preserved too).

Side effects that are improvements: consumers that passed the raw description to places where the composite was redundant (native function-calling schemas, LangGraph/OpenAI-agents adapters, evaluation metrics listing, the knowledge-tools prompt block that used to render Tool description:\nTool Name: …) now naturally get the clean authored text.

Follow-up candidates (intentionally not in this PR): remove the vestigial description_updated field, and drop the crewai-tools MCP adapter's own _generate_description override so MCP tools' description field also stays authored.

Testing

  • The ticket's clean-room repro now prints NOT REPRODUCED: description preserved as authored, with the identical composite available at formatted_description.
  • New TestAuthoredDescriptionPreserved regression class: authored text preserved, composite content/ordering, on-demand recomposition after edits, no double-wrap for pre-baked composites, and prompt rendering unchanged for both BaseTool and CrewStructuredTool.
  • Updated the two tests that encoded the old rewrite behavior; test_tool_usage_render (asserts the composite in the rendered prompt) passes untouched.
  • Green: tests/tools/ (138), tests/agents/test_agent.py + test_lite_agent.py (126), tool-related test_crew.py (11), experimental tool-metrics tests (8), ruff, and mypy.

🤖 Generated with Claude Code


Note

Medium Risk
Touches core agent prompt/tool rendering and OpenAI schema description handling; behavior is intended to be prompt-identical but any missed call site still reading raw description for LLM prompts could regress.

Overview
Tool description is no longer mutated at construction. BaseTool._generate_description() is a documented no-op; authored text stays on description, and the LLM-facing block (Tool Name / Tool Arguments / Tool Description) is built on demand via format_description_for_llm and the new formatted_description property on BaseTool and CrewStructuredTool.

Prompt and schema paths are wired to the split. ReAct rendering (render_text_description_and_args), function-calling tool selection (ToolUsage._render), and tool error messages use formatted_description so prompt text to the model should match the old composite. Native OpenAI schemas use strip_composite_description_prefix so checkpoints/adapters that still store a baked composite do not leak into function.description.

Backward compatibility for legacy composite strings. strip_composite_description_prefix only strips descriptions that match the full three-line composite shape (so prose mentioning Tool Description: is not truncated). format_description_for_llm is idempotent and avoids double-wrapping pre-baked descriptions.

Tests were updated for preserved authored descriptions and a new TestAuthoredDescriptionPreserved regression suite was added.

Reviewed by Cursor Bugbot for commit afce744. Bugbot is set up for automated code reviews on this repo. Configure here.

BaseTool.model_post_init silently replaced the public description field
with the LLM-facing composite ("Tool Name: ...\nTool Arguments: ...\n
Tool Description: <authored>"), breaking equality assertions on authored
text and hiding the extra prompt tokens from token-careful authors.

The authored description now survives construction as written. The
composite is composed on demand via a new formatted_description property
on BaseTool and CrewStructuredTool (shared format_description_for_llm
helper), and every prompt path that relied on the baked-in composite —
render_text_description_and_args, ToolUsage._render, and tool-usage
error messages — now renders through it, so the text the LLM sees is
unchanged.

The helper strips any pre-existing composite block before composing, so
tools deserialized from old checkpoints and adapters that still bake the
composite into the field (e.g. the crewai-tools MCP adapter) don't get
double-wrapped. BaseTool._generate_description remains as a no-op hook
because subclasses override it and model_post_init still calls it.

Fixes EPD-179.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@linear

linear Bot commented Jul 10, 2026

Copy link
Copy Markdown

EPD-179

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Tool Description Formatting

Layer / File(s) Summary
Description formatting contract
lib/crewai/src/crewai/tools/base_tool.py, lib/crewai/src/crewai/tools/structured_tool.py
Authored descriptions remain unchanged while formatted_description builds the LLM-facing name, argument schema, and description composite.
Formatted description consumers
lib/crewai/src/crewai/tools/tool_usage.py, lib/crewai/src/crewai/utilities/agent_utils.py
Tool errors, listings, prompt rendering, and OpenAI schema conversion now use formatted or authored descriptions as appropriate.
Description preservation validation
lib/crewai/tests/tools/test_base_tool.py
Tests cover authored-text preservation, dynamic updates, idempotent formatting, and rendered prompt output.

Sequence Diagram(s)

sequenceDiagram
  participant ToolRenderer
  participant BaseTool
  participant DescriptionFormatter
  ToolRenderer->>BaseTool: request formatted_description
  BaseTool->>DescriptionFormatter: pass name, args_schema, description
  DescriptionFormatter-->>BaseTool: return composed LLM description
  BaseTool-->>ToolRenderer: render formatted description
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.83% 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
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: preserving the authored tool description at construction.
Description check ✅ Passed The description accurately explains the change, including on-demand formatting, prompt paths, and backward compatibility.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch joao/epd-179-basetoolmodel_post_init-silently-overwrites-the-authored

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.

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4362a45. Configure here.

Comment thread lib/crewai/src/crewai/tools/structured_tool.py Outdated

@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

🤖 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/tools/structured_tool.py`:
- Around line 139-140: Update the idempotency logic in the structured tool
description rendering to strip the prefix only when the description starts with
the complete composite header sequence: “Tool Name: … Tool Arguments: … Tool
Description:”. Replace the naive substring check in the affected rendering
method with an anchored shape check that preserves authored prose containing
“Tool Description:” while still removing legacy pre-composed or double-wrapped
headers.
🪄 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: c83021b8-bf32-4d77-9fd2-2bdd220fdb82

📥 Commits

Reviewing files that changed from the base of the PR and between 7baf8f9 and 4362a45.

📒 Files selected for processing (5)
  • lib/crewai/src/crewai/tools/base_tool.py
  • lib/crewai/src/crewai/tools/structured_tool.py
  • lib/crewai/src/crewai/tools/tool_usage.py
  • lib/crewai/src/crewai/utilities/agent_utils.py
  • lib/crewai/tests/tools/test_base_tool.py

Comment thread lib/crewai/src/crewai/tools/structured_tool.py Outdated
- Anchor the pre-baked-composite check to the actual three-line block
  shape instead of a naive substring match, so authored prose that
  merely mentions "Tool Description:" is never truncated (CodeRabbit /
  Bugbot review finding). Shared as
  strip_composite_description_prefix() and reused by the function-
  calling schema builder, which had the same naive split.
- Make render_text_description_and_args tolerate duck-typed tools
  without a real formatted_description string (fixes CI: step-executor
  tests pass Mock tools whose auto-created attribute is not a str).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions github-actions Bot added size/L and removed size/M labels Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant