[BREAKING] Python: Refactor workflow events to unified discriminated union pattern (#3690)

* Refactor events

* Merge main

* Fixes

* Cleanup

* Update samples and tests

* Remove unused imports

* PR feedback

* Merge main. Add properties for events to help typing

* Formatting

* Cleanup

* use builtins.type to avoid shadowing by WorkflowEvent.type attribute

* Final improvements
This commit is contained in:
Evan Mattson
2026-02-06 16:47:20 +09:00
committed by GitHub
Unverified
parent 09f59b21ad
commit 0f3f4dbcaf
127 changed files with 1646 additions and 1703 deletions
@@ -7,7 +7,7 @@ the task in a round-robin fashion.
import asyncio
from agent_framework import AgentResponseUpdate, WorkflowOutputEvent
from agent_framework import AgentResponseUpdate
async def run_autogen() -> None:
@@ -55,8 +55,8 @@ async def run_autogen() -> None:
async def run_agent_framework() -> None:
"""Agent Framework's SequentialBuilder for sequential agent orchestration."""
from agent_framework import SequentialBuilder
from agent_framework.openai import OpenAIChatClient
from agent_framework.orchestrations import SequentialBuilder
client = OpenAIChatClient(model_id="gpt-4.1-mini")
@@ -83,15 +83,14 @@ async def run_agent_framework() -> None:
print("[Agent Framework] Sequential conversation:")
current_executor = None
async for event in workflow.run("Create a brief summary about electric vehicles", stream=True):
if isinstance(event, WorkflowOutputEvent):
if event.type == "output" and isinstance(event.data, AgentResponseUpdate):
# Print executor name header when switching to a new agent
if current_executor != event.executor_id:
if current_executor is not None:
print() # Newline after previous agent's message
print(f"---------- {event.executor_id} ----------")
current_executor = event.executor_id
if isinstance(event.data, AgentResponseUpdate):
print(event.data.text, end="", flush=True)
print(event.data.text, end="", flush=True)
print() # Final newline after conversation
@@ -100,9 +99,9 @@ async def run_agent_framework_with_cycle() -> None:
from agent_framework import (
AgentExecutorRequest,
AgentExecutorResponse,
AgentResponseUpdate,
WorkflowBuilder,
WorkflowContext,
WorkflowOutputEvent,
executor,
)
from agent_framework.openai import OpenAIChatClient
@@ -154,7 +153,10 @@ async def run_agent_framework_with_cycle() -> None:
print("[Agent Framework with Cycle] Cyclic conversation:")
current_executor = None
async for event in workflow.run("Create a brief summary about electric vehicles", stream=True):
if isinstance(event, WorkflowOutputEvent) and isinstance(event.data, AgentResponseUpdate):
if event.type == "output" and not isinstance(event.data, AgentResponseUpdate):
print("\n---------- Workflow Output ----------")
print(event.data)
elif event.type == "output" and isinstance(event.data, AgentResponseUpdate):
# Print executor name header when switching to a new agent
if current_executor != event.executor_id:
if current_executor is not None:
@@ -7,7 +7,7 @@ which agent should speak next based on the conversation context.
import asyncio
from agent_framework import AgentResponseUpdate, WorkflowOutputEvent
from agent_framework import AgentResponseUpdate
async def run_autogen() -> None:
@@ -61,8 +61,8 @@ async def run_autogen() -> None:
async def run_agent_framework() -> None:
"""Agent Framework's GroupChatBuilder with LLM-based speaker selection."""
from agent_framework import GroupChatBuilder
from agent_framework.openai import OpenAIChatClient
from agent_framework.orchestrations import GroupChatBuilder
client = OpenAIChatClient(model_id="gpt-4.1-mini")
@@ -102,7 +102,7 @@ async def run_agent_framework() -> None:
print("[Agent Framework] Group chat conversation:")
current_executor = None
async for event in workflow.run("How do I connect to a PostgreSQL database using Python?", stream=True):
if isinstance(event, WorkflowOutputEvent) and isinstance(event.data, AgentResponseUpdate):
if event.type == "output" and isinstance(event.data, AgentResponseUpdate):
# Print executor name header when switching to a new agent
if current_executor != event.executor_id:
if current_executor is not None:
@@ -7,7 +7,8 @@ to other specialized agents based on the task requirements.
import asyncio
from agent_framework import AgentResponseUpdate, HandoffAgentUserRequest, WorkflowOutputEvent
from agent_framework import WorkflowEvent
from orderedmultidict import Any
async def run_autogen() -> None:
@@ -98,12 +99,11 @@ async def run_autogen() -> None:
async def run_agent_framework() -> None:
"""Agent Framework's HandoffBuilder for agent coordination."""
from agent_framework import (
HandoffBuilder,
RequestInfoEvent,
AgentResponseUpdate,
WorkflowRunState,
WorkflowStatusEvent,
)
from agent_framework.openai import OpenAIChatClient
from agent_framework.orchestrations import HandoffAgentUserRequest, HandoffBuilder
client = OpenAIChatClient(model_id="gpt-4.1-mini")
@@ -159,10 +159,10 @@ async def run_agent_framework() -> None:
current_executor = None
stream_line_open = False
pending_requests: list[RequestInfoEvent] = []
pending_requests: list[WorkflowEvent] = []
async for event in workflow.run(scripted_responses[0], stream=True):
if isinstance(event, WorkflowOutputEvent) and isinstance(event.data, AgentResponseUpdate):
if event.type == "output" and isinstance(event.data, AgentResponseUpdate):
# Print executor name header when switching to a new agent
if current_executor != event.executor_id:
if stream_line_open:
@@ -173,10 +173,10 @@ async def run_agent_framework() -> None:
stream_line_open = True
if event.data:
print(event.data.text, end="", flush=True)
elif isinstance(event, RequestInfoEvent):
elif event.type == "request_info":
if isinstance(event.data, HandoffAgentUserRequest):
pending_requests.append(event)
elif isinstance(event, WorkflowStatusEvent):
elif event.type == "status":
if event.state in {WorkflowRunState.IDLE_WITH_PENDING_REQUESTS} and stream_line_open:
print()
stream_line_open = False
@@ -188,13 +188,13 @@ async def run_agent_framework() -> None:
print("---------- user ----------")
print(user_response)
responses = {req.request_id: user_response for req in pending_requests}
responses: dict[str, Any] = {req.request_id: user_response for req in pending_requests} # type: ignore
pending_requests = []
current_executor = None
stream_line_open = False
async for event in workflow.send_responses_streaming(responses):
if isinstance(event, WorkflowOutputEvent) and isinstance(event.data, AgentResponseUpdate):
if event.type == "output" and isinstance(event.data, AgentResponseUpdate):
# Print executor name header when switching to a new agent
if current_executor != event.executor_id:
if stream_line_open:
@@ -205,10 +205,10 @@ async def run_agent_framework() -> None:
stream_line_open = True
if event.data:
print(event.data.text, end="", flush=True)
elif isinstance(event, RequestInfoEvent):
elif event.type == "request_info":
if isinstance(event.data, HandoffAgentUserRequest):
pending_requests.append(event)
elif isinstance(event, WorkflowStatusEvent):
elif event.type == "status":
if (
event.state in {WorkflowRunState.IDLE_WITH_PENDING_REQUESTS, WorkflowRunState.IDLE}
and stream_line_open
@@ -12,10 +12,9 @@ from typing import cast
from agent_framework import (
AgentResponseUpdate,
ChatMessage,
MagenticOrchestratorEvent,
MagenticProgressLedger,
WorkflowOutputEvent,
WorkflowEvent,
)
from agent_framework.orchestrations import MagenticProgressLedger
async def run_autogen() -> None:
@@ -67,8 +66,8 @@ async def run_autogen() -> None:
async def run_agent_framework() -> None:
"""Agent Framework's MagenticBuilder for orchestrated collaboration."""
from agent_framework import MagenticBuilder
from agent_framework.openai import OpenAIChatClient
from agent_framework.orchestrations import MagenticBuilder
client = OpenAIChatClient(model_id="gpt-4.1-mini")
@@ -110,10 +109,10 @@ async def run_agent_framework() -> None:
# Run complex task
last_message_id: str | None = None
output_event: WorkflowOutputEvent | None = None
output_event: WorkflowEvent | None = None
print("[Agent Framework] Magentic conversation:")
async for event in workflow.run("Research Python async patterns and write a simple example", stream=True):
if isinstance(event, WorkflowOutputEvent) and isinstance(event.data, AgentResponseUpdate):
if event.type == "output" and isinstance(event.data, AgentResponseUpdate):
message_id = event.data.message_id
if message_id != last_message_id:
if last_message_id is not None:
@@ -122,21 +121,21 @@ async def run_agent_framework() -> None:
last_message_id = message_id
print(event.data, end="", flush=True)
elif isinstance(event, MagenticOrchestratorEvent):
print(f"\n[Magentic Orchestrator Event] Type: {event.event_type.name}")
if isinstance(event.data, ChatMessage):
print(f"Please review the plan:\n{event.data.text}")
elif isinstance(event.data, MagenticProgressLedger):
print(f"Please review progress ledger:\n{json.dumps(event.data.to_dict(), indent=2)}")
elif event.type == "magentic_orchestrator":
print(f"\n[Magentic Orchestrator Event] Type: {event.data.event_type.name}")
if isinstance(event.data.content, ChatMessage):
print(f"Please review the plan:\n{event.data.content.text}")
elif isinstance(event.data.content, MagenticProgressLedger):
print(f"Please review progress ledger:\n{json.dumps(event.data.content.to_dict(), indent=2)}")
else:
print(f"Unknown data type in MagenticOrchestratorEvent: {type(event.data)}")
print(f"Unknown data type in MagenticOrchestratorEvent: {type(event.data.content)}")
# Block to allow user to read the plan/progress before continuing
# Note: this is for demonstration only and is not the recommended way to handle human interaction.
# Please refer to `with_plan_review` for proper human interaction during planning phases.
await asyncio.get_event_loop().run_in_executor(None, input, "Press Enter to continue...")
elif isinstance(event, WorkflowOutputEvent):
elif event.type == "output":
output_event = event
if not output_event: