chore(standard-tests): add ruff rules D (#32347)

See https://docs.astral.sh/ruff/rules/#pydocstyle-d

Co-authored-by: Mason Daugherty <mason@langchain.dev>
This commit is contained in:
Christophe Bornet 2025-08-12 00:26:11 +02:00 committed by GitHub
parent 46bbd52e81
commit 09a616fe85
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 263 additions and 92 deletions

View File

@ -1,8 +1,13 @@
"""Standard tests."""
from abc import ABC from abc import ABC
class BaseStandardTests(ABC): class BaseStandardTests(ABC):
""":private:""" """Base class for standard tests.
:private:
"""
def test_no_overrides_DO_NOT_OVERRIDE(self) -> None: def test_no_overrides_DO_NOT_OVERRIDE(self) -> None:
"""Test that no standard tests are overridden. """Test that no standard tests are overridden.

View File

@ -1,3 +1,5 @@
"""Pytest conftest."""
import gzip import gzip
from os import PathLike from os import PathLike
from pathlib import Path from pathlib import Path
@ -88,7 +90,7 @@ _BASE_FILTER_HEADERS = [
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def _base_vcr_config() -> dict: def _base_vcr_config() -> dict:
"""Get configuration that every cassette will receive. """Return VCR configuration that every cassette will receive.
(Anything permitted by ``vcr.VCR(**kwargs)`` can be put here.) (Anything permitted by ``vcr.VCR(**kwargs)`` can be put here.)
""" """
@ -105,4 +107,5 @@ def _base_vcr_config() -> dict:
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def vcr_config(_base_vcr_config: dict) -> dict: def vcr_config(_base_vcr_config: dict) -> dict:
"""VCR config fixture."""
return _base_vcr_config return _base_vcr_config

View File

@ -1,3 +1,5 @@
"""Integration tests for LangChain components."""
# ruff: noqa: E402 # ruff: noqa: E402
import pytest import pytest

View File

@ -62,7 +62,9 @@ class BaseStoreSyncTests(BaseStandardTests, Generic[V]):
assert kv_store.mget(["foo", "bar"]) == [foo, bar] assert kv_store.mget(["foo", "bar"]) == [foo, bar]
def test_store_still_empty(self, kv_store: BaseStore[str, V]) -> None: def test_store_still_empty(self, kv_store: BaseStore[str, V]) -> None:
"""This test should follow a test that sets values. """Test that the store is still empty.
This test should follow a test that sets values.
This just verifies that the fixture is set up properly to be empty This just verifies that the fixture is set up properly to be empty
after each test. after each test.
@ -192,7 +194,9 @@ class BaseStoreAsyncTests(BaseStandardTests, Generic[V]):
assert await kv_store.amget(["foo", "bar"]) == [foo, bar] assert await kv_store.amget(["foo", "bar"]) == [foo, bar]
async def test_store_still_empty(self, kv_store: BaseStore[str, V]) -> None: async def test_store_still_empty(self, kv_store: BaseStore[str, V]) -> None:
"""This test should follow a test that sets values. """Test that the store is still empty.
This test should follow a test that sets values.
This just verifies that the fixture is set up properly to be empty This just verifies that the fixture is set up properly to be empty
after each test. after each test.

View File

@ -62,7 +62,9 @@ class SyncCacheTestSuite(BaseStandardTests):
assert cache.lookup(prompt, llm_string) == [generation] assert cache.lookup(prompt, llm_string) == [generation]
def test_cache_still_empty(self, cache: BaseCache) -> None: def test_cache_still_empty(self, cache: BaseCache) -> None:
"""This test should follow a test that updates the cache. """Test that the cache is still empty.
This test should follow a test that updates the cache.
This just verifies that the fixture is set up properly to be empty This just verifies that the fixture is set up properly to be empty
after each test. after each test.
@ -153,7 +155,9 @@ class AsyncCacheTestSuite(BaseStandardTests):
assert await cache.alookup(prompt, llm_string) == [generation] assert await cache.alookup(prompt, llm_string) == [generation]
async def test_cache_still_empty(self, cache: BaseCache) -> None: async def test_cache_still_empty(self, cache: BaseCache) -> None:
"""This test should follow a test that updates the cache. """Test that the cache is still empty.
This test should follow a test that updates the cache.
This just verifies that the fixture is set up properly to be empty This just verifies that the fixture is set up properly to be empty
after each test. after each test.

View File

@ -1,3 +1,5 @@
"""Integration tests for chat models."""
import base64 import base64
import inspect import inspect
import json import json
@ -39,8 +41,6 @@ from langchain_tests.utils.pydantic import PYDANTIC_MAJOR_VERSION
def _get_joke_class( def _get_joke_class(
schema_type: Literal["pydantic", "typeddict", "json_schema"], schema_type: Literal["pydantic", "typeddict", "json_schema"],
) -> Any: ) -> Any:
""":private:"""
class Joke(BaseModel): class Joke(BaseModel):
"""Joke to tell user.""" """Joke to tell user."""
@ -95,13 +95,13 @@ class _MagicFunctionSchema(BaseModel):
@tool(args_schema=_MagicFunctionSchema) @tool(args_schema=_MagicFunctionSchema)
def magic_function(_input: int) -> int: def magic_function(_input: int) -> int:
"""Applies a magic function to an input.""" """Apply a magic function to an input."""
return _input + 2 return _input + 2
@tool @tool
def magic_function_no_args() -> int: def magic_function_no_args() -> int:
"""Calculates a magic function.""" """Calculate a magic function."""
return 5 return 5
@ -143,7 +143,7 @@ def unicode_customer(customer_name: str, description: str) -> str:
class ChatModelIntegrationTests(ChatModelTests): class ChatModelIntegrationTests(ChatModelTests):
"""Base class for chat model integration tests. '''Base class for chat model integration tests.
Test subclasses must implement the ``chat_model_class`` and Test subclasses must implement the ``chat_model_class`` and
``chat_model_params`` properties to specify what model to test and its ``chat_model_params`` properties to specify what model to test and its
@ -587,7 +587,7 @@ class ChatModelIntegrationTests(ChatModelTests):
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def vcr_config(_base_vcr_config: dict) -> dict: # noqa: F811 def vcr_config(_base_vcr_config: dict) -> dict: # noqa: F811
\"\"\"Extend the default configuration from langchain_tests.\"\"\" """Extend the default configuration from langchain_tests."""
config = _base_vcr_config.copy() config = _base_vcr_config.copy()
config.setdefault("filter_headers", []).extend(_EXTRA_HEADERS) config.setdefault("filter_headers", []).extend(_EXTRA_HEADERS)
config["before_record_response"] = remove_response_headers config["before_record_response"] = remove_response_headers
@ -623,7 +623,7 @@ class ChatModelIntegrationTests(ChatModelTests):
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def vcr_config(_base_vcr_config: dict) -> dict: # noqa: F811 def vcr_config(_base_vcr_config: dict) -> dict: # noqa: F811
\"\"\"Extend the default configuration from langchain_tests.\"\"\" """Extend the default configuration from langchain_tests."""
config = _base_vcr_config.copy() config = _base_vcr_config.copy()
config.setdefault("filter_headers", []).extend(_EXTRA_HEADERS) config.setdefault("filter_headers", []).extend(_EXTRA_HEADERS)
config["before_record_response"] = remove_response_headers config["before_record_response"] = remove_response_headers
@ -675,11 +675,14 @@ class ChatModelIntegrationTests(ChatModelTests):
You can then commit the cassette to your repository. Subsequent test runs You can then commit the cassette to your repository. Subsequent test runs
will use the cassette instead of making HTTP calls. will use the cassette instead of making HTTP calls.
""" # noqa: E501 ''' # noqa: E501,D214
@property @property
def standard_chat_model_params(self) -> dict: def standard_chat_model_params(self) -> dict:
""":private:""" """Standard parameters for chat model.
:private:
"""
return {} return {}
def test_invoke(self, model: BaseChatModel) -> None: def test_invoke(self, model: BaseChatModel) -> None:
@ -1270,8 +1273,10 @@ class ChatModelIntegrationTests(ChatModelTests):
) )
def test_stop_sequence(self, model: BaseChatModel) -> None: def test_stop_sequence(self, model: BaseChatModel) -> None:
"""Test that model does not fail when invoked with the ``stop`` parameter, """Test that model does not fail when invoked with the ``stop`` parameter.
which is a standard parameter for stopping generation at a certain token.
The ``stop`` parameter is a standard parameter for stopping generation at a
certain token.
`More on standard parameters <https://python.langchain.com/docs/concepts/chat_models/#standard-parameters>`__ `More on standard parameters <https://python.langchain.com/docs/concepts/chat_models/#standard-parameters>`__
@ -1306,8 +1311,10 @@ class ChatModelIntegrationTests(ChatModelTests):
assert isinstance(result, AIMessage) assert isinstance(result, AIMessage)
def test_tool_calling(self, model: BaseChatModel) -> None: def test_tool_calling(self, model: BaseChatModel) -> None:
"""Test that the model generates tool calls. This test is skipped if the """Test that the model generates tool calls.
``has_tool_calling`` property on the test class is set to False.
This test is skipped if the ``has_tool_calling`` property on the test class is
set to False.
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
tool calling (see Configuration below). tool calling (see Configuration below).
@ -1381,8 +1388,10 @@ class ChatModelIntegrationTests(ChatModelTests):
_validate_tool_call_message(full) _validate_tool_call_message(full)
async def test_tool_calling_async(self, model: BaseChatModel) -> None: async def test_tool_calling_async(self, model: BaseChatModel) -> None:
"""Test that the model generates tool calls. This test is skipped if the """Test that the model generates tool calls.
``has_tool_calling`` property on the test class is set to False.
This test is skipped if the ``has_tool_calling`` property on the test class is
set to False.
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
tool calling (see Configuration below). tool calling (see Configuration below).
@ -1441,7 +1450,9 @@ class ChatModelIntegrationTests(ChatModelTests):
_validate_tool_call_message(full) _validate_tool_call_message(full)
def test_bind_runnables_as_tools(self, model: BaseChatModel) -> None: def test_bind_runnables_as_tools(self, model: BaseChatModel) -> None:
"""Test that the model generates tool calls for tools that are derived from """Test bind runnables as tools.
Test that the model generates tool calls for tools that are derived from
LangChain runnables. This test is skipped if the ``has_tool_calling`` property LangChain runnables. This test is skipped if the ``has_tool_calling`` property
on the test class is set to False. on the test class is set to False.
@ -1509,8 +1520,10 @@ class ChatModelIntegrationTests(ChatModelTests):
def test_tool_message_histories_string_content( def test_tool_message_histories_string_content(
self, model: BaseChatModel, my_adder_tool: BaseTool self, model: BaseChatModel, my_adder_tool: BaseTool
) -> None: ) -> None:
"""Test that message histories are compatible with string tool contents """Test that message histories are compatible with string tool contents.
(e.g. OpenAI format). If a model passes this test, it should be compatible
For instance with OpenAI format contents.
If a model passes this test, it should be compatible
with messages generated from providers following OpenAI format. with messages generated from providers following OpenAI format.
This test should be skipped if the model does not support tool calling This test should be skipped if the model does not support tool calling
@ -1581,8 +1594,9 @@ class ChatModelIntegrationTests(ChatModelTests):
model: BaseChatModel, model: BaseChatModel,
my_adder_tool: BaseTool, my_adder_tool: BaseTool,
) -> None: ) -> None:
"""Test that message histories are compatible with list tool contents """Test that message histories are compatible with list tool contents.
(e.g. Anthropic format).
For instance with Anthropic format contents.
These message histories will include ``AIMessage`` objects with "tool use" and These message histories will include ``AIMessage`` objects with "tool use" and
content blocks, e.g., content blocks, e.g.,
@ -1671,7 +1685,9 @@ class ChatModelIntegrationTests(ChatModelTests):
assert isinstance(result_list_content, AIMessage) assert isinstance(result_list_content, AIMessage)
def test_tool_choice(self, model: BaseChatModel) -> None: def test_tool_choice(self, model: BaseChatModel) -> None:
"""Test that the model can force tool calling via the ``tool_choice`` """Test ``tool_choice`` parameter.
Test that the model can force tool calling via the ``tool_choice``
parameter. This test is skipped if the ``has_tool_choice`` property on the parameter. This test is skipped if the ``has_tool_choice`` property on the
test class is set to False. test class is set to False.
@ -1723,6 +1739,7 @@ class ChatModelIntegrationTests(ChatModelTests):
def test_tool_calling_with_no_arguments(self, model: BaseChatModel) -> None: def test_tool_calling_with_no_arguments(self, model: BaseChatModel) -> None:
"""Test that the model generates tool calls for tools with no arguments. """Test that the model generates tool calls for tools with no arguments.
This test is skipped if the ``has_tool_calling`` property on the test class This test is skipped if the ``has_tool_calling`` property on the test class
is set to False. is set to False.
@ -2075,7 +2092,9 @@ class ChatModelIntegrationTests(ChatModelTests):
@pytest.mark.skipif(PYDANTIC_MAJOR_VERSION != 2, reason="Test requires pydantic 2.") @pytest.mark.skipif(PYDANTIC_MAJOR_VERSION != 2, reason="Test requires pydantic 2.")
def test_structured_output_pydantic_2_v1(self, model: BaseChatModel) -> None: def test_structured_output_pydantic_2_v1(self, model: BaseChatModel) -> None:
"""Test to verify we can generate structured output using ``pydantic.v1.BaseModel``. """Test structured output using pydantic.v1.BaseModel.
Verify we can generate structured output using ``pydantic.v1.BaseModel``.
``pydantic.v1.BaseModel`` is available in the Pydantic 2 package. ``pydantic.v1.BaseModel`` is available in the Pydantic 2 package.
@ -2140,7 +2159,9 @@ class ChatModelIntegrationTests(ChatModelTests):
assert set(chunk.keys()) == {"setup", "punchline"} assert set(chunk.keys()) == {"setup", "punchline"}
def test_structured_output_optional_param(self, model: BaseChatModel) -> None: def test_structured_output_optional_param(self, model: BaseChatModel) -> None:
"""Test to verify we can generate structured output that includes optional """Test structured output with optional parameters.
Test to verify we can generate structured output that includes optional
parameters. parameters.
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
@ -2847,8 +2868,10 @@ class ChatModelIntegrationTests(ChatModelTests):
assert len(result.content) > 0 assert len(result.content) > 0
def test_agent_loop(self, model: BaseChatModel) -> None: def test_agent_loop(self, model: BaseChatModel) -> None:
"""Test that the model supports a simple ReAct agent loop. This test is skipped """Test that the model supports a simple ReAct agent loop.
if the ``has_tool_calling`` property on the test class is set to False.
This test is skipped if the ``has_tool_calling`` property on the test class is
set to False.
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
tool calling (see Configuration below). tool calling (see Configuration below).
@ -2953,23 +2976,38 @@ class ChatModelIntegrationTests(ChatModelTests):
benchmark(_run) benchmark(_run)
def invoke_with_audio_input(self, *, stream: bool = False) -> AIMessage: def invoke_with_audio_input(self, *, stream: bool = False) -> AIMessage:
""":private:""" """Invoke with audio input.
:private:
"""
raise NotImplementedError raise NotImplementedError
def invoke_with_audio_output(self, *, stream: bool = False) -> AIMessage: def invoke_with_audio_output(self, *, stream: bool = False) -> AIMessage:
""":private:""" """Invoke with audio output.
:private:
"""
raise NotImplementedError raise NotImplementedError
def invoke_with_reasoning_output(self, *, stream: bool = False) -> AIMessage: def invoke_with_reasoning_output(self, *, stream: bool = False) -> AIMessage:
""":private:""" """Invoke with reasoning output.
:private:
"""
raise NotImplementedError raise NotImplementedError
def invoke_with_cache_read_input(self, *, stream: bool = False) -> AIMessage: def invoke_with_cache_read_input(self, *, stream: bool = False) -> AIMessage:
""":private:""" """Invoke with cache read input.
:private:
"""
raise NotImplementedError raise NotImplementedError
def invoke_with_cache_creation_input(self, *, stream: bool = False) -> AIMessage: def invoke_with_cache_creation_input(self, *, stream: bool = False) -> AIMessage:
""":private:""" """Invoke with cache creation input.
:private:
"""
raise NotImplementedError raise NotImplementedError
def test_unicode_tool_call_integration( def test_unicode_tool_call_integration(
@ -2979,7 +3017,7 @@ class ChatModelIntegrationTests(ChatModelTests):
tool_choice: Optional[str] = None, tool_choice: Optional[str] = None,
force_tool_call: bool = True, force_tool_call: bool = True,
) -> None: ) -> None:
"""Generic integration test for Unicode characters in tool calls. r"""Generic integration test for Unicode characters in tool calls.
Args: Args:
model: The chat model to test model: The chat model to test

