From 3fc353fe04a3781815f5178539685f16e14a4303 Mon Sep 17 00:00:00 2001 From: Andrew Neilson Date: Tue, 7 Jul 2026 23:46:19 -0700 Subject: [PATCH] Add workflow-copilot lite LLM env key (#7194) --- skyvern/config.py | 1 + skyvern/forge/forge_app.py | 6 ++++ skyvern/forge/sdk/copilot/llm_config.py | 10 ++++++ .../unit/test_workflow_copilot_llm_config.py | 31 ++++++++++++++++++- 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/skyvern/config.py b/skyvern/config.py index 5ef65e06c..d0d2c8cc2 100644 --- a/skyvern/config.py +++ b/skyvern/config.py @@ -353,6 +353,7 @@ class Settings(BaseSettings): WORKFLOW_COPILOT_LLM_KEY: str | None = None WORKFLOW_COPILOT_AGENT_LLM_KEY: str | None = None WORKFLOW_COPILOT_FAST_LLM_KEY: str | None = None + WORKFLOW_COPILOT_LITE_LLM_KEY: str | None = None # COMMON LLM_CONFIG_TIMEOUT: int = 300 LLM_CONFIG_MAX_TOKENS: int = 4096 diff --git a/skyvern/forge/forge_app.py b/skyvern/forge/forge_app.py index e3c808a42..34903e3db 100644 --- a/skyvern/forge/forge_app.py +++ b/skyvern/forge/forge_app.py @@ -92,6 +92,7 @@ class ForgeApp: WORKFLOW_COPILOT_LLM_API_HANDLER: LLMAPIHandler WORKFLOW_COPILOT_AGENT_LLM_API_HANDLER: LLMAPIHandler WORKFLOW_COPILOT_FAST_LLM_API_HANDLER: LLMAPIHandler + WORKFLOW_COPILOT_LITE_LLM_API_HANDLER: LLMAPIHandler | None WORKFLOW_CONTEXT_MANAGER: WorkflowContextManager WORKFLOW_SERVICE: WorkflowService AGENT_FUNCTION: AgentFunction @@ -274,6 +275,11 @@ def create_forge_app() -> ForgeApp: if settings.WORKFLOW_COPILOT_FAST_LLM_KEY else app.SECONDARY_LLM_API_HANDLER ) + app.WORKFLOW_COPILOT_LITE_LLM_API_HANDLER = ( + LLMAPIHandlerFactory.get_llm_api_handler(settings.WORKFLOW_COPILOT_LITE_LLM_KEY) + if settings.WORKFLOW_COPILOT_LITE_LLM_KEY + else None + ) app.WORKFLOW_CONTEXT_MANAGER = WorkflowContextManager() app.WORKFLOW_SERVICE = WorkflowService() diff --git a/skyvern/forge/sdk/copilot/llm_config.py b/skyvern/forge/sdk/copilot/llm_config.py index 387085d61..d1f84eeeb 100644 --- a/skyvern/forge/sdk/copilot/llm_config.py +++ b/skyvern/forge/sdk/copilot/llm_config.py @@ -50,6 +50,13 @@ def get_fast_copilot_handler() -> Any | None: return None +def get_lite_copilot_handler() -> Any | None: + try: + return app.WORKFLOW_COPILOT_LITE_LLM_API_HANDLER + except (RuntimeError, AttributeError): + return None + + async def resolve_main_copilot_handler(workflow_permanent_id: str | None, organization_id: str | None) -> Any | None: if workflow_permanent_id and organization_id: try: @@ -105,4 +112,7 @@ async def resolve_lite_copilot_handler(workflow_permanent_id: str | None, organi posthog_handler = None if posthog_handler is not None: return posthog_handler + handler = get_lite_copilot_handler() + if handler is not None: + return handler return await resolve_workflow_copilot_handler(workflow_permanent_id, organization_id) diff --git a/tests/unit/test_workflow_copilot_llm_config.py b/tests/unit/test_workflow_copilot_llm_config.py index 4fb5c420c..951b9e74b 100644 --- a/tests/unit/test_workflow_copilot_llm_config.py +++ b/tests/unit/test_workflow_copilot_llm_config.py @@ -3,7 +3,8 @@ Optional settings give operators independent control over the main Copilot lane, agent-specific lane, and fast-consumer lane: ``WORKFLOW_COPILOT_LLM_KEY``, ``WORKFLOW_COPILOT_AGENT_LLM_KEY``, and -``WORKFLOW_COPILOT_FAST_LLM_KEY``. +``WORKFLOW_COPILOT_FAST_LLM_KEY``. ``WORKFLOW_COPILOT_LITE_LLM_KEY`` controls +the dedicated RequestPolicy lite lane for local/dev fallback. These tests cover the public contract: defaults, fallback chains, and PostHog → env-specific → default resolution order. """ @@ -36,6 +37,10 @@ def test_workflow_copilot_fast_llm_key_default_is_none() -> None: assert Settings.model_fields["WORKFLOW_COPILOT_FAST_LLM_KEY"].default is None +def test_workflow_copilot_lite_llm_key_default_is_none() -> None: + assert Settings.model_fields["WORKFLOW_COPILOT_LITE_LLM_KEY"].default is None + + # --------------------------------------------------------------------------- # _get_narrator_handler fallback chain # --------------------------------------------------------------------------- @@ -361,6 +366,30 @@ async def test_resolve_lite_copilot_handler_falls_back_to_workflow_copilot( assert handler is workflow_handler +@pytest.mark.asyncio +async def test_resolve_lite_copilot_handler_prefers_dedicated_key_before_workflow_copilot( + monkeypatch: pytest.MonkeyPatch, +) -> None: + lite_handler = object() + + async def _posthog_lookup(*_args: object, **_kwargs: object) -> None: + return None + + async def _workflow_lookup(*_args: object, **_kwargs: object) -> object: + raise AssertionError("workflow fallback should not run when lite key is set") + + monkeypatch.setattr(copilot_llm_config, "get_llm_handler_for_prompt_type", _posthog_lookup) + monkeypatch.setattr(copilot_llm_config, "resolve_workflow_copilot_handler", _workflow_lookup) + monkeypatch.setattr( + copilot_llm_config, + "app", + SimpleNamespace(WORKFLOW_COPILOT_LITE_LLM_API_HANDLER=lite_handler), + ) + + handler = await copilot_llm_config.resolve_lite_copilot_handler("wpid_1", "org_1") + assert handler is lite_handler + + @pytest.mark.asyncio async def test_resolve_lite_copilot_handler_fallback_ignores_agent_specific_key( monkeypatch: pytest.MonkeyPatch,