mirror of
https://github.com/hwchase17/langchain.git
synced 2025-09-16 23:13:31 +00:00
multiple: pydantic 2 compatibility, v0.3 (#26443)
Signed-off-by: ChengZi <chen.zhang@zilliz.com> Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com> Co-authored-by: Bagatur <22008038+baskaryan@users.noreply.github.com> Co-authored-by: Dan O'Donovan <dan.odonovan@gmail.com> Co-authored-by: Tom Daniel Grande <tomdgrande@gmail.com> Co-authored-by: Grande <Tom.Daniel.Grande@statsbygg.no> Co-authored-by: Bagatur <baskaryan@gmail.com> Co-authored-by: ccurme <chester.curme@gmail.com> Co-authored-by: Harrison Chase <hw.chase.17@gmail.com> Co-authored-by: Tomaz Bratanic <bratanic.tomaz@gmail.com> Co-authored-by: ZhangShenao <15201440436@163.com> Co-authored-by: Friso H. Kingma <fhkingma@gmail.com> Co-authored-by: ChengZi <chen.zhang@zilliz.com> Co-authored-by: Nuno Campos <nuno@langchain.dev> Co-authored-by: Morgante Pell <morgantep@google.com>
This commit is contained in:
@@ -34,8 +34,9 @@ try:
|
||||
except ImportError:
|
||||
TypingAnnotated = ExtensionsAnnotated
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
|
||||
from langchain_core.pydantic_v1 import BaseModel, Field
|
||||
from langchain_core.runnables import Runnable, RunnableLambda
|
||||
from langchain_core.tools import BaseTool, StructuredTool, Tool, tool
|
||||
from langchain_core.utils.function_calling import (
|
||||
@@ -427,7 +428,9 @@ def test_convert_to_openai_function_nested_strict() -> None:
|
||||
assert actual == expected
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="Pydantic converts Optional[str] to str in .schema()")
|
||||
@pytest.mark.xfail(
|
||||
reason="Pydantic converts Optional[str] to str in .model_json_schema()"
|
||||
)
|
||||
def test_function_optional_param() -> None:
|
||||
@tool
|
||||
def func5(
|
||||
@@ -487,17 +490,17 @@ def test_multiple_tool_calls() -> None:
|
||||
{
|
||||
"id": messages[2].tool_call_id,
|
||||
"type": "function",
|
||||
"function": {"name": "FakeCall", "arguments": '{"data": "ToolCall1"}'},
|
||||
"function": {"name": "FakeCall", "arguments": '{"data":"ToolCall1"}'},
|
||||
},
|
||||
{
|
||||
"id": messages[3].tool_call_id,
|
||||
"type": "function",
|
||||
"function": {"name": "FakeCall", "arguments": '{"data": "ToolCall2"}'},
|
||||
"function": {"name": "FakeCall", "arguments": '{"data":"ToolCall2"}'},
|
||||
},
|
||||
{
|
||||
"id": messages[4].tool_call_id,
|
||||
"type": "function",
|
||||
"function": {"name": "FakeCall", "arguments": '{"data": "ToolCall3"}'},
|
||||
"function": {"name": "FakeCall", "arguments": '{"data":"ToolCall3"}'},
|
||||
},
|
||||
]
|
||||
|
||||
@@ -518,7 +521,7 @@ def test_tool_outputs() -> None:
|
||||
{
|
||||
"id": messages[2].tool_call_id,
|
||||
"type": "function",
|
||||
"function": {"name": "FakeCall", "arguments": '{"data": "ToolCall1"}'},
|
||||
"function": {"name": "FakeCall", "arguments": '{"data":"ToolCall1"}'},
|
||||
},
|
||||
]
|
||||
assert messages[2].content == "Output1"
|
||||
@@ -774,8 +777,9 @@ def test__convert_typed_dict_to_openai_function(
|
||||
@pytest.mark.parametrize("typed_dict", [ExtensionsTypedDict, TypingTypedDict])
|
||||
def test__convert_typed_dict_to_openai_function_fail(typed_dict: Type) -> None:
|
||||
class Tool(typed_dict):
|
||||
arg1: MutableSet # Pydantic doesn't support
|
||||
arg1: MutableSet # Pydantic 2 supports this, but pydantic v1 does not.
|
||||
|
||||
# Error should be raised since we're using v1 code path here
|
||||
with pytest.raises(TypeError):
|
||||
_convert_typed_dict_to_openai_function(Tool)
|
||||
|
||||
|
@@ -3,10 +3,12 @@
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import pytest
|
||||
from pydantic import ConfigDict
|
||||
|
||||
from langchain_core.utils.pydantic import (
|
||||
PYDANTIC_MAJOR_VERSION,
|
||||
_create_subset_model_v2,
|
||||
create_model_v2,
|
||||
get_fields,
|
||||
is_basemodel_instance,
|
||||
is_basemodel_subclass,
|
||||
@@ -15,7 +17,7 @@ from langchain_core.utils.pydantic import (
|
||||
|
||||
|
||||
def test_pre_init_decorator() -> None:
|
||||
from langchain_core.pydantic_v1 import BaseModel
|
||||
from pydantic import BaseModel
|
||||
|
||||
class Foo(BaseModel):
|
||||
x: int = 5
|
||||
@@ -34,7 +36,7 @@ def test_pre_init_decorator() -> None:
|
||||
|
||||
|
||||
def test_pre_init_decorator_with_more_defaults() -> None:
|
||||
from langchain_core.pydantic_v1 import BaseModel, Field
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class Foo(BaseModel):
|
||||
a: int = 1
|
||||
@@ -56,14 +58,15 @@ def test_pre_init_decorator_with_more_defaults() -> None:
|
||||
|
||||
|
||||
def test_with_aliases() -> None:
|
||||
from langchain_core.pydantic_v1 import BaseModel, Field
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class Foo(BaseModel):
|
||||
x: int = Field(default=1, alias="y")
|
||||
z: int
|
||||
|
||||
class Config:
|
||||
allow_population_by_field_name = True
|
||||
model_config = ConfigDict(
|
||||
populate_by_name=True,
|
||||
)
|
||||
|
||||
@pre_init
|
||||
def validator(cls, v: Dict[str, Any]) -> Dict[str, Any]:
|
||||
@@ -92,12 +95,12 @@ def test_with_aliases() -> None:
|
||||
def test_is_basemodel_subclass() -> None:
|
||||
"""Test pydantic."""
|
||||
if PYDANTIC_MAJOR_VERSION == 1:
|
||||
from pydantic import BaseModel as BaseModelV1Proper # pydantic: ignore
|
||||
from pydantic import BaseModel as BaseModelV1Proper
|
||||
|
||||
assert is_basemodel_subclass(BaseModelV1Proper)
|
||||
elif PYDANTIC_MAJOR_VERSION == 2:
|
||||
from pydantic import BaseModel as BaseModelV2 # pydantic: ignore
|
||||
from pydantic.v1 import BaseModel as BaseModelV1 # pydantic: ignore
|
||||
from pydantic import BaseModel as BaseModelV2
|
||||
from pydantic.v1 import BaseModel as BaseModelV1
|
||||
|
||||
assert is_basemodel_subclass(BaseModelV2)
|
||||
|
||||
@@ -109,15 +112,15 @@ def test_is_basemodel_subclass() -> None:
|
||||
def test_is_basemodel_instance() -> None:
|
||||
"""Test pydantic."""
|
||||
if PYDANTIC_MAJOR_VERSION == 1:
|
||||
from pydantic import BaseModel as BaseModelV1Proper # pydantic: ignore
|
||||
from pydantic import BaseModel as BaseModelV1Proper
|
||||
|
||||
class FooV1(BaseModelV1Proper):
|
||||
x: int
|
||||
|
||||
assert is_basemodel_instance(FooV1(x=5))
|
||||
elif PYDANTIC_MAJOR_VERSION == 2:
|
||||
from pydantic import BaseModel as BaseModelV2 # pydantic: ignore
|
||||
from pydantic.v1 import BaseModel as BaseModelV1 # pydantic: ignore
|
||||
from pydantic import BaseModel as BaseModelV2
|
||||
from pydantic.v1 import BaseModel as BaseModelV1
|
||||
|
||||
class Foo(BaseModelV2):
|
||||
x: int
|
||||
@@ -135,8 +138,8 @@ def test_is_basemodel_instance() -> None:
|
||||
@pytest.mark.skipif(PYDANTIC_MAJOR_VERSION != 2, reason="Only tests Pydantic v2")
|
||||
def test_with_field_metadata() -> None:
|
||||
"""Test pydantic with field metadata"""
|
||||
from pydantic import BaseModel as BaseModelV2 # pydantic: ignore
|
||||
from pydantic import Field as FieldV2 # pydantic: ignore
|
||||
from pydantic import BaseModel as BaseModelV2
|
||||
from pydantic import Field as FieldV2
|
||||
|
||||
class Foo(BaseModelV2):
|
||||
x: List[int] = FieldV2(
|
||||
@@ -163,18 +166,18 @@ def test_with_field_metadata() -> None:
|
||||
|
||||
@pytest.mark.skipif(PYDANTIC_MAJOR_VERSION != 1, reason="Only tests Pydantic v1")
|
||||
def test_fields_pydantic_v1() -> None:
|
||||
from pydantic import BaseModel # pydantic: ignore
|
||||
from pydantic import BaseModel
|
||||
|
||||
class Foo(BaseModel):
|
||||
x: int
|
||||
|
||||
fields = get_fields(Foo)
|
||||
assert fields == {"x": Foo.__fields__["x"]} # type: ignore[index]
|
||||
assert fields == {"x": Foo.model_fields["x"]} # type: ignore[index]
|
||||
|
||||
|
||||
@pytest.mark.skipif(PYDANTIC_MAJOR_VERSION != 2, reason="Only tests Pydantic v2")
|
||||
def test_fields_pydantic_v2_proper() -> None:
|
||||
from pydantic import BaseModel # pydantic: ignore
|
||||
from pydantic import BaseModel
|
||||
|
||||
class Foo(BaseModel):
|
||||
x: int
|
||||
@@ -185,10 +188,42 @@ def test_fields_pydantic_v2_proper() -> None:
|
||||
|
||||
@pytest.mark.skipif(PYDANTIC_MAJOR_VERSION != 2, reason="Only tests Pydantic v2")
|
||||
def test_fields_pydantic_v1_from_2() -> None:
|
||||
from pydantic.v1 import BaseModel # pydantic: ignore
|
||||
from pydantic.v1 import BaseModel
|
||||
|
||||
class Foo(BaseModel):
|
||||
x: int
|
||||
|
||||
fields = get_fields(Foo)
|
||||
assert fields == {"x": Foo.__fields__["x"]}
|
||||
|
||||
|
||||
def test_create_model_v2() -> None:
|
||||
"""Test that create model v2 works as expected."""
|
||||
|
||||
with pytest.warns(None) as record: # type: ignore
|
||||
foo = create_model_v2("Foo", field_definitions={"a": (int, None)})
|
||||
foo.model_json_schema()
|
||||
|
||||
assert list(record) == []
|
||||
|
||||
# schema is used by pydantic, but OK to re-use
|
||||
with pytest.warns(None) as record: # type: ignore
|
||||
foo = create_model_v2("Foo", field_definitions={"schema": (int, None)})
|
||||
foo.model_json_schema()
|
||||
|
||||
assert list(record) == []
|
||||
|
||||
# From protected namespaces, but definitely OK to use.
|
||||
with pytest.warns(None) as record: # type: ignore
|
||||
foo = create_model_v2("Foo", field_definitions={"model_id": (int, None)})
|
||||
foo.model_json_schema()
|
||||
|
||||
assert list(record) == []
|
||||
|
||||
with pytest.warns(None) as record: # type: ignore
|
||||
# Verify that we can use non-English characters
|
||||
field_name = "もしもし"
|
||||
foo = create_model_v2("Foo", field_definitions={field_name: (int, None)})
|
||||
foo.model_json_schema()
|
||||
|
||||
assert list(record) == []
|
||||
|
@@ -6,9 +6,9 @@ from typing import Any, Callable, Dict, Optional, Tuple, Type, Union
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from pydantic import SecretStr
|
||||
|
||||
from langchain_core import utils
|
||||
from langchain_core.pydantic_v1 import SecretStr
|
||||
from langchain_core.utils import (
|
||||
check_package_version,
|
||||
from_env,
|
||||
@@ -221,8 +221,8 @@ def test_guard_import_failure(
|
||||
|
||||
@pytest.mark.skipif(PYDANTIC_MAJOR_VERSION != 2, reason="Requires pydantic 2")
|
||||
def test_get_pydantic_field_names_v1_in_2() -> None:
|
||||
from pydantic.v1 import BaseModel as PydanticV1BaseModel # pydantic: ignore
|
||||
from pydantic.v1 import Field # pydantic: ignore
|
||||
from pydantic.v1 import BaseModel as PydanticV1BaseModel
|
||||
from pydantic.v1 import Field
|
||||
|
||||
class PydanticV1Model(PydanticV1BaseModel):
|
||||
field1: str
|
||||
@@ -236,7 +236,7 @@ def test_get_pydantic_field_names_v1_in_2() -> None:
|
||||
|
||||
@pytest.mark.skipif(PYDANTIC_MAJOR_VERSION != 2, reason="Requires pydantic 2")
|
||||
def test_get_pydantic_field_names_v2_in_2() -> None:
|
||||
from pydantic import BaseModel, Field # pydantic: ignore
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class PydanticModel(BaseModel):
|
||||
field1: str
|
||||
@@ -250,7 +250,7 @@ def test_get_pydantic_field_names_v2_in_2() -> None:
|
||||
|
||||
@pytest.mark.skipif(PYDANTIC_MAJOR_VERSION != 1, reason="Requires pydantic 1")
|
||||
def test_get_pydantic_field_names_v1() -> None:
|
||||
from pydantic import BaseModel, Field # pydantic: ignore
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class PydanticModel(BaseModel):
|
||||
field1: str
|
||||
@@ -365,7 +365,7 @@ def test_secret_from_env_with_custom_error_message(
|
||||
def test_using_secret_from_env_as_default_factory(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
from langchain_core.pydantic_v1 import BaseModel, Field
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class Foo(BaseModel):
|
||||
secret: SecretStr = Field(default_factory=secret_from_env("TEST_KEY"))
|
||||
|
Reference in New Issue
Block a user