View File

@ -1,3 +1,5 @@
"""Integration tests for embeddings."""
from langchain_core.embeddings import Embeddings from langchain_core.embeddings import Embeddings
from langchain_tests.unit_tests.embeddings import EmbeddingsTests from langchain_tests.unit_tests.embeddings import EmbeddingsTests

View File

@ -1,3 +1,5 @@
"""Integration tests for retrievers."""
from abc import abstractmethod from abc import abstractmethod
import pytest import pytest
@ -37,7 +39,10 @@ class RetrieversIntegrationTests(BaseStandardTests):
@pytest.fixture @pytest.fixture
def retriever(self) -> BaseRetriever: def retriever(self) -> BaseRetriever:
""":private:""" """Return retriever fixture.
:private:
"""
return self.retriever_constructor(**self.retriever_constructor_params) return self.retriever_constructor(**self.retriever_constructor_params)
def test_k_constructor_param(self) -> None: def test_k_constructor_param(self) -> None:
@ -148,7 +153,9 @@ class RetrieversIntegrationTests(BaseStandardTests):
assert all(isinstance(doc, Document) for doc in result_3) assert all(isinstance(doc, Document) for doc in result_3)
def test_invoke_returns_documents(self, retriever: BaseRetriever) -> None: def test_invoke_returns_documents(self, retriever: BaseRetriever) -> None:
"""If invoked with the example params, the retriever should return a list of """Test invoke returns documents.
If invoked with the example params, the retriever should return a list of
Documents. Documents.
.. dropdown:: Troubleshooting .. dropdown:: Troubleshooting
@ -163,7 +170,9 @@ class RetrieversIntegrationTests(BaseStandardTests):
assert all(isinstance(doc, Document) for doc in result) assert all(isinstance(doc, Document) for doc in result)
async def test_ainvoke_returns_documents(self, retriever: BaseRetriever) -> None: async def test_ainvoke_returns_documents(self, retriever: BaseRetriever) -> None:
"""If ainvoked with the example params, the retriever should return a list of """Test ainvoke returns documents.
If ainvoked with the example params, the retriever should return a list of
Documents. Documents.
See :meth:`test_invoke_returns_documents` for more information on See :meth:`test_invoke_returns_documents` for more information on

