Skip to content

Conversation

@ArneZsng
Copy link
Contributor

@ArneZsng ArneZsng commented Oct 30, 2025

Reopening #3095

Original Description

Using Gemini, we are frequently running into the MALFORMED_FUNCTION_CALL as the finish reason.
Google seemingly does not provide any context in the response for the reason of the MALFORMED_FUNCTION_CALL.

In these cases, the agent can continue retrying which I why I suggest to simply continue in the case of a MALFORMED_FUNCTION_CALL.

Closes #631

The issue is most prevalent when using Gemini 2.5 Flash (compared to Gemini 2.5 Pro where it also happens but less frequently).

Example chunk:

sdk_http_response=HttpResponse(
  headers=<dict len=11>
) candidates=[Candidate(
  finish_reason=<FinishReason.MALFORMED_FUNCTION_CALL: 'MALFORMED_FUNCTION_CALL'>,
  index=0
)] create_time=None model_version=None prompt_feedback=None response_id=None usage_metadata=GenerateContentResponseUsageMetadata(
  prompt_token_count=2595,
  prompt_tokens_details=[
    ModalityTokenCount(
      modality=<MediaModality.TEXT: 'TEXT'>,
      token_count=2595
    ),
  ],
  total_token_count=2595
) automatic_function_calling_history=None parsed=None

Newly added changes

Refactored Gemini response handling in GoogleModel and GeminiStreamedResponse to treat missing or malformed function call content uniformly, avoiding unnecessary exceptions.

Updated GoogleModel to handle 'MALFORMED_FUNCTION_CALL' finish reason by initializing parts as an empty list. Added and extended tests to cover retry logic for both streaming and non-streaming responses when a malformed function call is encountered.
@ArneZsng ArneZsng marked this pull request as draft October 30, 2025 21:02
@ArneZsng ArneZsng force-pushed the fix/google-continue-on-malformed-function-call branch from 168bc8d to 2997f66 Compare October 30, 2025 21:13
Refactored Gemini response handling in GoogleModel and GeminiStreamedResponse to treat missing or malformed function call content uniformly, avoiding unnecessary exceptions. Removed obsolete or redundant test helpers and tests related to malformed function call streaming and retry logic.
Removed a redundant PartDeltaEvent assertion and updated the expected identifier in the BinaryImage snapshot assertion to 'b037a4' in the image generation test.
@ArneZsng ArneZsng changed the title Google: Continue on MALFORMED_FUNCTION_CALL Google: Continue on MALFORMED_FUNCTION_CALL and other errors and empty response Oct 30, 2025
)
continue

parts = candidate.content.parts
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that we could get here if candidate.content is None

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't that call continue and thus let us proceed with the next chunk? I am not sure I can follow you right now.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're quite right, I think I had been at my desk for too long when I reviewed this :)

parts = candidate.content.parts or []
parts = []
else:
parts = candidate.content.parts or []
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that we could get here if candidate.content is None

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If candidate.content is None, parts would be [] and we would continue with that, no?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, you're totally right, I wasn't reading right

@ArneZsng ArneZsng marked this pull request as ready for review October 30, 2025 22:08
Marked the assignment of an empty list to 'parts' with 'pragma: no cover' to exclude it from test coverage analysis, as this branch is likely not expected to be covered.
@ArneZsng ArneZsng requested a review from DouweM October 30, 2025 23:38
parts = candidate.content.parts or []
parts = []
else:
parts = candidate.content.parts or []
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, you're totally right, I wasn't reading right

)
continue

parts = candidate.content.parts
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're quite right, I think I had been at my desk for too long when I reviewed this :)

@DouweM DouweM changed the title Google: Continue on MALFORMED_FUNCTION_CALL and other errors and empty response Retry instead of error when Google response fails with MALFORMED_FUNCTION_CALL or other recoverable finish reason Oct 31, 2025
@DouweM DouweM changed the title Retry instead of error when Google response fails with MALFORMED_FUNCTION_CALL or other recoverable finish reason Retry instead of error when Google response is empty with MALFORMED_FUNCTION_CALL or other recoverable finish reason Oct 31, 2025
@DouweM DouweM merged commit 1f3b100 into pydantic:main Oct 31, 2025
34 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.

MALFORMED_FUNCTION finishReason in Gemini candidate

2 participants