mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: [Breaking] Remove WorkflowCompletedEvent, introduce workflow output and migrate to ctx.yield_output() + a huge refactoring (#845)
* Introduce input and output types for executor and workflow * WorkflowOutputContext handles two types * Remove can_handle_types from Executor * Update validation * Move workflow executor * Move workflow executor * Fix issues in WorkflowExecutor * refactor executor * update execute signature to create workflow context within Executor * fix simple sub workflow test; fix validation * fix output types in WorkflowExecutor * fix issue in Executor handling of SubWorkflowRequestInfo * update tests to use proper workflow output * update orchestration patterns to use output * Update sample -- not finished * Update python/packages/main/tests/workflow/test_workflow_states.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update python/packages/main/tests/workflow/test_concurrent.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * address comments * WorkflowOutputContext --> WorkflowContext * remove WorkflowCompletedEvent * update samples * Update doc string for important classes; update WorkflowExecutor to support concurrent execution * use Never instead of None for default type * Update usage of WorkflowContext[None to WorkflowContext[Never * address comments * remove filter for None * address comments, minor fixes * quality of life improvement on interceptor types --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
0f913bcdeb
commit
2133043f11
@@ -2,7 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
|
||||
from agent_framework import AgentRunUpdateEvent, WorkflowBuilder, WorkflowCompletedEvent
|
||||
from agent_framework import AgentRunUpdateEvent, WorkflowBuilder, WorkflowOutputEvent
|
||||
from agent_framework.azure import AzureChatClient
|
||||
from azure.identity import AzureCliCredential
|
||||
|
||||
@@ -18,7 +18,7 @@ Show how to wire chat agents directly into a WorkflowBuilder pipeline where agen
|
||||
Demonstrate:
|
||||
- Automatic streaming of agent deltas via AgentRunUpdateEvent.
|
||||
- A simple console aggregator that groups updates by executor id and prints them as they arrive.
|
||||
- A final WorkflowCompletedEvent that contains the reviewer outcome after both agents finish.
|
||||
- The workflow completes when idle and outputs are available in events.get_outputs().
|
||||
|
||||
Prerequisites:
|
||||
- Azure OpenAI configured for AzureChatClient with required environment variables.
|
||||
@@ -54,12 +54,10 @@ async def main():
|
||||
workflow = WorkflowBuilder().set_start_executor(writer_agent).add_edge(writer_agent, reviewer_agent).build()
|
||||
|
||||
# Stream events from the workflow. We aggregate partial token updates per executor for readable output.
|
||||
completed_event: WorkflowCompletedEvent | None = None
|
||||
last_executor_id = None
|
||||
|
||||
async for event in workflow.run_stream(
|
||||
"Create a slogan for a new electric SUV that is affordable and fun to drive."
|
||||
):
|
||||
events = workflow.run_stream("Create a slogan for a new electric SUV that is affordable and fun to drive.")
|
||||
async for event in events:
|
||||
if isinstance(event, AgentRunUpdateEvent):
|
||||
# AgentRunUpdateEvent contains incremental text deltas from the underlying agent.
|
||||
# Print a prefix when the executor changes, then append updates on the same line.
|
||||
@@ -70,14 +68,9 @@ async def main():
|
||||
print(f"{eid}:", end=" ", flush=True)
|
||||
last_executor_id = eid
|
||||
print(event.data, end="", flush=True)
|
||||
elif isinstance(event, WorkflowCompletedEvent):
|
||||
# Terminal event with the final reviewer output.
|
||||
completed_event = event
|
||||
|
||||
# Print the final consolidated reviewer result.
|
||||
if completed_event:
|
||||
print("\n===== Final Output =====")
|
||||
print(completed_event.data)
|
||||
elif isinstance(event, WorkflowOutputEvent):
|
||||
print("===== Final Output =====")
|
||||
print(event.data)
|
||||
|
||||
"""
|
||||
Sample Output:
|
||||
|
||||
@@ -7,7 +7,6 @@ from agent_framework import (
|
||||
ChatMessage,
|
||||
Executor,
|
||||
WorkflowBuilder,
|
||||
WorkflowCompletedEvent,
|
||||
WorkflowContext,
|
||||
handler,
|
||||
)
|
||||
@@ -23,7 +22,7 @@ then hands the conversation to a Reviewer agent which evaluates and finalizes th
|
||||
Purpose:
|
||||
Show how to wrap chat agents created by AzureChatClient inside workflow executors. Demonstrate the @handler pattern
|
||||
with typed inputs and typed WorkflowContext[T] outputs, connect executors with the fluent WorkflowBuilder, and finish
|
||||
by emitting a WorkflowCompletedEvent from the terminal node.
|
||||
by yielding outputs from the terminal node.
|
||||
|
||||
Prerequisites:
|
||||
- Azure OpenAI configured for AzureChatClient with required environment variables.
|
||||
@@ -53,7 +52,7 @@ class Writer(Executor):
|
||||
super().__init__(agent=agent, id=id)
|
||||
|
||||
@handler
|
||||
async def handle(self, message: ChatMessage, ctx: WorkflowContext[list[ChatMessage]]) -> None:
|
||||
async def handle(self, message: ChatMessage, ctx: WorkflowContext[list[ChatMessage], str]) -> None:
|
||||
"""Generate content using the agent and forward the updated conversation.
|
||||
|
||||
Contract for this handler:
|
||||
@@ -79,7 +78,7 @@ class Reviewer(Executor):
|
||||
|
||||
This class demonstrates:
|
||||
- Consuming a typed payload produced upstream.
|
||||
- Emitting a terminal WorkflowCompletedEvent with the final text outcome.
|
||||
- Yielding the final text outcome to complete the workflow.
|
||||
"""
|
||||
|
||||
agent: ChatAgent
|
||||
@@ -94,14 +93,14 @@ class Reviewer(Executor):
|
||||
super().__init__(agent=agent, id=id)
|
||||
|
||||
@handler
|
||||
async def handle(self, messages: list[ChatMessage], ctx: WorkflowContext[str]) -> None:
|
||||
async def handle(self, messages: list[ChatMessage], ctx: WorkflowContext[list[ChatMessage], str]) -> None:
|
||||
"""Review the full conversation transcript and complete with a final string.
|
||||
|
||||
This node consumes all messages so far. It uses its agent to produce the final text,
|
||||
then signals completion by adding a WorkflowCompletedEvent to the event stream.
|
||||
then signals completion by yielding the output.
|
||||
"""
|
||||
response = await self.agent.run(messages)
|
||||
await ctx.add_event(WorkflowCompletedEvent(response.text))
|
||||
await ctx.yield_output(response.text)
|
||||
|
||||
|
||||
async def main():
|
||||
@@ -118,12 +117,14 @@ async def main():
|
||||
workflow = WorkflowBuilder().set_start_executor(writer).add_edge(writer, reviewer).build()
|
||||
|
||||
# Run the workflow with the user's initial message.
|
||||
# For foundational clarity, use run (non streaming) and print the terminal event.
|
||||
# For foundational clarity, use run (non streaming) and print the workflow output.
|
||||
events = await workflow.run(
|
||||
ChatMessage(role="user", text="Create a slogan for a new electric SUV that is affordable and fun to drive.")
|
||||
)
|
||||
# The terminal node emits a WorkflowCompletedEvent; print its contents.
|
||||
print(events.get_completed_event())
|
||||
# The terminal node yields output; print its contents.
|
||||
outputs = events.get_outputs()
|
||||
if outputs:
|
||||
print(outputs[-1])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -5,7 +5,7 @@ from collections.abc import Awaitable, Callable
|
||||
from contextlib import AsyncExitStack
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import AgentRunUpdateEvent, WorkflowBuilder, WorkflowCompletedEvent
|
||||
from agent_framework import AgentRunUpdateEvent, WorkflowBuilder, WorkflowOutputEvent
|
||||
from agent_framework.foundry import FoundryChatClient
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
@@ -21,7 +21,7 @@ Show how to wire chat agents directly into a WorkflowBuilder pipeline where agen
|
||||
Demonstrate:
|
||||
- Automatic streaming of agent deltas via AgentRunUpdateEvent.
|
||||
- A simple console aggregator that groups updates by executor id and prints them as they arrive.
|
||||
- A final WorkflowCompletedEvent that contains the reviewer outcome after both agents finish.
|
||||
- The workflow completes when idle and outputs are available in events.get_outputs().
|
||||
|
||||
Prerequisites:
|
||||
- Foundry Agent Service configured, along with the required environment variables.
|
||||
@@ -69,12 +69,10 @@ async def main() -> None:
|
||||
|
||||
workflow = WorkflowBuilder().set_start_executor(writer).add_edge(writer, reviewer).build()
|
||||
|
||||
completed: WorkflowCompletedEvent | None = None
|
||||
last_executor_id: str | None = None
|
||||
|
||||
async for event in workflow.run_stream(
|
||||
"Create a slogan for a new electric SUV that is affordable and fun to drive."
|
||||
):
|
||||
events = workflow.run_stream("Create a slogan for a new electric SUV that is affordable and fun to drive.")
|
||||
async for event in events:
|
||||
if isinstance(event, AgentRunUpdateEvent):
|
||||
eid = event.executor_id
|
||||
if eid != last_executor_id:
|
||||
@@ -83,13 +81,9 @@ async def main() -> None:
|
||||
print(f"{eid}:", end=" ", flush=True)
|
||||
last_executor_id = eid
|
||||
print(event.data, end="", flush=True)
|
||||
elif isinstance(event, WorkflowCompletedEvent):
|
||||
completed = event
|
||||
|
||||
if completed:
|
||||
print("\n===== Final Output =====")
|
||||
print(completed.data)
|
||||
|
||||
elif isinstance(event, WorkflowOutputEvent):
|
||||
print("\n===== Final output =====")
|
||||
print(event.data)
|
||||
finally:
|
||||
await close()
|
||||
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ This sample demonstrates how to build a workflow agent that escalates uncertain
|
||||
decisions to a human manager. A Worker generates results, while a Reviewer
|
||||
evaluates them. When the Reviewer is not confident, it escalates the decision
|
||||
to a human via RequestInfoExecutor, receives the human response, and then
|
||||
forwards that response back to the Worker.
|
||||
forwards that response back to the Worker. The workflow completes when idle.
|
||||
|
||||
Prerequisites:
|
||||
- OpenAI account configured and accessible for OpenAIChatClient.
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ This sample demonstrates how to wrap a workflow as an agent using WorkflowAgent.
|
||||
It uses a reflection pattern where a Worker executor generates responses and a
|
||||
Reviewer executor evaluates them. If the response is not approved, the Worker
|
||||
regenerates the output based on feedback until the Reviewer approves it. Only
|
||||
approved responses are emitted to the external consumer.
|
||||
approved responses are emitted to the external consumer. The workflow completes when idle.
|
||||
|
||||
Key Concepts Demonstrated:
|
||||
- WorkflowAgent: Wraps a workflow to behave like a regular agent.
|
||||
|
||||
Reference in New Issue
Block a user