-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Python: Fix workflow not pausing when agent calls declaration-only tool #3757
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
moonbox3
wants to merge
2
commits into
microsoft:main
Choose a base branch
from
moonbox3:3425-fix-latest
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+322
−7
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
95 changes: 95 additions & 0 deletions
95
...samples/getting_started/workflows/human-in-the-loop/agents_with_declaration_only_tools.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| # Copyright (c) Microsoft. All rights reserved. | ||
|
|
||
| """ | ||
| Sample: Declaration-only tools in a workflow (issue #3425) | ||
|
|
||
| A declaration-only tool (func=None) represents a client-side tool that the | ||
| framework cannot execute — the LLM can call it, but the workflow must pause | ||
| so the caller can supply the result. | ||
|
|
||
| Flow: | ||
| 1. The agent is given a declaration-only tool ("get_user_location"). | ||
| 2. When the LLM decides to call it, the workflow pauses and emits a | ||
| request_info event containing the FunctionCallContent. | ||
| 3. The caller inspects the tool name/args, runs the tool however it wants, | ||
| and feeds the result back via workflow.run(responses={...}). | ||
| 4. The workflow resumes — the agent sees the tool result and finishes. | ||
|
|
||
| Prerequisites: | ||
| - Azure OpenAI endpoint configured via environment variables. | ||
| - `az login` for AzureCliCredential. | ||
| """ | ||
|
|
||
| import asyncio | ||
| import json | ||
| from typing import Any | ||
|
|
||
| from agent_framework import Content, FunctionTool, WorkflowBuilder | ||
| from agent_framework.azure import AzureOpenAIChatClient | ||
| from azure.identity import AzureCliCredential | ||
|
|
||
| # A declaration-only tool: the schema is sent to the LLM, but the framework | ||
| # has no implementation to execute. The caller must supply the result. | ||
| get_user_location = FunctionTool( | ||
| name="get_user_location", | ||
| func=None, | ||
| description="Get the user's current city. Only the client application can resolve this.", | ||
| input_model={ | ||
| "type": "object", | ||
| "properties": { | ||
| "reason": {"type": "string", "description": "Why the location is needed"}, | ||
| }, | ||
| "required": ["reason"], | ||
| }, | ||
| ) | ||
|
|
||
|
|
||
| async def main() -> None: | ||
| agent = AzureOpenAIChatClient(credential=AzureCliCredential()).as_agent( | ||
| name="WeatherBot", | ||
| instructions=( | ||
| "You are a helpful weather assistant. " | ||
| "When the user asks about weather, call get_user_location first, " | ||
| "then make up a plausible forecast for that city." | ||
| ), | ||
| tools=[get_user_location], | ||
| ) | ||
|
|
||
| workflow = WorkflowBuilder(start_executor=agent).build() | ||
|
|
||
| # --- First run: the agent should call the declaration-only tool --- | ||
| print(">>> Sending: 'What's the weather like today?'") | ||
| result = await workflow.run("What's the weather like today?") | ||
|
|
||
| requests = result.get_request_info_events() | ||
| if not requests: | ||
| # The LLM chose not to call the tool — print whatever it said and exit | ||
| print(f"Agent replied without calling the tool: {result.get_outputs()}") | ||
| return | ||
|
|
||
| # --- Inspect what the agent wants --- | ||
| for req in requests: | ||
| data = req.data | ||
| args = json.loads(data.arguments) if isinstance(data.arguments, str) else data.arguments | ||
| print(f"Workflow paused — agent called: {data.name}({args})") | ||
|
|
||
| # --- "Execute" the tool on the client side and send results back --- | ||
| responses: dict[str, Any] = {} | ||
| for req in requests: | ||
| # In a real app this could be a GPS lookup, browser API, user prompt, etc. | ||
| client_result = "Seattle, WA" | ||
| print(f"Client provides result for {req.data.name}: {client_result!r}") | ||
| responses[req.request_id] = Content.from_function_result( | ||
| call_id=req.data.call_id, | ||
| result=client_result, | ||
| ) | ||
|
|
||
| result = await workflow.run(responses=responses) | ||
|
|
||
| # --- Final answer --- | ||
| for output in result.get_outputs(): | ||
| print(f"\nAgent: {output.text}") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| asyncio.run(main()) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.