feat(state-hub): integrate llm-connect as dependency (S3.1)

Add llm-connect as an editable local dependency via [tool.uv.sources].
Creates tests/test_llm_connect_integration.py: 7 offline smoke tests
covering public symbol imports, MockLLMAdapter execute/reset, RunConfig
and LLMResponse fields, and ErrorLLMAdapter error propagation.

All 7 tests pass. Satisfies workstream llm-shared-library S3.1.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 08:32:17 +01:00
parent 531f278f73
commit 444b35d68c
4 changed files with 103 additions and 0 deletions

View File

@@ -15,6 +15,7 @@ dependencies = [
"fastmcp>=2.0.0",
"python-dotenv>=1.0.0",
"psycopg2-binary>=2.9.0",
"llm-connect",
]
[project.scripts]
@@ -28,6 +29,9 @@ build-backend = "hatchling.build"
packages = ["api", "mcp_server"]
artifacts = ["custodian_cli.py"]
[tool.uv.sources]
llm-connect = { path = "/home/worsch/llm-connect", editable = true }
[tool.uv]
dev-dependencies = [
"pytest>=8.0.0",

View File

View File

@@ -0,0 +1,74 @@
"""
Smoke test: llm-connect integration in state-hub (S3.1).
Verifies that the package is importable from the state-hub venv and that
the core types and mock adapter work correctly. Does NOT call any live
LLM endpoint — CI-safe and offline-safe.
"""
import llm_connect
from llm_connect import (
MockLLMAdapter,
RunConfig,
LLMResponse,
LLMError,
create_adapter,
)
def test_package_version():
assert hasattr(llm_connect, "__version__") or True # version attr optional
def test_public_symbols_importable():
"""All expected public symbols are present in llm_connect namespace."""
expected = [
"RunConfig", "LLMResponse",
"LLMAdapter", "MockLLMAdapter", "ErrorLLMAdapter",
"OpenRouterAdapter", "GeminiAdapter", "OpenAIAdapter", "ClaudeCodeAdapter",
"create_adapter",
"LLMError", "LLMConfigurationError", "LLMAPIError",
"LLMRateLimitError", "LLMTimeoutError", "LLMSubprocessError",
"EmbeddingAdapter", "OpenAICompatibleEmbeddingAdapter", "EmbeddingCache",
"cosine_similarity",
]
missing = [name for name in expected if not hasattr(llm_connect, name)]
assert missing == [], f"Missing public symbols: {missing}"
def test_mock_adapter_execute():
adapter = MockLLMAdapter(mock_response="hello from mock")
config = RunConfig()
result = adapter.execute_prompt("any prompt", config)
assert isinstance(result, LLMResponse)
assert result.content == "hello from mock"
assert adapter.call_count == 1
def test_mock_adapter_reset():
adapter = MockLLMAdapter(mock_response="x")
adapter.execute_prompt("p", RunConfig())
adapter.reset()
assert adapter.call_count == 0
def test_run_config_defaults():
cfg = RunConfig()
assert cfg.temperature >= 0
assert cfg.max_tokens > 0
def test_llm_response_fields():
r = LLMResponse(content="ok", model="test-model")
assert r.content == "ok"
assert r.model == "test-model"
def test_error_adapter_raises():
from llm_connect import ErrorLLMAdapter
adapter = ErrorLLMAdapter(error_message="simulated failure")
try:
adapter.execute_prompt("p", RunConfig())
assert False, "should have raised"
except (LLMError, RuntimeError) as e:
assert "simulated failure" in str(e)

25
state-hub/uv.lock generated
View File

@@ -663,6 +663,20 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160 },
]
[[package]]
name = "llm-connect"
version = "0.1.0"
source = { editable = "../../llm-connect" }
dependencies = [
{ name = "toml" },
]
[package.metadata]
requires-dist = [
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0" },
{ name = "toml" },
]
[[package]]
name = "mako"
version = "1.3.10"
@@ -1405,6 +1419,7 @@ dependencies = [
{ name = "fastapi" },
{ name = "fastmcp" },
{ name = "httpx" },
{ name = "llm-connect" },
{ name = "psycopg2-binary" },
{ name = "pydantic" },
{ name = "pydantic-settings" },
@@ -1427,6 +1442,7 @@ requires-dist = [
{ name = "fastapi", specifier = ">=0.115.0" },
{ name = "fastmcp", specifier = ">=2.0.0" },
{ name = "httpx", specifier = ">=0.28.0" },
{ name = "llm-connect", editable = "../../llm-connect" },
{ name = "psycopg2-binary", specifier = ">=2.9.0" },
{ name = "pydantic", specifier = ">=2.10.0" },
{ name = "pydantic-settings", specifier = ">=2.7.0" },
@@ -1442,6 +1458,15 @@ dev = [
{ name = "pytest-asyncio", specifier = ">=0.24.0" },
]
[[package]]
name = "toml"
version = "0.10.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588 },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"