From 423ca88ec9a2f43aa0e6217dc8ab41996750f37b Mon Sep 17 00:00:00 2001 From: ujjwal Date: Thu, 4 Dec 2025 06:45:20 +0530 Subject: [PATCH 1/9] fix: make init_chat_model work with huggingface provider --- .../langchain/chat_models/base.py | 13 ++++++- .../chat_models/test_chat_models.py | 37 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/libs/langchain_v1/langchain/chat_models/base.py b/libs/langchain_v1/langchain/chat_models/base.py index a343c31e23cb1..ad75ab47ec356 100644 --- a/libs/langchain_v1/langchain/chat_models/base.py +++ b/libs/langchain_v1/langchain/chat_models/base.py @@ -401,9 +401,18 @@ def _init_chat_model_helper( return ChatMistralAI(model=model, **kwargs) # type: ignore[call-arg,unused-ignore] if model_provider == "huggingface": _check_pkg("langchain_huggingface") - from langchain_huggingface import ChatHuggingFace + from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint + + # Build a HuggingFaceEndpoint from the model id and wrap it in ChatHuggingFace. + # ChatHuggingFace expects an underlying HF LLM in `llm`, not a raw model_id. + llm = HuggingFaceEndpoint( + repo_id=model, + task="text-generation", + **kwargs, + ) + return ChatHuggingFace(llm=llm) + - return ChatHuggingFace(model_id=model, **kwargs) if model_provider == "groq": _check_pkg("langchain_groq") from langchain_groq import ChatGroq diff --git a/libs/langchain_v1/tests/unit_tests/chat_models/test_chat_models.py b/libs/langchain_v1/tests/unit_tests/chat_models/test_chat_models.py index 9c1f6be55589d..1874d7f56927a 100644 --- a/libs/langchain_v1/tests/unit_tests/chat_models/test_chat_models.py +++ b/libs/langchain_v1/tests/unit_tests/chat_models/test_chat_models.py @@ -283,3 +283,40 @@ def test_configurable_with_default() -> None: prompt = ChatPromptTemplate.from_messages([("system", "foo")]) chain = prompt | model_with_config assert isinstance(chain, RunnableSequence) +from typing import Any + +from langchain.chat_models import init_chat_model +import langchain_huggingface as lhf + + +def test_init_chat_model_huggingface(monkeypatch: Any) -> None: + created: dict[str, Any] = {} + + class DummyHFEndpoint: + def __init__(self, *args: Any, **kwargs: Any) -> None: + created["args"] = args + created["kwargs"] = kwargs + + class DummyChat: + def __init__(self, llm: Any, **kwargs: Any) -> None: + self.llm = llm + self.kwargs = kwargs + + # When _init_chat_model_helper imports from langchain_huggingface, + # it will get these dummy classes instead of the real ones. + monkeypatch.setattr(lhf, "HuggingFaceEndpoint", DummyHFEndpoint) + monkeypatch.setattr(lhf, "ChatHuggingFace", DummyChat) + + model = init_chat_model( + model="microsoft/Phi-3-mini-4k-instruct", + model_provider="huggingface", + temperature=0, + ) + + # We should get our DummyChat instance + assert isinstance(model, DummyChat) + + # And the helper should have wired through the correct HF kwargs + assert created["kwargs"]["repo_id"] == "microsoft/Phi-3-mini-4k-instruct" + assert created["kwargs"]["task"] == "text-generation" + assert created["kwargs"]["temperature"] == 0 From 3ebda7c780391b53a7e3fc5d04aca0d882afde27 Mon Sep 17 00:00:00 2001 From: ujjwal Date: Thu, 4 Dec 2025 07:00:25 +0530 Subject: [PATCH 2/9] fix(langchain_v1): make init_chat_model work with huggingface provider and add optional test --- .../chat_models/test_chat_models.py | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/libs/langchain_v1/tests/unit_tests/chat_models/test_chat_models.py b/libs/langchain_v1/tests/unit_tests/chat_models/test_chat_models.py index 1874d7f56927a..96c1ef2f22404 100644 --- a/libs/langchain_v1/tests/unit_tests/chat_models/test_chat_models.py +++ b/libs/langchain_v1/tests/unit_tests/chat_models/test_chat_models.py @@ -17,7 +17,6 @@ "BaseChatModel", ] - def test_all_imports() -> None: """Test that all expected imports are present in the module's __all__.""" assert set(__all__) == set(EXPECTED_ALL) @@ -283,29 +282,30 @@ def test_configurable_with_default() -> None: prompt = ChatPromptTemplate.from_messages([("system", "foo")]) chain = prompt | model_with_config assert isinstance(chain, RunnableSequence) +import pytest from typing import Any from langchain.chat_models import init_chat_model -import langchain_huggingface as lhf +@pytest.mark.requires("langchain_huggingface") def test_init_chat_model_huggingface(monkeypatch: Any) -> None: + # Import the optional integration lazily + lhf = pytest.importorskip("langchain_huggingface") + + ChatHuggingFace = lhf.ChatHuggingFace + HuggingFaceEndpoint = lhf.HuggingFaceEndpoint + created: dict[str, Any] = {} - class DummyHFEndpoint: + class DummyHFEndpoint(HuggingFaceEndpoint): # type: ignore[misc] def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(**kwargs) created["args"] = args created["kwargs"] = kwargs - class DummyChat: - def __init__(self, llm: Any, **kwargs: Any) -> None: - self.llm = llm - self.kwargs = kwargs - - # When _init_chat_model_helper imports from langchain_huggingface, - # it will get these dummy classes instead of the real ones. + # Force helper to use DummyHFEndpoint monkeypatch.setattr(lhf, "HuggingFaceEndpoint", DummyHFEndpoint) - monkeypatch.setattr(lhf, "ChatHuggingFace", DummyChat) model = init_chat_model( model="microsoft/Phi-3-mini-4k-instruct", @@ -313,10 +313,7 @@ def __init__(self, llm: Any, **kwargs: Any) -> None: temperature=0, ) - # We should get our DummyChat instance - assert isinstance(model, DummyChat) - - # And the helper should have wired through the correct HF kwargs + assert isinstance(model, ChatHuggingFace) assert created["kwargs"]["repo_id"] == "microsoft/Phi-3-mini-4k-instruct" assert created["kwargs"]["task"] == "text-generation" assert created["kwargs"]["temperature"] == 0 From 60c36fd363968f01c63dce978e95fd249d683238 Mon Sep 17 00:00:00 2001 From: ujjwal Date: Thu, 4 Dec 2025 07:24:22 +0530 Subject: [PATCH 3/9] chore: fix imports in chat models tests --- .../chat_models/test_chat_models.py | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/libs/langchain_v1/tests/unit_tests/chat_models/test_chat_models.py b/libs/langchain_v1/tests/unit_tests/chat_models/test_chat_models.py index 96c1ef2f22404..afcb645882023 100644 --- a/libs/langchain_v1/tests/unit_tests/chat_models/test_chat_models.py +++ b/libs/langchain_v1/tests/unit_tests/chat_models/test_chat_models.py @@ -1,5 +1,5 @@ import os -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any from unittest import mock import pytest @@ -56,7 +56,7 @@ def test_init_missing_dep() -> None: def test_init_unknown_provider() -> None: - with pytest.raises(ValueError, match="Unsupported model_provider='bar'."): + with pytest.raises(ValueError, match=r"Unsupported model_provider='bar'\."): init_chat_model("foo", model_provider="bar") @@ -282,29 +282,22 @@ def test_configurable_with_default() -> None: prompt = ChatPromptTemplate.from_messages([("system", "foo")]) chain = prompt | model_with_config assert isinstance(chain, RunnableSequence) -import pytest -from typing import Any - -from langchain.chat_models import init_chat_model - @pytest.mark.requires("langchain_huggingface") def test_init_chat_model_huggingface(monkeypatch: Any) -> None: - # Import the optional integration lazily lhf = pytest.importorskip("langchain_huggingface") - ChatHuggingFace = lhf.ChatHuggingFace - HuggingFaceEndpoint = lhf.HuggingFaceEndpoint + chat_huggingface_cls = lhf.ChatHuggingFace + huggingface_endpoint_cls = lhf.HuggingFaceEndpoint created: dict[str, Any] = {} - class DummyHFEndpoint(HuggingFaceEndpoint): # type: ignore[misc] + class DummyHFEndpoint(huggingface_endpoint_cls): # type: ignore[misc] def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(**kwargs) created["args"] = args created["kwargs"] = kwargs - # Force helper to use DummyHFEndpoint monkeypatch.setattr(lhf, "HuggingFaceEndpoint", DummyHFEndpoint) model = init_chat_model( @@ -313,7 +306,8 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: temperature=0, ) - assert isinstance(model, ChatHuggingFace) + assert isinstance(model, chat_huggingface_cls) assert created["kwargs"]["repo_id"] == "microsoft/Phi-3-mini-4k-instruct" assert created["kwargs"]["task"] == "text-generation" assert created["kwargs"]["temperature"] == 0 + From 7e7fb858655d486a9ef31306968dc21de46f70e4 Mon Sep 17 00:00:00 2001 From: ujjwal Date: Thu, 4 Dec 2025 07:34:29 +0530 Subject: [PATCH 4/9] Fix ruff formatting in tests --- .../tests/unit_tests/chat_models/test_chat_models.py | 3 ++- libs/langchain_v1/tests/unit_tests/embeddings/test_base.py | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/libs/langchain_v1/tests/unit_tests/chat_models/test_chat_models.py b/libs/langchain_v1/tests/unit_tests/chat_models/test_chat_models.py index afcb645882023..9647bb28840f2 100644 --- a/libs/langchain_v1/tests/unit_tests/chat_models/test_chat_models.py +++ b/libs/langchain_v1/tests/unit_tests/chat_models/test_chat_models.py @@ -17,6 +17,7 @@ "BaseChatModel", ] + def test_all_imports() -> None: """Test that all expected imports are present in the module's __all__.""" assert set(__all__) == set(EXPECTED_ALL) @@ -283,6 +284,7 @@ def test_configurable_with_default() -> None: chain = prompt | model_with_config assert isinstance(chain, RunnableSequence) + @pytest.mark.requires("langchain_huggingface") def test_init_chat_model_huggingface(monkeypatch: Any) -> None: lhf = pytest.importorskip("langchain_huggingface") @@ -310,4 +312,3 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: assert created["kwargs"]["repo_id"] == "microsoft/Phi-3-mini-4k-instruct" assert created["kwargs"]["task"] == "text-generation" assert created["kwargs"]["temperature"] == 0 - diff --git a/libs/langchain_v1/tests/unit_tests/embeddings/test_base.py b/libs/langchain_v1/tests/unit_tests/embeddings/test_base.py index 30bfaeb67773f..85c16aaab905c 100644 --- a/libs/langchain_v1/tests/unit_tests/embeddings/test_base.py +++ b/libs/langchain_v1/tests/unit_tests/embeddings/test_base.py @@ -88,7 +88,10 @@ def test_infer_model_and_provider_errors() -> None: _infer_model_and_provider("model", provider="") # Test invalid provider - with pytest.raises(ValueError, match="Provider 'invalid' is not supported.") as exc: + with pytest.raises( + ValueError, + match=r"Provider 'invalid' is not supported\.", + ) as exc: _infer_model_and_provider("model", provider="invalid") # Test provider list is in error for provider in _SUPPORTED_PROVIDERS: From d2f4ffb871bf947a7b5e69b84ee209f5562a7318 Mon Sep 17 00:00:00 2001 From: ujjwal Date: Thu, 4 Dec 2025 07:35:10 +0530 Subject: [PATCH 5/9] Fix init_chat_model HuggingFace logic in base chat model --- libs/langchain_v1/langchain/chat_models/base.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/libs/langchain_v1/langchain/chat_models/base.py b/libs/langchain_v1/langchain/chat_models/base.py index ad75ab47ec356..490e834ccb655 100644 --- a/libs/langchain_v1/langchain/chat_models/base.py +++ b/libs/langchain_v1/langchain/chat_models/base.py @@ -411,8 +411,6 @@ def _init_chat_model_helper( **kwargs, ) return ChatHuggingFace(llm=llm) - - if model_provider == "groq": _check_pkg("langchain_groq") from langchain_groq import ChatGroq From 42f0eda0127d637a404f12b1c59a1debf66a6983 Mon Sep 17 00:00:00 2001 From: ujjwal Date: Thu, 4 Dec 2025 07:42:11 +0530 Subject: [PATCH 6/9] Add langchain-huggingface to optional dependencies for tests --- libs/langchain_v1/pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/langchain_v1/pyproject.toml b/libs/langchain_v1/pyproject.toml index 8f44e5b3ccf7b..85eacb3b34f01 100644 --- a/libs/langchain_v1/pyproject.toml +++ b/libs/langchain_v1/pyproject.toml @@ -35,6 +35,7 @@ aws = ["langchain-aws"] deepseek = ["langchain-deepseek"] xai = ["langchain-xai"] perplexity = ["langchain-perplexity"] +huggingface = ["langchain-huggingface"] [project.urls] Homepage = "https://docs.langchain.com/" From d71aac86a07d535521b306a86ff74d8c3b254c1c Mon Sep 17 00:00:00 2001 From: ujjwal Date: Thu, 4 Dec 2025 07:56:13 +0530 Subject: [PATCH 7/9] Remove duplicate huggingface optional dependency --- libs/langchain_v1/pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/langchain_v1/pyproject.toml b/libs/langchain_v1/pyproject.toml index 85eacb3b34f01..8f44e5b3ccf7b 100644 --- a/libs/langchain_v1/pyproject.toml +++ b/libs/langchain_v1/pyproject.toml @@ -35,7 +35,6 @@ aws = ["langchain-aws"] deepseek = ["langchain-deepseek"] xai = ["langchain-xai"] perplexity = ["langchain-perplexity"] -huggingface = ["langchain-huggingface"] [project.urls] Homepage = "https://docs.langchain.com/" From b7f9abbe631af39292dd4a49eed76199a6377d2f Mon Sep 17 00:00:00 2001 From: ujjwal Date: Thu, 4 Dec 2025 07:56:23 +0530 Subject: [PATCH 8/9] Skip HuggingFace init test when langchain_huggingface is missing --- .../tests/unit_tests/chat_models/test_chat_models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/langchain_v1/tests/unit_tests/chat_models/test_chat_models.py b/libs/langchain_v1/tests/unit_tests/chat_models/test_chat_models.py index 9647bb28840f2..13983b93e9b9c 100644 --- a/libs/langchain_v1/tests/unit_tests/chat_models/test_chat_models.py +++ b/libs/langchain_v1/tests/unit_tests/chat_models/test_chat_models.py @@ -285,7 +285,6 @@ def test_configurable_with_default() -> None: assert isinstance(chain, RunnableSequence) -@pytest.mark.requires("langchain_huggingface") def test_init_chat_model_huggingface(monkeypatch: Any) -> None: lhf = pytest.importorskip("langchain_huggingface") From 7eb0b8a4886fd104416b883738390d4b0a9687aa Mon Sep 17 00:00:00 2001 From: ujjwal Date: Thu, 4 Dec 2025 08:08:20 +0530 Subject: [PATCH 9/9] Fix mypy valid-type ignore for DummyHFEndpoint in chat model tests --- .../tests/unit_tests/chat_models/test_chat_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langchain_v1/tests/unit_tests/chat_models/test_chat_models.py b/libs/langchain_v1/tests/unit_tests/chat_models/test_chat_models.py index 13983b93e9b9c..6ab6b94985b33 100644 --- a/libs/langchain_v1/tests/unit_tests/chat_models/test_chat_models.py +++ b/libs/langchain_v1/tests/unit_tests/chat_models/test_chat_models.py @@ -293,7 +293,7 @@ def test_init_chat_model_huggingface(monkeypatch: Any) -> None: created: dict[str, Any] = {} - class DummyHFEndpoint(huggingface_endpoint_cls): # type: ignore[misc] + class DummyHFEndpoint(huggingface_endpoint_cls): # type: ignore[misc, valid-type] def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(**kwargs) created["args"] = args