- Notifications
You must be signed in to change notification settings - Fork1k
Closed
Labels
Description
Initial Checks
- I confirm that I'm using the latest version of Pydantic AI
- I confirm that I searched for my issue inhttps://github.com/pydantic/pydantic-ai/issues before opening this issue
Description
When usingGoogleModel
with Vertex AI, requests fail with a 400 error becausepydantic_ai
generates empty text parts{'text': ''}
that Google's API rejects.
Error Message
400Bad Request. {"error": {"code":400,"message":"Unable to submit request because it must have a text parameter. Add a text parameter and try again. Learn more: https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/gemini","status":"INVALID_ARGUMENT" }}
Root Cause
Inpydantic_ai/models/google.py
, the_map_messages()
method at lines 362-364 adds empty text parts as a defensive measure:
# Google GenAI requires at least one part in the message.ifnotmessage_parts:message_parts= [{'text':''}]
However, Google's Vertex AI API strictly validates thetext
parameter and rejects empty strings, requiring non-empty text content.
Reproduction
This occurs when:
- Messages have no content after processing
- Message parts arrays become empty during conversion
- Certain edge cases in message handling
Impact
- Affects all users of
GoogleModel
with Vertex AI - Causes complete request failures with 400 errors
- Blocks streaming and non-streaming requests
Suggested Fix
Replace the empty string with a minimal valid text:
# Instead of:ifnotmessage_parts:message_parts= [{'text':''}]# Use:ifnotmessage_parts:message_parts= [{'text':' '}]# Single space
Or add provider-specific handling for Google's stricter validation requirements.
Temporary Workaround
We've implemented a subclass that filters empty text parts:
classFixedGoogleModel(GoogleModel):asyncdef_map_messages(self,messages):system_instruction,contents=awaitsuper()._map_messages(messages)# Filter out empty text parts and ensure valid structurecleaned_contents= []forcontent_itemincontents:if"parts"notincontent_item:cleaned_contents.append(content_item)continueoriginal_parts=content_item.get("parts", [])cleaned_parts= [partforpartinoriginal_partsifnot (isinstance(part,dict)andpart.get("text")==""andlen(part)==1) ]ifnotcleaned_parts:cleaned_parts= [{"text":" "}]content_item["parts"]=cleaned_partscleaned_contents.append(content_item)ifnotcleaned_contents:cleaned_contents= [{"role":"user","parts": [{"text":" "}]}]returnsystem_instruction,cleaned_contents
This workaround successfully resolves the 400 errors while maintaining API compatibility.
Example Code
importasynciofrompydantic_aiimportAgentfrompydantic_ai.models.googleimportGoogleModelfrompydantic_ai.providers.googleimportGoogleProviderasyncdefreproduce_bug():# Set up GoogleModel with Vertex AIprovider=GoogleProvider(vertexai=True,location="us-central1"# or your preferred location )model=GoogleModel("gemini-pro-2.5",provider=provider)# Create an agentagent=Agent(model=model)# This scenario can trigger empty message parts# Case 1: Empty string messagetry:result=awaitagent.run("")print("Empty string succeeded:",result)exceptExceptionase:print("Empty string failed:",str(e))# Case 2: Message that becomes empty after processingtry:result=awaitagent.run(" ")# Only whitespaceprint("Whitespace succeeded:",result)exceptExceptionase:print("Whitespace failed:",str(e))# Run the reproductionif__name__=="__main__":asyncio.run(reproduce_bug())
Python, Pydantic AI & LLM client version
Pydantic AI version: v0.3.1Python version: 3.12Provider: Google Vertex AIModel: gemini-pro-2.5 (via GoogleModel/Vertex)