View File

@ -1,3 +1,5 @@
"""Integration tests for tools."""
from langchain_core.messages import ToolCall from langchain_core.messages import ToolCall
from langchain_core.tools import BaseTool from langchain_core.tools import BaseTool
@ -66,7 +68,9 @@ class ToolsIntegrationTests(ToolsTests):
assert all(isinstance(c, (str, dict)) for c in tool_message.content) assert all(isinstance(c, (str, dict)) for c in tool_message.content)
def test_invoke_no_tool_call(self, tool: BaseTool) -> None: def test_invoke_no_tool_call(self, tool: BaseTool) -> None:
"""If invoked without a ToolCall, the tool can return anything """Test invoke without ToolCall.
If invoked without a ToolCall, the tool can return anything
but it shouldn't throw an error. but it shouldn't throw an error.
If this test fails, your tool may not be handling the input you defined If this test fails, your tool may not be handling the input you defined
@ -78,7 +82,9 @@ class ToolsIntegrationTests(ToolsTests):
tool.invoke(self.tool_invoke_params_example) tool.invoke(self.tool_invoke_params_example)
async def test_async_invoke_no_tool_call(self, tool: BaseTool) -> None: async def test_async_invoke_no_tool_call(self, tool: BaseTool) -> None:
"""If ainvoked without a ToolCall, the tool can return anything """Test async invoke without ToolCall.
If ainvoked without a ToolCall, the tool can return anything
but it shouldn't throw an error. but it shouldn't throw an error.
For debugging tips, see :meth:`test_invoke_no_tool_call`. For debugging tips, see :meth:`test_invoke_no_tool_call`.

