Files
cleveragents-core/features/actor_options_propagation.feature
T
CoreRasurae d5fd6b1765 fix(strategize): propagate actor options.openai_api_base and options.openai_api_key to LLM client creation in Strategize/Execute paths
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
2026-05-28 17:35:08 -04:00

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"