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
@@ -6,12 +6,11 @@ from typing import Annotated, Any
|
||||
|
||||
from agent_framework import (
|
||||
ChatMessage,
|
||||
SequentialBuilder,
|
||||
WorkflowExecutor,
|
||||
WorkflowOutputEvent,
|
||||
tool,
|
||||
)
|
||||
from agent_framework.openai import OpenAIChatClient
|
||||
from agent_framework.orchestrations import SequentialBuilder
|
||||
|
||||
"""
|
||||
Sample: Sub-Workflow kwargs Propagation
|
||||
@@ -32,7 +31,9 @@ Prerequisites:
|
||||
|
||||
|
||||
# Define tools that access custom context via **kwargs
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
|
||||
# see samples/getting_started/tools/function_tool_with_approval.py and
|
||||
# samples/getting_started/tools/function_tool_with_approval_and_threads.py.
|
||||
@tool(approval_mode="never_require")
|
||||
def get_authenticated_data(
|
||||
resource: Annotated[str, "The resource to fetch"],
|
||||
@@ -129,7 +130,7 @@ async def main() -> None:
|
||||
user_token=user_token,
|
||||
service_config=service_config,
|
||||
):
|
||||
if isinstance(event, WorkflowOutputEvent):
|
||||
if event.type == "output":
|
||||
output_data = event.data
|
||||
if isinstance(output_data, list):
|
||||
for item in output_data: # type: ignore
|
||||
@@ -140,6 +141,50 @@ async def main() -> None:
|
||||
print("Sample Complete - kwargs successfully flowed through sub-workflow!")
|
||||
print("=" * 70)
|
||||
|
||||
"""
|
||||
Sample Output:
|
||||
|
||||
======================================================================
|
||||
Sub-Workflow kwargs Propagation Demo
|
||||
======================================================================
|
||||
|
||||
Context being passed to parent workflow:
|
||||
user_token: {
|
||||
"user_name": "alice@contoso.com",
|
||||
"access_level": "admin",
|
||||
"session_id": "sess_12345"
|
||||
}
|
||||
service_config: {
|
||||
"services": {
|
||||
"users": "https://api.example.com/v1/users",
|
||||
"orders": "https://api.example.com/v1/orders",
|
||||
"inventory": "https://api.example.com/v1/inventory"
|
||||
},
|
||||
"timeout": 30
|
||||
}
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Workflow Execution (kwargs flow: parent -> sub-workflow -> agent -> tool):
|
||||
----------------------------------------------------------------------
|
||||
|
||||
[get_authenticated_data] kwargs keys: ['user_token', 'service_config']
|
||||
[get_authenticated_data] User: alice@contoso.com, Access: admin
|
||||
|
||||
[call_configured_service] kwargs keys: ['user_token', 'service_config']
|
||||
[call_configured_service] Available services: ['users', 'orders', 'inventory']
|
||||
|
||||
[Final Answer]: Please fetch my profile data and then call the users service.
|
||||
|
||||
[Final Answer]: - Your profile data has been fetched.
|
||||
- The users service has been called.
|
||||
|
||||
Would you like details from either the profile data or the users service response?
|
||||
|
||||
======================================================================
|
||||
Sample Complete - kwargs successfully flowed through sub-workflow!
|
||||
======================================================================
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
|
||||
+6
-6
@@ -3,16 +3,16 @@
|
||||
import asyncio
|
||||
import uuid
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal
|
||||
from typing import Any, Literal
|
||||
|
||||
from agent_framework import (
|
||||
Executor,
|
||||
RequestInfoEvent,
|
||||
SubWorkflowRequestMessage,
|
||||
SubWorkflowResponseMessage,
|
||||
Workflow,
|
||||
WorkflowBuilder,
|
||||
WorkflowContext,
|
||||
WorkflowEvent,
|
||||
WorkflowExecutor,
|
||||
handler,
|
||||
response_handler,
|
||||
@@ -192,7 +192,7 @@ class ResourceAllocator(Executor):
|
||||
super().__init__(id)
|
||||
self._cache: dict[str, int] = {"cpu": 10, "memory": 50, "disk": 100}
|
||||
# Record pending requests to match responses
|
||||
self._pending_requests: dict[str, RequestInfoEvent] = {}
|
||||
self._pending_requests: dict[str, WorkflowEvent[Any]] = {}
|
||||
|
||||
async def _handle_resource_request(self, request: ResourceRequest) -> ResourceResponse | None:
|
||||
"""Allocates resources based on request and available cache."""
|
||||
@@ -207,7 +207,7 @@ class ResourceAllocator(Executor):
|
||||
self, request: SubWorkflowRequestMessage, ctx: WorkflowContext[SubWorkflowResponseMessage]
|
||||
) -> None:
|
||||
"""Handles requests from sub-workflows."""
|
||||
source_event: RequestInfoEvent = request.source_event
|
||||
source_event: WorkflowEvent[Any] = request.source_event
|
||||
if not isinstance(source_event.data, ResourceRequest):
|
||||
return
|
||||
|
||||
@@ -246,14 +246,14 @@ class PolicyEngine(Executor):
|
||||
"disk": 1000, # Liberal disk policy
|
||||
}
|
||||
# Record pending requests to match responses
|
||||
self._pending_requests: dict[str, RequestInfoEvent] = {}
|
||||
self._pending_requests: dict[str, WorkflowEvent[Any]] = {}
|
||||
|
||||
@handler
|
||||
async def handle_subworkflow_request(
|
||||
self, request: SubWorkflowRequestMessage, ctx: WorkflowContext[SubWorkflowResponseMessage]
|
||||
) -> None:
|
||||
"""Handles requests from sub-workflows."""
|
||||
source_event: RequestInfoEvent = request.source_event
|
||||
source_event: WorkflowEvent[Any] = request.source_event
|
||||
if not isinstance(source_event.data, PolicyRequest):
|
||||
return
|
||||
|
||||
|
||||
+1
-2
@@ -11,7 +11,6 @@ from agent_framework import (
|
||||
WorkflowBuilder,
|
||||
WorkflowContext,
|
||||
WorkflowExecutor,
|
||||
WorkflowOutputEvent,
|
||||
handler,
|
||||
response_handler,
|
||||
)
|
||||
@@ -303,7 +302,7 @@ async def main() -> None:
|
||||
for email in test_emails:
|
||||
print(f"\n🚀 Processing email to '{email.recipient}'")
|
||||
async for event in workflow.run(email, stream=True):
|
||||
if isinstance(event, WorkflowOutputEvent):
|
||||
if event.type == "output":
|
||||
print(f"🎉 Final result for '{email.recipient}': {'Delivered' if event.data else 'Blocked'}")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user