core[major]: Add restrictions on create_model field names to match pydantic constraints (#26345)

Pydantic 2 is stricter in terms of which field names are allowed in
pydantic models.

This PR results in the following breaking changes:

These will raise ValueErrors: 

```python
ChatPromptTemplate([("system", "{_private}")]).get_input_schema()
ChatPromptTemplate([("system","{model_json_schema}")]).get_input_schema()
```

This PR should properly suppress warnings for the following cases:

```python
ChatPromptTemplate([("system", "{schema}")]).get_input_schema()
ChatPromptTemplate([("system","{model_id}")]).get_input_schema()
```
This commit is contained in:
Eugene Yurtsev
2024-09-12 11:34:16 -04:00
committed by GitHub
parent 6b24eeb884
commit bde3dbaed2
9 changed files with 422 additions and 210 deletions

View File

@@ -864,3 +864,34 @@ async def test_chat_tmpl_serdes(snapshot: SnapshotAssertion) -> None:
)
assert dumpd(template) == snapshot()
assert load(dumpd(template)) == template
def test_chat_prompt_template_variable_names() -> None:
"""This test was written for an edge case that triggers a warning from Pydantic.
Verify that no run time warnings are raised.
"""
with pytest.warns(None) as record: # type: ignore
prompt = ChatPromptTemplate([("system", "{schema}")])
prompt.get_input_schema()
if record:
error_msg = []
for warning in record:
error_msg.append(
f"Warning type: {warning.category.__name__}, "
f"Warning message: {warning.message}, "
f"Warning location: {warning.filename}:{warning.lineno}"
)
msg = "\n".join(error_msg)
else:
msg = ""
assert list(record) == [], msg
# Verify value errors raised from illegal names
with pytest.raises(ValueError):
ChatPromptTemplate([("system", "{_private}")]).get_input_schema()
with pytest.raises(ValueError):
ChatPromptTemplate([("system", "{model_json_schema}")]).get_input_schema()

View File

@@ -35,3 +35,9 @@ EXPECTED_ALL = [
def test_all_imports() -> None:
assert set(__all__) == set(EXPECTED_ALL)
def test_imports_for_specific_funcs() -> None:
"""Test that a few specific imports in more internal namespaces."""
# create_model implementation has been moved to langchain_core.utils.pydantic
from langchain_core.runnables.utils import create_model # noqa

View File

@@ -8,6 +8,7 @@ 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,
@@ -194,3 +195,44 @@ def test_fields_pydantic_v1_from_2() -> None:
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) == []
# Used by pydantic, not OK to re-use
with pytest.raises(ValueError):
create_model_v2("Foo", field_definitions={"model_json_schema": (int, None)})
# Private attributes raise an error for now since pydantic 2 considers them
# to be private attributes.
with pytest.raises(ValueError):
create_model_v2("Foo", field_definitions={"_a": (int, None)})
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) == []