mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
[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:
committed by
GitHub
Unverified
parent
09f59b21ad
commit
0f3f4dbcaf
@@ -57,9 +57,9 @@ from agent_framework.orchestrations import (
|
||||
**Sequential orchestration note**: Sequential orchestration uses a few small adapter nodes for plumbing:
|
||||
- `input-conversation` normalizes input to `list[ChatMessage]`
|
||||
- `to-conversation:<participant>` converts agent responses into the shared conversation
|
||||
- `complete` publishes the final `WorkflowOutputEvent`
|
||||
- `complete` publishes the final output event (type='output')
|
||||
|
||||
These may appear in event streams (ExecutorInvoke/Completed). They're analogous to concurrent's dispatcher and aggregator and can be ignored if you only care about agent activity.
|
||||
These may appear in event streams (executor_invoked/executor_completed). They're analogous to concurrent's dispatcher and aggregator and can be ignored if you only care about agent activity.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ Demonstrates:
|
||||
|
||||
Prerequisites:
|
||||
- Azure OpenAI access configured for AzureOpenAIChatClient (use az login + env vars)
|
||||
- Familiarity with Workflow events (WorkflowOutputEvent)
|
||||
- Familiarity with Workflow events (WorkflowEvent)
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ from agent_framework import (
|
||||
AgentResponseUpdate,
|
||||
ChatAgent,
|
||||
ChatMessage,
|
||||
WorkflowOutputEvent,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.orchestrations import GroupChatBuilder
|
||||
@@ -74,7 +73,7 @@ async def main() -> None:
|
||||
# The agent orchestrator will intelligently decide when to end before this limit but just in case
|
||||
.with_termination_condition(lambda messages: sum(1 for msg in messages if msg.role == "assistant") >= 4)
|
||||
# Enable intermediate outputs to observe the conversation as it unfolds
|
||||
# Intermediate outputs will be emitted as WorkflowOutputEvent events
|
||||
# Intermediate outputs will be emitted as WorkflowEvent with type "output" events
|
||||
.with_intermediate_outputs()
|
||||
.build()
|
||||
)
|
||||
@@ -88,7 +87,7 @@ async def main() -> None:
|
||||
# Keep track of the last response to format output nicely in streaming mode
|
||||
last_response_id: str | None = None
|
||||
async for event in workflow.run(task, stream=True):
|
||||
if isinstance(event, WorkflowOutputEvent):
|
||||
if event.type == "output":
|
||||
data = event.data
|
||||
if isinstance(data, AgentResponseUpdate):
|
||||
rid = data.response_id
|
||||
@@ -98,7 +97,7 @@ async def main() -> None:
|
||||
print(f"{data.author_name}:", end=" ", flush=True)
|
||||
last_response_id = rid
|
||||
print(data.text, end="", flush=True)
|
||||
else:
|
||||
elif event.type == "output":
|
||||
# The output of the group chat workflow is a collection of chat messages from all participants
|
||||
outputs = cast(list[ChatMessage], event.data)
|
||||
print("\n" + "=" * 80)
|
||||
|
||||
@@ -8,7 +8,6 @@ from agent_framework import (
|
||||
AgentResponseUpdate,
|
||||
ChatAgent,
|
||||
ChatMessage,
|
||||
WorkflowOutputEvent,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.orchestrations import GroupChatBuilder
|
||||
@@ -214,7 +213,7 @@ Share your perspective authentically. Feel free to:
|
||||
.participants([farmer, developer, teacher, activist, spiritual_leader, artist, immigrant, doctor])
|
||||
.with_termination_condition(lambda messages: sum(1 for msg in messages if msg.role == "assistant") >= 10)
|
||||
# Enable intermediate outputs to observe the conversation as it unfolds
|
||||
# Intermediate outputs will be emitted as WorkflowOutputEvent events
|
||||
# Intermediate outputs will be emitted as WorkflowEvent with type "output" events
|
||||
.with_intermediate_outputs()
|
||||
.build()
|
||||
)
|
||||
@@ -241,7 +240,7 @@ Share your perspective authentically. Feel free to:
|
||||
# Keep track of the last response to format output nicely in streaming mode
|
||||
last_response_id: str | None = None
|
||||
async for event in workflow.run(f"Please begin the discussion on: {topic}", stream=True):
|
||||
if isinstance(event, WorkflowOutputEvent):
|
||||
if event.type == "output":
|
||||
data = event.data
|
||||
if isinstance(data, AgentResponseUpdate):
|
||||
rid = data.response_id
|
||||
@@ -251,7 +250,7 @@ Share your perspective authentically. Feel free to:
|
||||
print(f"{data.author_name}:", end=" ", flush=True)
|
||||
last_response_id = rid
|
||||
print(data.text, end="", flush=True)
|
||||
else:
|
||||
elif event.type == "output":
|
||||
# The output of the group chat workflow is a collection of chat messages from all participants
|
||||
outputs = cast(list[ChatMessage], event.data)
|
||||
print("\n" + "=" * 80)
|
||||
|
||||
@@ -7,7 +7,6 @@ from agent_framework import (
|
||||
AgentResponseUpdate,
|
||||
ChatAgent,
|
||||
ChatMessage,
|
||||
WorkflowOutputEvent,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.orchestrations import GroupChatBuilder, GroupChatState
|
||||
@@ -92,7 +91,7 @@ async def main() -> None:
|
||||
# have nothing to add, but for demo purposes we want to see at least one full round of interaction.
|
||||
.with_termination_condition(lambda conversation: len(conversation) >= 6)
|
||||
# Enable intermediate outputs to observe the conversation as it unfolds
|
||||
# Intermediate outputs will be emitted as WorkflowOutputEvent events
|
||||
# Intermediate outputs will be emitted as WorkflowEvent with type "output" events
|
||||
.with_intermediate_outputs()
|
||||
.build()
|
||||
)
|
||||
@@ -106,7 +105,7 @@ async def main() -> None:
|
||||
# Keep track of the last response to format output nicely in streaming mode
|
||||
last_response_id: str | None = None
|
||||
async for event in workflow.run(task, stream=True):
|
||||
if isinstance(event, WorkflowOutputEvent):
|
||||
if event.type == "output":
|
||||
data = event.data
|
||||
if isinstance(data, AgentResponseUpdate):
|
||||
rid = data.response_id
|
||||
@@ -116,7 +115,7 @@ async def main() -> None:
|
||||
print(f"{data.author_name}:", end=" ", flush=True)
|
||||
last_response_id = rid
|
||||
print(data.text, end="", flush=True)
|
||||
else:
|
||||
elif event.type == "output":
|
||||
# The output of the group chat workflow is a collection of chat messages from all participants
|
||||
outputs = cast(list[ChatMessage], event.data)
|
||||
print("\n" + "=" * 80)
|
||||
|
||||
@@ -8,8 +8,6 @@ from agent_framework import (
|
||||
AgentResponseUpdate,
|
||||
ChatAgent,
|
||||
ChatMessage,
|
||||
HandoffSentEvent,
|
||||
WorkflowOutputEvent,
|
||||
resolve_agent_id,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
@@ -112,9 +110,9 @@ async def main() -> None:
|
||||
|
||||
last_response_id: str | None = None
|
||||
async for event in workflow.run(request, stream=True):
|
||||
if isinstance(event, HandoffSentEvent):
|
||||
print(f"\nHandoff Event: from {event.source} to {event.target}\n")
|
||||
elif isinstance(event, WorkflowOutputEvent):
|
||||
if event.type == "handoff_sent":
|
||||
print(f"\nHandoff Event: from {event.data.source} to {event.data.target}\n")
|
||||
elif event.type == "output":
|
||||
data = event.data
|
||||
if isinstance(data, AgentResponseUpdate):
|
||||
if not data.text:
|
||||
@@ -128,8 +126,8 @@ async def main() -> None:
|
||||
print(f"{data.author_name}:", end=" ", flush=True)
|
||||
last_response_id = rid
|
||||
print(data.text, end="", flush=True)
|
||||
else:
|
||||
# The output of the group chat workflow is a collection of chat messages from all participants
|
||||
elif event.type == "output":
|
||||
# The output of the handoff workflow is a collection of chat messages from all participants
|
||||
outputs = cast(list[ChatMessage], event.data)
|
||||
print("\n" + "=" * 80)
|
||||
print("\nFinal Conversation Transcript:\n")
|
||||
|
||||
@@ -8,16 +8,13 @@ from agent_framework import (
|
||||
AgentResponse,
|
||||
ChatAgent,
|
||||
ChatMessage,
|
||||
RequestInfoEvent,
|
||||
Workflow,
|
||||
WorkflowEvent,
|
||||
WorkflowOutputEvent,
|
||||
WorkflowRunState,
|
||||
WorkflowStatusEvent,
|
||||
tool,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.orchestrations import HandoffAgentUserRequest, HandoffBuilder, HandoffSentEvent
|
||||
from agent_framework.orchestrations import HandoffAgentUserRequest, HandoffBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
logging.basicConfig(level=logging.ERROR)
|
||||
@@ -107,35 +104,35 @@ def create_return_agent() -> ChatAgent:
|
||||
)
|
||||
|
||||
|
||||
def _handle_events(events: list[WorkflowEvent]) -> list[RequestInfoEvent]:
|
||||
def _handle_events(events: list[WorkflowEvent]) -> list[WorkflowEvent[HandoffAgentUserRequest]]:
|
||||
"""Process workflow events and extract any pending user input requests.
|
||||
|
||||
This function inspects each event type and:
|
||||
- Prints workflow status changes (IDLE, IDLE_WITH_PENDING_REQUESTS, etc.)
|
||||
- Displays final conversation snapshots when workflow completes
|
||||
- Prints user input request prompts
|
||||
- Collects all RequestInfoEvent instances for response handling
|
||||
- Collects all request_info events for response handling
|
||||
|
||||
Args:
|
||||
events: List of WorkflowEvent to process
|
||||
|
||||
Returns:
|
||||
List of RequestInfoEvent representing pending user input requests
|
||||
List of WorkflowEvent[HandoffAgentUserRequest] representing pending user input requests
|
||||
"""
|
||||
requests: list[RequestInfoEvent] = []
|
||||
requests: list[WorkflowEvent[HandoffAgentUserRequest]] = []
|
||||
|
||||
for event in events:
|
||||
if isinstance(event, HandoffSentEvent):
|
||||
# HandoffSentEvent: Indicates a handoff has been initiated
|
||||
print(f"\n[Handoff from {event.source} to {event.target} initiated.]")
|
||||
elif isinstance(event, WorkflowStatusEvent) and event.state in {
|
||||
if event.type == "handoff_sent":
|
||||
# handoff_sent event: Indicates a handoff has been initiated
|
||||
print(f"\n[Handoff from {event.data.source} to {event.data.target} initiated.]")
|
||||
elif event.type == "status" and event.state in {
|
||||
WorkflowRunState.IDLE,
|
||||
WorkflowRunState.IDLE_WITH_PENDING_REQUESTS,
|
||||
}:
|
||||
# WorkflowStatusEvent: Indicates workflow state changes
|
||||
# Status event: Indicates workflow state changes
|
||||
print(f"\n[Workflow Status] {event.state.name}")
|
||||
elif isinstance(event, WorkflowOutputEvent):
|
||||
# WorkflowOutputEvent: Contains contents generated by the workflow
|
||||
elif event.type == "output":
|
||||
# Output event: Contains contents generated by the workflow
|
||||
data = event.data
|
||||
if isinstance(data, AgentResponse):
|
||||
for message in data.messages:
|
||||
@@ -144,7 +141,7 @@ def _handle_events(events: list[WorkflowEvent]) -> list[RequestInfoEvent]:
|
||||
continue
|
||||
speaker = message.author_name or message.role
|
||||
print(f"- {speaker}: {message.text}")
|
||||
else:
|
||||
elif event.type == "output":
|
||||
# The output of the handoff workflow is a collection of chat messages from all participants
|
||||
conversation = cast(list[ChatMessage], event.data)
|
||||
if isinstance(conversation, list):
|
||||
@@ -153,11 +150,11 @@ def _handle_events(events: list[WorkflowEvent]) -> list[RequestInfoEvent]:
|
||||
speaker = message.author_name or message.role
|
||||
print(f"- {speaker}: {message.text or [content.type for content in message.contents]}")
|
||||
print("===================================")
|
||||
elif isinstance(event, RequestInfoEvent):
|
||||
# RequestInfoEvent: Workflow is requesting user input
|
||||
elif event.type == "request_info":
|
||||
# Request info event: Workflow is requesting user input
|
||||
if isinstance(event.data, HandoffAgentUserRequest):
|
||||
_print_handoff_agent_user_request(event.data.agent_response)
|
||||
requests.append(event)
|
||||
requests.append(cast(WorkflowEvent[HandoffAgentUserRequest], event))
|
||||
|
||||
return requests
|
||||
|
||||
|
||||
@@ -7,15 +7,12 @@ from agent_framework import (
|
||||
AgentResponse,
|
||||
ChatAgent,
|
||||
ChatMessage,
|
||||
RequestInfoEvent,
|
||||
WorkflowEvent,
|
||||
WorkflowOutputEvent,
|
||||
WorkflowRunState,
|
||||
WorkflowStatusEvent,
|
||||
tool,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.orchestrations import HandoffAgentUserRequest, HandoffBuilder, HandoffSentEvent
|
||||
from agent_framework.orchestrations import HandoffAgentUserRequest, HandoffBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
"""Sample: Simple handoff workflow.
|
||||
@@ -102,35 +99,35 @@ def create_agents(chat_client: AzureOpenAIChatClient) -> tuple[ChatAgent, ChatAg
|
||||
return triage_agent, refund_agent, order_agent, return_agent
|
||||
|
||||
|
||||
def _handle_events(events: list[WorkflowEvent]) -> list[RequestInfoEvent]:
|
||||
def _handle_events(events: list[WorkflowEvent]) -> list[WorkflowEvent[HandoffAgentUserRequest]]:
|
||||
"""Process workflow events and extract any pending user input requests.
|
||||
|
||||
This function inspects each event type and:
|
||||
- Prints workflow status changes (IDLE, IDLE_WITH_PENDING_REQUESTS, etc.)
|
||||
- Displays final conversation snapshots when workflow completes
|
||||
- Prints user input request prompts
|
||||
- Collects all RequestInfoEvent instances for response handling
|
||||
- Collects all request_info events for response handling
|
||||
|
||||
Args:
|
||||
events: List of WorkflowEvent to process
|
||||
|
||||
Returns:
|
||||
List of RequestInfoEvent representing pending user input requests
|
||||
List of WorkflowEvent[HandoffAgentUserRequest] representing pending user input requests
|
||||
"""
|
||||
requests: list[RequestInfoEvent] = []
|
||||
requests: list[WorkflowEvent[HandoffAgentUserRequest]] = []
|
||||
|
||||
for event in events:
|
||||
if isinstance(event, HandoffSentEvent):
|
||||
# HandoffSentEvent: Indicates a handoff has been initiated
|
||||
print(f"\n[Handoff from {event.source} to {event.target} initiated.]")
|
||||
elif isinstance(event, WorkflowStatusEvent) and event.state in {
|
||||
if event.type == "handoff_sent":
|
||||
# handoff_sent event: Indicates a handoff has been initiated
|
||||
print(f"\n[Handoff from {event.data.source} to {event.data.target} initiated.]")
|
||||
elif event.type == "status" and event.state in {
|
||||
WorkflowRunState.IDLE,
|
||||
WorkflowRunState.IDLE_WITH_PENDING_REQUESTS,
|
||||
}:
|
||||
# WorkflowStatusEvent: Indicates workflow state changes
|
||||
print(f"\n[Workflow Status] {event.state.name}")
|
||||
elif isinstance(event, WorkflowOutputEvent):
|
||||
# WorkflowOutputEvent: Contains contents generated by the workflow
|
||||
# Status event: Indicates workflow state changes
|
||||
print(f"\n[Workflow Status] {event.state}")
|
||||
elif event.type == "output":
|
||||
# Output event: Contains contents generated by the workflow
|
||||
data = event.data
|
||||
if isinstance(data, AgentResponse):
|
||||
for message in data.messages:
|
||||
@@ -139,7 +136,7 @@ def _handle_events(events: list[WorkflowEvent]) -> list[RequestInfoEvent]:
|
||||
continue
|
||||
speaker = message.author_name or message.role
|
||||
print(f"- {speaker}: {message.text}")
|
||||
else:
|
||||
elif event.type == "output":
|
||||
# The output of the handoff workflow is a collection of chat messages from all participants
|
||||
conversation = cast(list[ChatMessage], event.data)
|
||||
if isinstance(conversation, list):
|
||||
@@ -148,11 +145,9 @@ def _handle_events(events: list[WorkflowEvent]) -> list[RequestInfoEvent]:
|
||||
speaker = message.author_name or message.role
|
||||
print(f"- {speaker}: {message.text or [content.type for content in message.contents]}")
|
||||
print("===================================")
|
||||
elif isinstance(event, RequestInfoEvent):
|
||||
# RequestInfoEvent: Workflow is requesting user input
|
||||
if isinstance(event.data, HandoffAgentUserRequest):
|
||||
_print_handoff_agent_user_request(event.data.agent_response)
|
||||
requests.append(event)
|
||||
elif event.type == "request_info" and isinstance(event.data, HandoffAgentUserRequest):
|
||||
_print_handoff_agent_user_request(event.data.agent_response)
|
||||
requests.append(cast(WorkflowEvent[HandoffAgentUserRequest], event))
|
||||
|
||||
return requests
|
||||
|
||||
|
||||
+12
-21
@@ -6,7 +6,7 @@ Handoff Workflow with Code Interpreter File Generation Sample
|
||||
This sample demonstrates retrieving file IDs from code interpreter output
|
||||
in a handoff workflow context. A triage agent routes to a code specialist
|
||||
that generates a text file, and we verify the file_id is captured correctly
|
||||
from the streaming WorkflowOutputEvent events.
|
||||
from the streaming workflow events.
|
||||
|
||||
Verifies GitHub issue #2718: files generated by code interpreter in
|
||||
HandoffBuilder workflows can be properly retrieved.
|
||||
@@ -34,13 +34,9 @@ from agent_framework import (
|
||||
AgentResponseUpdate,
|
||||
ChatAgent,
|
||||
ChatMessage,
|
||||
HandoffSentEvent,
|
||||
HostedCodeInterpreterTool,
|
||||
RequestInfoEvent,
|
||||
WorkflowEvent,
|
||||
WorkflowOutputEvent,
|
||||
WorkflowRunState,
|
||||
WorkflowStatusEvent,
|
||||
)
|
||||
from agent_framework.orchestrations import HandoffAgentUserRequest, HandoffBuilder
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
@@ -54,30 +50,29 @@ async def _drain(stream: AsyncIterable[WorkflowEvent]) -> list[WorkflowEvent]:
|
||||
return [event async for event in stream]
|
||||
|
||||
|
||||
def _handle_events(events: list[WorkflowEvent]) -> tuple[list[RequestInfoEvent], list[str]]:
|
||||
def _handle_events(events: list[WorkflowEvent]) -> tuple[list[WorkflowEvent[HandoffAgentUserRequest]], list[str]]:
|
||||
"""Process workflow events and extract file IDs and pending requests.
|
||||
|
||||
Returns:
|
||||
Tuple of (pending_requests, file_ids_found)
|
||||
"""
|
||||
requests: list[RequestInfoEvent] = []
|
||||
|
||||
requests: list[WorkflowEvent[HandoffAgentUserRequest]] = []
|
||||
file_ids: list[str] = []
|
||||
|
||||
for event in events:
|
||||
if isinstance(event, HandoffSentEvent):
|
||||
# HandoffSentEvent: Indicates a handoff has been initiated
|
||||
print(f"\n[Handoff from {event.source} to {event.target} initiated.]")
|
||||
elif isinstance(event, WorkflowStatusEvent) and event.state in {
|
||||
if event.type == "handoff_sent":
|
||||
print(f"\n[Handoff from {event.data.source} to {event.data.target} initiated.]")
|
||||
elif event.type == "status" and event.state in {
|
||||
WorkflowRunState.IDLE,
|
||||
WorkflowRunState.IDLE_WITH_PENDING_REQUESTS,
|
||||
}:
|
||||
# WorkflowStatusEvent: Indicates workflow state changes
|
||||
print(f"\n[Workflow Status] {event.state.name}")
|
||||
elif isinstance(event, WorkflowOutputEvent):
|
||||
# WorkflowOutputEvent: Contains contents generated by the workflow
|
||||
print(f"[status] {event.state.name}")
|
||||
elif event.type == "request_info" and isinstance(event.data, HandoffAgentUserRequest):
|
||||
requests.append(cast(WorkflowEvent[HandoffAgentUserRequest], event))
|
||||
elif event.type == "output":
|
||||
data = event.data
|
||||
if isinstance(data, AgentResponseUpdate):
|
||||
# AgentResponseUpdate: Intermediate output from an agent
|
||||
for content in data.contents:
|
||||
if content.type == "hosted_file":
|
||||
file_ids.append(content.file_id) # type: ignore
|
||||
@@ -87,8 +82,7 @@ def _handle_events(events: list[WorkflowEvent]) -> tuple[list[RequestInfoEvent],
|
||||
file_id = annotation["file_id"] # type: ignore
|
||||
file_ids.append(file_id)
|
||||
print(f"[Found file annotation: file_id={file_id}]")
|
||||
else:
|
||||
# The output of the handoff workflow is a collection of chat messages from all participants
|
||||
elif event.type == "output":
|
||||
conversation = cast(list[ChatMessage], event.data)
|
||||
if isinstance(conversation, list):
|
||||
print("\n=== Final Conversation Snapshot ===")
|
||||
@@ -96,9 +90,6 @@ def _handle_events(events: list[WorkflowEvent]) -> tuple[list[RequestInfoEvent],
|
||||
speaker = message.author_name or message.role
|
||||
print(f"- {speaker}: {message.text or [content.type for content in message.contents]}")
|
||||
print("===================================")
|
||||
elif isinstance(event, RequestInfoEvent):
|
||||
# RequestInfoEvent: Workflow is requesting user input
|
||||
requests.append(event)
|
||||
|
||||
return requests, file_ids
|
||||
|
||||
|
||||
@@ -9,12 +9,11 @@ from agent_framework import (
|
||||
AgentResponseUpdate,
|
||||
ChatAgent,
|
||||
ChatMessage,
|
||||
GroupChatRequestSentEvent,
|
||||
HostedCodeInterpreterTool,
|
||||
WorkflowOutputEvent,
|
||||
WorkflowEvent,
|
||||
)
|
||||
from agent_framework.openai import OpenAIChatClient, OpenAIResponsesClient
|
||||
from agent_framework.orchestrations import MagenticBuilder, MagenticOrchestratorEvent, MagenticProgressLedger
|
||||
from agent_framework.orchestrations import GroupChatRequestSentEvent, MagenticBuilder, MagenticProgressLedger
|
||||
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -85,7 +84,7 @@ async def main() -> None:
|
||||
max_reset_count=2,
|
||||
)
|
||||
# Enable intermediate outputs to observe the conversation as it unfolds
|
||||
# Intermediate outputs will be emitted as WorkflowOutputEvent events
|
||||
# Intermediate outputs will be emitted as WorkflowEvent events
|
||||
.with_intermediate_outputs()
|
||||
.build()
|
||||
)
|
||||
@@ -104,41 +103,44 @@ async def main() -> None:
|
||||
|
||||
# Keep track of the last executor to format output nicely in streaming mode
|
||||
last_response_id: str | None = None
|
||||
output_event: WorkflowEvent | None = None
|
||||
async for event in workflow.run(task, stream=True):
|
||||
if 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)}")
|
||||
if event.type == "output" and isinstance(event.data, AgentResponseUpdate):
|
||||
response_id = event.data.response_id
|
||||
if response_id != last_response_id:
|
||||
if last_response_id is not None:
|
||||
print("\n")
|
||||
print(f"- {event.executor_id}:", end=" ", flush=True)
|
||||
last_response_id = response_id
|
||||
print(event.data, end="", flush=True)
|
||||
|
||||
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, GroupChatRequestSentEvent):
|
||||
print(f"\n[REQUEST SENT ({event.round_index})] to agent: {event.participant_name}")
|
||||
elif event.type == "group_chat" and isinstance(event.data, GroupChatRequestSentEvent):
|
||||
print(f"\n[REQUEST SENT ({event.data.round_index})] to agent: {event.data.participant_name}")
|
||||
|
||||
elif isinstance(event, WorkflowOutputEvent):
|
||||
data = event.data
|
||||
if isinstance(data, AgentResponseUpdate):
|
||||
response_id = data.response_id
|
||||
if response_id != last_response_id:
|
||||
if last_response_id is not None:
|
||||
print("\n")
|
||||
print(f"- {event.executor_id}:", end=" ", flush=True)
|
||||
last_response_id = response_id
|
||||
print(event.data, end="", flush=True)
|
||||
else:
|
||||
# The output of the magentic workflow is a collection of chat messages from all participants
|
||||
outputs = cast(list[ChatMessage], event.data)
|
||||
print("\n" + "=" * 80)
|
||||
print("\nFinal Conversation Transcript:\n")
|
||||
for message in outputs:
|
||||
print(f"{message.author_name or message.role}: {message.text}\n")
|
||||
elif event.type == "output":
|
||||
output_event = event
|
||||
|
||||
if output_event:
|
||||
# The output of the magentic workflow is a collection of chat messages from all participants
|
||||
outputs = cast(list[ChatMessage], output_event.data)
|
||||
print("\n" + "=" * 80)
|
||||
print("\nFinal Conversation Transcript:\n")
|
||||
for message in outputs:
|
||||
print(f"{message.author_name or message.role}: {message.text}\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -9,11 +9,9 @@ from agent_framework import (
|
||||
ChatAgent,
|
||||
ChatMessage,
|
||||
FileCheckpointStorage,
|
||||
RequestInfoEvent,
|
||||
WorkflowCheckpoint,
|
||||
WorkflowOutputEvent,
|
||||
WorkflowEvent,
|
||||
WorkflowRunState,
|
||||
WorkflowStatusEvent,
|
||||
)
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.orchestrations import MagenticBuilder, MagenticPlanReviewRequest
|
||||
@@ -105,16 +103,16 @@ async def main() -> None:
|
||||
print("\n=== Stage 1: run until plan review request (checkpointing active) ===")
|
||||
workflow = build_workflow(checkpoint_storage)
|
||||
|
||||
# Run the workflow until the first RequestInfoEvent is surfaced. The event carries the
|
||||
# Run the workflow until the first is surfaced. The event carries the
|
||||
# request_id we must reuse on resume. In a real system this is where the UI would present
|
||||
# the plan for human review.
|
||||
plan_review_request: MagenticPlanReviewRequest | None = None
|
||||
async for event in workflow.run(TASK, stream=True):
|
||||
if isinstance(event, RequestInfoEvent) and event.request_type is MagenticPlanReviewRequest:
|
||||
if event.type == "request_info" and event.request_type is MagenticPlanReviewRequest:
|
||||
plan_review_request = event.data
|
||||
print(f"Captured plan review request: {event.request_id}")
|
||||
|
||||
if isinstance(event, WorkflowStatusEvent) and event.state is WorkflowRunState.IDLE_WITH_PENDING_REQUESTS:
|
||||
if event.type == "status" and event.state is WorkflowRunState.IDLE_WITH_PENDING_REQUESTS:
|
||||
break
|
||||
|
||||
if plan_review_request is None:
|
||||
@@ -147,9 +145,9 @@ async def main() -> None:
|
||||
approval = plan_review_request.approve()
|
||||
|
||||
# Resume execution and capture the re-emitted plan review request.
|
||||
request_info_event: RequestInfoEvent | None = None
|
||||
request_info_event: WorkflowEvent | None = None
|
||||
async for event in resumed_workflow.run(checkpoint_id=resume_checkpoint.checkpoint_id, stream=True):
|
||||
if isinstance(event, RequestInfoEvent) and isinstance(event.data, MagenticPlanReviewRequest):
|
||||
if event.type == "request_info" and isinstance(event.data, MagenticPlanReviewRequest):
|
||||
request_info_event = event
|
||||
|
||||
if request_info_event is None:
|
||||
@@ -158,9 +156,9 @@ async def main() -> None:
|
||||
print(f"Resumed plan review request: {request_info_event.request_id}")
|
||||
|
||||
# Supply the approval and continue to run to completion.
|
||||
final_event: WorkflowOutputEvent | None = None
|
||||
final_event: WorkflowEvent | None = None
|
||||
async for event in resumed_workflow.send_responses_streaming({request_info_event.request_id: approval}):
|
||||
if isinstance(event, WorkflowOutputEvent):
|
||||
if event.type == "output":
|
||||
final_event = event
|
||||
|
||||
if final_event is None:
|
||||
@@ -218,12 +216,12 @@ async def main() -> None:
|
||||
if pending_messages == 0:
|
||||
print("Checkpoint has no pending messages; no additional work expected on resume.")
|
||||
|
||||
final_event_post: WorkflowOutputEvent | None = None
|
||||
final_event_post: WorkflowEvent | None = None
|
||||
post_emitted_events = False
|
||||
post_plan_workflow = build_workflow(checkpoint_storage)
|
||||
async for event in post_plan_workflow.run(checkpoint_id=post_plan_checkpoint.checkpoint_id, stream=True):
|
||||
post_emitted_events = True
|
||||
if isinstance(event, WorkflowOutputEvent):
|
||||
if event.type == "output":
|
||||
final_event_post = event
|
||||
|
||||
if final_event_post is None:
|
||||
|
||||
@@ -9,9 +9,7 @@ from agent_framework import (
|
||||
AgentResponseUpdate,
|
||||
ChatAgent,
|
||||
ChatMessage,
|
||||
RequestInfoEvent,
|
||||
WorkflowEvent,
|
||||
WorkflowOutputEvent,
|
||||
)
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.orchestrations import MagenticBuilder, MagenticPlanReviewRequest, MagenticPlanReviewResponse
|
||||
@@ -46,10 +44,10 @@ async def process_event_stream(stream: AsyncIterable[WorkflowEvent]) -> dict[str
|
||||
|
||||
requests: dict[str, MagenticPlanReviewRequest] = {}
|
||||
async for event in stream:
|
||||
if isinstance(event, RequestInfoEvent) and event.request_type is MagenticPlanReviewRequest:
|
||||
if event.type == "request_info" and event.request_type is MagenticPlanReviewRequest:
|
||||
requests[event.request_id] = cast(MagenticPlanReviewRequest, event.data)
|
||||
|
||||
if isinstance(event, WorkflowOutputEvent):
|
||||
if event.type == "output":
|
||||
data = event.data
|
||||
if isinstance(data, AgentResponseUpdate):
|
||||
rid = data.response_id
|
||||
@@ -68,7 +66,7 @@ async def process_event_stream(stream: AsyncIterable[WorkflowEvent]) -> dict[str
|
||||
# To make the type checker happy, we cast event.data to the expected type
|
||||
outputs = cast(list[ChatMessage], event.data)
|
||||
for msg in outputs:
|
||||
speaker = msg.author_name or msg.role.value
|
||||
speaker = msg.author_name or msg.role
|
||||
print(f"[{speaker}]: {msg.text}")
|
||||
|
||||
responses: dict[str, MagenticPlanReviewResponse] = {}
|
||||
@@ -129,7 +127,7 @@ async def main() -> None:
|
||||
# Request human input for plan review
|
||||
.with_plan_review()
|
||||
# Enable intermediate outputs to observe the conversation as it unfolds
|
||||
# Intermediate outputs will be emitted as WorkflowOutputEvent events
|
||||
# Intermediate outputs will be emitted as WorkflowEvent with type "output"
|
||||
.with_intermediate_outputs()
|
||||
.build()
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import asyncio
|
||||
from typing import cast
|
||||
|
||||
from agent_framework import ChatMessage, WorkflowOutputEvent
|
||||
from agent_framework import ChatMessage
|
||||
from agent_framework.azure import AzureOpenAIChatClient
|
||||
from agent_framework.orchestrations import SequentialBuilder
|
||||
from azure.identity import AzureCliCredential
|
||||
@@ -48,7 +48,7 @@ async def main() -> None:
|
||||
# 3) Run and collect outputs
|
||||
outputs: list[list[ChatMessage]] = []
|
||||
async for event in workflow.run("Write a tagline for a budget-friendly eBike.", stream=True):
|
||||
if isinstance(event, WorkflowOutputEvent):
|
||||
if event.type == "output":
|
||||
outputs.append(cast(list[ChatMessage], event.data))
|
||||
|
||||
if outputs:
|
||||
|
||||
Reference in New Issue
Block a user