Skip to content

Commit bc12dd0

Browse files
Add function approvals support (#6745)
- Adding an ApprovalRequiredAIFunction that can be used to decorate an AIFunction. - Adding UserInputRequestContent and UserInputResponseContent base types to represent all user input. - Adding FunctionApprovalRequestContent and FunctionApprovalResponseContent for function approvals. - Updated FunctionInvokingChatClient with approval generation handling. All new types are currently [Experimental]. Co-authored-by: Stephen Toub <stoub@microsoft.com>
1 parent 06ad449 commit bc12dd0

19 files changed

+2017
-142
lines changed

src/Libraries/Microsoft.Extensions.AI.Abstractions/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## NOT YET RELEASED
44

55
- Added non-invocable `AIFunctionDeclaration` (base class for `AIFunction`), `AIFunctionFactory.CreateDeclaration`, and `AIFunction.AsDeclarationOnly`.
6+
- Added `[Experimental]` support for user approval of function invocations via `ApprovalRequiredAIFunction`, `FunctionApprovalRequestContent`, and friends.
67

78
## 9.8.0
89

src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/AIContent.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ namespace Microsoft.Extensions.AI;
1818
[JsonDerivedType(typeof(TextReasoningContent), typeDiscriminator: "reasoning")]
1919
[JsonDerivedType(typeof(UriContent), typeDiscriminator: "uri")]
2020
[JsonDerivedType(typeof(UsageContent), typeDiscriminator: "usage")]
21+
22+
// These should be added in once they're no longer [Experimental]. If they're included while still
23+
// experimental, any JsonSerializerContext that includes AIContent will incur errors about using
24+
// experimental types in its source generated files.
25+
// [JsonDerivedType(typeof(FunctionApprovalRequestContent), typeDiscriminator: "functionApprovalRequest")]
26+
// [JsonDerivedType(typeof(FunctionApprovalResponseContent), typeDiscriminator: "functionApprovalResponse")]
27+
2128
public class AIContent
2229
{
2330
/// <summary>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Diagnostics.CodeAnalysis;
6+
using Microsoft.Shared.Diagnostics;
7+
8+
namespace Microsoft.Extensions.AI;
9+
10+
/// <summary>
11+
/// Represents a request for user approval of a function call.
12+
/// </summary>
13+
[Experimental("MEAI001")]
14+
public sealed class FunctionApprovalRequestContent : UserInputRequestContent
15+
{
16+
/// <summary>
17+
/// Initializes a new instance of the <see cref="FunctionApprovalRequestContent"/> class.
18+
/// </summary>
19+
/// <param name="id">The ID that uniquely identifies the function approval request/response pair.</param>
20+
/// <param name="functionCall">The function call that requires user approval.</param>
21+
/// <exception cref="ArgumentNullException"><paramref name="id"/> is <see langword="null"/>.</exception>
22+
/// <exception cref="ArgumentException"><paramref name="id"/> is empty or composed entirely of whitespace.</exception>
23+
/// <exception cref="ArgumentNullException"><paramref name="functionCall"/> is <see langword="null"/>.</exception>
24+
public FunctionApprovalRequestContent(string id, FunctionCallContent functionCall)
25+
: base(id)
26+
{
27+
FunctionCall = Throw.IfNull(functionCall);
28+
}
29+
30+
/// <summary>
31+
/// Gets the function call that pre-invoke approval is required for.
32+
/// </summary>
33+
public FunctionCallContent FunctionCall { get; }
34+
35+
/// <summary>
36+
/// Creates a <see cref="FunctionApprovalResponseContent"/> to indicate whether the function call is approved or rejected based on the value of <paramref name="approved"/>.
37+
/// </summary>
38+
/// <param name="approved"><see langword="true"/> if the function call is approved; otherwise, <see langword="false"/>.</param>
39+
/// <returns>The <see cref="FunctionApprovalResponseContent"/> representing the approval response.</returns>
40+
public FunctionApprovalResponseContent CreateResponse(bool approved) => new(Id, approved, FunctionCall);
41+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Diagnostics.CodeAnalysis;
6+
using Microsoft.Shared.Diagnostics;
7+
8+
namespace Microsoft.Extensions.AI;
9+
10+
/// <summary>
11+
/// Represents a response to a function approval request.
12+
/// </summary>
13+
[Experimental("MEAI001")]
14+
public sealed class FunctionApprovalResponseContent : UserInputResponseContent
15+
{
16+
/// <summary>
17+
/// Initializes a new instance of the <see cref="FunctionApprovalResponseContent"/> class.
18+
/// </summary>
19+
/// <param name="id">The ID that uniquely identifies the function approval request/response pair.</param>
20+
/// <param name="approved"><see langword="true"/> if the function call is approved; otherwise, <see langword="false"/>.</param>
21+
/// <param name="functionCall">The function call that requires user approval.</param>
22+
/// <exception cref="ArgumentNullException"><paramref name="id"/> is <see langword="null"/>.</exception>
23+
/// <exception cref="ArgumentException"><paramref name="id"/> is empty or composed entirely of whitespace.</exception>
24+
/// <exception cref="ArgumentNullException"><paramref name="functionCall"/> is <see langword="null"/>.</exception>
25+
public FunctionApprovalResponseContent(string id, bool approved, FunctionCallContent functionCall)
26+
: base(id)
27+
{
28+
Approved = approved;
29+
FunctionCall = Throw.IfNull(functionCall);
30+
}
31+
32+
/// <summary>
33+
/// Gets a value indicating whether the user approved the request.
34+
/// </summary>
35+
public bool Approved { get; }
36+
37+
/// <summary>
38+
/// Gets the function call for which approval was requested.
39+
/// </summary>
40+
public FunctionCallContent FunctionCall { get; }
41+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Diagnostics.CodeAnalysis;
6+
using Microsoft.Shared.Diagnostics;
7+
8+
namespace Microsoft.Extensions.AI;
9+
10+
/// <summary>
11+
/// Represents a request for user input.
12+
/// </summary>
13+
[Experimental("MEAI001")]
14+
public class UserInputRequestContent : AIContent
15+
{
16+
/// <summary>
17+
/// Initializes a new instance of the <see cref="UserInputRequestContent"/> class.
18+
/// </summary>
19+
/// <param name="id">The ID that uniquely identifies the user input request/response pair.</param>
20+
/// <exception cref="ArgumentNullException"><paramref name="id"/> is <see langword="null"/>.</exception>
21+
/// <exception cref="ArgumentException"><paramref name="id"/> is empty or composed entirely of whitespace.</exception>
22+
protected UserInputRequestContent(string id)
23+
{
24+
Id = Throw.IfNullOrWhitespace(id);
25+
}
26+
27+
/// <summary>
28+
/// Gets the ID that uniquely identifies the user input request/response pair.
29+
/// </summary>
30+
public string Id { get; }
31+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Diagnostics.CodeAnalysis;
6+
using Microsoft.Shared.Diagnostics;
7+
8+
namespace Microsoft.Extensions.AI;
9+
10+
/// <summary>
11+
/// Represents the response to a request for user input.
12+
/// </summary>
13+
[Experimental("MEAI001")]
14+
public class UserInputResponseContent : AIContent
15+
{
16+
/// <summary>
17+
/// Initializes a new instance of the <see cref="UserInputResponseContent"/> class.
18+
/// </summary>
19+
/// <param name="id">The ID that uniquely identifies the user input request/response pair.</param>
20+
/// <exception cref="ArgumentNullException"><paramref name="id"/> is <see langword="null"/>.</exception>
21+
/// <exception cref="ArgumentException"><paramref name="id"/> is empty or composed entirely of whitespace.</exception>
22+
protected UserInputResponseContent(string id)
23+
{
24+
Id = Throw.IfNullOrWhitespace(id);
25+
}
26+
27+
/// <summary>
28+
/// Gets the ID that uniquely identifies the user input request/response pair.
29+
/// </summary>
30+
public string Id { get; }
31+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Diagnostics.CodeAnalysis;
6+
7+
namespace Microsoft.Extensions.AI;
8+
9+
/// <summary>
10+
/// Represents an <see cref="AIFunction"/> that can be described to an AI service and invoked, but for which
11+
/// the invoker should obtain user approval before the function is actually invoked.
12+
/// </summary>
13+
/// <remarks>
14+
/// This class simply augments an <see cref="AIFunction"/> with an indication that approval is required before invocation.
15+
/// It does not enforce the requirement for user approval; it is the responsibility of the invoker to obtain that approval before invoking the function.
16+
/// </remarks>
17+
[Experimental("MEAI001")]
18+
public sealed class ApprovalRequiredAIFunction : DelegatingAIFunction
19+
{
20+
/// <summary>
21+
/// Initializes a new instance of the <see cref="ApprovalRequiredAIFunction"/> class.
22+
/// </summary>
23+
/// <param name="innerFunction">The <see cref="AIFunction"/> represented by this instance.</param>
24+
/// <exception cref="ArgumentNullException"><paramref name="innerFunction"/> is <see langword="null"/>.</exception>
25+
public ApprovalRequiredAIFunction(AIFunction innerFunction)
26+
: base(innerFunction)
27+
{
28+
}
29+
}

src/Libraries/Microsoft.Extensions.AI/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## NOT YET RELEASED
44

55
- Added `FunctionInvokingChatClient` support for non-invocable tools and `TerminateOnUnknownCalls` property.
6+
- Added support to `FunctionInvokingChatClient` for user approval of function invocations.
67
- Updated the Open Telemetry instrumentation to conform to the latest 1.37.0 draft specification of the Semantic Conventions for Generative AI systems.
78
- Fixed `GetResponseAsync<T>` to only look at the contents of the last message in the response.
89

0 commit comments

Comments
 (0)