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,8 @@
|
||||
|
||||
import asyncio
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from typing_extensions import Never
|
||||
|
||||
from agent_framework import ( # Core chat primitives to build LLM requests
|
||||
AgentExecutor, # Wraps an LLM agent for use inside a workflow
|
||||
@@ -13,8 +14,8 @@ from agent_framework import ( # Core chat primitives to build LLM requests
|
||||
Executor, # Base class for custom Python executors
|
||||
Role, # Enum of chat roles (user, assistant, system)
|
||||
WorkflowBuilder, # Fluent builder for wiring the workflow graph
|
||||
WorkflowCompletedEvent, # Terminal event carrying the final result
|
||||
WorkflowContext, # Per run context and event bus
|
||||
WorkflowOutputEvent, # Event emitted when workflow yields output
|
||||
handler, # Decorator to mark an Executor method as invokable
|
||||
)
|
||||
from agent_framework.azure import AzureChatClient # Client wrapper for Azure OpenAI chat models
|
||||
@@ -75,7 +76,7 @@ class AggregateInsights(Executor):
|
||||
self._expert_ids = expert_ids
|
||||
|
||||
@handler
|
||||
async def aggregate(self, results: list[AgentExecutorResponse], ctx: WorkflowContext[Any]) -> None:
|
||||
async def aggregate(self, results: list[AgentExecutorResponse], ctx: WorkflowContext[Never, str]) -> None:
|
||||
# Map responses to text by executor id for a simple, predictable demo.
|
||||
by_id: dict[str, str] = {}
|
||||
for r in results:
|
||||
@@ -101,7 +102,7 @@ class AggregateInsights(Executor):
|
||||
f"Legal/Compliance Notes:\n{aggregated.legal}\n"
|
||||
)
|
||||
|
||||
await ctx.add_event(WorkflowCompletedEvent(data=consolidated))
|
||||
await ctx.yield_output(consolidated)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
@@ -151,17 +152,13 @@ async def main() -> None:
|
||||
)
|
||||
|
||||
# 3) Run with a single prompt and print progress plus the final consolidated output
|
||||
completion: WorkflowCompletedEvent | None = None
|
||||
async for event in workflow.run_stream("We are launching a new budget-friendly electric bike for urban commuters."):
|
||||
if isinstance(event, AgentRunEvent):
|
||||
# Show which agent ran and what step completed for lightweight observability.
|
||||
print(event)
|
||||
if isinstance(event, WorkflowCompletedEvent):
|
||||
completion = event
|
||||
|
||||
if completion:
|
||||
print("===== Final Aggregated Output =====")
|
||||
print(completion.data)
|
||||
elif isinstance(event, WorkflowOutputEvent):
|
||||
print("===== Final Aggregated Output =====")
|
||||
print(event.data)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+10
-13
@@ -5,14 +5,15 @@ import asyncio
|
||||
import os
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
import aiofiles
|
||||
from typing_extensions import Never
|
||||
|
||||
from agent_framework import (
|
||||
Executor, # Base class for custom workflow steps
|
||||
WorkflowBuilder, # Fluent graph builder for executors and edges
|
||||
WorkflowCompletedEvent, # Terminal event that carries final output
|
||||
WorkflowBuilder, # Fluent builder for executors and edges
|
||||
WorkflowContext, # Per run context with shared state and messaging
|
||||
WorkflowOutputEvent, # Event emitted when workflow yields output
|
||||
WorkflowViz, # Utility to visualize a workflow graph
|
||||
handler, # Decorator to expose an Executor method as a step
|
||||
)
|
||||
@@ -246,12 +247,12 @@ class Reduce(Executor):
|
||||
|
||||
|
||||
class CompletionExecutor(Executor):
|
||||
"""Joins all reducer outputs and emits the final completion event."""
|
||||
"""Joins all reducer outputs and yields the final output."""
|
||||
|
||||
@handler
|
||||
async def complete(self, data: list[ReduceCompleted], ctx: WorkflowContext[Any]) -> None:
|
||||
"""Collect reducer output file paths and publish a terminal event."""
|
||||
await ctx.add_event(WorkflowCompletedEvent(data=[result.file_path for result in data]))
|
||||
async def complete(self, data: list[ReduceCompleted], ctx: WorkflowContext[Never, list[str]]) -> None:
|
||||
"""Collect reducer output file paths and yield final output."""
|
||||
await ctx.yield_output([result.file_path for result in data])
|
||||
|
||||
|
||||
async def main():
|
||||
@@ -303,14 +304,10 @@ async def main():
|
||||
raw_text = await f.read()
|
||||
|
||||
# Step 4: Run the workflow with the raw text as input.
|
||||
completion_event = None
|
||||
async for event in workflow.run_stream(raw_text):
|
||||
print(f"Event: {event}")
|
||||
if isinstance(event, WorkflowCompletedEvent):
|
||||
completion_event = event
|
||||
|
||||
if completion_event:
|
||||
print(f"Completion Event: {completion_event}")
|
||||
if isinstance(event, WorkflowOutputEvent):
|
||||
print(f"Final Output: {event.data}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user