[BREAKING] Python: Fix workflow as agent streaming output (#3649)

* WIP: with_output_from

* Add with_output_from to other modules; next: workflow as agent

* WIP: remove agent run events

* orchestrations

* WIP: update samples; next start at guessing_game_With_human_input.py

* Update all samples

* WIP: consolidate workflow as agent streaming vs non-streaming

* Consolidate workflow as agent streaming vs non-streaming

* Move request info event processing to a share method

* Final pass on the samples

* Fix mypy

* Fix mypy

* Comments

---------

Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
This commit is contained in:
Tao Chen
2026-02-04 16:16:45 -08:00
committed by GitHub
Unverified
parent 907654a489
commit a971d24f1e
68 changed files with 2652 additions and 2247 deletions
@@ -7,6 +7,7 @@ from pathlib import Path
from typing import cast
from agent_framework import (
AgentResponse,
ChatAgent,
ChatMessage,
Content,
@@ -26,7 +27,7 @@ from azure.identity import AzureCliCredential
Sample: Handoff Workflow with Tool Approvals + Checkpoint Resume
Demonstrates the two-step pattern for resuming a handoff workflow from a checkpoint
while handling both HandoffUserInputRequest prompts and function approval request Content
while handling both HandoffAgentUserRequest prompts and function approval request Content
for tool calls (e.g., submit_refund).
Scenario:
@@ -124,7 +125,7 @@ def _print_handoff_agent_user_request(response: AgentResponse) -> None:
for message in response.messages:
if not message.text:
continue
speaker = message.author_name or message.role.value
speaker = message.author_name or message.role
print(f" {speaker}: {message.text}")
@@ -133,6 +134,7 @@ def _print_handoff_request(request: HandoffAgentUserRequest, request_id: str) ->
print(f"\n{'=' * 60}")
print("WORKFLOW PAUSED - User input needed")
print(f"Request ID: {request_id}")
print(f"Awaiting agent: {request.agent_response.agent_id}")
_print_handoff_agent_user_request(request.agent_response)
@@ -141,11 +143,11 @@ def _print_handoff_request(request: HandoffAgentUserRequest, request_id: str) ->
def _print_function_approval_request(request: Content, request_id: str) -> None:
"""Log pending tool approval details for debugging."""
args = request.function_call.parse_arguments() or {}
args = request.function_call.parse_arguments() or {} # type: ignore
print(f"\n{'=' * 60}")
print("WORKFLOW PAUSED - Tool approval required")
print(f"Request ID: {request_id}")
print(f"Function: {request.function_call.name}")
print(f"Function: {request.function_call.name}") # type: ignore
print(f"Arguments:\n{json.dumps(args, indent=2)}")
print(f"{'=' * 60}\n")
@@ -161,7 +163,7 @@ def _build_responses_for_requests(
for request in pending_requests:
if isinstance(request.data, HandoffAgentUserRequest):
if user_response is None:
raise ValueError("User response is required for HandoffUserInputRequest")
raise ValueError("User response is required for HandoffAgentUserRequest")
responses[request.request_id] = user_response
elif isinstance(request.data, Content) and request.data.type == "function_approval_request":
if approve_tools is None:
@@ -281,9 +283,9 @@ async def resume_with_responses(
elif isinstance(event, WorkflowOutputEvent):
print("\n[Workflow Output Event - Conversation Update]")
if event.data and isinstance(event.data, list) and all(isinstance(msg, ChatMessage) for msg in event.data):
if event.data and isinstance(event.data, list) and all(isinstance(msg, ChatMessage) for msg in event.data): # type: ignore
# Now safe to cast event.data to list[ChatMessage]
conversation = cast(list[ChatMessage], event.data)
conversation = cast(list[ChatMessage], event.data) # type: ignore
for msg in conversation[-3:]: # Show last 3 messages
author = msg.author_name or msg.role
text = msg.text[:100] + "..." if len(msg.text) > 100 else msg.text