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

View File

@@ -15,7 +15,6 @@ from langchain_core.messages import (
AIMessage,
AIMessageChunk,
BaseMessage,
BaseMessageChunk,
HumanMessage,
SystemMessage,
ToolMessage,
@@ -67,8 +66,6 @@ def _get_joke_class(
if schema_type == "json_schema":
return Joke.model_json_schema(), validate_joke_dict
msg = "Invalid schema type"
raise ValueError(msg)
class _TestCallbackHandler(BaseCallbackHandler):
@@ -1381,7 +1378,7 @@ class ChatModelIntegrationTests(ChatModelTests):
_validate_tool_call_message(result)
# Test stream
full: Optional[BaseMessageChunk] = None
full: Optional[BaseMessage] = None
for chunk in model_with_tools.stream(query):
full = chunk if full is None else full + chunk # type: ignore[assignment]
assert isinstance(full, AIMessage)
@@ -1443,7 +1440,7 @@ class ChatModelIntegrationTests(ChatModelTests):
_validate_tool_call_message(result)
# Test astream
full: Optional[BaseMessageChunk] = None
full: Optional[BaseMessage] = None
async for chunk in model_with_tools.astream(query):
full = chunk if full is None else full + chunk # type: ignore[assignment]
assert isinstance(full, AIMessage)
@@ -1791,7 +1788,7 @@ class ChatModelIntegrationTests(ChatModelTests):
result = model_with_tools.invoke(query)
_validate_tool_call_message_no_args(result)
full: Optional[BaseMessageChunk] = None
full: Optional[BaseMessage] = None
for chunk in model_with_tools.stream(query):
full = chunk if full is None else full + chunk # type: ignore[assignment]
assert isinstance(full, AIMessage)
@@ -1932,7 +1929,11 @@ class ChatModelIntegrationTests(ChatModelTests):
assert isinstance(result, AIMessage)
@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.
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:
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)
mock_callback = MagicMock()
mock_callback.on_chat_model_start = MagicMock()
@@ -2012,7 +2013,9 @@ class ChatModelIntegrationTests(ChatModelTests):
@pytest.mark.parametrize("schema_type", ["pydantic", "typeddict", "json_schema"])
async def test_structured_output_async(
self, model: BaseChatModel, schema_type: str
self,
model: BaseChatModel,
schema_type: Literal["pydantic", "typeddict", "json_schema"],
) -> None:
"""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:
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)
ainvoke_callback = _TestCallbackHandler()
@@ -2269,9 +2272,6 @@ class ChatModelIntegrationTests(ChatModelTests):
punchline: str = FieldProper(description="answer to resolve the joke")
# 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")
msg = (
"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
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_tests.base import BaseStandardTests

View File

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

View File

@@ -32,25 +32,29 @@ repository = "https://github.com/langchain-ai/langchain"
[dependency-groups]
test = ["langchain-core"]
test_integration = []
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]
langchain-core = { path = "../core", editable = true }
[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]]
module = "yaml"
module = ["vcr.*",]
ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "vcr.*"
ignore_missing_imports = true
[tool.ruff]
target-version = "py39"

File diff suppressed because it is too large Load Diff