fix(sdk): nudge instead of finishing on a malformed-function-call content leak (gemini/OpenRouter)#2
Open
Viz-gov wants to merge 1 commit into
Conversation
…dge instead of finishing on junk no-tool-call turn)
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
gemini-3.5-flash(and other Gemini models) intermittently hit Gemini'sMALFORMED_FUNCTION_CALLfinish reason — especially on tool-heavy / multimodal turns. When served via OpenRouter's OpenAI-compat layer, that native reason is remapped to a normalfinish_reason="stop"(exposed only asnative_finish_reason), and a scrap of the botched call leaks into the message content (e.g."5","}","000Z").classify_responsethen sees non-blank content + no tool call →CONTENT→_handle_content_response, which setsexecution_status = FINISHED. The episode ends on what is really a fumbled tool call. Empty / reasoning-only turns are already recovered by_send_corrective_nudge, but a non-empty stray token slips straight through and silently kills the run.Fix
In
_handle_content_response, if the only visible content is an implausibly-short fragment (≤12 chars, single line) with no tool call, treat it as a malformed-function-call leak and send the existing corrective nudge instead of finishing. Substantive text answers are unaffected.Evidence
Task
auth-service-latency-investigation(multi-app-200,gemini-3.5-flashvia OpenRouter, OpenHands SDK):"5")The leak still occurs ~30× in the run — every one is now caught and nudged, and the agent recovers and completes the task. Canyon (text-only, no images) never tripped it, consistent with the multimodal trigger.
Notes / follow-ups
native_finish_reason == "MALFORMED_FUNCTION_CALL"for the OpenRouter path, or use the native Google provider wherefinishReason=9is visible and retryable. That signal isn't currently plumbed through; this content-heuristic backstop is provider-agnostic and complements re-enabling stuck detection.MALFORMED_FUNCTION_CALL= finishReason 9; OpenRouter remaps tostop(pydantic-ai chore(ci): use GITHUB_TOKEN for label and stale workflows OpenHands/software-agent-sdk#2844, agno chore(deps): bump lewagon/wait-on-check-action from 1.7.0 to 1.8.0 OpenHands/software-agent-sdk#3649).