From 2d1492a8643ec260e998f64001784249f7da80cf Mon Sep 17 00:00:00 2001 From: Tanzim Hossain Romel Date: Mon, 23 Feb 2026 04:32:00 +0600 Subject: [PATCH] fix(core): improve error message for non-JSON-serializable tool schemas (#34376) --- .../langchain_core/utils/function_calling.py | 30 ++++++++++++---- .../unit_tests/utils/test_function_calling.py | 35 ++++++++++++++++++- 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/libs/core/langchain_core/utils/function_calling.py b/libs/core/langchain_core/utils/function_calling.py index 944b552d16a..cda5569f795 100644 --- a/libs/core/langchain_core/utils/function_calling.py +++ b/libs/core/langchain_core/utils/function_calling.py @@ -22,6 +22,7 @@ from typing import ( import typing_extensions from pydantic import BaseModel +from pydantic.errors import PydanticInvalidForJsonSchema from pydantic.v1 import BaseModel as BaseModelV1 from pydantic.v1 import Field as Field_v1 from pydantic.v1 import create_model as create_model_v1 @@ -176,17 +177,32 @@ def _convert_pydantic_to_openai_function( Raises: TypeError: If the model is not a Pydantic model. + TypeError: If the model contains types that cannot be converted to JSON schema. Returns: The function description. """ - if hasattr(model, "model_json_schema"): - schema = model.model_json_schema() # Pydantic 2 - elif hasattr(model, "schema"): - schema = model.schema() # Pydantic 1 - else: - msg = "Model must be a Pydantic model." - raise TypeError(msg) + try: + if hasattr(model, "model_json_schema"): + schema = model.model_json_schema() # Pydantic 2 + elif hasattr(model, "schema"): + schema = model.schema() # Pydantic 1 + else: + msg = "Model must be a Pydantic model." + raise TypeError(msg) + except PydanticInvalidForJsonSchema as e: + model_name = getattr(model, "__name__", str(model)) + msg = ( + f"Failed to generate JSON schema for '{model_name}': {e}\n\n" + "Tool argument schemas must be JSON-serializable. If your schema includes " + "custom Python classes, consider:\n" + " 1. Converting them to Pydantic models with JSON-compatible fields\n" + " 2. Using primitive types (str, int, float, bool, list, dict) instead\n" + " 3. Passing the data as serialized JSON strings\n\n" + "For more information, see: " + "https://python.langchain.com/docs/how_to/custom_tools/" + ) + raise PydanticInvalidForJsonSchema(msg) from e return _convert_json_schema_to_openai_function( schema, name=name, description=description, rm_titles=rm_titles ) diff --git a/libs/core/tests/unit_tests/utils/test_function_calling.py b/libs/core/tests/unit_tests/utils/test_function_calling.py index 29749b34a61..e56ae47e221 100644 --- a/libs/core/tests/unit_tests/utils/test_function_calling.py +++ b/libs/core/tests/unit_tests/utils/test_function_calling.py @@ -22,7 +22,8 @@ except ImportError: from importlib.metadata import version from packaging.version import parse -from pydantic import BaseModel, Field +from pydantic import BaseModel, ConfigDict, Field +from pydantic.errors import PydanticInvalidForJsonSchema from langchain_core.messages import AIMessage, HumanMessage, ToolMessage from langchain_core.runnables import Runnable, RunnableLambda @@ -1171,6 +1172,38 @@ def test_convert_to_openai_function_strict_required() -> None: assert actual == expected +def test_convert_to_openai_function_arbitrary_type_error() -> None: + """Test that a helpful error is raised for non-JSON-serializable types. + + When a Pydantic model contains a custom Python class that cannot be + serialized to JSON schema, we should raise a PydanticInvalidForJsonSchema + with a helpful error message explaining the issue and suggesting solutions. + + See: https://github.com/langchain-ai/langchain/issues/34371 + """ + + # Define a custom Python class that isn't JSON-serializable + class CustomClass: + def __init__(self, name: str) -> None: + self.name = name + + class SchemaWithArbitraryType(BaseModel): + """Schema with arbitrary type.""" + + model_config = ConfigDict(arbitrary_types_allowed=True) + custom_obj: CustomClass = Field(..., description="A custom object") + name: str = Field(..., description="A name") + + with pytest.raises(PydanticInvalidForJsonSchema) as exc_info: + convert_to_openai_function(SchemaWithArbitraryType) + + error_message = str(exc_info.value) + # Check that the error message contains helpful information + assert "SchemaWithArbitraryType" in error_message + assert "JSON-serializable" in error_message + assert "Pydantic models" in error_message + + def test_convert_to_openai_function_strict_defaults() -> None: class MyModel(BaseModel): """Dummy schema."""