Skip to content

Python: fix(declarative): treat omitted workflow inputs as blank in PowerFx - #9730

Draft
Naveen Chatlapalli (1aifanatic) wants to merge 2 commits into
microsoft:mainfrom
1aifanatic:contrib/python-declarative-missing-inputs-blank
Draft

Naveen Chatlapalli (1aifanatic) wants to merge 2 commits into
microsoft:mainfrom
1aifanatic:contrib/python-declarative-missing-inputs-blank

Conversation

@1aifanatic

Copy link
Copy Markdown
Contributor

Motivation & Context

=If(IsBlank(inputs.name), "World", inputs.name) never applies its default when the caller omits name, so the simple_workflow sample prints Hello, ! for workflow.run({}). PowerFx fails to compile inputs.name when the inputs record has no such field, and DeclarativeWorkflowState.eval() returns None for the whole expression. Details and an input matrix are in #9729.

Description & Review Guide

  • What are the major changes?
    • _bind_missing_input_references() finds inputs.<name> / Workflow.Inputs.<name> references in the formula. Any input the caller did not supply is bound to blank (None) in that evaluation's PowerFx symbols, for both aliases. Workflow state is not modified, and other undefined names keep the existing None behavior. The regex follows the existing _ENV_REFERENCE_RE approach for Env.<name>.
    • New parametrized test test_if_with_isblank_when_input_is_missing, next to test_if_with_isblank, which only covered {"name": ""}. It checks both aliases and that the missing key is not written to state.
    • The simple_workflow sample now prints the outputs of its second ("Alice") run, which were discarded.
  • What is the impact of these changes? An omitted input now evaluates the same as an input passed as None. Expressions that previously evaluated to None only because an input was absent now evaluate normally with that input blank.
  • What do you want reviewers to focus on? Whether blank is the semantics you want for omitted inputs. An alternative is binding only inputs declared in the workflow's inputs: schema, but simple_workflow does not declare its inputs.

Verification, with the .NET 10 runtime for PowerFx:

main this PR
new test (both aliases) assert None == 'World' passes
simple_workflow sample Hello, !, then no output for Alice Hello, World!, Hello, Alice!
packages/declarative/tests all pass

Pyright on _declarative_base.py reports 0 errors. Mypy reports the same 5 errors before and after (none on the changed lines). ruff check and ruff format --check pass.

Related Issue

Fixes #9729

AI Assistance

  • No material AI assistance was used.
  • This is an AI-assisted contribution. I reviewed, understood, and verified all submitted content and accept responsibility for it.

AI assistance details: Claude Code helped find the bug by running the declarative samples locally, trace it to DeclarativeWorkflowState.eval(), implement the fix and test, and draft this description and the linked issue.

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR links to an agreed issue with no competing open PR, or the Related Issue section documents a trivial-change or repository-automation exception.
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

🤖 Generated with Claude Code

…owerFx

When a caller omits an input that a formula references, PowerFx fails to
compile inputs.<name> / Workflow.Inputs.<name> ("Name isn't valid") and
eval() returns None for the whole expression. That breaks the documented
default pattern =If(IsBlank(inputs.name), "World", inputs.name): the
simple_workflow sample printed "Hello, !" for workflow.run({}).

Bind referenced-but-missing inputs to blank for that evaluation only, the
same result as passing the input as None, without writing them to state.
Also print the simple_workflow sample's second run, which was discarded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 26, 2026 18:36
@agent-framework-automation agent-framework-automation Bot added the python Usage: [Issues, PRs], Target: Python label Oct 26, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Reference discovery matches literal and comment text while missing valid quoted and whitespace-separated references.

2 open issues
What changed in this PR

This PR addresses #9729 by binding omitted workflow inputs as blank during PowerFx evaluation, allowing default expressions to run without modifying workflow state.

Changes:

  • Adds evaluation-only blank bindings for references through both input aliases.
  • Tests omitted-input defaults and verifies state remains unchanged.
  • Prints the custom-name run’s outputs in the simple workflow sample.
File Description
python/​samples/​03-workflows/​declarative/​simple_workflow/​main.py Prints outputs from the “Alice” run.
python/​packages/​declarative/​tests/​test_powerfx_yaml_compatibility.py Adds missing-input regression tests for both aliases.
python/​packages/​declarative/​agent_framework_declarative/​_workflows/​_declarative_base.py Discovers missing input references and binds evaluation-only blanks.

Review effort: Balanced

Skip comments and string literals when looking for inputs.<name> and
Workflow.Inputs.<name>, and accept single-quoted field names and
whitespace or comments around the dots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
if member is None or member[0] != "Inputs":
continue
after_root = member[1]
elif root_name != "inputs":

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naveen Chatlapalli (@1aifanatic) This can treat a member name or locally bound inputs as the global workflow alias. After state.initialize({"other": 1}) and state.set("Local.inputs", {"name": "local"}), =If(Local . inputs . name = "local", inputs, inputs) returns {"other": 1.0, "name": None} instead of the base result {"other": 1.0}.

With({inputs: {name: "local"}}, {Chosen: inputs.name, Received: Workflow.Inputs}) likewise adds a global blank field solely from its local reference. Please distinguish actual global aliases from member names and local bindings before adding missing fields.

if not missing:
return symbols
bound_inputs: dict[str, Any] = {**input_values, **dict.fromkeys(sorted(missing))}
return {**symbols, "Workflow": {**symbols["Workflow"], "Inputs": bound_inputs}, "inputs": bound_inputs}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naveen Chatlapalli (@1aifanatic) The inputs symbol can be a custom namespace rather than the canonical input record. Replacing both bindings from it loses supplied values: after state.initialize({"name": "Alice"}) and state.set("inputs.other", 1), state.eval("=Workflow.Inputs.name") now returns None; base returns "Alice".

The public YAML path also reproduces this: SetValue at inputs.other, followed by SendActivity reading Workflow.Inputs.name, loses the message. Please preserve independently resolved records and synchronize aliases only when they still refer to the same canonical input namespace.

This branch was previously deployed

1 inactive deployment
github-app-auth — d77e9713 Deployed Oct 27, 2026 by 1aifanatic via add_label #26270
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

.NET: Python: [Bug]: Declarative workflow IsBlank defaults ignore inputs the caller omitted

3 participants