View File

@ -116,7 +116,9 @@ class VectorStoreIntegrationTests(BaseStandardTests):
@staticmethod @staticmethod
def get_embeddings() -> Embeddings: def get_embeddings() -> Embeddings:
"""A pre-defined embeddings model that should be used for this test. """Get embeddings.
A pre-defined embeddings model that should be used for this test.
This currently uses ``DeterministicFakeEmbedding`` from ``langchain-core``, This currently uses ``DeterministicFakeEmbedding`` from ``langchain-core``,
which uses numpy to generate random numbers based on a hash of the input text. which uses numpy to generate random numbers based on a hash of the input text.
@ -173,7 +175,9 @@ class VectorStoreIntegrationTests(BaseStandardTests):
] ]
def test_vectorstore_still_empty(self, vectorstore: VectorStore) -> None: def test_vectorstore_still_empty(self, vectorstore: VectorStore) -> None:
"""This test should follow a test that adds documents. """Test that the vectorstore is still empty.
This test should follow a test that adds documents.
This just verifies that the fixture is set up properly to be empty This just verifies that the fixture is set up properly to be empty
after each test. after each test.
@ -499,7 +503,9 @@ class VectorStoreIntegrationTests(BaseStandardTests):
async def test_vectorstore_still_empty_async( async def test_vectorstore_still_empty_async(
self, vectorstore: VectorStore self, vectorstore: VectorStore
) -> None: ) -> None:
"""This test should follow a test that adds documents. """Test that the vectorstore is still empty.
This test should follow a test that adds documents.
This just verifies that the fixture is set up properly to be empty This just verifies that the fixture is set up properly to be empty
after each test. after each test.

