108 lines
3.0 KiB
Python
108 lines
3.0 KiB
Python
from __future__ import annotations
|
|
|
|
from pydantic import BaseModel
|
|
|
|
from wf_authoring import WorkflowBuilder, input_from, input_path, output_to, state_path
|
|
from wf_core import END, InterruptRequest, RunState
|
|
|
|
|
|
class ChildInput(BaseModel):
|
|
"""Question forwarded into the child workflow."""
|
|
|
|
question: str
|
|
|
|
|
|
class ChildState(BaseModel):
|
|
"""Child-owned answer populated only after resume."""
|
|
|
|
answer: str = ""
|
|
|
|
|
|
class ChildOutput(BaseModel):
|
|
"""Child result projected through the parent subgraph boundary."""
|
|
|
|
answer: str
|
|
|
|
|
|
class ParentInput(BaseModel):
|
|
"""Parent input used to build the child interrupt request."""
|
|
|
|
question: str
|
|
|
|
|
|
class ParentState(BaseModel):
|
|
"""Parent state updated only after the child resumes and completes."""
|
|
|
|
result: str = ""
|
|
|
|
|
|
class ParentOutput(BaseModel):
|
|
"""Final result exposed by the parent workflow."""
|
|
|
|
result: str
|
|
|
|
|
|
def build_interrupting_child() -> WorkflowBuilder:
|
|
"""Build a child graph whose first step pauses for one supplied answer."""
|
|
child = WorkflowBuilder(
|
|
name="answer_child",
|
|
input_schema=ChildInput,
|
|
state_schema=ChildState,
|
|
output_schema=ChildOutput,
|
|
)
|
|
ask = child.interrupt(
|
|
id="ask",
|
|
kind="input",
|
|
request=[input_from(input_path("question"), "question")],
|
|
resume=[output_to("answer", state_path("answer"))],
|
|
)
|
|
child.set_entry_point(ask)
|
|
child.connect(ask, "submitted", END)
|
|
return child
|
|
|
|
|
|
def build_interrupting_parent(child: WorkflowBuilder) -> WorkflowBuilder:
|
|
"""Build a parent graph that invokes the interrupting child natively."""
|
|
parent = WorkflowBuilder(
|
|
name="answer_parent",
|
|
input_schema=ParentInput,
|
|
state_schema=ParentState,
|
|
output_schema=ParentOutput,
|
|
)
|
|
child_workflow = parent.prepare_subgraph(child)
|
|
request_answer = parent.subgraph(
|
|
workflow=child_workflow,
|
|
id="request_answer",
|
|
input=[input_from(input_path("question"), "question")],
|
|
output=[output_to("answer", state_path("result"))],
|
|
)
|
|
parent.set_entry_point(request_answer)
|
|
parent.connect(request_answer, "ok", END)
|
|
return parent
|
|
|
|
|
|
def run_native_subgraph_interrupt_example() -> tuple[InterruptRequest, RunState]:
|
|
"""Pause in a child workflow, then resume it through authoring helpers.
|
|
|
|
`RunState` is resumed in place, so the example retains the emitted request
|
|
before continuing the run.
|
|
"""
|
|
parent = build_interrupting_parent(build_interrupting_child())
|
|
paused = parent.execute({"question": "What is your answer?"})
|
|
if paused.interrupt is None:
|
|
raise AssertionError("expected child workflow to interrupt")
|
|
request = paused.interrupt
|
|
resumed = parent.resume(paused, payload={"answer": "confirmed"})
|
|
return request, resumed
|
|
|
|
|
|
def main() -> None:
|
|
"""Run the native interrupt/resume example from the command line."""
|
|
request, resumed = run_native_subgraph_interrupt_example()
|
|
print("interrupt", request.payload)
|
|
print("result", resumed.output)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|