Skip to content

Commit f58afb4

Browse files
authored
fix(vertexai): fix "Object of type 'FieldInfo' is not JSON serializable" across all schema generation (#1317)
## Description Fixes "TypeError: Object of type 'FieldInfo' is not JSON serializable" that occurred when using with_structured_output(..., method="json_mode") with Pydantic v2 models containing Field metadata. The issue occurred because model_json_schema() was called without the mode parameter, causing Pydantic v2 to use validation mode which can include non-serializable FieldInfo objects in certain contexts. Adding mode='serialization' tells Pydantic to use its serialization schema generation mode, which properly handles FieldInfo objects. Using mode='serialization' is also semantically correct since the schema describes the model's output format (what Gemini should generate), not input validation rules. Changes: - Add mode='serialization' to model_json_schema() in chat_models.py:2562 - Add unit test for Pydantic v2 FieldInfo serialization - Add unit test for Pydantic v1 backward compatibility - Verify both Pydantic v1 (schema() method) and v2 paths work correctly Backward compatibility: - Pydantic v1 models: Continue using schema() method (line 2560) - Pydantic v2 models: Now use model_json_schema(mode='serialization') - No public API changes, fully backward compatible ## Relevant issues <!-- e.g. "Fixes #000" --> ## Type <!-- Select the type of Pull Request --> <!-- Keep only the necessary ones --> 🆕 New Feature 🐛 Bug Fix 🧹 Refactoring 📖 Documentation 🚄 Infrastructure ✅ Test ## Changes(optional) <!-- List of changes --> ## Testing(optional) <!-- Test procedure --> <!-- Test result --> ## Note(optional) <!-- Information about the errors fixed by PR --> <!-- Remaining issue or something --> <!-- Other information about PR --> Signed-off-by: jitokim <pigberger70@gmail.com>
1 parent 23aea19 commit f58afb4

File tree

4 files changed

+67
-4
lines changed

4 files changed

+67
-4
lines changed

libs/vertexai/langchain_google_vertexai/chains.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def _create_structured_runnable_extra_step(
4848
prompt: BasePromptTemplate | None = None,
4949
) -> Runnable:
5050
names = [
51-
schema.model_json_schema()["title"]
51+
schema.model_json_schema(mode="serialization")["title"]
5252
if hasattr(schema, "model_json_schema")
5353
else schema.schema()["title"]
5454
for schema in functions

libs/vertexai/langchain_google_vertexai/chat_models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2614,7 +2614,7 @@ class Explanation(BaseModel):
26142614
if issubclass(schema, BaseModelV1):
26152615
schema_json = schema.schema()
26162616
else:
2617-
schema_json = schema.model_json_schema() # type: ignore[attr-defined]
2617+
schema_json = schema.model_json_schema(mode="serialization") # type: ignore[attr-defined]
26182618
parser = PydanticOutputParser(pydantic_object=schema)
26192619
else:
26202620
if is_typeddict(schema):

libs/vertexai/langchain_google_vertexai/functions_utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ def _format_base_tool_to_function_declaration(
192192
)
193193

194194
if hasattr(tool.args_schema, "model_json_schema"):
195-
schema = tool.args_schema.model_json_schema()
195+
schema = tool.args_schema.model_json_schema(mode="serialization")
196196
pydantic_version = "v2"
197197
else:
198198
schema = tool.args_schema.schema() # type: ignore[attr-defined]
@@ -211,7 +211,7 @@ def _format_pydantic_to_function_declaration(
211211
pydantic_model: type[BaseModel],
212212
) -> gapic.FunctionDeclaration:
213213
if hasattr(pydantic_model, "model_json_schema"):
214-
schema = pydantic_model.model_json_schema()
214+
schema = pydantic_model.model_json_schema(mode="serialization")
215215
pydantic_version = "v2"
216216
else:
217217
schema = pydantic_model.schema()

libs/vertexai/tests/unit_tests/test_chat_models.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1822,6 +1822,69 @@ def test_thinking_budget_in_invocation_params() -> None:
18221822
assert invocation_params["include_thoughts"] is False
18231823

18241824

1825+
def test_json_mode_with_pydantic_v2_fieldinfo_serialization() -> None:
1826+
"""Test that json_mode uses serialization mode for Pydantic v2 model_json_schema.
1827+
1828+
This ensures that FieldInfo objects are properly serialized when generating
1829+
the JSON schema for structured output in json_mode. Using mode='serialization'
1830+
is semantically correct since the schema describes the model's output format,
1831+
not its input validation rules.
1832+
"""
1833+
from pydantic import Field
1834+
1835+
class TestModel(BaseModel):
1836+
"""Test model with Pydantic v2 FieldInfo metadata."""
1837+
1838+
name: str = Field(description="Person's name")
1839+
age: int = Field(gt=0, le=150, description="Person's age")
1840+
1841+
llm = ChatVertexAI(model_name="gemini-2.5-flash", project="test-project")
1842+
1843+
# This should not raise any errors when creating structured output
1844+
structured_llm = llm.with_structured_output(TestModel, method="json_mode")
1845+
assert structured_llm is not None
1846+
1847+
# Verify that model_json_schema works with mode='serialization'
1848+
schema = TestModel.model_json_schema(mode="serialization")
1849+
assert "properties" in schema
1850+
assert "name" in schema["properties"]
1851+
assert "age" in schema["properties"]
1852+
# Verify field metadata is preserved
1853+
assert "description" in schema["properties"]["name"]
1854+
assert schema["properties"]["name"]["description"] == "Person's name"
1855+
1856+
1857+
def test_json_mode_pydantic_v1_backward_compatibility() -> None:
1858+
"""Test that Pydantic v1 models continue to work with json_mode.
1859+
1860+
This ensures backward compatibility - Pydantic v1 models use schema()
1861+
method while v2 models use model_json_schema(mode='serialization').
1862+
"""
1863+
from pydantic.v1 import BaseModel as BaseModelV1
1864+
1865+
class V1Model(BaseModelV1):
1866+
"""Test model using Pydantic v1 API."""
1867+
1868+
name: str
1869+
age: int
1870+
1871+
llm = ChatVertexAI(model_name="gemini-2.5-flash", project="test-project")
1872+
1873+
# V1 models should work without issues
1874+
structured_llm = llm.with_structured_output(V1Model, method="json_mode")
1875+
assert structured_llm is not None
1876+
1877+
# Verify V1 model uses schema() method, not model_json_schema()
1878+
assert hasattr(V1Model, "schema")
1879+
assert not hasattr(V1Model, "model_json_schema")
1880+
1881+
# Verify schema generation works
1882+
schema = V1Model.schema()
1883+
assert "properties" in schema
1884+
assert "name" in schema["properties"]
1885+
assert "age" in schema["properties"]
1886+
1887+
18251888
def test_thought_signature_conversion() -> None:
18261889
# Test ReasoningContentBlock with signature
18271890
reasoning_block = {

0 commit comments

Comments
 (0)