From 0f3e994fd51a995f15cad3b19ddf575c2076ebd9 Mon Sep 17 00:00:00 2001 From: Thomas Howe Date: Wed, 3 Jun 2026 18:19:29 -0400 Subject: [PATCH 1/6] Make transformers an opt-in dependency (conserver-local group) transformers is the single largest installed package (~48MB, ~67MB with tokenizers/huggingface_hub/hf_xet) but its only consumer is hugging_llm_link, and only the off-by-default LocalHuggingFaceLLM path (use_local_model=true) actually uses it. That path needs a model backend (torch) which is not a project dependency, so it cannot run today regardless. The default path calls the HuggingFace HTTP API and needs none of transformers. - Move transformers>=4.48.0 out of the base `conserver` group into a new opt-in `conserver-local` group. - Defer `import transformers` into LocalHuggingFaceLLM.__init__ (and HuggingLLMLink.__init__ in main.py) with a clear ImportError pointing at the install command, so importing the link no longer drags in transformers. - Document the opt-in in the link README. - Regenerate uv.lock (transformers node unchanged, just regrouped). Removes ~67MB from the base conserver install with no functional change to the default API-based path. test_link_metrics.py: 25/25 pass. Co-Authored-By: Claude Opus 4.8 --- conserver/links/hugging_llm_link/README.md | 7 +++++++ conserver/links/hugging_llm_link/__init__.py | 14 +++++++++++++- conserver/links/hugging_llm_link/main.py | 4 +++- pyproject.toml | 11 ++++++++++- uv.lock | 6 ++++-- 5 files changed, 37 insertions(+), 5 deletions(-) diff --git a/conserver/links/hugging_llm_link/README.md b/conserver/links/hugging_llm_link/README.md index f06ff8c6..4a21d00c 100644 --- a/conserver/links/hugging_llm_link/README.md +++ b/conserver/links/hugging_llm_link/README.md @@ -20,6 +20,13 @@ The link can be configured through environment variables or the link configurati HUGGINGFACE_API_KEY=your-api-key # Required for API-based inference ``` +> **Local inference is opt-in.** The default path (`use_local_model: false`) calls the +> HuggingFace HTTP API and pulls in no ML dependencies. Setting `use_local_model: true` +> runs the model on-device via `transformers`, which is **not** part of the base +> `conserver` install. Install it with `uv sync --group conserver --group conserver-local` +> and add a model backend (e.g. `torch`) yourself. Without it, the local path raises a +> clear `ImportError` at runtime. + Additional configuration options: - `model`: HuggingFace model ID (default: "meta-llama/Llama-2-70b-chat-hf") - `use_local_model`: Toggle local model inference (default: false) diff --git a/conserver/links/hugging_llm_link/__init__.py b/conserver/links/hugging_llm_link/__init__.py index f3450207..2985e506 100644 --- a/conserver/links/hugging_llm_link/__init__.py +++ b/conserver/links/hugging_llm_link/__init__.py @@ -32,7 +32,6 @@ RetryError, before_sleep_log, ) -import transformers import anyio # Local imports @@ -154,6 +153,19 @@ class LocalHuggingFaceLLM(BaseLLM): def __init__(self, config: LLMConfig): super().__init__(config) + # transformers (and a backend such as torch) is an optional dependency. + # The default, API-based path (use_local_model=false) does not need it, so + # the import is deferred to here rather than loaded at module import time. + try: + import transformers + except ImportError as e: + raise ImportError( + "Local HuggingFace inference requires the optional 'conserver-local' " + "dependency group plus a model backend (e.g. torch). " + "Install it with: uv sync --group conserver-local. " + "The default path (use_local_model=false) calls the HuggingFace API " + "and needs none of this." + ) from e logger.info(f"Initializing local model: {self.config.model}") device = "cpu" # Always use CPU for local models logger.info(f"Using device: {device}") diff --git a/conserver/links/hugging_llm_link/main.py b/conserver/links/hugging_llm_link/main.py index a76d3a71..06d53b7a 100644 --- a/conserver/links/hugging_llm_link/main.py +++ b/conserver/links/hugging_llm_link/main.py @@ -1,5 +1,4 @@ from typing import Dict, Any, Optional -from transformers import pipeline AUDIT_META = { "third_party_service": "Hugging Face", @@ -21,6 +20,9 @@ def __init__(self, model_name: str = "facebook/bart-large-mnli", **kwargs): model_name: The HuggingFace model to use **kwargs: Additional arguments passed to the model pipeline """ + # transformers is an optional dependency (group: conserver-local); import lazily. + from transformers import pipeline + self.classifier = pipeline( "zero-shot-classification", model=model_name, **kwargs ) diff --git a/pyproject.toml b/pyproject.toml index 556b4e83..50bf72cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,6 @@ api = [ conserver = [ {include-group = "storage"}, # Processing links - "transformers>=4.48.0", "openai>=1.60.0", "groq>=0.4.0", "deepgram-sdk>=3.1.5,<4.0.0", @@ -68,6 +67,16 @@ conserver = [ "watchdog", ] +# Optional: on-device HuggingFace inference for hugging_llm_link's local-model path. +# transformers is ~48MB (plus tokenizers/huggingface_hub, ~67MB total) and additionally +# needs a model backend such as torch installed separately. The default hugging_llm_link +# path (use_local_model=false) calls the HuggingFace HTTP API and needs none of this, so +# transformers is kept out of the base conserver install. +# Install with: uv sync --group conserver --group conserver-local +conserver-local = [ + "transformers>=4.48.0", +] + # Development / test dependencies. dev = [ "black>=24.2.0", diff --git a/uv.lock b/uv.lock index c343e112..aaf9aaa7 100644 --- a/uv.lock +++ b/uv.lock @@ -2356,9 +2356,11 @@ conserver = [ { name = "pymilvus" }, { name = "pymongo" }, { name = "slack-sdk" }, - { name = "transformers" }, { name = "watchdog" }, ] +conserver-local = [ + { name = "transformers" }, +] dev = [ { name = "anyio" }, { name = "black" }, @@ -2432,9 +2434,9 @@ conserver = [ { name = "pymilvus", specifier = ">=2.3.0" }, { name = "pymongo", specifier = ">=4.7.2" }, { name = "slack-sdk", specifier = ">=3.27.1" }, - { name = "transformers", specifier = ">=4.48.0" }, { name = "watchdog" }, ] +conserver-local = [{ name = "transformers", specifier = ">=4.48.0" }] dev = [ { name = "anyio", specifier = ">=4.8.0" }, { name = "black", specifier = ">=24.2.0" }, From 5af5a4cc8f360896c95ccd2307129f351328392e Mon Sep 17 00:00:00 2001 From: Thomas Howe Date: Wed, 3 Jun 2026 18:30:06 -0400 Subject: [PATCH 2/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- conserver/links/hugging_llm_link/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conserver/links/hugging_llm_link/__init__.py b/conserver/links/hugging_llm_link/__init__.py index 2985e506..39361639 100644 --- a/conserver/links/hugging_llm_link/__init__.py +++ b/conserver/links/hugging_llm_link/__init__.py @@ -162,7 +162,7 @@ def __init__(self, config: LLMConfig): raise ImportError( "Local HuggingFace inference requires the optional 'conserver-local' " "dependency group plus a model backend (e.g. torch). " - "Install it with: uv sync --group conserver-local. " + "Install it with: uv sync --group conserver --group conserver-local. " "The default path (use_local_model=false) calls the HuggingFace API " "and needs none of this." ) from e From 49e8a1b840f858b17d48bdd890ce1b52a049ef5b Mon Sep 17 00:00:00 2001 From: Thomas Howe Date: Wed, 3 Jun 2026 18:30:20 -0400 Subject: [PATCH 3/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- conserver/links/hugging_llm_link/main.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/conserver/links/hugging_llm_link/main.py b/conserver/links/hugging_llm_link/main.py index 06d53b7a..e40b1f31 100644 --- a/conserver/links/hugging_llm_link/main.py +++ b/conserver/links/hugging_llm_link/main.py @@ -21,8 +21,13 @@ def __init__(self, model_name: str = "facebook/bart-large-mnli", **kwargs): **kwargs: Additional arguments passed to the model pipeline """ # transformers is an optional dependency (group: conserver-local); import lazily. - from transformers import pipeline - + try: + from transformers import pipeline + except ImportError as e: + raise ImportError( + "HuggingLLMLink requires the optional 'conserver-local' dependency group. " + "Install it with: uv sync --group conserver --group conserver-local." + ) from e self.classifier = pipeline( "zero-shot-classification", model=model_name, **kwargs ) From b3db32ae085f7daea5f3effd18d150154a87430a Mon Sep 17 00:00:00 2001 From: Thomas Howe Date: Thu, 4 Jun 2026 15:56:23 -0400 Subject: [PATCH 4/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- conserver/links/hugging_llm_link/main.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/conserver/links/hugging_llm_link/main.py b/conserver/links/hugging_llm_link/main.py index e40b1f31..1148b872 100644 --- a/conserver/links/hugging_llm_link/main.py +++ b/conserver/links/hugging_llm_link/main.py @@ -23,7 +23,9 @@ def __init__(self, model_name: str = "facebook/bart-large-mnli", **kwargs): # transformers is an optional dependency (group: conserver-local); import lazily. try: from transformers import pipeline - except ImportError as e: + except ModuleNotFoundError as e: + if e.name != "transformers": + raise raise ImportError( "HuggingLLMLink requires the optional 'conserver-local' dependency group. " "Install it with: uv sync --group conserver --group conserver-local." From d31902d26e363eee6a2c136d2ea5a18530b71361 Mon Sep 17 00:00:00 2001 From: Thomas Howe Date: Thu, 4 Jun 2026 15:56:39 -0400 Subject: [PATCH 5/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- conserver/links/hugging_llm_link/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conserver/links/hugging_llm_link/main.py b/conserver/links/hugging_llm_link/main.py index 1148b872..ce042249 100644 --- a/conserver/links/hugging_llm_link/main.py +++ b/conserver/links/hugging_llm_link/main.py @@ -1,4 +1,4 @@ -from typing import Dict, Any, Optional +from typing import Any, Dict AUDIT_META = { "third_party_service": "Hugging Face", From 9a12e459ece144305fda2e90f9acec5512985ecc Mon Sep 17 00:00:00 2001 From: Pavan Kumar Date: Sun, 7 Jun 2026 15:25:04 +0530 Subject: [PATCH 6/6] Narrow transformers import guard to ModuleNotFoundError The lazy `import transformers` in LocalHuggingFaceLLM caught any ImportError and re-raised generic "install conserver-local" guidance. That masks failures where transformers is installed but raises while importing a transitive dependency/backend. Catch ModuleNotFoundError and re-raise unless the missing module is transformers itself, matching the pattern already used in main.py. Addresses Copilot review comment on the broad except ImportError. Co-Authored-By: Claude Opus 4.8 (1M context) --- conserver/links/hugging_llm_link/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/conserver/links/hugging_llm_link/__init__.py b/conserver/links/hugging_llm_link/__init__.py index 39361639..97919904 100644 --- a/conserver/links/hugging_llm_link/__init__.py +++ b/conserver/links/hugging_llm_link/__init__.py @@ -158,7 +158,12 @@ def __init__(self, config: LLMConfig): # the import is deferred to here rather than loaded at module import time. try: import transformers - except ImportError as e: + except ModuleNotFoundError as e: + # Only translate the "transformers itself is missing" case. If transformers + # is present but raises while importing a transitive dep/backend, re-raise the + # original error so the real cause isn't masked by the guidance below. + if e.name != "transformers": + raise raise ImportError( "Local HuggingFace inference requires the optional 'conserver-local' " "dependency group plus a model backend (e.g. torch). "