Empty content on tool-call envelopes to stop model echo#240
Merged
Conversation
When a tool call is recorded in the conversation transcript, the
envelope's `content` field carried a human-readable string ("Calling
<tool_name>"). Adapters that flatten the transcript to provider text
(e.g. openclawp's WP AI Client adapter) pass that string back to the
model on subsequent turns as a previous assistant text turn.
On long multi-tool conversations — especially with smaller / cheaper
models like Gemini Flash — the model pattern-matches that recurring
"Calling X" precedent and starts emitting the literal string as its
own text output instead of issuing a real function call or producing
a final answer. The user sees the debug string as the bot's reply.
Concrete failure mode observed on a wp-carpeta deployment (Gemini
gemini-3.5-flash, 5-turn convo with 4 search-* tool calls): turn 5
emitted `"Calling carpeta__search-wa-messages"` as plain text with
`tool_call_count=0`, breaking the channel response.
Fix: pass `''` as content. The structured tool-call data
(tool_name + parameters + tool_call_id + turn) is already in the
envelope's metadata/payload, which is what providers should be using
to construct the actual function_call structure. Adapters that need
a human-readable label for log/UI surfaces can derive one from
`metadata.tool_name`.
Smoke tests pass unchanged — no assertion in the suite relied on the
"Calling X" content string.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
WP_Agent_Conversation_Looprecords each tool call in the transcript as an envelope whosecontentis the human-readable string"Calling <tool_name>". Adapters that flatten the transcript to provider-side text (e.g. openclawp's WP AI Client adapter, which projects{role, content}per message and drops empty-content entries) pass that string back to the model on subsequent turns as a previous assistant text turn.On long multi-tool conversations — especially with cheaper / faster models like Gemini Flash — the model pattern-matches the recurring
"Calling X"precedent and begins emitting the literal string as its own text output instead of issuing a real function call or producing a final answer. The user sees the debug string as the bot's reply.Repro (production-observed)
Surfaced in wp-carpeta (WhatsApp bot channel, Gemini
gemini-3.5-flash, 5-turn conversation, 4 priorsearch-*tool calls):OpenclaWP_Message_Adapter::last_assistant_text()then returns that string as the channelreply. The bot delivers"Calling carpeta__search-wa-messages"to the user.Fix
Pass
''as the envelopecontent. The structured tool-call data (tool_name+parameters+tool_call_id+turn) already lives in the envelope's metadata/payload, which is what providers should be using to construct the actualfunction_callstructure. Adapters that need a human-readable label for log/UI surfaces can derive one frommetadata.tool_name.This means downstream adapters that previously skipped empty-text messages (
if ( '' === \$text ) continue;) will now naturally exclude these envelopes from the LLM-facing transcript — which is the correct behavior, since the function-calling pathway uses the structured tool declarations + responses, not in-transcript text.Tests
All existing smoke tests pass — no assertion in the suite relied on the
"Calling X"content string.Compat notes
metadata.tool_name(e.g."Calling " . \$msg['metadata']['tool_name']).type=tool_call+ payload — only the renderedcontentchanges.🤖 Generated with Claude Code