From 9fe4a61dd0ceb8b4f42dc6ea54bc0ce57506345b Mon Sep 17 00:00:00 2001 From: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com> Date: Thu, 9 Oct 2025 10:31:36 -0700 Subject: [PATCH] Python: Added function approval example with streaming (#1365) * Added function approval example with streaming * Update python/samples/getting_started/tools/ai_tool_with_approval.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../tools/ai_tool_with_approval.py | 77 ++++++++++++++++--- 1 file changed, 65 insertions(+), 12 deletions(-) diff --git a/python/samples/getting_started/tools/ai_tool_with_approval.py b/python/samples/getting_started/tools/ai_tool_with_approval.py index 96bd1189ac..bdc673bb2c 100644 --- a/python/samples/getting_started/tools/ai_tool_with_approval.py +++ b/python/samples/getting_started/tools/ai_tool_with_approval.py @@ -4,7 +4,7 @@ import asyncio from random import randrange from typing import TYPE_CHECKING, Annotated, Any -from agent_framework import ChatAgent, ai_function +from agent_framework import AgentRunResponse, ChatAgent, ChatMessage, ai_function from agent_framework.openai import OpenAIResponsesClient if TYPE_CHECKING: @@ -39,14 +39,12 @@ def get_weather_detail(location: Annotated[str, "The city and state, e.g. San Fr ) -async def handle_approvals(query: str, agent: "AgentProtocol"): +async def handle_approvals(query: str, agent: "AgentProtocol") -> AgentRunResponse: """Handle function call approvals. When we don't have a thread, we need to ensure we include the original query, the approval request, and the approval response in each iteration. """ - from agent_framework import ChatMessage - result = await agent.run(query) while len(result.user_input_requests) > 0: # Start with the original query @@ -63,7 +61,7 @@ async def handle_approvals(query: str, agent: "AgentProtocol"): new_inputs.append(ChatMessage(role="assistant", contents=[user_input_needed])) # Get user approval - user_approval = input("\nApprove function call? (y/n): ") + user_approval = await asyncio.to_thread(input, "\nApprove function call? (y/n): ") # Add the user's approval response new_inputs.append( @@ -76,9 +74,57 @@ async def handle_approvals(query: str, agent: "AgentProtocol"): return result -async def run_weather_agent_with_approval() -> None: +async def handle_approvals_streaming(query: str, agent: "AgentProtocol") -> None: + """Handle function call approvals with streaming responses. + + When we don't have a thread, we need to ensure we include the original query, + the approval request, and the approval response in each iteration. + """ + current_input: str | list[Any] = query + has_user_input_requests = True + while has_user_input_requests: + has_user_input_requests = False + user_input_requests: list[Any] = [] + + # Stream the response + async for chunk in agent.run_stream(current_input): + if chunk.text: + print(chunk.text, end="", flush=True) + + # Collect user input requests from the stream + if chunk.user_input_requests: + user_input_requests.extend(chunk.user_input_requests) + + if user_input_requests: + has_user_input_requests = True + # Start with the original query + new_inputs: list[Any] = [query] + + for user_input_needed in user_input_requests: + print( + f"\n\nUser Input Request for function from {agent.name}:" + f"\n Function: {user_input_needed.function_call.name}" + f"\n Arguments: {user_input_needed.function_call.arguments}" + ) + + # Add the assistant message with the approval request + new_inputs.append(ChatMessage(role="assistant", contents=[user_input_needed])) + + # Get user approval + user_approval = await asyncio.to_thread(input, "\nApprove function call? (y/n): ") + + # Add the user's approval response + new_inputs.append( + ChatMessage(role="user", contents=[user_input_needed.create_response(user_approval.lower() == "y")]) + ) + + # Update input with all the context for next iteration + current_input = new_inputs + + +async def run_weather_agent_with_approval(is_streaming: bool) -> None: """Example showing AI function with approval requirement.""" - print("\n=== Weather Agent WITH Approval Required ===\n") + print(f"\n=== Weather Agent with Approval Required ({'Streaming' if is_streaming else 'Non-Streaming'}) ===\n") async with ChatAgent( chat_client=OpenAIResponsesClient(), @@ -86,16 +132,23 @@ async def run_weather_agent_with_approval() -> None: instructions=("You are a helpful weather assistant. Use the get_weather tool to provide weather information."), tools=[get_weather, get_weather_detail], ) as agent: - query2 = "Can you give me an update of the weather in LA and Portland and detailed weather for Seattle?" - print(f"User: {query2}") - result2 = await handle_approvals(query2, agent) - print(f"\n{agent.name}: {result2}\n") + query = "Can you give me an update of the weather in LA and Portland and detailed weather for Seattle?" + print(f"User: {query}") + + if is_streaming: + print(f"\n{agent.name}: ", end="", flush=True) + await handle_approvals_streaming(query, agent) + print() + else: + result = await handle_approvals(query, agent) + print(f"\n{agent.name}: {result}\n") async def main() -> None: print("=== Demonstration of a tool with approvals ===\n") - await run_weather_agent_with_approval() + await run_weather_agent_with_approval(is_streaming=False) + await run_weather_agent_with_approval(is_streaming=True) if __name__ == "__main__":