Summary
Passing a nested Pydantic model to GenerateContentConfig(response_schema=...) silently discards per-field Field(description=...) annotations when the fields reference a shared model. Both outgoing properties receive the shared model docstring instead. This reproduces in google-genai 2.25.0.
This report concerns SDK conversion, not model compliance: the reproducer captures the HTTP request with httpx.MockTransport, uses a dummy key, and never contacts an API.
Environment
- macOS, Python 3.13.3
- google-genai 2.25.0, pydantic 2.13.5, httpx 0.28.1
- Also reproduced with Python 3.12.10, google-genai 1.67.0, pydantic 2.12.5
- Gemini Developer API request conversion
Reproducer
"""Capture synthetic structured-output requests without contacting any server."""
import json
import platform
from importlib.metadata import version
from typing import Any
import httpx
from google import genai
from google.genai import types
from pydantic import BaseModel, Field
class Score(BaseModel):
"""A generic score container."""
point: int
class Review(BaseModel):
"""Two criteria sharing the same value shape."""
clarity: Score = Field(description="Rate clarity only.")
accuracy: Score = Field(description="Rate factual accuracy only.")
def capture(use_json_schema: bool) -> dict[str, Any]:
"""Return the request body captured at the HTTP transport boundary."""
captured: dict[str, Any] = {}
def handler(request: httpx.Request) -> httpx.Response:
captured.update(json.loads(request.content))
return httpx.Response(
200,
json={
"candidates": [{
"content": {"role": "model", "parts": [{
"text": '{"clarity":{"point":3},"accuracy":{"point":4}}'
}]},
"finishReason": "STOP",
}]
},
)
config = types.GenerateContentConfig(response_mime_type="application/json")
if use_json_schema:
config.response_json_schema = Review.model_json_schema()
else:
config.response_schema = Review
with genai.Client(
api_key="synthetic-not-a-real-key",
vertexai=False,
http_options=types.HttpOptions(
client_args={"transport": httpx.MockTransport(handler), "trust_env": False}
),
) as client:
client.models.generate_content(
model="gemini-3.1-flash-lite", contents="Rate this text.", config=config
)
return captured["generationConfig"]
if __name__ == "__main__":
result = {
"python": platform.python_version(),
"google_genai": version("google-genai"),
"pydantic": version("pydantic"),
"before": Review.model_json_schema(),
"response_schema": capture(False),
"response_json_schema": capture(True),
}
print(json.dumps(result, indent=2))
Expected / actual
Before SDK conversion, Pydantic generates:
{"clarity":{"$ref":"#/$defs/Score","description":"Rate clarity only."},"accuracy":{"$ref":"#/$defs/Score","description":"Rate factual accuracy only."}}
In the outgoing responseSchema, both property descriptions are instead A generic score container.. Their object shapes remain valid, so this loss is silent.
I would expect the Pydantic adapter to preserve these field-specific annotations when inlining the referenced object, or explicitly warn/reject the unsupported annotation rather than silently replace it. This matters when two properties share a value shape but have distinct instructions.
Relevant implementation
In _transformers.process_schema, _recurse replaces a schema containing $ref with the referenced definition (sub_schema = defs[...]), discarding sibling annotations. A separate path uses schema.update(defs[...]), which also gives the referenced description precedence. This report is limited to descriptive annotations, not a proposal to blindly merge arbitrary validation keywords.
Scope / documented limitations
I understand that response_schema represents an OpenAPI subset rather than arbitrary JSON Schema. The SDK recommends trying response_json_schema when conversion is unsuitable, and the current README shows Model.model_json_schema() with that parameter.
The reproducer's control confirms that response_json_schema preserves both descriptions in the outbound JSON. This is not proof of server-side support or a complete workaround: its documented restrictions say $ref cannot have non-$ siblings, and the generated Pydantic description is such a sibling. No live API behavior is claimed here.
Could maintainers clarify whether per-field descriptions on nested Pydantic models are intended to be supported through response_schema? If so, could reference inlining retain these annotations? If not, could the limitation and a supported way to retain distinct field descriptions be documented explicitly?
Related: #1992 also loses description metadata, but in nullable Schema.from_json_schema unwrapping; this example uses required, non-nullable fields and the response_schema reference-inlining path.
Summary
Passing a nested Pydantic model to
GenerateContentConfig(response_schema=...)silently discards per-fieldField(description=...)annotations when the fields reference a shared model. Both outgoing properties receive the shared model docstring instead. This reproduces in google-genai 2.25.0.This report concerns SDK conversion, not model compliance: the reproducer captures the HTTP request with
httpx.MockTransport, uses a dummy key, and never contacts an API.Environment
Reproducer
Expected / actual
Before SDK conversion, Pydantic generates:
{"clarity":{"$ref":"#/$defs/Score","description":"Rate clarity only."},"accuracy":{"$ref":"#/$defs/Score","description":"Rate factual accuracy only."}}In the outgoing
responseSchema, both property descriptions are insteadA generic score container.. Their object shapes remain valid, so this loss is silent.I would expect the Pydantic adapter to preserve these field-specific annotations when inlining the referenced object, or explicitly warn/reject the unsupported annotation rather than silently replace it. This matters when two properties share a value shape but have distinct instructions.
Relevant implementation
In
_transformers.process_schema,_recursereplaces a schema containing$refwith the referenced definition (sub_schema = defs[...]), discarding sibling annotations. A separate path usesschema.update(defs[...]), which also gives the referenced description precedence. This report is limited to descriptive annotations, not a proposal to blindly merge arbitrary validation keywords.Scope / documented limitations
I understand that
response_schemarepresents an OpenAPI subset rather than arbitrary JSON Schema. The SDK recommends tryingresponse_json_schemawhen conversion is unsuitable, and the current README showsModel.model_json_schema()with that parameter.The reproducer's control confirms that
response_json_schemapreserves both descriptions in the outbound JSON. This is not proof of server-side support or a complete workaround: its documented restrictions say$refcannot have non-$siblings, and the generated Pydantic description is such a sibling. No live API behavior is claimed here.Could maintainers clarify whether per-field descriptions on nested Pydantic models are intended to be supported through
response_schema? If so, could reference inlining retain these annotations? If not, could the limitation and a supported way to retain distinct field descriptions be documented explicitly?Related: #1992 also loses description metadata, but in nullable
Schema.from_json_schemaunwrapping; this example uses required, non-nullable fields and theresponse_schemareference-inlining path.