View File

@ -1,3 +1,5 @@
"""Unit tests for LangChain components."""
# ruff: noqa: E402 # ruff: noqa: E402
import pytest import pytest

View File

@ -83,7 +83,10 @@ class ChatModelTests(BaseStandardTests):
@property @property
def standard_chat_model_params(self) -> dict: def standard_chat_model_params(self) -> dict:
""":private:""" """Standard chat model parameters.
:private:
"""
return { return {
"temperature": 0, "temperature": 0,
"max_tokens": 100, "max_tokens": 100,
@ -94,7 +97,10 @@ class ChatModelTests(BaseStandardTests):
@pytest.fixture @pytest.fixture
def model(self) -> BaseChatModel: def model(self) -> BaseChatModel:
""":private:""" """Model fixture.
:private:
"""
return self.chat_model_class( return self.chat_model_class(
**{ **{
**self.standard_chat_model_params, **self.standard_chat_model_params,
@ -104,11 +110,17 @@ class ChatModelTests(BaseStandardTests):
@pytest.fixture @pytest.fixture
def my_adder_tool(self) -> BaseTool: def my_adder_tool(self) -> BaseTool:
""":private:""" """Adder tool fixture.
:private:
"""
@tool @tool
def my_adder_tool(a: int, b: int) -> int: def my_adder_tool(a: int, b: int) -> int:
"""Takes two integers, a and b, and returns their sum.""" """Tool that adds two integers.
Takes two integers, a and b, and returns their sum.
"""
return a + b return a + b
return my_adder_tool return my_adder_tool
@ -151,12 +163,22 @@ class ChatModelTests(BaseStandardTests):
@property @property
def supports_image_inputs(self) -> bool: def supports_image_inputs(self) -> bool:
"""(bool) whether the chat model supports image inputs, defaults to ``False``.""" # noqa: E501 """Supports image inputs.
(bool) whether the chat model supports image inputs, defaults to
``False``.
"""
return False return False
@property @property
def supports_image_urls(self) -> bool: def supports_image_urls(self) -> bool:
"""(bool) whether the chat model supports image inputs from URLs, defaults to ``False``.""" # noqa: E501 """Supports image inputs from URLs.
(bool) whether the chat model supports image inputs from URLs, defaults to
``False``.
"""
return False return False
@property @property
@ -166,12 +188,18 @@ class ChatModelTests(BaseStandardTests):
@property @property
def supports_audio_inputs(self) -> bool: def supports_audio_inputs(self) -> bool:
"""(bool) whether the chat model supports audio inputs, defaults to ``False``.""" # noqa: E501 """Supports audio inputs.
(bool) whether the chat model supports audio inputs, defaults to ``False``.
"""
return False return False
@property @property
def supports_video_inputs(self) -> bool: def supports_video_inputs(self) -> bool:
"""(bool) whether the chat model supports video inputs, defaults to ``False``. """Supports video inputs.
(bool) whether the chat model supports video inputs, defaults to ``False``.
No current tests are written for this feature. No current tests are written for this feature.
@ -180,7 +208,12 @@ class ChatModelTests(BaseStandardTests):
@property @property
def returns_usage_metadata(self) -> bool: def returns_usage_metadata(self) -> bool:
"""(bool) whether the chat model returns usage metadata on invoke and streaming responses.""" # noqa: E501 """Returns usage metadata.
(bool) whether the chat model returns usage metadata on invoke and streaming
responses.
"""
return True return True
@property @property
@ -190,7 +223,12 @@ class ChatModelTests(BaseStandardTests):
@property @property
def supports_image_tool_message(self) -> bool: def supports_image_tool_message(self) -> bool:
"""(bool) whether the chat model supports ``ToolMessage``s that include image content.""" # noqa: E501 """Supports image ToolMessages.
(bool) whether the chat model supports ToolMessages that include image
content.
"""
return False return False
@property @property
@ -219,14 +257,16 @@ class ChatModelTests(BaseStandardTests):
] ]
], ],
]: ]:
"""(dict) what usage metadata details are emitted in invoke and stream. Only """Supported usage metadata details.
(dict) what usage metadata details are emitted in invoke and stream. Only
needs to be overridden if these details are returned by the model. needs to be overridden if these details are returned by the model.
""" """
return {"invoke": [], "stream": []} return {"invoke": [], "stream": []}
class ChatModelUnitTests(ChatModelTests): class ChatModelUnitTests(ChatModelTests):
"""Base class for chat model unit tests. '''Base class for chat model unit tests.
Test subclasses must implement the ``chat_model_class`` and Test subclasses must implement the ``chat_model_class`` and
``chat_model_params`` properties to specify what model to test and its ``chat_model_params`` properties to specify what model to test and its
@ -669,7 +709,7 @@ class ChatModelUnitTests(ChatModelTests):
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def vcr_config(_base_vcr_config: dict) -> dict: # noqa: F811 def vcr_config(_base_vcr_config: dict) -> dict: # noqa: F811
\"\"\"Extend the default configuration from langchain_tests.\"\"\" """Extend the default configuration from langchain_tests."""
config = _base_vcr_config.copy() config = _base_vcr_config.copy()
config.setdefault("filter_headers", []).extend(_EXTRA_HEADERS) config.setdefault("filter_headers", []).extend(_EXTRA_HEADERS)
config["before_record_response"] = remove_response_headers config["before_record_response"] = remove_response_headers
@ -705,7 +745,7 @@ class ChatModelUnitTests(ChatModelTests):
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def vcr_config(_base_vcr_config: dict) -> dict: # noqa: F811 def vcr_config(_base_vcr_config: dict) -> dict: # noqa: F811
\"\"\"Extend the default configuration from langchain_tests.\"\"\" """Extend the default configuration from langchain_tests."""
config = _base_vcr_config.copy() config = _base_vcr_config.copy()
config.setdefault("filter_headers", []).extend(_EXTRA_HEADERS) config.setdefault("filter_headers", []).extend(_EXTRA_HEADERS)
config["before_record_response"] = remove_response_headers config["before_record_response"] = remove_response_headers
@ -789,18 +829,23 @@ class ChatModelUnitTests(ChatModelTests):
}, },
) )
""" # noqa: E501 ''' # noqa: E501,D214
@property @property
def standard_chat_model_params(self) -> dict: def standard_chat_model_params(self) -> dict:
""":private:""" """Standard chat model parameters.
:private:
"""
params = super().standard_chat_model_params params = super().standard_chat_model_params
params["api_key"] = "test" params["api_key"] = "test"
return params return params
@property @property
def init_from_env_params(self) -> tuple[dict, dict, dict]: def init_from_env_params(self) -> tuple[dict, dict, dict]:
"""(tuple) environment variables, additional initialization args, and expected """Init from env params.
(tuple) environment variables, additional initialization args, and expected
instance attributes for testing initialization from environment variables. instance attributes for testing initialization from environment variables.
""" """
@ -826,9 +871,10 @@ class ChatModelUnitTests(ChatModelTests):
assert model is not None assert model is not None
def test_init_from_env(self) -> None: def test_init_from_env(self) -> None:
"""Test initialization from environment variables. Relies on the """Test initialization from environment variables.
``init_from_env_params`` property. Test is skipped if that property is not
set. Relies on the ``init_from_env_params`` property. Test is skipped if that
property is not set.
.. dropdown:: Troubleshooting .. dropdown:: Troubleshooting
@ -853,8 +899,9 @@ class ChatModelUnitTests(ChatModelTests):
def test_init_streaming( def test_init_streaming(
self, self,
) -> None: ) -> None:
"""Test that model can be initialized with ``streaming=True``. This is for """Test that model can be initialized with ``streaming=True``.
backward-compatibility purposes.
This is for backward-compatibility purposes.
.. dropdown:: Troubleshooting .. dropdown:: Troubleshooting
@ -876,7 +923,9 @@ class ChatModelUnitTests(ChatModelTests):
model: BaseChatModel, model: BaseChatModel,
my_adder_tool: BaseTool, my_adder_tool: BaseTool,
) -> None: ) -> None:
"""Test that chat model correctly handles Pydantic models that are passed """Test bind tools with Pydantic models.
Test that chat model correctly handles Pydantic models that are passed
into ``bind_tools``. Test is skipped if the ``has_tool_calling`` property into ``bind_tools``. Test is skipped if the ``has_tool_calling`` property
on the test class is False. on the test class is False.
@ -893,7 +942,7 @@ class ChatModelUnitTests(ChatModelTests):
return return
def my_adder(a: int, b: int) -> int: def my_adder(a: int, b: int) -> int:
"""Takes two integers, a and b, and returns their sum.""" """Return the sum of two integers."""
return a + b return a + b
tools = [my_adder_tool, my_adder] tools = [my_adder_tool, my_adder]
@ -918,8 +967,10 @@ class ChatModelUnitTests(ChatModelTests):
model: BaseChatModel, model: BaseChatModel,
schema: Any, schema: Any,
) -> None: ) -> None:
"""Test ``with_structured_output`` method. Test is skipped if the """Test ``with_structured_output`` method.
``has_structured_output`` property on the test class is False.
Test is skipped if the ``has_structured_output`` property on the test class is
False.
.. dropdown:: Troubleshooting .. dropdown:: Troubleshooting
@ -942,8 +993,9 @@ class ChatModelUnitTests(ChatModelTests):
) )
def test_standard_params(self, model: BaseChatModel) -> None: def test_standard_params(self, model: BaseChatModel) -> None:
"""Test that model properly generates standard parameters. These are used """Test that model properly generates standard parameters.
for tracing purposes.
These are used for tracing purposes.
.. dropdown:: Troubleshooting .. dropdown:: Troubleshooting
@ -981,9 +1033,10 @@ class ChatModelUnitTests(ChatModelTests):
pytest.fail(f"Validation error: {e}") pytest.fail(f"Validation error: {e}")
def test_serdes(self, model: BaseChatModel, snapshot: SnapshotAssertion) -> None: def test_serdes(self, model: BaseChatModel, snapshot: SnapshotAssertion) -> None:
"""Test serialization and deserialization of the model. Test is skipped if the """Test serialization and deserialization of the model.
``is_lc_serializable`` property on the chat model class is not overwritten
to return ``True``. Test is skipped if the ``is_lc_serializable`` property on the chat model class
is not overwritten to return ``True``.
.. dropdown:: Troubleshooting .. dropdown:: Troubleshooting
@ -1007,7 +1060,9 @@ class ChatModelUnitTests(ChatModelTests):
@pytest.mark.benchmark @pytest.mark.benchmark
def test_init_time(self, benchmark: BenchmarkFixture) -> None: def test_init_time(self, benchmark: BenchmarkFixture) -> None:
"""Test initialization time of the chat model. If this test fails, check that """Test initialization time of the chat model.
If this test fails, check that
we are not introducing undue overhead in the model's initialization. we are not introducing undue overhead in the model's initialization.
""" """

