diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalResponseContent.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalResponseContent.cs index ab073f36b2d..7a12aa1ca74 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalResponseContent.cs +++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/ToolApprovalResponseContent.cs @@ -42,5 +42,9 @@ public ToolApprovalResponseContent(string requestId, bool approved, ToolCallCont /// /// Gets or sets the optional reason for the approval or rejection. /// + /// + /// When a function call is rejected by function invocation middleware, this reason is appended to the rejection message. + /// Callers can use the reason to indicate whether the rejection is final or a new call may be requested for approval. + /// public string? Reason { get; set; } } diff --git a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs index 0eb4bd29538..643c3e2178f 100644 --- a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs @@ -1552,7 +1552,7 @@ FunctionResultContent CreateFunctionResultContent(FunctionInvocationResult resul { LogFunctionRejected(m.ResponseFunctionCallContent.Name, m.Response.Reason); - string result = "Tool call invocation was rejected by the approver and must not be retried."; + string result = "Tool call invocation rejected."; if (!string.IsNullOrWhiteSpace(m.Response.Reason)) { result = $"{result} {m.Response.Reason}"; diff --git a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs index b6efd117377..535787748d8 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs @@ -435,8 +435,8 @@ public async Task RejectedApprovalResponsesAreFailedAsync() new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("callId1", "Func1"), new FunctionCallContent("callId2", "Func2", arguments: new Dictionary { { "i", 42 } })]), new ChatMessage(ChatRole.Tool, [ - new FunctionResultContent("callId1", result: "Tool call invocation was rejected by the approver and must not be retried."), - new FunctionResultContent("callId2", result: "Tool call invocation was rejected by the approver and must not be retried.") + new FunctionResultContent("callId1", result: "Tool call invocation rejected."), + new FunctionResultContent("callId2", result: "Tool call invocation rejected.") ]), ]; @@ -450,8 +450,8 @@ public async Task RejectedApprovalResponsesAreFailedAsync() new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("callId1", "Func1"), new FunctionCallContent("callId2", "Func2", arguments: new Dictionary { { "i", 42 } })]), new ChatMessage(ChatRole.Tool, [ - new FunctionResultContent("callId1", result: "Tool call invocation was rejected by the approver and must not be retried."), - new FunctionResultContent("callId2", result: "Tool call invocation was rejected by the approver and must not be retried.") + new FunctionResultContent("callId1", result: "Tool call invocation rejected."), + new FunctionResultContent("callId2", result: "Tool call invocation rejected.") ]), new ChatMessage(ChatRole.Assistant, "world"), ]; @@ -492,7 +492,7 @@ public async Task MixedApprovedAndRejectedApprovalResponsesAreExecutedAndFailedA [ new ChatMessage(ChatRole.User, "hello"), new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("callId1", "Func1"), new FunctionCallContent("callId2", "Func2", arguments: new Dictionary { { "i", 42 } })]), - new ChatMessage(ChatRole.Tool, [new FunctionResultContent("callId1", result: "Tool call invocation was rejected by the approver and must not be retried.")]), + new ChatMessage(ChatRole.Tool, [new FunctionResultContent("callId1", result: "Tool call invocation rejected.")]), new ChatMessage(ChatRole.Tool, [new FunctionResultContent("callId2", result: "Result 2: 42")]), ]; @@ -504,7 +504,7 @@ public async Task MixedApprovedAndRejectedApprovalResponsesAreExecutedAndFailedA List nonStreamingOutput = [ new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("callId1", "Func1"), new FunctionCallContent("callId2", "Func2", arguments: new Dictionary { { "i", 42 } })]), - new ChatMessage(ChatRole.Tool, [new FunctionResultContent("callId1", result: "Tool call invocation was rejected by the approver and must not be retried.")]), + new ChatMessage(ChatRole.Tool, [new FunctionResultContent("callId1", result: "Tool call invocation rejected.")]), new ChatMessage(ChatRole.Tool, [new FunctionResultContent("callId2", result: "Result 2: 42")]), new ChatMessage(ChatRole.Assistant, "world"), ]; @@ -514,7 +514,7 @@ public async Task MixedApprovedAndRejectedApprovalResponsesAreExecutedAndFailedA new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("callId1", "Func1"), new FunctionCallContent("callId2", "Func2", arguments: new Dictionary { { "i", 42 } })]), new ChatMessage(ChatRole.Tool, [ - new FunctionResultContent("callId1", result: "Tool call invocation was rejected by the approver and must not be retried."), + new FunctionResultContent("callId1", result: "Tool call invocation rejected."), new FunctionResultContent("callId2", result: "Result 2: 42") ]), new ChatMessage(ChatRole.Assistant, "world"), @@ -564,8 +564,8 @@ public async Task RejectedApprovalResponsesWithCustomReasonAsync() new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("callId1", "Func1"), new FunctionCallContent("callId2", "Func2", arguments: new Dictionary { { "i", 42 } })]), new ChatMessage(ChatRole.Tool, [ - new FunctionResultContent("callId1", result: "Tool call invocation was rejected by the approver and must not be retried. User denied permission for this operation"), - new FunctionResultContent("callId2", result: "Tool call invocation was rejected by the approver and must not be retried. Function Func2 is not allowed at this time") + new FunctionResultContent("callId1", result: "Tool call invocation rejected. User denied permission for this operation"), + new FunctionResultContent("callId2", result: "Tool call invocation rejected. Function Func2 is not allowed at this time") ]), ]; @@ -579,8 +579,8 @@ public async Task RejectedApprovalResponsesWithCustomReasonAsync() new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("callId1", "Func1"), new FunctionCallContent("callId2", "Func2", arguments: new Dictionary { { "i", 42 } })]), new ChatMessage(ChatRole.Tool, [ - new FunctionResultContent("callId1", result: "Tool call invocation was rejected by the approver and must not be retried. User denied permission for this operation"), - new FunctionResultContent("callId2", result: "Tool call invocation was rejected by the approver and must not be retried. Function Func2 is not allowed at this time") + new FunctionResultContent("callId1", result: "Tool call invocation rejected. User denied permission for this operation"), + new FunctionResultContent("callId2", result: "Tool call invocation rejected. Function Func2 is not allowed at this time") ]), new ChatMessage(ChatRole.Assistant, "world"), ]; @@ -631,8 +631,8 @@ public async Task MixedApprovalResponsesWithCustomAndDefaultReasonsAsync() ]), new ChatMessage(ChatRole.Tool, [ - new FunctionResultContent("callId1", result: "Tool call invocation was rejected by the approver and must not be retried. Custom rejection for Func1"), - new FunctionResultContent("callId2", result: "Tool call invocation was rejected by the approver and must not be retried.") + new FunctionResultContent("callId1", result: "Tool call invocation rejected. Custom rejection for Func1"), + new FunctionResultContent("callId2", result: "Tool call invocation rejected.") ]), new ChatMessage(ChatRole.Tool, [new FunctionResultContent("callId3", result: "Result 3: test")]), ]; @@ -652,8 +652,8 @@ public async Task MixedApprovalResponsesWithCustomAndDefaultReasonsAsync() ]), new ChatMessage(ChatRole.Tool, [ - new FunctionResultContent("callId1", result: "Tool call invocation was rejected by the approver and must not be retried. Custom rejection for Func1"), - new FunctionResultContent("callId2", result: "Tool call invocation was rejected by the approver and must not be retried.") + new FunctionResultContent("callId1", result: "Tool call invocation rejected. Custom rejection for Func1"), + new FunctionResultContent("callId2", result: "Tool call invocation rejected.") ]), new ChatMessage(ChatRole.Tool, [new FunctionResultContent("callId3", result: "Result 3: test")]), new ChatMessage(ChatRole.Assistant, "world"), @@ -669,8 +669,8 @@ public async Task MixedApprovalResponsesWithCustomAndDefaultReasonsAsync() ]), new ChatMessage(ChatRole.Tool, [ - new FunctionResultContent("callId1", result: "Tool call invocation was rejected by the approver and must not be retried. Custom rejection for Func1"), - new FunctionResultContent("callId2", result: "Tool call invocation was rejected by the approver and must not be retried."), + new FunctionResultContent("callId1", result: "Tool call invocation rejected. Custom rejection for Func1"), + new FunctionResultContent("callId2", result: "Tool call invocation rejected."), new FunctionResultContent("callId3", result: "Result 3: test") ]), new ChatMessage(ChatRole.Assistant, "world"), @@ -717,7 +717,7 @@ public async Task RejectedApprovalResponsesWithEmptyOrWhitespaceReasonUsesDefaul new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("callId1", "Func1")]), new ChatMessage(ChatRole.Tool, [ - new FunctionResultContent("callId1", result: "Tool call invocation was rejected by the approver and must not be retried.") + new FunctionResultContent("callId1", result: "Tool call invocation rejected.") ]), ]; @@ -731,7 +731,7 @@ public async Task RejectedApprovalResponsesWithEmptyOrWhitespaceReasonUsesDefaul new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("callId1", "Func1")]), new ChatMessage(ChatRole.Tool, [ - new FunctionResultContent("callId1", result: "Tool call invocation was rejected by the approver and must not be retried.") + new FunctionResultContent("callId1", result: "Tool call invocation rejected.") ]), new ChatMessage(ChatRole.Assistant, "world"), ]; @@ -1944,7 +1944,7 @@ public async Task RejectedApprovalResponses_MixedWithMcpApprovalAsync(bool useAd [ approveFuncCall ? new FunctionResultContent("callId1", result: "Result 1") : - new FunctionResultContent("callId1", result: "Tool call invocation was rejected by the approver and must not be retried.") + new FunctionResultContent("callId1", result: "Tool call invocation rejected.") ]), new ChatMessage(ChatRole.User, [ @@ -1971,7 +1971,7 @@ [new McpServerToolResultContent("callId2") { Outputs = [new TextContent("Result [ approveFuncCall ? new FunctionResultContent("callId1", result: "Result 1") : - new FunctionResultContent("callId1", result: "Tool call invocation was rejected by the approver and must not be retried.") + new FunctionResultContent("callId1", result: "Tool call invocation rejected.") ]), new ChatMessage(ChatRole.Assistant, [ new TextContent("world"),