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:
parent
c2b1ca076d
commit
d2104555c6
2 changed files with 89 additions and 2 deletions
|
|
@ -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
83
tests/test_packaging.py
Normal 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)
|
||||||
Loading…
Reference in a new issue