diff --git a/state-hub/pyproject.toml b/state-hub/pyproject.toml index 1e04d18..122f4d3 100644 --- a/state-hub/pyproject.toml +++ b/state-hub/pyproject.toml @@ -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", diff --git a/state-hub/tests/__init__.py b/state-hub/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/state-hub/tests/test_llm_connect_integration.py b/state-hub/tests/test_llm_connect_integration.py new file mode 100644 index 0000000..e414eda --- /dev/null +++ b/state-hub/tests/test_llm_connect_integration.py @@ -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) diff --git a/state-hub/uv.lock b/state-hub/uv.lock index afb9d88..ad8b2e6 100644 --- a/state-hub/uv.lock +++ b/state-hub/uv.lock @@ -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"