Skip to content

Commit bfedad2

Browse files
committed
fix(core): Prevent 'FieldInfo' is not JSON serializable error in Pydantic v2
Fixes TypeError when generating JSON schemas: "Object of type 'FieldInfo' is not JSON serializable". This occurs when model_json_schema() includes internal Pydantic metadata in the output. Solution: Add mode='serialization' parameter to model_json_schema() to ensure only JSON-serializable data structures are generated. Changes: - Modified _convert_pydantic_to_openai_function() to pass mode='serialization' - Added test_pydantic_fieldinfo_serialization() to verify correct behavior - Resolves nested Pydantic v2 model conversion issues Impact: - Fixes schema generation for nested Pydantic v2 models - No breaking changes (Pydantic v1 code path unchanged) - Previously failing test_convert_to_openai_function_nested_v2 now passes Signed-off-by: jitokim <[email protected]>
1 parent 637bb1c commit bfedad2

File tree

2 files changed

+63
-1
lines changed

2 files changed

+63
-1
lines changed

libs/core/langchain_core/utils/function_calling.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ def _convert_pydantic_to_openai_function(
157157
The function description.
158158
"""
159159
if hasattr(model, "model_json_schema"):
160-
schema = model.model_json_schema() # Pydantic 2
160+
schema = model.model_json_schema(mode="serialization") # Pydantic 2
161161
elif hasattr(model, "schema"):
162162
schema = model.schema() # Pydantic 1
163163
else:

libs/core/tests/unit_tests/utils/test_function_calling.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
import typing
23
from collections.abc import Callable, Iterable, Mapping, MutableMapping, Sequence
34
from typing import Annotated as ExtensionsAnnotated
@@ -11,6 +12,7 @@
1112
import pytest
1213
from pydantic import BaseModel as BaseModelV2Maybe # pydantic: ignore
1314
from pydantic import Field as FieldV2Maybe # pydantic: ignore
15+
from pydantic.fields import FieldInfo
1416
from typing_extensions import TypedDict as ExtensionsTypedDict
1517

1618
try:
@@ -1168,3 +1170,63 @@ class MyModel(BaseModel):
11681170
func = convert_to_openai_function(MyModel, strict=True)
11691171
actual = func["parameters"]["required"]
11701172
assert actual == expected
1173+
1174+
1175+
def test_pydantic_fieldinfo_serialization() -> None:
1176+
"""Test that FieldInfo objects are properly serialized in Pydantic v2 schemas.
1177+
1178+
This test ensures that using mode='serialization' prevents FieldInfo objects
1179+
from appearing in the generated JSON schema, which would cause serialization
1180+
errors in integrations.
1181+
"""
1182+
1183+
class MyModel(BaseModel):
1184+
"""Test model with various field configurations."""
1185+
1186+
required_field: str = Field(..., description="A required field")
1187+
optional_field: str | None = Field(None, description="An optional field")
1188+
default_field: int = Field(default=42, description="A field with default")
1189+
1190+
# Convert to OpenAI function format
1191+
result = convert_to_openai_function(MyModel)
1192+
1193+
# This should not raise an error if FieldInfo objects are properly serialized
1194+
json_str = json.dumps(result)
1195+
assert json_str is not None
1196+
1197+
# Verify the schema structure is correct
1198+
assert result["name"] == "MyModel"
1199+
assert result["description"] == "Test model with various field configurations."
1200+
assert "parameters" in result
1201+
1202+
params = result["parameters"]
1203+
assert params["type"] == "object"
1204+
assert "properties" in params
1205+
1206+
# Check that all fields are present and properly formatted
1207+
props = params["properties"]
1208+
assert "required_field" in props
1209+
assert props["required_field"]["type"] == "string"
1210+
assert props["required_field"]["description"] == "A required field"
1211+
1212+
assert "optional_field" in props
1213+
assert props["optional_field"]["description"] == "An optional field"
1214+
1215+
assert "default_field" in props
1216+
assert props["default_field"]["type"] == "integer"
1217+
assert props["default_field"]["description"] == "A field with default"
1218+
1219+
# Ensure no FieldInfo objects in the schema by checking all nested values
1220+
def check_no_fieldinfo(obj: Any) -> None:
1221+
"""Recursively check that no FieldInfo objects exist in the structure."""
1222+
if isinstance(obj, FieldInfo):
1223+
msg = "FieldInfo object found in schema - serialization mode not applied"
1224+
raise TypeError(msg)
1225+
if isinstance(obj, dict):
1226+
for value in obj.values():
1227+
check_no_fieldinfo(value)
1228+
elif isinstance(obj, list):
1229+
for item in obj:
1230+
check_no_fieldinfo(item)
1231+
1232+
check_no_fieldinfo(result)

0 commit comments

Comments
 (0)