-
Notifications
You must be signed in to change notification settings - Fork 662
.NET: ADR: Supporting user approvals #209
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
Closed
Closed
Changes from 5 commits
Commits
Show all changes
72 commits
Select commit
Hold shift + click to select a range
c73282b
ADR: Supporting user approvals
westey-m 9e3b98b
Add more context.
westey-m a9a6a97
Add further justification
westey-m 61063c0
Add decision drivers.
westey-m 3a0dabf
Update with Copilot Suggestion
westey-m 786374c
Merge branch 'main' into userapproval-adr
westey-m eace4e7
Update adr with feedback.
westey-m 4076ed3
Adding another generalized option for all user input.
westey-m 23b4fe5
Merge branch 'main' into userapproval-adr
westey-m 12817f1
Add another base class based UserInput option
westey-m 84e2855
Remove TextContent inheritance.
westey-m 632635c
Change Schema and Strucutured input data types to JsonElement
westey-m 461c8ef
Rename JsonSchema to Schema
westey-m 7de1787
Merge branch 'main' into userapproval-adr
westey-m aa3c14e
Merge branch 'main' into userapproval-adr
crickman ab04590
Address some feedback.
westey-m aba7dc6
Merge branch 'main' into userapproval-adr
westey-m 89ff266
Merge branch 'main' into userapproval-adr
crickman 629204e
Add base user input request and response types with FunctionApproval …
westey-m 3783074
Merge branch 'main' into userapproval-adr
crickman d3a9319
Merge branch 'main' into userapproval-adr
westey-m 74d1f5d
Add POC ApprovalGeneratingChatClient
westey-m 3c40615
Merge branch 'main' into userapproval-adr
crickman c068247
Add pre-FunctionInvokingChatClient ApprovalGeneratingChatClient POC
westey-m d8626a4
Merge branch 'userapproval-adr' of https://github.com/westey-m/agent-…
westey-m 45254f3
Merge branch 'main' into userapproval-adr
westey-m 3300dce
Merge branch 'main' into userapproval-adr
westey-m c8e42de
Fix namespaces
westey-m 97352a0
Add combined FICC with approvals
westey-m 2c39648
Bug fix.
westey-m 5293568
Add MCP and ApprovalRequiredAIFunction POC
westey-m c5bedd0
Bug fixes
westey-m edcad34
Address PR Feedback.
westey-m 0a61294
Add AIFunctionApprovalContext with some small efficiency improvements.
westey-m 436c490
Remove unecessary helpers that are only used in one place.
westey-m 0d5fcbf
Replace dictionary with array and lazy initialize.
westey-m 8beb0f6
Improve sample code
westey-m 9fbd0a7
Make user input id required and rename it to be more general.
westey-m 302d43d
Merge branch 'main' into userapproval-adr
westey-m 8ad466b
Upgrade packages to resolve conflicts.
westey-m 049c9d3
Move some code into methods to simply main GetResponse.
westey-m 39ef6bd
Adding approvals support to streaming
westey-m 14d4773
Address PR comments.
westey-m b288fed
Change approval response type and naming
westey-m fc0bf74
Remove commented out validation checks that won't work for server sid…
westey-m d1939ad
Remove original metadata and read metadata from approval request mess…
westey-m 6d8b20e
Small code refactoring and clarifying comments.
westey-m 2bc90af
Remove failed POCs
westey-m d12cdf8
Update ADR with latest changes
westey-m 3420337
Explain case when pre FICC AGCC fails
westey-m 6a6f9dd
Add support for mcp servers over http.
westey-m f05bf90
Merge branch 'main' into userapproval-adr
westey-m 3105941
Addressing PR comments.
westey-m 26eb742
Address PR comments
westey-m 0adf519
Merge branch 'main' into userapproval-adr
westey-m b2a4915
Merge latest updates from main
westey-m 5e52c11
Add FICC unit tests and fix bugs.
westey-m b1289e3
Add additional unit test.
westey-m 0b4405e
Refactor ConvertToFunctionCallContentMessages to avoid unecessary all…
westey-m 545c231
Refactor preinvocation logic to share between streaming and non-strea…
westey-m e6aeebb
Move some approval generation code to a separate method to simplify m…
westey-m a1b2973
More refactoring, additional unit tests and bug fixes.
westey-m 906ad2f
Remove comments delineating additions.
westey-m 4c59b25
Merge latest changes from main
westey-m 66b904e
Fixing some typos.
westey-m d3dd02e
Merge branch 'main' into userapproval-adr
westey-m 21680be
Address PR comments.
westey-m a712408
Address PR comments.
westey-m c1da46b
Address PR feedback.
westey-m 091f754
Fix bug for server side threads.
westey-m 7518b70
Address PR comments
westey-m a349c79
Merge from main
westey-m 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,181 @@ | ||
| --- | ||
| # These are optional elements. Feel free to remove any of them. | ||
| status: proposed | ||
| contact: westey-m | ||
| date: 2025-07-16 {YYYY-MM-DD when the decision was last updated} | ||
| deciders: sergeymenshykh, markwallace, rbarreto, dmytrostruk, westey-m, eavanvalkenburg, stephentoub | ||
| consulted: | ||
| informed: | ||
| --- | ||
|
|
||
| # Agent User Approvals Content Types Design | ||
|
|
||
| ## Context and Problem Statement | ||
|
|
||
| When agents are operating on behalf of a user, there may be cases where the agent requires user approval to continue an operation. | ||
westey-m marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| This is complicated by the fact that an agent may be remote and the user may not immediately be available to provide the approval. | ||
|
|
||
| Inference services are also increasingly supporting built-in tools or service side MCP invocation, which may require user approval before the tool can be invoked. | ||
|
|
||
| This document aims to provide options and capture the decision on how to model this user approval interaction with the agent caller. | ||
|
|
||
| ## Decision Drivers | ||
|
|
||
| - Agents should encapsulate their internal logic and not leak it to the caller. | ||
| - We need to support approvals for local actions as well as remote actions. | ||
| - We need to support approvals for existing protocols and services, such as OpenAI's MCP and function calling. | ||
westey-m marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ## Considered Options | ||
westey-m marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| ### Return a FunctionCallContent to the agent caller, that it executes | ||
|
|
||
| This introduces a manual function calling element to agents, where the caller of the agent is expected to invoke the function if the user approves it. | ||
|
|
||
| This approach is problematic for a number of reasons: | ||
|
|
||
| - This may not work for remote agents (e.g. via A2A), where the function that the agent wants to call does not reside on the caller's machine. | ||
| - The main value prop of an agent is to encapsulate the internal logic of the agent, but this leaks that logic to the caller, requiring the caller to know how to invoke the agent's function calls. | ||
| - Inference services are introducing their own approval content types for server side tool or function invocation, and will not be addressed by this approach. | ||
|
|
||
| ### Introduce new ApprovalRequestContent and ApprovalResponseContent types | ||
|
|
||
| The agent would return an `ApprovalRequestContent` to the caller, which would then be responsible for getting approval from the user in whatever way is appropriate for the application. | ||
westey-m marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| The caller would then invoke the agent again with an `ApprovalResponseContent` to the agent containing the user decision. | ||
|
|
||
| When an agent returns an `ApprovalRequestContent`, the run is finished for the time being, and to continue, the agent must be invoked again with an `ApprovalResponseContent` on the same thread as the original request. | ||
westey-m marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| The `ApprovalRequestContent` could contain an optional `FunctionCallContent` if the approval is for a function call, along with any additional information that the agent wants to provide to the user to help them make a decision. | ||
westey-m marked this conversation as resolved.
Show resolved
Hide resolved
westey-m marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| It is up to the agent to decide when and if a user approval is required, and therefore when to return an `ApprovalRequestContent`. | ||
|
|
||
| `ApprovalRequestContent` and `ApprovalResponseContent` will not necessarily always map to a supported content type for the underlying service or agent thread storage. | ||
westey-m marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Specifically, when we are deciding in the IChatClient stack to ask for approval from the user, for a function call, this does not mean that the underlying ai service or | ||
| service side thread type (where applicable) supports the concept of a function call approval request. We therefore need the ability to temporarily store the approval request in the | ||
| AgentThread, without it becoming part of the thread history. This will serve as a temporary record of the fact that there is an outstanding approval request that the agent is waiting for to continue. | ||
| There will be no long term record of an approval request in the chat history, but if the server side thread doesn't support this, there is nothing we can do to change that. | ||
westey-m marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
westey-m marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| Suggested Types: | ||
|
|
||
| ```csharp | ||
| class ApprovalRequestContent : TextContent // TextContent.Text may contain text to explain to the user what they are approving. This is important if the approval is not for a function call. | ||
westey-m marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| // An ID to uniquely identify the approval request/response pair. | ||
| public string ApprovalId { get; set; } | ||
|
|
||
| // Optional: If the approval is for a function call, this will contain the function call content. | ||
| public FunctionCallContent? FunctionCall { get; set; } | ||
westey-m marked this conversation as resolved.
Show resolved
Hide resolved
westey-m marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| public ChatMessage Approve() | ||
| { | ||
| return new ChatMessage(ChatRole.User, | ||
| [ | ||
| new ApprovalResponseContent | ||
| { | ||
| ApprovalId = this.ApprovalId, | ||
| Approved = true, | ||
| FunctionCall = this.FunctionCall | ||
| } | ||
| ]); | ||
| } | ||
|
|
||
| public ChatMessage Reject() | ||
| { | ||
| return new ChatMessage(ChatRole.User, | ||
| [ | ||
| new ApprovalResponseContent | ||
| { | ||
| ApprovalId = this.ApprovalId, | ||
| Approved = false, | ||
| FunctionCall = this.FunctionCall | ||
| } | ||
| ]); | ||
| } | ||
| } | ||
|
|
||
| class ApprovalResponseContent : AIContent | ||
| { | ||
| // An ID to uniquely identify the approval request/response pair. | ||
| public string ApprovalId { get; set; } | ||
|
|
||
| // Indicates whether the user approved the request. | ||
| public bool Approved { get; set; } | ||
|
|
||
| // Optional: If the approval is for a function call, this will contain the function call content. | ||
| public FunctionCallContent? FunctionCall { get; set; } | ||
westey-m marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| var response = await agent.RunAsync("Please book me a flight for Friday to Paris.", thread); | ||
| while (response is not null && response.ApprovalRequests.Count > 0) | ||
| { | ||
| List<ChatMessage> messages = new List<ChatMessage>(); | ||
| foreach (var approvalRequest in response.ApprovalRequests) | ||
westey-m marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| // Show the approval request to the user in the appropriate format. | ||
| // The user can then approve or reject the request. | ||
| // The optional FunctionCallContent can be used to show the user what function the agent wants to call with the parameter set: | ||
| // approvalRequest.FunctionCall?.Arguments. | ||
| // The Text property of the ApprovalRequestContent can also be used to show the user any additional textual context about the request. | ||
|
|
||
| // If the user approves: | ||
| var approvalMessage = approvalRequest.Approve(); | ||
| messages.Add(approvalMessage); | ||
| } | ||
|
|
||
| // Get the next response from the agent. | ||
| response = await agent.RunAsync(messages, thread); | ||
| } | ||
|
|
||
| class AgentThread | ||
| { | ||
| ... | ||
|
|
||
| // The thread state may need to store the approval requests and responses. | ||
| // TODO: Consider whether we should have a more generic ActiveUserRequests list, which could include other types of user requests in the future. | ||
| // This may mean a base class for all user requests. | ||
| public List<ApprovalRequestContent> ActiveApprovalRequests { get; set; } | ||
westey-m marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ... | ||
| } | ||
| ``` | ||
|
|
||
| - Also see [dotnet issue 6492](https://github.com/dotnet/extensions/issues/6492), which discusses the need for a similar pattern in the context of MCP approvals. | ||
| - Also see [the openai RunToolApprovalItem](https://openai.github.io/openai-agents-js/openai/agents/classes/runtoolapprovalitem/). | ||
| - Also see [the openai human-in-the-loop guide](https://openai.github.io/openai-agents-js/guides/human-in-the-loop/#approval-requests). | ||
| - Also see [MCP Approval Requests from OpenAI](https://platform.openai.com/docs/guides/tools-remote-mcp#approvals). | ||
westey-m marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ### ChatClientAgent Approval Process Flow | ||
|
|
||
| 1. User asks agent to perform a task and request is added to the thread. | ||
| 1. Agent calls model with registered functions. | ||
| 1. Model responds with function calls to make. | ||
| 1. ConfirmingFunctionInvokingChatClient decorator (new feature / enhancement to FunctionInvokingChatClient) identifies any function calls that require user approval and returns an ApprovalRequestContent. | ||
westey-m marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ChatClient implementations should also convert any approval requests from the service into ApprovalRequestContent. | ||
| 1. Agent updates the thread with the FunctionCallContent (or this may have already been done by a service threaded agent) if the approval request is for a function call. | ||
| 1. Agent stores the ApprovalRequestContent in its AgentThread under ActiveApprovalRequests, so that it knows that there is an outstanding user request. | ||
| 1. Agent returns the ApprovalRequestContent to the caller which shows it to the user in the appropriate format. | ||
| 1. User (via caller) invokes the agent again with ApprovalResponseContent. | ||
| 1. Agent removes the ApprovalRequestContent from its AgentThread ActiveApprovalRequests. | ||
| 1. Agent invokes IChatClient with ApprovalResponseContent and the ConfirmingFunctionInvokingChatClient decorator identifies the response as an approval for the function call. | ||
| If it isn't an approval for a manual function call, it can be passed through to the underlying ChatClient to be converted to the appropriate Approval content type for the service. | ||
| 1. ConfirmingFunctionInvokingChatClient decorator invokes the function call and invokes the underlying IChatClient with a FunctionResultContent. | ||
westey-m marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 1. Model responds with the result. | ||
| 1. Agent responds to caller with result message and thread is updated with the result message. | ||
|
|
||
| At construction time the set of functions that require user approval will need to be registered with the `ConfirmingFunctionInvokingChatClient` decorator | ||
| so that it can identify which function calls should be returned as an `ApprovalRequestContent`. | ||
|
|
||
| ### CustomAgent Approval Process Flow | ||
|
|
||
| 1. User asks agent to perform a task and request is added to the thread. | ||
| 1. Agent executes various steps. | ||
| 1. Agent encounters a step for which it requires user approval to continue. | ||
| 1. Agent responds with an ApprovalRequestContent. | ||
| 1. Agent updates its own state with the progress that it has made up to that point and adds the ApprovalRequestContent to its AgentThread ActiveApprovalRequests. | ||
westey-m marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 1. User (via caller) invokes the agent again with ApprovalResponseContent. | ||
| 1. Agent loads its progress from state and continues processing. | ||
| 1. Agent removes its ApprovalRequestContent from its AgentThread ActiveApprovalRequests. | ||
| 1. Agent responds to caller with result message and thread is updated with the result message. | ||
|
|
||
| ## Decision Outcome | ||
|
|
||
| Chosen approach: Introduce new ApprovalRequestContent and ApprovalResponseContent types. | ||
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.