d5fd6b1765
Extract shared build_llm_kwargs_from_options() utility in actor/config.py that converts actor-level YAML options (openai_api_base, openai_api_key, temperature, max_tokens, etc.) into kwargs for ProviderRegistry.create_llm(). Add options field to ActorConfigSchema so it survives Pydantic validation instead of being silently dropped by extra='ignore' default. Add resolve_actor_options() to PlanLifecycleService and both LifecycleService and PlanLifecycleProtocol protocols so every call site can retrieve the actor's config_blob.options dict. Fix the four call sites that previously called create_llm() with zero extra kwargs, preventing local/custom LLM backends from working: - StrategyActor._execute_with_llm() in strategy_actor.py - LLMStrategizeActor.execute() in llm_actors.py - LLMExecuteActor.execute() in llm_actors.py - SessionWorkflow._resolve_llm() in session_workflow.py Refactor reactive path (stream_router.py, tool_caller.py) to use the shared build_llm_kwargs_from_options() utility instead of inline copies. Wire actor_options_resolver through CLI session builder and A2A facade. ISSUES CLOSED: #11256
109 lines
6.0 KiB
Gherkin
109 lines
6.0 KiB
Gherkin
Feature: Actor options propagation to LLM constructor
|
|
Tests that actor-level `options` (openai_api_base, openai_api_key, etc.)
|
|
are correctly forwarded to `ProviderRegistry.create_llm()` across all
|
|
call sites: Strategize phase, Execute phase, and SessionWorkflow.
|
|
|
|
Forgejo: #11256
|
|
|
|
# ------------------------------------------------------------------
|
|
# build_llm_kwargs_from_options shared utility
|
|
# ------------------------------------------------------------------
|
|
|
|
Scenario: build_llm_kwargs_from_options forwards openai_api_base
|
|
Given aop actor options with "openai_api_base" set to "http://localhost:8000/v1"
|
|
When I call build_llm_kwargs_from_options
|
|
Then the result should contain "openai_api_base" with value "http://localhost:8000/v1"
|
|
|
|
Scenario: build_llm_kwargs_from_options routes openai_api_key through sentinel
|
|
Given aop actor options with "openai_api_key" set to "none"
|
|
When I call build_llm_kwargs_from_options
|
|
Then the result should contain "__api_key_sentinel" with value "none"
|
|
And the result should not contain "openai_api_key"
|
|
|
|
Scenario: build_llm_kwargs_from_options forwards allowed temperature
|
|
Given aop actor options with "temperature" set to "0.7"
|
|
When I call build_llm_kwargs_from_options
|
|
Then the result should contain "temperature" with value 0.7
|
|
|
|
Scenario: build_llm_kwargs_from_options rejects reserved keys
|
|
Given aop actor options with "provider_type" set to "anthropic"
|
|
When I call build_llm_kwargs_from_options
|
|
Then the result should not contain "provider_type"
|
|
|
|
Scenario: build_llm_kwargs_from_options rejects unknown keys
|
|
Given aop actor options with "dangerous_param" set to "value"
|
|
When I call build_llm_kwargs_from_options
|
|
Then the result should not contain "dangerous_param"
|
|
|
|
Scenario: build_llm_kwargs_from_options with None returns empty dict
|
|
Given aop actor options is None
|
|
When I call build_llm_kwargs_from_options
|
|
Then the result should be an empty dict
|
|
|
|
Scenario: build_llm_kwargs_from_options with empty dict returns empty dict
|
|
Given aop actor options is an empty dict
|
|
When I call build_llm_kwargs_from_options
|
|
Then the result should be an empty dict
|
|
|
|
Scenario: build_llm_kwargs_from_options forwards multiple allowed keys
|
|
Given aop actor options with "openai_api_base" set to "http://local:8000/v1"
|
|
And aop actor options with "max_tokens" set to "2048"
|
|
And aop actor options with "timeout" set to "60"
|
|
When I call build_llm_kwargs_from_options
|
|
Then the result should contain "openai_api_base" with value "http://local:8000/v1"
|
|
And the result should contain "max_tokens" with value 2048
|
|
And the result should contain "timeout" with value 60
|
|
|
|
# ------------------------------------------------------------------
|
|
# StrategyActor forwards actor options to create_llm
|
|
# ------------------------------------------------------------------
|
|
|
|
Scenario: StrategyActor resolves actor options and forwards to create_llm
|
|
Given aop a mock lifecycle service that resolves actor "local/test-strategist"
|
|
And aop the resolved actor options contain "openai_api_base" set to "http://backend:9000/v1"
|
|
And aop the resolved actor options contain "openai_api_key" set to "test-key"
|
|
And aop a mock provider registry that captures create_llm kwargs with LLM response
|
|
When aop the StrategyActor executes with LLM for plan "01KSQ4ADB3AVXTWEK2DD0ZJDKN"
|
|
Then aop create_llm should have been called
|
|
And aop create_llm kwargs should contain "openai_api_base" with value "http://backend:9000/v1"
|
|
And aop create_llm kwargs should contain "__api_key_sentinel" with value "test-key"
|
|
|
|
Scenario: StrategyActor creates LLM without options when actor has no options
|
|
Given aop a mock lifecycle service that resolves actor "openai/gpt-4"
|
|
And aop the resolved actor options is None
|
|
And aop a mock provider registry that captures create_llm kwargs with LLM response
|
|
When aop the StrategyActor executes with LLM for plan "01KSQ4ADB3AVXTWEK2DD0ZJCKN"
|
|
Then aop create_llm should have been called
|
|
And aop create_llm kwargs should not contain "openai_api_base"
|
|
|
|
# ------------------------------------------------------------------
|
|
# LLMStrategizeActor forwards actor options to create_llm
|
|
# ------------------------------------------------------------------
|
|
|
|
Scenario: LLMStrategizeActor forwards actor options to create_llm
|
|
Given aop a valid LLMStrategizeActor with actor options
|
|
When aop I call strategize execute with plan_id "PLAN_OPT" and stream callback
|
|
Then aop the strategize create_llm should have received "openai_api_base" with value "http://custom-backend:7000/v1"
|
|
And aop the strategize create_llm should have received "__api_key_sentinel" with value "local-key"
|
|
|
|
Scenario: LLMStrategizeActor creates LLM without options when none exist
|
|
Given aop a valid LLMStrategizeActor without actor options
|
|
When aop I call strategize execute with plan_id "PLAN_NOOPT" and no stream callback
|
|
Then aop the strategize create_llm should not have received "openai_api_base"
|
|
And aop the strategize create_llm should not have received "__api_key_sentinel"
|
|
|
|
# ------------------------------------------------------------------
|
|
# LLMExecuteActor forwards actor options to create_llm
|
|
# ------------------------------------------------------------------
|
|
|
|
Scenario: LLMExecuteActor forwards actor options to create_llm
|
|
Given aop a valid LLMExecuteActor with actor options
|
|
When aop I call execute actor with plan_id "EXEC_OPT" and read_only False
|
|
Then aop the execute create_llm should have received "openai_api_base" with value "http://custom-executor:9000/v1"
|
|
And aop the execute create_llm should have received "__api_key_sentinel" with value "exec-key"
|
|
|
|
Scenario: LLMExecuteActor creates LLM without options when none exist
|
|
Given aop a valid LLMExecuteActor without actor options
|
|
When aop I call execute actor with plan_id "EXEC_NOOPT" and read_only False
|
|
Then aop the execute create_llm should not have received "openai_api_base"
|
|
And aop the execute create_llm should not have received "__api_key_sentinel" |