From e71285b9ec67bd0b9b809ba97fc9f329782ca8fe Mon Sep 17 00:00:00 2001 From: Adrian Zawadzki Date: Mon, 28 Sep 2026 23:06:41 +0200 Subject: [PATCH 1/2] removed required provider settings --- statek/llm_api.py | 44 +++++++++++++++++++++++++++++++------------ tests/test_llm_api.py | 38 ++++++++++++++++++++++++++++++++++++- 2 files changed, 69 insertions(+), 13 deletions(-) diff --git a/statek/llm_api.py b/statek/llm_api.py index 0a02bb6..d901312 100644 --- a/statek/llm_api.py +++ b/statek/llm_api.py @@ -636,6 +636,21 @@ async def process_request( # pylint: disable=too-many-arguments,too-many-positi Returns: LLM_Response containing step data and provider usage stats. """ + settings = getattr(self, "settings", None) + if settings is not None or getattr(self, "_provider_settings_missing", False): + missing = [ + suffix for suffix, value in ( + ("API_URL", getattr(settings, "api_url", None)), + ("API_KEY", getattr(settings, "api_key", None)), + ) if not value + ] + if missing: + provider = getattr(self, "_provider_name", type(self).__name__.upper()) + names = ", ".join(f"{provider}_{suffix}" for suffix in missing) + raise ValueError( + f"Cannot send an LLM request for {provider}: set {names}." + ) + request_kwargs = self._prepare_request_kwargs( system_prompt=system_prompt, model=model, @@ -733,21 +748,26 @@ def get(provider_name: str = None, model: str = None, **kwargs): settings = custom_provider["settings"] else: settings = get_provider_settings(provider_key) - if not settings: - raise ValueError(f"No settings found for {provider_key} provider.") + settings_missing = settings is None + if settings_missing: + settings = LLM_API_Settings(api_url="", api_key="") if custom_provider is not None: provider_kwargs = {**custom_provider["kwargs"], **kwargs} - return custom_provider["impl"](settings=settings, **provider_kwargs) - - compatible_provider_cls = OPENAI_COMPATIBLE_API_PROVIDERS.get(provider_key) - if compatible_provider_cls is not None: - return compatible_provider_cls(settings=settings, **kwargs) - if provider_key in ('VERTEXAI', 'VERTEX_AI', 'GOOGLE_VERTEXAI', 'GOOGLE'): - return VertexAI_API(settings=settings, **kwargs) - if provider_key in ('CLAUDEAI', 'CLAUDE_AI', 'CLAUDE', 'ANTHROPIC'): - return ClaudeAI_API(settings=settings, **kwargs) - raise ValueError(f"Unsupported LLM API provider: {provider_name}") + api = custom_provider["impl"](settings=settings, **provider_kwargs) + else: + compatible_provider_cls = OPENAI_COMPATIBLE_API_PROVIDERS.get(provider_key) + if compatible_provider_cls is not None: + api = compatible_provider_cls(settings=settings, **kwargs) + elif provider_key in ('VERTEXAI', 'VERTEX_AI', 'GOOGLE_VERTEXAI', 'GOOGLE'): + api = VertexAI_API(settings=settings, **kwargs) + elif provider_key in ('CLAUDEAI', 'CLAUDE_AI', 'CLAUDE', 'ANTHROPIC'): + api = ClaudeAI_API(settings=settings, **kwargs) + else: + raise ValueError(f"Unsupported LLM API provider: {provider_name}") + api._provider_name = provider_key # pylint: disable=protected-access,attribute-defined-outside-init + api._provider_settings_missing = settings_missing # pylint: disable=protected-access,attribute-defined-outside-init + return api class DefaultLLM_API_Impl(LLM_API): """Default OpenAI-compatible chat-completions implementation of LLM_API. diff --git a/tests/test_llm_api.py b/tests/test_llm_api.py index 46abe02..5697ac9 100644 --- a/tests/test_llm_api.py +++ b/tests/test_llm_api.py @@ -62,7 +62,7 @@ def my_sys_tool(n: int, **kwargs): # pylint: disable=unused-argument # LLM_API.get factory # --------------------------------------------------------------------------- -class TestLLMAPIGetFactory: +class TestLLMAPIGetFactory: # pylint: disable=too-many-public-methods """Tests for provider factory selection and caching.""" def setup_method(self): @@ -83,6 +83,42 @@ def test_get_openai_provider(self): assert isinstance(api, OpenAI_API) + @pytest.mark.parametrize("provider_name", ["OPENROUTER", "CLAUDEAI", "VERTEXAI"]) + def test_preview_without_provider_settings(self, provider_name): + with patch("statek.llm_api.get_provider_settings", return_value=None): + api = LLM_API.get(provider_name=provider_name) + + payload = api.preview_request(model="test-model", system_prompt="hello") + assert payload + + @pytest.mark.asyncio + @pytest.mark.parametrize("provider_name", ["OPENROUTER", "CLAUDEAI", "VERTEXAI"]) + async def test_send_without_provider_settings_names_required_variables(self, provider_name): + with patch("statek.llm_api.get_provider_settings", return_value=None): + api = LLM_API.get(provider_name=provider_name) + + with patch.object(api, "_process_request", new_callable=AsyncMock) as send: + with pytest.raises(ValueError) as error: + await api.process_request(model="test-model") + + assert f"{provider_name}_API_URL" in str(error.value) + assert f"{provider_name}_API_KEY" in str(error.value) + send.assert_not_awaited() + + @pytest.mark.asyncio + async def test_send_with_empty_api_key_names_only_missing_variable(self): + settings = LLM_API_Settings(api_url="https://example.test", api_key="") + with patch("statek.llm_api.get_provider_settings", return_value=settings): + api = LLM_API.get(provider_name="OPENAI") + + assert api.preview_request(model="test-model") + with patch.object(api, "_process_request", new_callable=AsyncMock) as send: + with pytest.raises(ValueError, match="OPENAI_API_KEY") as error: + await api.process_request(model="test-model") + + assert "OPENAI_API_URL" not in str(error.value) + send.assert_not_awaited() + def test_openai_and_openrouter_wrap_default_implementation(self): openai_settings = LLM_API_Settings( api_url="https://api.openai.com/v1/chat/completions", From b7721ae2871f15900fdb077161318313ac81ba6a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 28 Sep 2026 21:13:10 +0000 Subject: [PATCH 2/2] ci action: release version 1.3.3 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index eb3f23d..dbb7480 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "statek" -version = "1.3.2" +version = "1.3.3" description = "Durable Python workspaces for stateful LLM agents" readme = "README.md" requires-python = ">=3.10"