forked from microsoft/agent-framework
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsequential_builder_tool_approval.py
More file actions
164 lines (135 loc) · 6.8 KB
/
sequential_builder_tool_approval.py
File metadata and controls
164 lines (135 loc) · 6.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import os
from collections.abc import AsyncIterable
from typing import Annotated, cast
from agent_framework import (
Content,
Message,
WorkflowEvent,
tool,
)
from agent_framework.azure import AzureOpenAIResponsesClient
from agent_framework.orchestrations import SequentialBuilder
from azure.identity import AzureCliCredential
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
"""
Sample: Sequential Workflow with Tool Approval Requests
This sample demonstrates how to use SequentialBuilder with tools that require human
approval before execution. The approval flow uses the existing @tool decorator
with approval_mode="always_require" to trigger human-in-the-loop interactions.
This sample works as follows:
1. A SequentialBuilder workflow is created with a single agent that has tools requiring approval.
2. The agent receives a user task and determines it needs to call a sensitive tool.
3. The tool call triggers a function_approval_request Content, pausing the workflow.
4. The sample simulates human approval by responding to the .
5. Once approved, the tool executes and the agent completes its response.
6. The workflow outputs the final conversation with all messages.
Purpose:
Show how tool call approvals integrate seamlessly with SequentialBuilder without
requiring any additional builder configuration.
Demonstrate:
- Using @tool(approval_mode="always_require") for sensitive operations.
- Handling request_info events with function_approval_request Content in sequential workflows.
- Resuming workflow execution after approval via run(responses=..., stream=True).
Prerequisites:
- AZURE_AI_PROJECT_ENDPOINT must be your Azure AI Foundry Agent Service (V2) project endpoint.
- OpenAI or Azure OpenAI configured with the required environment variables.
- Basic familiarity with SequentialBuilder and streaming workflow events.
"""
# 1. Define tools - one requiring approval, one that doesn't
@tool(approval_mode="always_require")
def execute_database_query(
query: Annotated[str, "The SQL query to execute against the production database"],
) -> str:
"""Execute a SQL query against the production database. Requires human approval."""
# In a real implementation, this would execute the query
return f"Query executed successfully. Results: 3 rows affected by '{query}'"
# NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production;
# see samples/02-agents/tools/function_tool_with_approval.py and
# samples/02-agents/tools/function_tool_with_approval_and_sessions.py.
@tool(approval_mode="never_require")
def get_database_schema() -> str:
"""Get the current database schema. Does not require approval."""
return """
Tables:
- users (id, name, email, created_at)
- orders (id, user_id, total, status, created_at)
- products (id, name, price, stock)
"""
async def process_event_stream(stream: AsyncIterable[WorkflowEvent]) -> dict[str, Content] | None:
"""Process events from the workflow stream to capture human feedback requests."""
requests: dict[str, Content] = {}
async for event in stream:
if event.type == "request_info" and isinstance(event.data, Content):
# We are only expecting tool approval requests in this sample
requests[event.request_id] = event.data
elif event.type == "output":
# The output of the workflow comes from the orchestrator and it's a list of messages
print("\n" + "=" * 60)
print("Workflow summary:")
outputs = cast(list[Message], event.data)
for msg in outputs:
speaker = msg.author_name or msg.role
print(f"[{speaker}]: {msg.text}")
responses: dict[str, Content] = {}
if requests:
for request_id, request in requests.items():
if request.type == "function_approval_request":
print("\n[APPROVAL REQUIRED]")
print(f" Tool: {request.function_call.name}") # type: ignore
print(f" Arguments: {request.function_call.arguments}") # type: ignore
print(f"Simulating human approval for: {request.function_call.name}") # type: ignore
# Create approval response
responses[request_id] = request.to_function_approval_response(approved=True)
return responses if responses else None
async def main() -> None:
# 2. Create the agent with tools (approval mode is set per-tool via decorator)
client = AzureOpenAIResponsesClient(
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
credential=AzureCliCredential(),
)
database_agent = client.as_agent(
name="DatabaseAgent",
instructions=(
"You are a database assistant. You can view the database schema and execute "
"queries. Always check the schema before running queries. Be careful with "
"queries that modify data."
),
tools=[get_database_schema, execute_database_query],
)
# 3. Build a sequential workflow with the agent
workflow = SequentialBuilder(participants=[database_agent]).build()
# 4. Start the workflow with a user task
print("Starting sequential workflow with tool approval...")
print("-" * 60)
# Initiate the first run of the workflow.
# Runs are not isolated; state is preserved across multiple calls to run.
stream = workflow.run(
"Check the schema and then update all orders with status 'pending' to 'processing'", stream=True
)
pending_responses = await process_event_stream(stream)
while pending_responses is not None:
# Run the workflow until there is no more human feedback to provide,
# in which case this workflow completes.
stream = workflow.run(stream=True, responses=pending_responses)
pending_responses = await process_event_stream(stream)
"""
Sample Output:
Starting sequential workflow with tool approval...
------------------------------------------------------------
Approval requested for tool: execute_database_query
Arguments: {"query": "UPDATE orders SET status = 'processing' WHERE status = 'pending'"}
Simulating human approval (auto-approving for demo)...
------------------------------------------------------------
Workflow completed. Final conversation:
[user]: Check the schema and then update all orders with status 'pending' to 'processing'
[assistant]: I've checked the schema and executed the update query. The query
"UPDATE orders SET status = 'processing' WHERE status = 'pending'"
was executed successfully, affecting 3 rows.
"""
if __name__ == "__main__":
asyncio.run(main())