first reducer sweep of failure
i will put second reducer sweep of logic error or second of success
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
from wf_core.conditions import safe_resolve_path
|
||||
@@ -9,6 +10,7 @@ from wf_core.models.workflow import Workflow
|
||||
from wf_core.run_state import InterruptRequest, RunState, StepExecutionResult
|
||||
from wf_core.runtime.ops.flow import advance_frame, append_step_result_trace
|
||||
from wf_core.runtime.ops.index import WorkflowIndex
|
||||
from wf_core.runtime.ops.merges import ReducerDefinition
|
||||
from wf_core.runtime.ops.state import apply_mapped_state
|
||||
|
||||
|
||||
@@ -45,6 +47,7 @@ def resume_interrupt(
|
||||
index: WorkflowIndex,
|
||||
resume_payload: dict[str, Any],
|
||||
resume_outcome: str,
|
||||
reducers: Mapping[str, ReducerDefinition] | None = None,
|
||||
) -> None:
|
||||
if run.current_frame_id is None:
|
||||
raise WorkflowExecutionError("interrupted run has no current frame")
|
||||
@@ -69,6 +72,7 @@ def resume_interrupt(
|
||||
resume_payload,
|
||||
step.out_map,
|
||||
run.state,
|
||||
reducers=reducers,
|
||||
missing_field_message="interrupt resume payload is missing required field {field}",
|
||||
)
|
||||
next_node_id = index.next_node_id(frame.node_id, resume_outcome)
|
||||
|
||||
@@ -149,10 +149,17 @@ def apply_reducer(
|
||||
current_value: Any,
|
||||
incoming_value: Any,
|
||||
destination_path: str,
|
||||
reducers: Mapping[str, ReducerDefinition] = DEFAULT_REDUCER_DEFINITIONS,
|
||||
reducers: Mapping[str, ReducerDefinition] | None = None,
|
||||
) -> Any:
|
||||
"""Apply one named pure reducer to a state write."""
|
||||
definition = reducers.get(reducer.name)
|
||||
"""Apply one named pure reducer to a state write.
|
||||
|
||||
Injected reducer definitions are additive over the built-ins so authoring
|
||||
tests and local packages can provide custom reducers without re-registering
|
||||
every `wf.std.*` reducer.
|
||||
"""
|
||||
definition = None if reducers is None else reducers.get(reducer.name)
|
||||
if definition is None:
|
||||
definition = DEFAULT_REDUCER_DEFINITIONS.get(reducer.name)
|
||||
if definition is None:
|
||||
raise WorkflowExecutionError(f"unknown reducer {reducer.name!r}")
|
||||
return definition.apply(
|
||||
|
||||
@@ -12,6 +12,7 @@ from wf_core.models.steps import NodeUse
|
||||
from wf_core.models.workflow import Workflow
|
||||
from wf_core.run_state import RunState, RuntimeContext, StepExecutionResult
|
||||
from wf_core.runtime.ops.frames import frame_context_values
|
||||
from wf_core.runtime.ops.merges import ReducerDefinition
|
||||
from wf_core.runtime.ops.schemas import validate_payload_against_schema
|
||||
from wf_core.runtime.ops.state import apply_output_map
|
||||
|
||||
@@ -65,6 +66,7 @@ def _finalize_node_execution(
|
||||
node_def: NodeDef,
|
||||
resolved_input: dict[str, Any],
|
||||
raw_result: NodeResult | dict[str, Any],
|
||||
reducers: Mapping[str, ReducerDefinition] | None = None,
|
||||
) -> StepExecutionResult:
|
||||
result = coerce_node_result(raw_result)
|
||||
|
||||
@@ -76,7 +78,13 @@ def _finalize_node_execution(
|
||||
validate_payload_against_schema(
|
||||
node_def.output_schema, result.output, f"node output for {node.id}"
|
||||
)
|
||||
state_changes = apply_output_map(workflow, node, result.output, run.state)
|
||||
state_changes = apply_output_map(
|
||||
workflow,
|
||||
node,
|
||||
result.output,
|
||||
run.state,
|
||||
reducers=reducers,
|
||||
)
|
||||
return StepExecutionResult(
|
||||
outcome=result.outcome,
|
||||
resolved_input=resolved_input,
|
||||
@@ -91,6 +99,7 @@ def execute_node_use(
|
||||
node: NodeUse,
|
||||
node_def: NodeDef,
|
||||
registry: Mapping[str, NodeHandler],
|
||||
reducers: Mapping[str, ReducerDefinition] | None = None,
|
||||
) -> StepExecutionResult:
|
||||
handler = registry.get(node.node)
|
||||
if handler is None:
|
||||
@@ -112,6 +121,7 @@ def execute_node_use(
|
||||
node_def=node_def,
|
||||
resolved_input=resolved_input,
|
||||
raw_result=raw_result,
|
||||
reducers=reducers,
|
||||
)
|
||||
|
||||
|
||||
@@ -121,6 +131,7 @@ async def execute_node_use_async(
|
||||
node: NodeUse,
|
||||
node_def: NodeDef,
|
||||
registry: Mapping[str, AsyncNodeHandler],
|
||||
reducers: Mapping[str, ReducerDefinition] | None = None,
|
||||
) -> StepExecutionResult:
|
||||
handler = registry.get(node.node)
|
||||
if handler is None:
|
||||
@@ -146,6 +157,7 @@ async def execute_node_use_async(
|
||||
node_def=node_def,
|
||||
resolved_input=resolved_input,
|
||||
raw_result=cast(NodeResult | dict[str, Any], raw_result),
|
||||
reducers=reducers,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
from wf_core.errors import WorkflowExecutionError
|
||||
@@ -13,7 +14,7 @@ from wf_core.paths import (
|
||||
set_nested_value,
|
||||
split_graph_path,
|
||||
)
|
||||
from wf_core.runtime.ops.merges import apply_reducer
|
||||
from wf_core.runtime.ops.merges import ReducerDefinition, apply_reducer
|
||||
|
||||
|
||||
def apply_output_map(
|
||||
@@ -21,12 +22,14 @@ def apply_output_map(
|
||||
node: NodeUse,
|
||||
node_output: dict[str, Any],
|
||||
state: dict[str, Any],
|
||||
reducers: Mapping[str, ReducerDefinition] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return apply_mapped_state(
|
||||
workflow,
|
||||
node_output,
|
||||
node.out_map,
|
||||
state,
|
||||
reducers=reducers,
|
||||
missing_field_message=f"node {node.id!r} did not return required mapped field {{field}}",
|
||||
)
|
||||
|
||||
@@ -37,6 +40,7 @@ def apply_mapped_state(
|
||||
mapping: dict[str, str],
|
||||
state: dict[str, Any],
|
||||
*,
|
||||
reducers: Mapping[str, ReducerDefinition] | None = None,
|
||||
missing_field_message: str,
|
||||
) -> dict[str, Any]:
|
||||
if has_overlapping_paths(mapping.values()):
|
||||
@@ -55,12 +59,23 @@ def apply_mapped_state(
|
||||
patch[destination_path] = value
|
||||
|
||||
for destination_path, value in patch.items():
|
||||
write_state_value(workflow, state, destination_path, value)
|
||||
write_state_value(
|
||||
workflow,
|
||||
state,
|
||||
destination_path,
|
||||
value,
|
||||
reducers=reducers,
|
||||
)
|
||||
return dict(patch)
|
||||
|
||||
|
||||
def write_state_value(
|
||||
workflow: Workflow, state: dict[str, Any], destination_path: str, value: Any
|
||||
workflow: Workflow,
|
||||
state: dict[str, Any],
|
||||
destination_path: str,
|
||||
value: Any,
|
||||
*,
|
||||
reducers: Mapping[str, ReducerDefinition] | None = None,
|
||||
) -> None:
|
||||
try:
|
||||
root, parts = split_graph_path(destination_path)
|
||||
@@ -74,7 +89,9 @@ def write_state_value(
|
||||
|
||||
declared_path = ".".join(parts)
|
||||
declared_field = workflow.state_schema.fields.get(declared_path)
|
||||
reducer = declared_field.reducer if declared_field else ReducerRef(name="wf.std.replace")
|
||||
reducer = (
|
||||
declared_field.reducer if declared_field else ReducerRef(name="wf.std.replace")
|
||||
)
|
||||
key_path = parts
|
||||
current_value = get_nested_value(state, key_path)
|
||||
merged_value = apply_reducer(
|
||||
@@ -82,6 +99,7 @@ def write_state_value(
|
||||
current_value=current_value,
|
||||
incoming_value=value,
|
||||
destination_path=destination_path,
|
||||
reducers=reducers,
|
||||
)
|
||||
safe_set_nested_value(state, key_path, merged_value)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user