From aecc82aecdc39ee81072b2f0f5aaf1905a7164b8 Mon Sep 17 00:00:00 2001 From: Elias TOURNEUX Date: Thu, 6 Nov 2025 14:56:35 -0500 Subject: [PATCH 1/4] feat(ovhcloud): Update OVHcloud AI Endpoints --- .../chat_models/__init__.py | 5 + .../chat_models/ovhcloud.py | 161 ++++++++++++++++++ .../embeddings/ovhcloud.py | 52 +++--- .../unit_tests/chat_models/test_imports.py | 1 + .../unit_tests/chat_models/test_ovhcloud.py | 52 ++++++ 5 files changed, 246 insertions(+), 25 deletions(-) create mode 100644 libs/community/langchain_community/chat_models/ovhcloud.py create mode 100644 libs/community/tests/unit_tests/chat_models/test_ovhcloud.py diff --git a/libs/community/langchain_community/chat_models/__init__.py b/libs/community/langchain_community/chat_models/__init__.py index 9c83bdecb..e9c8fbe0e 100644 --- a/libs/community/langchain_community/chat_models/__init__.py +++ b/libs/community/langchain_community/chat_models/__init__.py @@ -140,6 +140,9 @@ from langchain_community.chat_models.ollama import ( ChatOllama, ) + from langchain_community.chat_models.ovhcloud import ( + ChatOVHcloud, + ) from langchain_community.chat_models.openai import ( ChatOpenAI, ) @@ -204,6 +207,7 @@ "ChatCohere", "ChatCoze", "ChatOctoAI", + "ChatOVHcloud", "ChatDatabricks", "ChatDeepInfra", "ChatEdenAI", @@ -290,6 +294,7 @@ "ChatMlflow": "langchain_community.chat_models.mlflow", "ChatNebula": "langchain_community.chat_models.symblai_nebula", "ChatOctoAI": "langchain_community.chat_models.octoai", + "ChatOVHcloud": "langchain_community.chat_models.ovhcloud", "ChatOCIGenAI": "langchain_community.chat_models.oci_generative_ai", "ChatOCIModelDeployment": "langchain_community.chat_models.oci_data_science", "ChatOCIModelDeploymentVLLM": "langchain_community.chat_models.oci_data_science", diff --git a/libs/community/langchain_community/chat_models/ovhcloud.py b/libs/community/langchain_community/chat_models/ovhcloud.py new file mode 100644 index 000000000..212c1cd9d --- /dev/null +++ b/libs/community/langchain_community/chat_models/ovhcloud.py @@ -0,0 +1,161 @@ +"""OVHcloud AI Endpoints chat wrapper. Relies heavily on ChatOpenAI.""" + +from typing import ( + Any, + Callable, + Dict, + Literal, + Optional, + Sequence, + Type, + Union, +) + +from langchain_core.language_models import LanguageModelInput +from langchain_core.messages import AIMessage +from langchain_core.runnables import Runnable +from langchain_core.tools import BaseTool +from langchain_core.utils import convert_to_secret_str, get_from_dict_or_env, pre_init +from langchain_core.utils.function_calling import convert_to_openai_tool +from pydantic import Field, SecretStr + +from langchain_community.chat_models.openai import ChatOpenAI +from langchain_community.utils.openai import is_openai_v1 + +DEFAULT_API_BASE = "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1" +DEFAULT_MODEL = "gpt-oss-120b" + + +class ChatOVHcloud(ChatOpenAI): + """OVHcloud AI Endpoints Chat large language models. + + See https://www.ovhcloud.com/en/public-cloud/ai-endpoints/catalog/ for information about OVHcloud AI Endpoints. + + To use, you should have the ``openai`` python package installed and the + environment variable ``OVHCLOUD_API_TOKEN`` set with your API token. + Alternatively, you can use the ovhcloud_api_token keyword argument. + + Any parameters that are valid to be passed to the `openai.create` call can be passed + in, even if not explicitly saved on this class. + + Example: + .. code-block:: python + + from langchain_community.chat_models import ChatOVHcloud + chat = ChatOVHcloud(model_name="gpt-oss-120b") + """ + + ovhcloud_api_base: str = Field(default=DEFAULT_API_BASE) + ovhcloud_api_token: SecretStr = Field(default=SecretStr(""), alias="api_key") + model_name: str = Field(default=DEFAULT_MODEL, alias="model") + + @property + def _llm_type(self) -> str: + """Return type of chat model.""" + return "ovhcloud-chat" + + @property + def lc_secrets(self) -> Dict[str, str]: + return {"ovhcloud_api_token": "OVHCLOUD_API_TOKEN"} + + @classmethod + def is_lc_serializable(cls) -> bool: + return False + + @pre_init + def validate_environment(cls, values: Dict) -> Dict: + """Validate that api key and python package exists in environment.""" + values["ovhcloud_api_base"] = get_from_dict_or_env( + values, + "ovhcloud_api_base", + "OVHCLOUD_API_BASE", + default=DEFAULT_API_BASE, + ) + values["ovhcloud_api_token"] = convert_to_secret_str( + get_from_dict_or_env( + values, "ovhcloud_api_token", "OVHCLOUD_API_TOKEN" + ) + ) + values["model_name"] = get_from_dict_or_env( + values, + "model_name", + "OVHCLOUD_MODEL_NAME", + default=DEFAULT_MODEL, + ) + + try: + import openai + + if is_openai_v1(): + client_params = { + "api_key": values["ovhcloud_api_token"].get_secret_value(), + "base_url": values["ovhcloud_api_base"], + } + if not values.get("client"): + values["client"] = openai.OpenAI(**client_params).chat.completions + if not values.get("async_client"): + values["async_client"] = openai.AsyncOpenAI( + **client_params + ).chat.completions + else: + values["openai_api_base"] = values["ovhcloud_api_base"] + values["openai_api_key"] = values["ovhcloud_api_token"].get_secret_value() + values["client"] = openai.ChatCompletion + except ImportError: + raise ImportError( + "Could not import openai python package. " + "Please install it with `pip install openai`." + ) + + return values + + def bind_tools( + self, + tools: Sequence[Union[Dict[str, Any], Type, Callable, BaseTool]], + *, + tool_choice: Optional[ + Union[dict, str, Literal["auto", "none", "required", "any"], bool] + ] = None, + strict: Optional[bool] = None, + **kwargs: Any, + ) -> Runnable[LanguageModelInput, AIMessage]: + """Imitating bind_tool method from langchain_openai.ChatOpenAI""" + + formatted_tools = [ + convert_to_openai_tool(tool, strict=strict) for tool in tools + ] + if tool_choice: + if isinstance(tool_choice, str): + # tool_choice is a tool/function name + if tool_choice not in ("auto", "none", "any", "required"): + tool_choice = { + "type": "function", + "function": {"name": tool_choice}, + } + # 'any' is not natively supported by OpenAI API. + # We support 'any' since other models use this instead of 'required'. + if tool_choice == "any": + tool_choice = "required" + elif isinstance(tool_choice, bool): + tool_choice = "required" + elif isinstance(tool_choice, dict): + tool_names = [ + formatted_tool["function"]["name"] + for formatted_tool in formatted_tools + ] + if not any( + tool_name == tool_choice["function"]["name"] + for tool_name in tool_names + ): + raise ValueError( + f"Tool choice {tool_choice} was specified, but the only " + f"provided tools were {tool_names}." + ) + else: + raise ValueError( + f"Unrecognized tool_choice type. Expected str, bool or dict. " + f"Received: {tool_choice}" + ) + kwargs["tool_choice"] = tool_choice + return super().bind(tools=formatted_tools, **kwargs) + diff --git a/libs/community/langchain_community/embeddings/ovhcloud.py b/libs/community/langchain_community/embeddings/ovhcloud.py index 49b9cbfa2..8d0af8fe7 100644 --- a/libs/community/langchain_community/embeddings/ovhcloud.py +++ b/libs/community/langchain_community/embeddings/ovhcloud.py @@ -1,4 +1,3 @@ -import json import logging import time from typing import Any, List @@ -21,9 +20,12 @@ class OVHCloudEmbeddings(BaseModel, Embeddings): """ OVHcloud AI Endpoints model name for embeddings generation""" model_name: str = "" - """ OVHcloud AI Endpoints region""" + """ OVHcloud AI Endpoints region (deprecated, kept for backward compatibility)""" region: str = "kepler" + """ OVHcloud AI Endpoints base URL""" + base_url: str = "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1" + model_config = ConfigDict(extra="forbid", protected_namespaces=()) def __init__(self, **kwargs: Any): @@ -32,8 +34,6 @@ def __init__(self, **kwargs: Any): raise ValueError("Access token is required for OVHCloud embeddings.") if self.model_name == "": raise ValueError("Model name is required for OVHCloud embeddings.") - if self.region == "": - raise ValueError("Region is required for OVHCloud embeddings.") def _generate_embedding(self, text: str) -> List[float]: """Generate embeddings from OVHCLOUD AIE. @@ -42,8 +42,7 @@ def _generate_embedding(self, text: str) -> List[float]: Returns: List[float]: Embeddings for the text. """ - - return self._send_request_to_ai_endpoints("text/plain", text, "text2vec") + return self._send_request_to_ai_endpoints([text])[0] def embed_documents(self, texts: List[str]) -> List[List[float]]: """Embed a list of documents. @@ -54,10 +53,7 @@ def embed_documents(self, texts: List[str]) -> List[List[float]]: List[List[float]]: List of embeddings, one for each input text. """ - - return self._send_request_to_ai_endpoints( - "application/json", json.dumps(texts), "batch_text2vec" - ) + return self._send_request_to_ai_endpoints(texts) def embed_query(self, text: str) -> List[float]: """Embed a single query text. @@ -68,29 +64,31 @@ def embed_query(self, text: str) -> List[float]: """ return self._generate_embedding(text) - def _send_request_to_ai_endpoints( - self, contentType: str, payload: str, route: str - ) -> Any: - """Send a HTTPS request to OVHcloud AI Endpoints + def _send_request_to_ai_endpoints(self, texts: List[str]) -> List[List[float]]: + """Send a HTTPS request to OVHcloud AI Endpoints using OpenAI-compatible API Args: - contentType (str): The content type of the request, application/json or text/plain. - payload (str): The payload of the request. - route (str): The route of the request, batch_text2vec or text2vec. - """ # noqa: E501 + texts (List[str]): The list of texts to embed. + Returns: + List[List[float]]: List of embeddings, one for each input text. + """ headers = { - "content-type": contentType, + "Content-Type": "application/json", "Authorization": f"Bearer {self.access_token}", } + # Prepare request body in OpenAI format + # OpenAI API accepts both string and array, but we always use array for consistency + payload = { + "model": self.model_name, + "input": texts, + } + session = requests.session() while True: response = session.post( - ( - f"https://{self.model_name}.endpoints.{self.region}" - f".ai.cloud.ovh.net/api/{route}" - ), + f"{self.base_url}/embeddings", headers=headers, - data=payload, + json=payload, ) if response.status_code != 200: if response.status_code == 429: @@ -112,4 +110,8 @@ def _send_request_to_ai_endpoints( status_code=response.status_code, text=response.text ) ) - return response.json() + # Parse OpenAI-compatible response format + response_data = response.json() + # OpenAI format: {"data": [{"embedding": [...]}, ...]} + embeddings = [item["embedding"] for item in response_data["data"]] + return embeddings diff --git a/libs/community/tests/unit_tests/chat_models/test_imports.py b/libs/community/tests/unit_tests/chat_models/test_imports.py index 1c7dff198..2df2c9bb1 100644 --- a/libs/community/tests/unit_tests/chat_models/test_imports.py +++ b/libs/community/tests/unit_tests/chat_models/test_imports.py @@ -63,6 +63,7 @@ "QianfanChatEndpoint", "VolcEngineMaasChat", "ChatOctoAI", + "ChatOVHcloud", "ChatSnowflakeCortex", "ChatYi", ] diff --git a/libs/community/tests/unit_tests/chat_models/test_ovhcloud.py b/libs/community/tests/unit_tests/chat_models/test_ovhcloud.py new file mode 100644 index 000000000..50bdb3ba5 --- /dev/null +++ b/libs/community/tests/unit_tests/chat_models/test_ovhcloud.py @@ -0,0 +1,52 @@ +import pytest +from pydantic import SecretStr, ValidationError + +from langchain_community.chat_models.ovhcloud import ChatOVHcloud + +DEFAULT_API_BASE = "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1" +DEFAULT_MODEL = "gpt-oss-120b" + + +@pytest.mark.requires("openai") +def test__default_ovhcloud_api_base() -> None: + chat = ChatOVHcloud(ovhcloud_api_token=SecretStr("test_token")) # type: ignore[call-arg] + assert chat.ovhcloud_api_base == DEFAULT_API_BASE + + +@pytest.mark.requires("openai") +def test__default_ovhcloud_api_token() -> None: + chat = ChatOVHcloud(ovhcloud_api_token=SecretStr("test_token")) # type: ignore[call-arg] + assert chat.ovhcloud_api_token.get_secret_value() == "test_token" + + +@pytest.mark.requires("openai") +def test__default_model_name() -> None: + chat = ChatOVHcloud(ovhcloud_api_token=SecretStr("test_token")) # type: ignore[call-arg] + assert chat.model_name == DEFAULT_MODEL + + +@pytest.mark.requires("openai") +def test__field_aliases() -> None: + chat = ChatOVHcloud(ovhcloud_api_token=SecretStr("test_token"), model="custom-model") # type: ignore[call-arg] + assert chat.model_name == "custom-model" + assert chat.ovhcloud_api_token.get_secret_value() == "test_token" + + +@pytest.mark.requires("openai") +def test__missing_ovhcloud_api_token() -> None: + with pytest.raises(ValidationError) as e: + ChatOVHcloud() + assert "Did not find ovhcloud_api_token" in str(e) + + +@pytest.mark.requires("openai") +def test__all_fields_provided() -> None: + chat = ChatOVHcloud( # type: ignore[call-arg] + ovhcloud_api_token=SecretStr("test_token"), + model="custom-model", + ovhcloud_api_base="https://custom.api/base/", + ) + assert chat.ovhcloud_api_base == "https://custom.api/base/" + assert chat.ovhcloud_api_token.get_secret_value() == "test_token" + assert chat.model_name == "custom-model" + From b68433543ba7e3a3cbae43e3b6113ef9f478852c Mon Sep 17 00:00:00 2001 From: Elias TOURNEUX Date: Mon, 10 Nov 2025 09:47:07 -0500 Subject: [PATCH 2/4] Add deprecated for OVHcloud region --- libs/community/langchain_community/embeddings/ovhcloud.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libs/community/langchain_community/embeddings/ovhcloud.py b/libs/community/langchain_community/embeddings/ovhcloud.py index 8d0af8fe7..57aab5091 100644 --- a/libs/community/langchain_community/embeddings/ovhcloud.py +++ b/libs/community/langchain_community/embeddings/ovhcloud.py @@ -3,6 +3,7 @@ from typing import Any, List import requests +from langchain_core._api.deprecation import deprecated from langchain_core.embeddings import Embeddings from pydantic import BaseModel, ConfigDict @@ -20,6 +21,10 @@ class OVHCloudEmbeddings(BaseModel, Embeddings): """ OVHcloud AI Endpoints model name for embeddings generation""" model_name: str = "" + @deprecated( + since="0.4.2", + removal="1.0", + ) """ OVHcloud AI Endpoints region (deprecated, kept for backward compatibility)""" region: str = "kepler" From 118992613635addc8a24cafa1f9e64da57baf5e7 Mon Sep 17 00:00:00 2001 From: Elias TOURNEUX Date: Thu, 13 Nov 2025 13:35:20 -0500 Subject: [PATCH 3/4] Fix ChatOpenAI dependency --- libs/community/langchain_community/chat_models/ovhcloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/community/langchain_community/chat_models/ovhcloud.py b/libs/community/langchain_community/chat_models/ovhcloud.py index 212c1cd9d..018a9efac 100644 --- a/libs/community/langchain_community/chat_models/ovhcloud.py +++ b/libs/community/langchain_community/chat_models/ovhcloud.py @@ -19,7 +19,7 @@ from langchain_core.utils.function_calling import convert_to_openai_tool from pydantic import Field, SecretStr -from langchain_community.chat_models.openai import ChatOpenAI +from langchain_openai import ChatOpenAI from langchain_community.utils.openai import is_openai_v1 DEFAULT_API_BASE = "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1" From be767509ff01f275b5d8e2d6b42b4adce5ae8d24 Mon Sep 17 00:00:00 2001 From: Elias TOURNEUX Date: Thu, 13 Nov 2025 13:44:26 -0500 Subject: [PATCH 4/4] Edit tests to remove region since it's not used anymore --- .../langchain_community/chat_models/ovhcloud.py | 11 +++++++---- .../langchain_community/embeddings/ovhcloud.py | 8 -------- libs/community/pyproject.toml | 1 + .../tests/unit_tests/chat_models/test_ovhcloud.py | 14 ++++++++------ .../tests/unit_tests/embeddings/test_ovhcloud.py | 13 +++---------- .../tests/unit_tests/test_dependencies.py | 1 + 6 files changed, 20 insertions(+), 28 deletions(-) diff --git a/libs/community/langchain_community/chat_models/ovhcloud.py b/libs/community/langchain_community/chat_models/ovhcloud.py index 018a9efac..880848d8f 100644 --- a/libs/community/langchain_community/chat_models/ovhcloud.py +++ b/libs/community/langchain_community/chat_models/ovhcloud.py @@ -15,9 +15,9 @@ from langchain_core.messages import AIMessage from langchain_core.runnables import Runnable from langchain_core.tools import BaseTool -from langchain_core.utils import convert_to_secret_str, get_from_dict_or_env, pre_init +from langchain_core.utils import convert_to_secret_str, get_from_dict_or_env from langchain_core.utils.function_calling import convert_to_openai_tool -from pydantic import Field, SecretStr +from pydantic import Field, SecretStr, model_validator from langchain_openai import ChatOpenAI from langchain_community.utils.openai import is_openai_v1 @@ -62,9 +62,12 @@ def lc_secrets(self) -> Dict[str, str]: def is_lc_serializable(cls) -> bool: return False - @pre_init - def validate_environment(cls, values: Dict) -> Dict: + @model_validator(mode="before") + @classmethod + def validate_environment(cls, values: Any) -> Any: """Validate that api key and python package exists in environment.""" + if not isinstance(values, dict): + return values values["ovhcloud_api_base"] = get_from_dict_or_env( values, "ovhcloud_api_base", diff --git a/libs/community/langchain_community/embeddings/ovhcloud.py b/libs/community/langchain_community/embeddings/ovhcloud.py index 57aab5091..ea21db2ce 100644 --- a/libs/community/langchain_community/embeddings/ovhcloud.py +++ b/libs/community/langchain_community/embeddings/ovhcloud.py @@ -3,7 +3,6 @@ from typing import Any, List import requests -from langchain_core._api.deprecation import deprecated from langchain_core.embeddings import Embeddings from pydantic import BaseModel, ConfigDict @@ -21,13 +20,6 @@ class OVHCloudEmbeddings(BaseModel, Embeddings): """ OVHcloud AI Endpoints model name for embeddings generation""" model_name: str = "" - @deprecated( - since="0.4.2", - removal="1.0", - ) - """ OVHcloud AI Endpoints region (deprecated, kept for backward compatibility)""" - region: str = "kepler" - """ OVHcloud AI Endpoints base URL""" base_url: str = "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1" diff --git a/libs/community/pyproject.toml b/libs/community/pyproject.toml index 3931459bf..f15b57197 100644 --- a/libs/community/pyproject.toml +++ b/libs/community/pyproject.toml @@ -53,6 +53,7 @@ test = [ "blockbuster>=1.5.18,<1.6.0", "cffi<1.17.1; python_version < \"3.10\"", "cffi; python_version >= \"3.10\"", + "langchain-openai>=0.2.1", "langchain-tests>=1.0.0,<2.0.0", "toml>=0.10.2,<1.0.0", "mypy-extensions>=1.0.0", diff --git a/libs/community/tests/unit_tests/chat_models/test_ovhcloud.py b/libs/community/tests/unit_tests/chat_models/test_ovhcloud.py index 50bdb3ba5..bd1370621 100644 --- a/libs/community/tests/unit_tests/chat_models/test_ovhcloud.py +++ b/libs/community/tests/unit_tests/chat_models/test_ovhcloud.py @@ -1,45 +1,47 @@ import pytest from pydantic import SecretStr, ValidationError +pytest.importorskip("langchain_openai", reason="langchain-openai not installed") + from langchain_community.chat_models.ovhcloud import ChatOVHcloud DEFAULT_API_BASE = "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1" DEFAULT_MODEL = "gpt-oss-120b" -@pytest.mark.requires("openai") +@pytest.mark.requires("openai", "langchain_openai") def test__default_ovhcloud_api_base() -> None: chat = ChatOVHcloud(ovhcloud_api_token=SecretStr("test_token")) # type: ignore[call-arg] assert chat.ovhcloud_api_base == DEFAULT_API_BASE -@pytest.mark.requires("openai") +@pytest.mark.requires("openai", "langchain_openai") def test__default_ovhcloud_api_token() -> None: chat = ChatOVHcloud(ovhcloud_api_token=SecretStr("test_token")) # type: ignore[call-arg] assert chat.ovhcloud_api_token.get_secret_value() == "test_token" -@pytest.mark.requires("openai") +@pytest.mark.requires("openai", "langchain_openai") def test__default_model_name() -> None: chat = ChatOVHcloud(ovhcloud_api_token=SecretStr("test_token")) # type: ignore[call-arg] assert chat.model_name == DEFAULT_MODEL -@pytest.mark.requires("openai") +@pytest.mark.requires("openai", "langchain_openai") def test__field_aliases() -> None: chat = ChatOVHcloud(ovhcloud_api_token=SecretStr("test_token"), model="custom-model") # type: ignore[call-arg] assert chat.model_name == "custom-model" assert chat.ovhcloud_api_token.get_secret_value() == "test_token" -@pytest.mark.requires("openai") +@pytest.mark.requires("openai", "langchain_openai") def test__missing_ovhcloud_api_token() -> None: with pytest.raises(ValidationError) as e: ChatOVHcloud() assert "Did not find ovhcloud_api_token" in str(e) -@pytest.mark.requires("openai") +@pytest.mark.requires("openai", "langchain_openai") def test__all_fields_provided() -> None: chat = ChatOVHcloud( # type: ignore[call-arg] ovhcloud_api_token=SecretStr("test_token"), diff --git a/libs/community/tests/unit_tests/embeddings/test_ovhcloud.py b/libs/community/tests/unit_tests/embeddings/test_ovhcloud.py index c06c1550e..bd7b9d6a4 100644 --- a/libs/community/tests/unit_tests/embeddings/test_ovhcloud.py +++ b/libs/community/tests/unit_tests/embeddings/test_ovhcloud.py @@ -7,25 +7,18 @@ def test_ovhcloud_correct_instantiation() -> None: llm = OVHCloudEmbeddings(model_name="multilingual-e5-base", access_token="token") assert isinstance(llm, OVHCloudEmbeddings) llm = OVHCloudEmbeddings( - model_name="multilingual-e5-base", region="kepler", access_token="token" + model_name="multilingual-e5-base", access_token="token" ) assert isinstance(llm, OVHCloudEmbeddings) def test_ovhcloud_empty_model_name_should_raise_error() -> None: with pytest.raises(ValueError): - OVHCloudEmbeddings(model_name="", region="kepler", access_token="token") - - -def test_ovhcloud_empty_region_should_raise_error() -> None: - with pytest.raises(ValueError): - OVHCloudEmbeddings( - model_name="multilingual-e5-base", region="", access_token="token" - ) + OVHCloudEmbeddings(model_name="", access_token="token") def test_ovhcloud_empty_access_token_should_raise_error() -> None: with pytest.raises(ValueError): OVHCloudEmbeddings( - model_name="multilingual-e5-base", region="kepler", access_token="" + model_name="multilingual-e5-base", access_token="" ) diff --git a/libs/community/tests/unit_tests/test_dependencies.py b/libs/community/tests/unit_tests/test_dependencies.py index 1ce576f81..3092c5043 100644 --- a/libs/community/tests/unit_tests/test_dependencies.py +++ b/libs/community/tests/unit_tests/test_dependencies.py @@ -62,6 +62,7 @@ def test_test_group_dependencies(uv_conf: Mapping[str, Any]) -> None: [ "duckdb-engine", "freezegun", + "langchain-openai", "langchain-tests", "lark", "mypy-extensions",