View File

@ -1,3 +1,5 @@
"""Embeddings unit tests."""
import os import os
from abc import abstractmethod from abc import abstractmethod
from unittest import mock from unittest import mock
@ -10,18 +12,24 @@ from langchain_tests.base import BaseStandardTests
class EmbeddingsTests(BaseStandardTests): class EmbeddingsTests(BaseStandardTests):
""":private:""" """Embeddings tests base class.
:private:
"""
@property @property
@abstractmethod @abstractmethod
def embeddings_class(self) -> type[Embeddings]: ... def embeddings_class(self) -> type[Embeddings]:
"""Embeddings class."""
@property @property
def embedding_model_params(self) -> dict: def embedding_model_params(self) -> dict:
"""Embeddings model parameters."""
return {} return {}
@pytest.fixture @pytest.fixture
def model(self) -> Embeddings: def model(self) -> Embeddings:
"""Embeddings model fixture."""
return self.embeddings_class(**self.embedding_model_params) return self.embeddings_class(**self.embedding_model_params)
@ -87,7 +95,7 @@ class EmbeddingsUnitTests(EmbeddingsTests):
}, },
) )
""" """ # noqa: D214
def test_init(self) -> None: def test_init(self) -> None:
"""Test model initialization. """Test model initialization.
@ -102,7 +110,9 @@ class EmbeddingsUnitTests(EmbeddingsTests):
@property @property
def init_from_env_params(self) -> tuple[dict, dict, dict]: def init_from_env_params(self) -> tuple[dict, dict, dict]:
"""This property is used in unit tests to test initialization from environment """Init from env params.
This property is used in unit tests to test initialization from environment
variables. It should return a tuple of three dictionaries that specify the variables. It should return a tuple of three dictionaries that specify the
environment variables, additional initialization args, and expected instance environment variables, additional initialization args, and expected instance
attributes to check. attributes to check.
@ -110,9 +120,10 @@ class EmbeddingsUnitTests(EmbeddingsTests):
return {}, {}, {} return {}, {}, {}
def test_init_from_env(self) -> None: def test_init_from_env(self) -> None:
"""Test initialization from environment variables. Relies on the """Test initialization from environment variables.
``init_from_env_params`` property. Test is skipped if that property is not
set. Relies on the ``init_from_env_params`` property.
Test is skipped if that property is not set.
.. dropdown:: Troubleshooting .. dropdown:: Troubleshooting

