Structured-output continuation merge raises AttributeError: __pydantic_generic_metadata__ when a hop has parsed JSON
Mantenedores costumam responder em até 1 dia
@Venkaiahbabuneelam já está trabalhando nisso.
Desde 11/11/2026.
- #3351 de @copybara-service — aberto
Avaliação
Esta issue ainda não foi avaliada.
Descrição
Summary
Structured-output generation can fail inside merge_continuation_responses() when a continuation hop already has a non-null parsed dictionary. The merge serializes that derived field back into GenerateContentResponse._from_response(). Extra-field pruning then treats the dictionary as the bare pydantic.BaseModel and calls BaseModel.model_rebuild(), raising:
AttributeError: __pydantic_generic_metadata__
We originally encountered this SDK stack during an asynchronous Vertex AI generate_content() call with response_json_schema. The deterministic reproduction below uses synthetic responses only: no credentials, model access, network requests, or application data are required.
Environment details
- Programming language: Python
- OS for local reproduction: macOS (Darwin)
- Language runtime version: Python 3.12.13
- Package versions:
google-genai==3.1.0,pydantic==2.13.5 - Also reproduced against unmodified
mainatf1670003f5eaf9d367ab414b8c5d757c991d8cc0with the same Python/Pydantic versions.
Steps to reproduce
-
Install the released packages in an isolated Python 3.12 environment:
python -m pip install "google-genai==3.1.0" "pydantic==2.13.5" -
Run the following script. It models a thought-only continuation hop followed by a complete JSON answer. The final hop's
parsedvalue is produced by the SDK's own_from_response(), not assigned manually.
import platform
import traceback
import pydantic
from google import genai
from google.genai import _extra_utils, types
print(f"Python={platform.python_version()} OS={platform.system()}")
print(f"google-genai={genai.__version__} pydantic={pydantic.__version__}")
schema = {
"type": "object",
"properties": {"name": {"type": "string"}},
"required": ["name"],
}
config = {"response_mime_type": "application/json", "response_json_schema": schema}
# A thought-only continuation hop, followed by a complete JSON response.
first = types.GenerateContentResponse(
candidates=[types.Candidate(
content=types.Content(role="model", parts=[
types.Part(text="Thinking about the answer.", thought=True)
]),
finish_reason="CONTINUATION",
)]
)
last = types.GenerateContentResponse._from_response(
response={"candidates": [{
"content": {"role": "model", "parts": [{"text": '{"name":"Example"}'}]},
"finish_reason": "STOP",
}]},
kwargs={"config": config},
)
print("last.parsed:", last.parsed)
assert last.parsed == {"name": "Example"}
print("one hop:", _extra_utils.merge_continuation_responses([last], config=config).parsed)
try:
merged = _extra_utils.merge_continuation_responses([first, last], config=config)
print("two hops:", merged.parsed)
except Exception:
traceback.print_exc()
raise SystemExit(1)
Expected behavior
Both the single-hop control and the two-hop merge return parsed == {"name": "Example"}. The derived parsed field should be recomputed from the merged answer without being treated as an API response field.
Actual behavior
The single-hop control succeeds; the two-hop merge raises the exception above and exits with status 1:
Python=3.12.13 OS=Darwin
google-genai=3.1.0 pydantic=2.13.5
last.parsed: {'name': 'Example'}
one hop: {'name': 'Example'}
Relevant stack frames from the local reproduction (environment-specific path prefixes omitted):
google/genai/_extra_utils.py:1338, in merge_continuation_responses
reparsed = types.GenerateContentResponse._from_response(...)
google/genai/types.py:9000, in _from_response
result = super()._from_response(response=response, kwargs=kwargs)
google/genai/_common.py:688, in _from_response
_remove_extra_fields(cls, response)
google/genai/_common.py:393, in _remove_extra_fields
_remove_extra_fields(model_type, value)
google/genai/_common.py:365, in _remove_extra_fields
model.model_rebuild(raise_errors=False)
pydantic/main.py:683, in model_rebuild
return _model_construction.complete_model_class(...)
pydantic/_internal/_model_construction.py:632, in complete_model_class
typevars_map = get_model_typevars_map(cls)
pydantic/_internal/_generics.py:236, in get_model_typevars_map
generic_metadata = cls.__pydantic_generic_metadata__
AttributeError: __pydantic_generic_metadata__
Analysis and possible fix
merge_continuation_responses()retains a hop'sparsedvalue and calls_from_response(response=merged.model_dump(exclude_none=True), ...)to recompute structured output.GenerateContentResponse.parsedis annotated asOptional[Union[pydantic.BaseModel, dict[Any, Any], Enum]]._remove_extra_fields()takes the first union member, then recursively visits the actual dictionary aspydantic.BaseModel. Itsmodel_rebuild()call on that base class causes this exception.
A narrowly scoped candidate is to exclude the derived parsed field from the serialization used for reparsing:
reparsed = types.GenerateContentResponse._from_response(
- response=merged.model_dump(exclude_none=True),
+ response=merged.model_dump(exclude_none=True, exclude={'parsed'}),
kwargs={'config': config_dict},
)
I verified that applying only this change to the checkout above makes the same reproduction return two hops: {'name': 'Example'}. This is a candidate fix, not a claim that the full SDK test suite has been validated.
A regression test should cover a non-null parsed dictionary on a hop, including the thought-only-continuation/final-JSON case above. The existing structured continuation test starts with two partial JSON responses whose parsed values are unset, so it does not exercise this case.
- Linguagem predominante
- Python
- Estrelas
- 4.1k
- Forks
- 1.1k
- Merge médio
- 1d 20h
- PRs com merge (30d)
- 90
Preparar o ambiente
- Sem Dockerfile nem arquivo Docker Compose
- Sem modelo de pull request
- Ler o guia de contribuição
Primeiros passos
- Leia a issue inteira e depois o guia de contribuição do projeto.
- Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
- Faça um fork do repositório e trabalhe em uma branch.
- Abra um pull request que referencie o número da issue.
Mais de googleapis/python-genai
-
Interactions retries ignore the documented `initial_delay` and `max_delay` defaultsTalvez já em andamento @Venkaiahbabuneelam assumiu há 5 dias. Abertapriority: p2 status:awaiting user response type: bug
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 68/100
googleapis/python-genai#3317 · 1 comentário · 1 responsável ·
Mantenedores costumam responder em até 1 dia
-
Test import module that doesn't existTalvez já em andamento @Venkaiahbabuneelam assumiu há 31 dias. Abertapriority: p2 type: bug
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 66/100
googleapis/python-genai#3122 · 6 comentários · 1 responsável ·
Mantenedores costumam responder em até 1 dia
-
Curated history keeps half a user turn when the model turn is invalidTalvez já em andamento @Venkaiahbabuneelam assumiu há 40 dias. Abertapriority: p2 type: bug
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 62/100
googleapis/python-genai#3051 · 3 comentários · 1 responsável ·
Mantenedores costumam responder em até 1 dia
-
[Bug]: Unsubscripted typing.List and typing.Dict crash convert_if_exist_pydantic_model in AFCTalvez já em andamento @Venkaiahbabuneelam assumiu há 40 dias. Abertapriority: p2 type: bug
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 82/100
googleapis/python-genai#3044 · 1 comentário · 1 responsável ·
Mantenedores costumam responder em até 1 dia
-
Automatic function calling rejects `datetime` and `UUID` argumentsTalvez já em andamento @Venkaiahbabuneelam assumiu há 3 dias. Abertapriority: p2 type: bug
Dificuldade 3/5 1-2 dias Facilidade para iniciantes 75/100
googleapis/python-genai#3331 · 1 comentário · 1 responsável ·
Mantenedores costumam responder em até 1 dia
Todas as issues de googleapis/python-genai
Issues semelhantes
-
Dificuldade 1/5 Menos de uma hora Facilidade para iniciantes 85/100
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 62/100
OCA/account-invoicing#2529 ·
Mantenedores costumam responder em até 1 dia
-
area/ci
Dificuldade 1/5 Menos de uma hora Facilidade para iniciantes 62/100
Mantenedores costumam responder em até 1 dia
-
ImageGen generate-batch aborts valid JSONL jobs containing a Unicode line separator in poster textAbertabug imagen skills
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 78/100
Mantenedores costumam responder em até 1 dia
-
Email validation accepts a trailing newlineTalvez já em andamento @marcello-pichini assumiu hoje. Aberta
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 78/100
Mantenedores costumam responder em até 1 dia