Skip to content

Commit bfc4fdb

Browse files
authored
Python: fix(declarative): honor the variable field in SetValue (#9428)
* Python: test that declarative SetValue writes to its variable field * Python: fix(declarative): honor the variable field in SetValue * Python: test that SetValue path wins over variable * Python: test SetValue variable alias through a real factory run
1 parent a32cf0b commit bfc4fdb

3 files changed

Lines changed: 63 additions & 1 deletion

File tree

‎python/packages/declarative/agent_framework_declarative/_workflows/_executors_basic.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ async def handle_action(
5353
"""Handle the SetValue action."""
5454
state = await self._ensure_state_initialized(ctx, trigger)
5555

56-
path = self._action_def.get("path")
56+
# The builder accepts 'variable' in place of 'path' for SetValue
57+
path = self._action_def.get("path") or _get_variable_path(self._action_def)
5758
value = self._action_def.get("value")
5859

5960
if path:

‎python/packages/declarative/tests/test_graph_coverage.py‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,34 @@ async def test_set_variable_executor(self, mock_context, mock_state):
451451
result = state.get("Local.result")
452452
assert result == "test value"
453453

454+
@pytest.mark.parametrize("variable", ["Local.target", {"path": "Local.target"}])
455+
async def test_set_value_executor_with_variable_field(self, mock_context, mock_state, variable):
456+
"""SetValue accepts 'variable' in place of 'path', so it must write to it."""
457+
from agent_framework_declarative._workflows._executors_basic import SetValueExecutor
458+
459+
state = DeclarativeWorkflowState(mock_state)
460+
state.initialize()
461+
462+
action_def = {"kind": "SetValue", "variable": variable, "value": "test value"}
463+
executor = SetValueExecutor(action_def)
464+
await executor.handle_action(ActionTrigger(), mock_context)
465+
466+
assert state.get("Local.target") == "test value"
467+
468+
async def test_set_value_executor_path_wins_over_variable(self, mock_context, mock_state):
469+
"""When SetValue has both 'path' and 'variable', 'path' is used."""
470+
from agent_framework_declarative._workflows._executors_basic import SetValueExecutor
471+
472+
state = DeclarativeWorkflowState(mock_state)
473+
state.initialize()
474+
475+
action_def = {"kind": "SetValue", "path": "Local.a", "variable": "Local.b", "value": "v"}
476+
executor = SetValueExecutor(action_def)
477+
await executor.handle_action(ActionTrigger(), mock_context)
478+
479+
assert state.get("Local.a") == "v"
480+
assert state.get("Local.b") is None
481+
454482
async def test_set_variable_executor_with_nested_variable(self, mock_context, mock_state):
455483
"""Test SetVariableExecutor with nested variable object."""
456484
from agent_framework_declarative._workflows._executors_basic import (

‎python/packages/declarative/tests/test_workflow_factory.py‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,39 @@ async def test_devui_declarative_workflow_categorizes_message_input(self, age: i
359359
assert any(f"categorized as: {category}" in str(output) for output in outputs)
360360

361361

362+
class TestWorkflowFactorySetValueAlias:
363+
"""SetValue's 'variable' alias through a real factory run. Literal values, so no PowerFx needed."""
364+
365+
@pytest.mark.parametrize(
366+
"variable_yaml",
367+
["variable: Local.target", "variable:\n path: Local.target"],
368+
ids=["string", "nested"],
369+
)
370+
async def test_set_value_variable_alias_persists_in_checkpoint(self, variable_yaml: str) -> None:
371+
"""The builder accepts 'variable' for SetValue, so a real run must write and checkpoint it."""
372+
from agent_framework import InMemoryCheckpointStorage
373+
374+
from agent_framework_declarative._workflows._declarative_base import DECLARATIVE_STATE_KEY
375+
376+
storage = InMemoryCheckpointStorage()
377+
workflow = WorkflowFactory(checkpoint_storage=storage).create_workflow_from_yaml(f"""
378+
name: set-value-variable-alias
379+
actions:
380+
- kind: SetValue
381+
{variable_yaml}
382+
value: from variable
383+
""")
384+
await workflow.run({})
385+
386+
checkpoints = await storage.list_checkpoints(workflow_name=workflow.name)
387+
assert checkpoints
388+
# Early checkpoints are taken before the declarative state exists.
389+
saved = [
390+
checkpoint.state.get(DECLARATIVE_STATE_KEY, {}).get("Local", {}).get("target") for checkpoint in checkpoints
391+
]
392+
assert "from variable" in saved
393+
394+
362395
@_requires_powerfx
363396
class TestWorkflowFactoryExecution:
364397
"""Tests for workflow execution."""

0 commit comments

Comments
 (0)