chore(standard-tests): add mypy strict checking (#32384)

Co-authored-by: Mason Daugherty <mason@langchain.dev>
This commit is contained in:
Christophe Bornet
2025-09-08 16:50:38 +02:00
committed by GitHub
parent 0c3e8ccd0e
commit 323729915a
6 changed files with 1107 additions and 1092 deletions

View File

@@ -3,7 +3,7 @@
import gzip import gzip
from os import PathLike from os import PathLike
from pathlib import Path from pathlib import Path
from typing import Union from typing import Any, Union
import pytest import pytest
import yaml import yaml
@@ -36,7 +36,7 @@ class CustomSerializer:
def deserialize(data: bytes) -> dict: def deserialize(data: bytes) -> dict:
"""Decompress data and convert it from YAML.""" """Decompress data and convert it from YAML."""
text = gzip.decompress(data).decode("utf-8") text = gzip.decompress(data).decode("utf-8")
cassette = yaml.safe_load(text) cassette: dict[str, Any] = yaml.safe_load(text)
cassette["requests"] = [ cassette["requests"] = [
Request._from_dict(request) for request in cassette["requests"] Request._from_dict(request) for request in cassette["requests"]
] ]

View File

@@ -15,7 +15,6 @@ from langchain_core.messages import (
AIMessage, AIMessage,
AIMessageChunk, AIMessageChunk,
BaseMessage, BaseMessage,
BaseMessageChunk,
HumanMessage, HumanMessage,
SystemMessage, SystemMessage,
ToolMessage, ToolMessage,
@@ -67,8 +66,6 @@ def _get_joke_class(
if schema_type == "json_schema": if schema_type == "json_schema":
return Joke.model_json_schema(), validate_joke_dict return Joke.model_json_schema(), validate_joke_dict
msg = "Invalid schema type"
raise ValueError(msg)
class _TestCallbackHandler(BaseCallbackHandler): class _TestCallbackHandler(BaseCallbackHandler):
@@ -1381,7 +1378,7 @@ class ChatModelIntegrationTests(ChatModelTests):
_validate_tool_call_message(result) _validate_tool_call_message(result)
# Test stream # Test stream
full: Optional[BaseMessageChunk] = None full: Optional[BaseMessage] = None
for chunk in model_with_tools.stream(query): for chunk in model_with_tools.stream(query):
full = chunk if full is None else full + chunk # type: ignore[assignment] full = chunk if full is None else full + chunk # type: ignore[assignment]
assert isinstance(full, AIMessage) assert isinstance(full, AIMessage)
@@ -1443,7 +1440,7 @@ class ChatModelIntegrationTests(ChatModelTests):
_validate_tool_call_message(result) _validate_tool_call_message(result)
# Test astream # Test astream
full: Optional[BaseMessageChunk] = None full: Optional[BaseMessage] = None
async for chunk in model_with_tools.astream(query): async for chunk in model_with_tools.astream(query):
full = chunk if full is None else full + chunk # type: ignore[assignment] full = chunk if full is None else full + chunk # type: ignore[assignment]
assert isinstance(full, AIMessage) assert isinstance(full, AIMessage)
@@ -1791,7 +1788,7 @@ class ChatModelIntegrationTests(ChatModelTests):
result = model_with_tools.invoke(query) result = model_with_tools.invoke(query)
_validate_tool_call_message_no_args(result) _validate_tool_call_message_no_args(result)
full: Optional[BaseMessageChunk] = None full: Optional[BaseMessage] = None
for chunk in model_with_tools.stream(query): for chunk in model_with_tools.stream(query):
full = chunk if full is None else full + chunk # type: ignore[assignment] full = chunk if full is None else full + chunk # type: ignore[assignment]
assert isinstance(full, AIMessage) assert isinstance(full, AIMessage)
@@ -1932,7 +1929,11 @@ class ChatModelIntegrationTests(ChatModelTests):
assert isinstance(result, AIMessage) assert isinstance(result, AIMessage)
@pytest.mark.parametrize("schema_type", ["pydantic", "typeddict", "json_schema"]) @pytest.mark.parametrize("schema_type", ["pydantic", "typeddict", "json_schema"])
def test_structured_output(self, model: BaseChatModel, schema_type: str) -> None: def test_structured_output(
self,
model: BaseChatModel,
schema_type: Literal["pydantic", "typeddict", "json_schema"],
) -> None:
"""Test to verify structured output is generated both on invoke and stream. """Test to verify structured output is generated both on invoke and stream.
This test is optional and should be skipped if the model does not support This test is optional and should be skipped if the model does not support
@@ -1968,7 +1969,7 @@ class ChatModelIntegrationTests(ChatModelTests):
if not self.has_structured_output: if not self.has_structured_output:
pytest.skip("Test requires structured output.") pytest.skip("Test requires structured output.")
schema, validation_function = _get_joke_class(schema_type) # type: ignore[arg-type] schema, validation_function = _get_joke_class(schema_type)
chat = model.with_structured_output(schema, **self.structured_output_kwargs) chat = model.with_structured_output(schema, **self.structured_output_kwargs)
mock_callback = MagicMock() mock_callback = MagicMock()
mock_callback.on_chat_model_start = MagicMock() mock_callback.on_chat_model_start = MagicMock()
@@ -2012,7 +2013,9 @@ class ChatModelIntegrationTests(ChatModelTests):
@pytest.mark.parametrize("schema_type", ["pydantic", "typeddict", "json_schema"]) @pytest.mark.parametrize("schema_type", ["pydantic", "typeddict", "json_schema"])
async def test_structured_output_async( async def test_structured_output_async(
self, model: BaseChatModel, schema_type: str self,
model: BaseChatModel,
schema_type: Literal["pydantic", "typeddict", "json_schema"],
) -> None: ) -> None:
"""Test to verify structured output is generated both on invoke and stream. """Test to verify structured output is generated both on invoke and stream.
@@ -2049,7 +2052,7 @@ class ChatModelIntegrationTests(ChatModelTests):
if not self.has_structured_output: if not self.has_structured_output:
pytest.skip("Test requires structured output.") pytest.skip("Test requires structured output.")
schema, validation_function = _get_joke_class(schema_type) # type: ignore[arg-type] schema, validation_function = _get_joke_class(schema_type)
chat = model.with_structured_output(schema, **self.structured_output_kwargs) chat = model.with_structured_output(schema, **self.structured_output_kwargs)
ainvoke_callback = _TestCallbackHandler() ainvoke_callback = _TestCallbackHandler()
@@ -2269,9 +2272,6 @@ class ChatModelIntegrationTests(ChatModelTests):
punchline: str = FieldProper(description="answer to resolve the joke") punchline: str = FieldProper(description="answer to resolve the joke")
# Pydantic class # Pydantic class
# Type ignoring since the interface only officially supports pydantic 1
# or pydantic.v1.BaseModel but not pydantic.BaseModel from pydantic 2.
# We'll need to do a pass updating the type signatures.
chat = model.with_structured_output(Joke, method="json_mode") chat = model.with_structured_output(Joke, method="json_mode")
msg = ( msg = (
"Tell me a joke about cats. Return the result as a JSON with 'setup' and " "Tell me a joke about cats. Return the result as a JSON with 'setup' and "

View File

@@ -4,7 +4,7 @@ from abc import abstractmethod
import pytest import pytest
from langchain_core.documents import Document from langchain_core.documents import Document
from langchain_core.embeddings.fake import DeterministicFakeEmbedding, Embeddings from langchain_core.embeddings import DeterministicFakeEmbedding, Embeddings
from langchain_core.vectorstores import VectorStore from langchain_core.vectorstores import VectorStore
from langchain_tests.base import BaseStandardTests from langchain_tests.base import BaseStandardTests

View File

@@ -1022,8 +1022,8 @@ class ChatModelUnitTests(ChatModelTests):
# Test optional params # Test optional params
model = self.chat_model_class( model = self.chat_model_class(
max_tokens=10, # type: ignore[call-arg] max_tokens=10,
stop=["test"], # type: ignore[call-arg] stop=["test"],
**self.chat_model_params, **self.chat_model_params,
) )
ls_params = model._get_ls_params() ls_params = model._get_ls_params()

View File

@@ -32,25 +32,29 @@ repository = "https://github.com/langchain-ai/langchain"
[dependency-groups] [dependency-groups]
test = ["langchain-core"] test = ["langchain-core"]
test_integration = [] test_integration = []
lint = ["ruff<0.13,>=0.12.10"] lint = ["ruff<0.13,>=0.12.10"]
typing = ["mypy<2,>=1.17.1", "langchain-core"] typing = [
"mypy<1.18,>=1.17.1",
"types-pyyaml<7.0.0.0,>=6.0.12.2",
"langchain-core",
]
[tool.uv.sources] [tool.uv.sources]
langchain-core = { path = "../core", editable = true } langchain-core = { path = "../core", editable = true }
[tool.mypy] [tool.mypy]
disallow_untyped_defs = "True" plugins = ["pydantic.mypy"]
strict = true
enable_error_code = "deprecated"
warn_unreachable = true
# TODO: activate for 'strict' checking
disallow_any_generics = false
[[tool.mypy.overrides]] [[tool.mypy.overrides]]
module = "yaml" module = ["vcr.*",]
ignore_missing_imports = true ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "vcr.*"
ignore_missing_imports = true
[tool.ruff] [tool.ruff]
target-version = "py39" target-version = "py39"

File diff suppressed because it is too large Load Diff