[BREAKING] Python: Merge send_responses into run method (#3720)

* Streamline workflow run api with send responses in one method

* Fixes

* Address copilot feedback
This commit is contained in:
Evan Mattson
2026-02-07 07:32:38 +09:00
committed by GitHub
Unverified
parent 15256bb616
commit a17f13598b
39 changed files with 561 additions and 335 deletions
@@ -249,7 +249,7 @@ async def run_interactive_session(
while True:
if responses:
event_stream = workflow.send_responses_streaming(responses)
event_stream = workflow.run(stream=True, responses=responses)
requests.clear()
responses = None
else:
@@ -23,22 +23,24 @@ 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 HandoffAgentUserRequest prompts and function approval request Content
for tool calls (e.g., submit_refund).
Demonstrates resuming a handoff workflow from a checkpoint while handling both
HandoffAgentUserRequest prompts and function approval request Content for tool calls
(e.g., submit_refund).
Scenario:
1. User starts a conversation with the workflow.
2. Agents may emit user input requests or tool approval requests.
3. Workflow writes a checkpoint capturing pending requests and pauses.
4. Process can exit/restart.
5. On resume: Load the checkpoint, surface pending approvals/user prompts, and provide responses.
5. On resume: Restore checkpoint, inspect pending requests, then provide responses.
6. Workflow continues from the saved state.
Pattern:
- Step 1: workflow.run(checkpoint_id=..., stream=True) to restore checkpoint and pending requests.
- Step 2: workflow.send_responses_streaming(responses) to supply human replies and approvals.
- Two-step approach is required because send_responses_streaming does not accept checkpoint_id.
- workflow.run(checkpoint_id=..., stream=True) to restore checkpoint and discover pending requests.
- workflow.run(stream=True, responses=responses) to supply human replies and approvals.
(Two steps are needed here because the sample must inspect request types before building responses.
When response payloads are already known, use the single-call form:
workflow.run(stream=True, checkpoint_id=..., responses=responses).)
Prerequisites:
- Azure CLI authentication (az login).
@@ -228,13 +230,13 @@ async def resume_with_responses(
approve_tools: bool | None = None,
) -> tuple[list[WorkflowEvent], str | None]:
"""
Two-step resume pattern (answers customer questions and tool approvals):
Resume from checkpoint and send responses.
Step 1: Restore checkpoint to load pending requests into workflow state
Step 2: Send user responses using send_responses_streaming
Step 1: Restore checkpoint to discover pending request types.
Step 2: Build typed responses and send via workflow.run(responses=...).
This is the current pattern required because send_responses_streaming
doesn't accept a checkpoint_id parameter.
When response payloads are already known, these can be combined into a single
workflow.run(stream=True, checkpoint_id=..., responses=...) call.
"""
print(f"\n{'=' * 60}")
print("RESUMING WORKFLOW WITH HUMAN INPUT")
@@ -253,10 +255,9 @@ async def resume_with_responses(
checkpoints.sort(key=lambda cp: cp.timestamp, reverse=True)
latest_checkpoint = checkpoints[0]
print(f"Step 1: Restoring checkpoint {latest_checkpoint.checkpoint_id}")
print(f"Restoring checkpoint {latest_checkpoint.checkpoint_id}")
# Step 1: Restore the checkpoint to load pending requests into memory
# The checkpoint restoration re-emits pending request_info events
# First, restore checkpoint to discover pending requests
restored_requests: list[WorkflowEvent] = []
async for event in workflow.run(checkpoint_id=latest_checkpoint.checkpoint_id, stream=True): # type: ignore[attr-defined]
if event.type == "request_info":
@@ -274,11 +275,11 @@ async def resume_with_responses(
user_response=user_response,
approve_tools=approve_tools,
)
print(f"Step 2: Sending responses for {len(responses)} request(s)")
print(f"Sending responses for {len(responses)} request(s)")
new_pending_requests: list[WorkflowEvent] = []
async for event in workflow.send_responses_streaming(responses):
async for event in workflow.run(stream=True, responses=responses):
if event.type == "status":
print(f"[Status] {event.state}")
@@ -309,7 +310,7 @@ async def main() -> None:
This sample shows:
1. Starting a workflow and getting a HandoffAgentUserRequest
2. Pausing (checkpoint is saved automatically)
3. Resuming from checkpoint with a user response or tool approval (two-step pattern)
3. Resuming from checkpoint with a user response or tool approval
4. Continuing the conversation until completion
"""
@@ -380,7 +380,7 @@ async def main() -> None:
approval_response = "approve"
output_event: WorkflowEvent | None = None
async for event in workflow2.send_responses_streaming({request_info_event.request_id: approval_response}):
async for event in workflow2.run(stream=True, responses={request_info_event.request_id: approval_response}):
if event.type == "output":
output_event = event