#2631·instructor

Gemini structured-output reask always fails: request ends on a model turn

Author: degCreated Sep 9, 2026Updated Sep 14, 2026

Affected: instructor/v2/providers/genai/handlers.py::reask_genai_structured_outputs, on Mode.GENAI_STRUCTURED_OUTPUTS (alias of Mode.JSON for Provider.GENAI). Versions: 1.16.0 (reproduced), 1.17.0 (function is byte-identical at the tag). SDK: google-genai 2.20.0. Model: gemini-3.7-flash (any Gemini model behaves the same).

What happens

After a validation failure, reask_genai_structured_outputs appends the correction to kwargs["contents"] as a single types.ModelContent:

python
kwargs["contents"].append(
    types.ModelContent(
        parts=[types.Part.from_text(text=f"Validation Error found:\n{exception}\n...")]
    ),
)

So the rebuilt request ends on a model turn, and Gemini rejects every reask:

400 INVALID_ARGUMENT. {'error': {'code': 400, 'message': 'Requests ending with a model turn are not supported.', 'status': 'INVALID_ARGUMENT'}}

The effect is that on Gemini structured outputs, every retry attempt after the first is a guaranteed-failing provider call, and the exception the caller finally sees is the 400 rather than the validation error that caused the reask. The sibling reask_genai_tools gets the shape right on its no-function-call branch: it appends the model content and then a types.Content(role="user", ...) carrying the error.

A second, smaller issue in the same function: kwargs = kwargs.copy() is a shallow copy, so the append mutates the caller's original contents list.

Minimal reproduction

python
from pydantic import BaseModel, field_validator
from instructor import Mode, from_provider

class Color(BaseModel):
    name: str

    @field_validator("name")
    @classmethod
    def _never(cls, v: str) -> str:
        raise ValueError("every name is rejected on purpose")

client = from_provider("google/gemini-3.7-flash", api_key=KEY, async_client=False,
                       mode=Mode.GENAI_STRUCTURED_OUTPUTS)
client.on("completion:error", lambda err, **_: print("provider error:", str(err)[:120]))
client.chat.completions.create_with_completion(
    response_model=Color,
    messages=[{"role": "user", "content": "Name one color."}],
)

Output (1.16.0):

provider error: 400 INVALID_ARGUMENT. {'error': {'code': 400, 'message': 'Requests ending with a model turn are not supported.', ...
InstructorRetryException: <failed_attempts> ... 1 validation error for Color ...

Two provider requests are sent; the second always 400s.

Expected

The reask should end on a user turn, mirroring reask_genai_tools: append the model's previous attempt as model content, then the validation error as types.Content(role="user", ...).

Workaround

Pass max_retries=0 on Gemini calls so no reask is attempted (the argument counts retries after the first attempt); the validation failure then surfaces immediately on InstructorRetryException with the failed attempt attached.