Skip to content

Conversation

@naveen-corpusant
Copy link
Contributor

We observe when using gemini 2.5 pro, a significant number of errors with the message Please return text or call a tool. On debugging, it seems gemini 2.5 will sometimes return an empty text part after a tool call + tool return node, rather than a message with no parts.

This updates the check to handle this case as well. There's a few threads like this one: https://discuss.ai.google.dev/t/gemini-2-5-pro-with-empty-response-text/81175 that discuss why gemini is returning an empty text response, but from my testing, this happens even when there is a successful tool call and return

Example of empty message

ModelResponse(parts=[TextPart(content='')], usage=RequestUsage(input_tokens=25980, cache_read_tokens=23733, details={'cached_content_tokens': 23733, 'text_prompt_tokens': 25980, 'text_cache_tokens': 23733}), model_name='gemini-2.5-pro', timestamp=datetime.datetime(2025, 11, 6, 20, 59, 48, 480954, tzinfo=TzInfo(UTC)), provider_name='google-vertex', provider_details={'finish_reason': 'STOP'}, provider_response_id='XXX', finish_reason='stop')

@naveen-corpusant
Copy link
Contributor Author

Here's an example of how ctx.state.message_history looks when we hit this case (last few model response / requests). So in this case, before we even get back the tool return, there's a TextPart with the content, and then we get an empty TextPart afterwards.

ModelResponse(parts=[ThinkingPart(content='**Processing User Query**\n\nI\'ve successfully identified the user\'s request for ...This is the logical next step.\n\n\n', signature='XX', provider_name='google-vertex'), TextPart(content='Sure, going to do this task'), ToolCallPart(tool_name='my_custom_tool', args={xx}, tool_call_id='xx')], usage=RequestUsage(input_tokens=25289, cache_read_tokens=23597, output_tokens=210, details={'cached_content_tokens': 23597, 'thoughts_tokens': 126, 'text_prompt_tokens': 25289, 'text_cache_tokens': 23597, 'text_candidates_tokens': 84}), model_name='gemini-2.5-pro', timestamp=datetime.datetime(2025, 11, 6, 23, 7, 4, 709356, tzinfo=TzInfo(UTC)), provider_name='google-vertex', provider_details={'finish_reason': 'STOP'}, provider_response_id='xx', finish_reason='stop')--

ModelRequest(parts=[ToolReturnPart(tool_name='my_custom_tool', content=None, tool_call_id='XX', timestamp=datetime.datetime(2025, 11, 6, 23, 7, 8, 108794, tzinfo=datetime.timezone.utc))])


-------

ModelResponse(parts=[TextPart(content='')], usage=RequestUsage(input_tokens=25771, cache_read_tokens=23648, details={'cached_content_tokens': 23648, 'text_prompt_tokens': 25771, 'text_cache_tokens': 23648}), model_name='gemini-2.5-pro', timestamp=datetime.datetime(2025, 11, 6, 23, 7, 8, 246945, tzinfo=TzInfo(UTC)), provider_name='google-vertex', provider_details={'finish_reason': 'STOP'}, provider_response_id='XX', finish_reason='stop')

@DouweM
Copy link
Collaborator

DouweM commented Nov 7, 2025

@naveen-corpusant What do you think about instead not creating TextParts at all when text is empty?

That'd mean changing if part.text is not None: to just if part.text: here:

elif part.text is not None:
if part.thought:
item = ThinkingPart(content=part.text)
else:
item = TextPart(content=part.text)

And here:

if part.text is not None:
if part.thought:
yield self._parts_manager.handle_thinking_delta(vendor_part_id='thinking', content=part.text)
else:
maybe_event = self._parts_manager.handle_text_delta(vendor_part_id='content', content=part.text)
if maybe_event is not None: # pragma: no branch
yield maybe_event

@naveen-corpusant
Copy link
Contributor Author

@DouweM good suggestion- was thinking about this as I was planning to monkey patch for current usage, and realized it'd be cleaner to do this at the google level rather than apply it to all models at the _agent_graph. Let's go with that, I'll update the PR now.

@naveen-corpusant
Copy link
Contributor Author

updated! thanks for the review

@DouweM DouweM changed the title Fix: gemini 2.5 empty text part in tool calls node Ignore empty text parts in GoogleModel Nov 7, 2025
@DouweM DouweM merged commit f666597 into pydantic:main Nov 7, 2025
31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants