Python: Workflow event source updates (#789)

* Workflow event source updates

* Add WorkflowLifecycleEvent TypeAlias. Update docstrings

* Updates

* Rename

---------

Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>
This commit is contained in:
Evan Mattson
2025-09-19 00:17:55 +09:00
committed by GitHub
Unverified
parent 6a66ad517a
commit 960196cd52
18 changed files with 350 additions and 118 deletions
@@ -29,7 +29,8 @@ Purpose:
Show how to wrap chat agents created by AzureChatClient inside workflow executors, wire them with WorkflowBuilder,
and consume streaming events from the workflow. Demonstrate the @handler pattern with typed inputs and typed
WorkflowContext[T] outputs, and finish by emitting a WorkflowCompletedEvent from the terminal node while printing
intermediate events for observability.
intermediate events for observability. The streaming loop also surfaces WorkflowEvent.origin so you can
distinguish runner-generated lifecycle events from executor-generated data-plane events.
Prerequisites:
- Azure OpenAI configured for AzureChatClient with required environment variables.
@@ -123,36 +124,42 @@ async def main():
ChatMessage(role="user", text="Create a slogan for a new electric SUV that is affordable and fun to drive.")
):
if isinstance(event, WorkflowStatusEvent):
prefix = f"State ({event.origin.value}): "
if event.state == WorkflowRunState.IN_PROGRESS:
print("State: IN_PROGRESS")
print(prefix + "IN_PROGRESS")
elif event.state == WorkflowRunState.COMPLETED:
print("State: COMPLETED")
print(prefix + "COMPLETED")
elif event.state == WorkflowRunState.IN_PROGRESS_PENDING_REQUESTS:
print("State: IN_PROGRESS_PENDING_REQUESTS (requests in flight)")
print(prefix + "IN_PROGRESS_PENDING_REQUESTS (requests in flight)")
elif event.state == WorkflowRunState.IDLE:
print("State: IDLE (no active work)")
print(prefix + "IDLE (no active work)")
elif event.state == WorkflowRunState.IDLE_WITH_PENDING_REQUESTS:
print("State: IDLE_WITH_PENDING_REQUESTS (prompt user or UI now)")
print(prefix + "IDLE_WITH_PENDING_REQUESTS (prompt user or UI now)")
else:
print(f"State: {event.state}")
print(prefix + str(event.state))
elif isinstance(event, WorkflowCompletedEvent):
print(f"Workflow completed ({event.origin.value}): {event.data}")
elif isinstance(event, ExecutorFailedEvent):
print(f"Executor failed: {event.executor_id} {event.details.error_type}: {event.details.message}")
print(
f"Executor failed ({event.origin.value}): "
f"{event.executor_id} {event.details.error_type}: {event.details.message}"
)
elif isinstance(event, WorkflowFailedEvent):
details = event.details
print(f"Workflow failed: {details.error_type}: {details.message}")
print(f"Workflow failed ({event.origin.value}): {details.error_type}: {details.message}")
else:
print(event)
print(f"{event.__class__.__name__} ({event.origin.value}): {event}")
"""
Sample Output:
State: IN_PROGRESS
ExecutorInvokeEvent(executor_id=writer)
ExecutorCompletedEvent(executor_id=writer)
ExecutorInvokeEvent(executor_id=reviewer)
WorkflowCompletedEvent(data=Drive the Future. Affordable Adventure, Electrified.)
ExecutorCompletedEvent(executor_id=reviewer)
State: COMPLETED
State (RUNNER): IN_PROGRESS
ExecutorInvokeEvent (RUNNER): ExecutorInvokeEvent(executor_id=writer)
ExecutorCompletedEvent (RUNNER): ExecutorCompletedEvent(executor_id=writer)
ExecutorInvokeEvent (RUNNER): ExecutorInvokeEvent(executor_id=reviewer)
Workflow completed (EXECUTOR): Drive the Future. Affordable Adventure, Electrified.
ExecutorCompletedEvent (RUNNER): ExecutorCompletedEvent(executor_id=reviewer)
State (RUNNER): COMPLETED
"""
@@ -275,15 +275,15 @@ async def main():
Running workflow with initial message...
UpperCaseExecutor: 'hello world' -> 'HELLO WORLD'
Event: ExecutorInvokeEvent(executor_id=upper_case_executor)
Event: ExecutorInvokedEvent(executor_id=upper_case_executor)
Event: ExecutorCompletedEvent(executor_id=upper_case_executor)
ReverseTextExecutor: 'HELLO WORLD' -> 'DLROW OLLEH'
Event: ExecutorInvokeEvent(executor_id=reverse_text_executor)
Event: ExecutorInvokedEvent(executor_id=reverse_text_executor)
Event: ExecutorCompletedEvent(executor_id=reverse_text_executor)
LowerAgent (shared_state): original_input='hello world', upper_output='HELLO WORLD'
Event: ExecutorInvokeEvent(executor_id=submit_lower)
Event: ExecutorInvokeEvent(executor_id=lower_agent)
Event: ExecutorInvokeEvent(executor_id=finalize)
Event: ExecutorInvokedEvent(executor_id=submit_lower)
Event: ExecutorInvokedEvent(executor_id=lower_agent)
Event: ExecutorInvokedEvent(executor_id=finalize)
Event: WorkflowCompletedEvent(data=dlrow olleh)
Checkpoint summary:
@@ -300,9 +300,9 @@ async def main():
Resuming from checkpoint: a78c345a-e5d9-45ba-82c0-cb725452d91b
LowerAgent (shared_state): original_input='hello world', upper_output='HELLO WORLD'
Resumed Event: ExecutorInvokeEvent(executor_id=submit_lower)
Resumed Event: ExecutorInvokeEvent(executor_id=lower_agent)
Resumed Event: ExecutorInvokeEvent(executor_id=finalize)
Resumed Event: ExecutorInvokedEvent(executor_id=submit_lower)
Resumed Event: ExecutorInvokedEvent(executor_id=lower_agent)
Resumed Event: ExecutorInvokedEvent(executor_id=finalize)
Resumed Event: WorkflowCompletedEvent(data=dlrow olleh)
""" # noqa: E501
@@ -13,7 +13,7 @@ The second reverses the text and completes the workflow. Events are printed as t
Purpose:
Show how to declare executors with the @executor decorator, connect them with WorkflowBuilder,
pass intermediate values using ctx.send_message, and signal completion with ctx.add_event by emitting a
WorkflowCompletedEvent. Demonstrate how streaming exposes ExecutorInvokeEvent and WorkflowCompletedEvent
WorkflowCompletedEvent. Demonstrate how streaming exposes ExecutorInvokedEvent and WorkflowCompletedEvent
for observability.
Prerequisites:
@@ -72,9 +72,9 @@ async def main():
"""
Sample Output:
Event: ExecutorInvokeEvent(executor_id=upper_case_executor)
Event: ExecutorInvokedEvent(executor_id=upper_case_executor)
Event: ExecutorCompletedEvent(executor_id=upper_case_executor)
Event: ExecutorInvokeEvent(executor_id=reverse_text_executor)
Event: ExecutorInvokedEvent(executor_id=reverse_text_executor)
Event: WorkflowCompletedEvent(data=DLROW OLLEH)
Event: ExecutorCompletedEvent(executor_id=reverse_text_executor)
Workflow completed with result: DLROW OLLEH