View File

@ -1,3 +1,5 @@
"""Tools unit tests."""
import os import os
from abc import abstractmethod from abc import abstractmethod
from typing import Union from typing import Union
@ -11,9 +13,11 @@ from langchain_tests.base import BaseStandardTests
class ToolsTests(BaseStandardTests): class ToolsTests(BaseStandardTests):
""":private: """Base class for testing tools.
Base class for testing tools. This won't show in the documentation, but
the docstrings will be inherited by subclasses. :private:
This won't show in the documentation, but the docstrings will be inherited by
subclasses.
""" """
@property @property
@ -38,7 +42,10 @@ class ToolsTests(BaseStandardTests):
@pytest.fixture @pytest.fixture
def tool(self) -> BaseTool: def tool(self) -> BaseTool:
""":private:""" """Tool fixture.
:private:
"""
if isinstance(self.tool_constructor, BaseTool): if isinstance(self.tool_constructor, BaseTool):
if self.tool_constructor_params != {}: if self.tool_constructor_params != {}:
msg = ( msg = (
@ -55,13 +62,17 @@ class ToolsUnitTests(ToolsTests):
@property @property
def init_from_env_params(self) -> tuple[dict, dict, dict]: def init_from_env_params(self) -> tuple[dict, dict, dict]:
"""Return env vars, init args, and expected instance attrs for initializing """Init from env params.
Return env vars, init args, and expected instance attrs for initializing
from env vars. from env vars.
""" """
return {}, {}, {} return {}, {}, {}
def test_init(self) -> None: def test_init(self) -> None:
"""Test that the tool can be initialized with :attr:`tool_constructor` and """Test init.
Test that the tool can be initialized with :attr:`tool_constructor` and
:attr:`tool_constructor_params`. If this fails, check that the :attr:`tool_constructor_params`. If this fails, check that the
keyword args defined in :attr:`tool_constructor_params` are valid. keyword args defined in :attr:`tool_constructor_params` are valid.
""" """
@ -72,6 +83,7 @@ class ToolsUnitTests(ToolsTests):
assert tool is not None assert tool is not None
def test_init_from_env(self) -> None: def test_init_from_env(self) -> None:
"""Test that the tool can be initialized from environment variables."""
env_params, tools_params, expected_attrs = self.init_from_env_params env_params, tools_params, expected_attrs = self.init_from_env_params
if env_params: if env_params:
with mock.patch.dict(os.environ, env_params): with mock.patch.dict(os.environ, env_params):

View File

@ -0,0 +1 @@
"""Langchain tests utilities."""

View File

@ -55,9 +55,12 @@ ignore_missing_imports = true
target-version = "py39" target-version = "py39"
[tool.ruff.lint] [tool.ruff.lint]
select = ["E", "F", "I", "PGH", "T201", "UP",] select = ["D", "E", "F", "I", "PGH", "T201", "UP",]
pyupgrade.keep-runtime-typing = true pyupgrade.keep-runtime-typing = true
[tool.ruff.lint.per-file-ignores]
"tests/**" = [ "D1",]
[tool.coverage.run] [tool.coverage.run]
omit = ["tests/*"] omit = ["tests/*"]

View File

@ -1,3 +1,5 @@
"""Check imports script."""
import secrets import secrets
import string import string
import sys import sys

View File

@ -10,7 +10,9 @@ from pydantic import Field
class ChatParrotLink(BaseChatModel): class ChatParrotLink(BaseChatModel):
"""A custom chat model that echoes the first `parrot_buffer_length` characters """Chat Parrot Link.
A custom chat model that echoes the first `parrot_buffer_length` characters
of the input. of the input.
When contributing an implementation to LangChain, carefully document When contributing an implementation to LangChain, carefully document
@ -60,6 +62,8 @@ class ChatParrotLink(BaseChatModel):
it makes it much easier to parse the output of the model it makes it much easier to parse the output of the model
downstream and understand why generation stopped. downstream and understand why generation stopped.
run_manager: A run manager with callbacks for the LLM. run_manager: A run manager with callbacks for the LLM.
**kwargs: Additional keyword arguments.
""" """
# Replace this with actual logic to generate a response from a list # Replace this with actual logic to generate a response from a list
# of messages. # of messages.
@ -111,6 +115,8 @@ class ChatParrotLink(BaseChatModel):
it makes it much easier to parse the output of the model it makes it much easier to parse the output of the model
downstream and understand why generation stopped. downstream and understand why generation stopped.
run_manager: A run manager with callbacks for the LLM. run_manager: A run manager with callbacks for the LLM.
**kwargs: Additional keyword arguments.
""" """
_ = stop # Mark as used to avoid unused variable warning _ = stop # Mark as used to avoid unused variable warning
_ = kwargs # Mark as used to avoid unused variable warning _ = kwargs # Mark as used to avoid unused variable warning