fix(deps): promote anthropic + openai to core dependencies (#176)

LLM provider SDKs are imported by services/corporate_memory and
services/verification_detector — both production code paths. Listing
them only in [project.optional-dependencies].dev caused the scheduler
container to boot-loop with ModuleNotFoundError on default
`docker compose up` deploys, because the Dockerfile installs core
deps only (`uv pip install --system --no-cache .`).

Adds tests/test_packaging.py to lock the contract: anthropic + openai
must live in [project].dependencies, not in dev extras.
This commit is contained in:
ZdenekSrotyr 2026-05-04 23:52:30 +02:00
parent c2b1ca076d
commit d2104555c6
2 changed files with 89 additions and 2 deletions

View file

@ -56,6 +56,12 @@ dependencies = [
# default — fine for single-replica deploys. Multi-replica rollouts can # default — fine for single-replica deploys. Multi-replica rollouts can
# swap the storage backend via slowapi's `storage_uri` (Redis, Memcached). # swap the storage backend via slowapi's `storage_uri` (Redis, Memcached).
"slowapi>=0.1.9", "slowapi>=0.1.9",
# LLM provider SDKs — core (not dev) because connectors/llm/*_provider.py
# is imported by services/{corporate_memory, verification_detector} which
# the scheduler drives in production. Promoted from [dev] in #176 to fix
# ModuleNotFoundError boot loops on default Compose deploys.
"anthropic>=0.30.0",
"openai>=1.30.0",
] ]
[project.optional-dependencies] [project.optional-dependencies]
@ -66,8 +72,6 @@ dev = [
"pytest-timeout>=2.0.0", "pytest-timeout>=2.0.0",
"pytest-xdist>=3.0.0", "pytest-xdist>=3.0.0",
"faker>=24.0.0", "faker>=24.0.0",
"anthropic>=0.30.0",
"openai>=1.30.0",
# jsonschema validates the corporate-memory extraction-tool golden fixtures # jsonschema validates the corporate-memory extraction-tool golden fixtures
# under tests/test_corporate_memory_v1.py (extraction.json, correction.json, # under tests/test_corporate_memory_v1.py (extraction.json, correction.json,
# confidence_calibration.json). Production code does not depend on it. # confidence_calibration.json). Production code does not depend on it.

83
tests/test_packaging.py Normal file
View file

@ -0,0 +1,83 @@
"""Packaging regression tests — guard against silent prod-vs-dev dep drift.
`anthropic` and `openai` are imported by `connectors/llm/anthropic_provider.py`
and `connectors/llm/openai_compat.py`. Those modules run in production from
`services/corporate_memory` and `services/verification_detector`. If they
slip back into `[project.optional-dependencies].dev` the Dockerfile (which
only installs core deps) will boot-loop on `ModuleNotFoundError`. See #176.
"""
from __future__ import annotations
from pathlib import Path
import pytest
def _read_pyproject() -> dict:
"""Load pyproject.toml from the repo root."""
try:
import tomllib # py3.11+
except ImportError: # pragma: no cover
import tomli as tomllib # type: ignore
root = Path(__file__).resolve().parent.parent
with (root / "pyproject.toml").open("rb") as f:
return tomllib.load(f)
def test_anthropic_is_a_core_dependency():
"""anthropic must live in [project].dependencies, not [dev].
Production code (connectors/llm/anthropic_provider.py) imports the SDK
unconditionally. Demoting it to dev resurrects the #176 boot loop.
"""
cfg = _read_pyproject()
core = cfg["project"]["dependencies"]
assert any(dep.startswith("anthropic") for dep in core), (
"anthropic must be in [project].dependencies — see #176"
)
def test_openai_is_a_core_dependency():
"""openai must live in [project].dependencies, not [dev]."""
cfg = _read_pyproject()
core = cfg["project"]["dependencies"]
assert any(dep.startswith("openai") for dep in core), (
"openai must be in [project].dependencies — see #176"
)
def test_anthropic_not_in_optional_dev_extras():
"""Belt-and-suspenders: dev extras should not double-list anthropic."""
cfg = _read_pyproject()
dev = cfg["project"].get("optional-dependencies", {}).get("dev", [])
assert not any(dep.startswith("anthropic") for dep in dev), (
"anthropic should not be duplicated in [dev] — keep it core-only"
)
def test_openai_not_in_optional_dev_extras():
"""Belt-and-suspenders: dev extras should not double-list openai."""
cfg = _read_pyproject()
dev = cfg["project"].get("optional-dependencies", {}).get("dev", [])
assert not any(dep.startswith("openai") for dep in dev), (
"openai should not be duplicated in [dev] — keep it core-only"
)
def test_llm_provider_modules_import_cleanly():
"""A fresh interpreter with only core deps installed must import the
LLM provider modules without ImportError. This is the actual behavior
that breaks the scheduler container when anthropic/openai are dev-only.
"""
# Just importing here proves the deps resolve in the active env. The
# pyproject.toml assertions above keep the contract going forward.
import importlib
for mod in (
"connectors.llm.anthropic_provider",
"connectors.llm.openai_compat",
"connectors.llm.factory",
):
importlib.import_module(mod)