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:
Erick Friis
2024-09-13 14:38:45 -07:00
committed by GitHub
parent d9813bdbbc
commit c2a3021bb0
1402 changed files with 38318 additions and 30410 deletions

View File

@@ -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)

View File

@@ -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) == []

View File

@@ -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"))