Skip to content

fix(agents): keep structured content out of planning step plan text - #2730

Open
BlueX888 wants to merge 1 commit into
huggingface:mainfrom
BlueX888:fix/planning-step-structured-content
Open

BlueX888 wants to merge 1 commit into
huggingface:mainfrom
BlueX888:fix/planning-step-structured-content

Conversation

@BlueX888

@BlueX888 BlueX888 commented Sep 2, 2026

Copy link
Copy Markdown

What this PR does

When a provider returns structured content (a list of content blocks) for the <end_plan> call, _generate_planning_step embedded that list directly into the plan f-string. The plan field is a string, so the result was the list's Python repr rather than readable text — e.g.:

[{'type': 'text', 'text': 'Step 1: do the thing.'}]

instead of:

Step 1: do the thing.

The plan text is what gets logged and read back by the next model call, so this corrupted the plan in the structured-content case. It affected both the initial-plan branch and the update-plan branch.

How it's fixed

Added _plan_message_content_to_text, which returns the text blocks of a ChatMessage.content. It is applied when building the plan in both branches. str content is unchanged; a list of blocks keeps only type == "text" blocks (images and other block types have no place in the markdown plan text); everything else yields "".

Test

test_planning_step_with_structured_plan_content uses a mock model that returns real structured content for the plan call and asserts the plan contains the text block and not a repr. It fails on main and passes with this fix.

Validation

  • pytest tests/test_agents.py -k plan → 4 passed
  • pytest tests/test_memory.py → 12 passed
  • ruff check + ruff format --check on the two changed files → clean

I attempted this contribution using an AI coding agent. I have read the diff, run the tests above, and can explain each line and answer review questions.

Note: PRs are auto-closed until the linked issue carries status:accepted. This links Fixes #2720; if it is closed on that basis, it can be reopened once a maintainer accepts the issue.

Fixes #2720

A model may return a list of content blocks for the <end_plan> call. The
plan text is a string, so interpolating that list directly put its Python
repr into PlanningStep.plan. Extract the text blocks (used by both the
initial-plan and update-plan branches) so the plan stays readable.

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

Community review, does not clear the merge gate.

Traced this in src/smolagents/agents.py's _generate_planning_step. plan_message_content = plan_message.content comes straight from ChatMessage.content, and I confirmed elsewhere in the same file (the plan_update_pre SYSTEM message a few lines below) that ChatMessage.content is genuinely used as either a plain string or a list of {"type": ..., "text": ...} blocks in this codebase, so a provider returning structured content for the plan response is a real possibility, not a hypothetical. Before this fix, that list got embedded directly into an f-string, so plan (a markdown string meant for display and for later reuse as conversation history) would contain the Python repr of the list of dicts instead of readable text.

The new _plan_message_content_to_text helper passes strings through unchanged, and for a list keeps only blocks with type == "text", joining their text values. I checked the change is scoped to only the two non-streaming generate() branches (initial plan and update plan); the streaming branch above builds plan_message_content by concatenating event.content as a string already, so it was never affected by this bug and this diff correctly leaves it untouched.

The new test uses a fake model that returns content=[{"type": "text", "text": "Step 1: do the thing."}] for the planning call and asserts the resulting plan text contains "Step 1: do the thing." and does not contain "[{" (i.e. no repr leakage). That's a direct regression test for the described bug (issue #2720).

Traced end to end, comfortable approving.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: _generate_planning_step embeds a structured model response's Python repr into the plan text

2 participants