project workflow state -> output

This commit is contained in:
lda
2026-05-26 00:43:23 +07:00 Verified
parent fad915a186
commit 6846c649b6
16 changed files with 307 additions and 23 deletions
+5 -1
View File
@@ -96,7 +96,11 @@ def advance_frame(
def finalize_run(workflow: Workflow, run: RunState) -> RunState:
if run.outcome is None:
run.outcome = "ok"
run.output = project_output(workflow, run.state)
run.output = project_output(
workflow,
run.state,
workflow_input=run.workflow_input,
)
validate_payload_against_schema(
workflow.output_schema, run.output, "workflow output"
)
+43 -6
View File
@@ -5,11 +5,22 @@ from copy import deepcopy
from dataclasses import dataclass, field as dataclass_field
from typing import Any
from wf_core.conditions import safe_resolve_path
from wf_core.errors import WorkflowExecutionError
from wf_core.local_paths import LocalPathError, get_local_value, has_overlapping_paths
from wf_core.local_paths import (
LocalPathError,
get_local_value,
has_overlapping_paths,
set_local_value,
)
from wf_core.models.reducers import ReducerRef
from wf_core.models.schemas import StateFieldDecl
from wf_core.models.steps import NodeUse, OutputBinding
from wf_core.models.steps import (
InputPathBinding,
InputValueBinding,
NodeUse,
OutputBinding,
)
from wf_core.models.workflow import Workflow
from wf_core.paths import (
PathResolutionError,
@@ -437,12 +448,38 @@ def reducer_for_state_path(
)
def project_output(workflow: Workflow, state: dict[str, Any]) -> dict[str, Any]:
"""Project final workflow output from same-named state fields.
def project_output(
workflow: Workflow,
state: dict[str, Any],
*,
workflow_input: Mapping[str, Any] | None = None,
context: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
"""Project final workflow output from explicit bindings or state fields.
Node output bindings write into state during execution. At END, the runtime
exposes only state keys declared by `workflow.output_schema.properties`.
`workflow.output` is the canonical root-output mapping for workflows whose
public output shape does not mirror top-level state keys. Older workflows
without explicit output bindings keep the same-name state projection.
"""
if workflow.output:
output: dict[str, Any] = {}
for binding in workflow.output:
if isinstance(binding, InputValueBinding):
value = binding.value
elif isinstance(binding, InputPathBinding):
value = safe_resolve_path(
str(binding.path),
state=state,
workflow_input=workflow_input or {},
context=context or {},
)
else:
raise WorkflowExecutionError("unsupported workflow output binding")
try:
set_local_value(output, binding.target, value)
except LocalPathError as exc:
raise WorkflowExecutionError(str(exc)) from exc
return output
return {
key: state[key] for key in workflow.output_schema.properties if key in state
}