Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 12, 2025

Implementation: Add Reason property to FunctionApprovalResponseContent

Changes Completed

  • Add nullable Reason property to FunctionApprovalResponseContent class

    • Added "optional" to property documentation as suggested
    • Removed verbose <remarks> section for cleaner documentation
  • Update FunctionInvokingChatClient.GenerateRejectedFunctionResults method

    • Changed rejection message format to always include "Tool call invocation rejected."
    • Appends custom reason when provided: "Tool call invocation rejected. {Reason}"
    • Uses IsNullOrWhiteSpace check as suggested
  • Update all tests for new message format

    • Updated default rejection message expectations
    • Updated custom reason expectations to include prefix
    • All FunctionInvokingChatClientApprovalsTests pass across .NET 8, 9, and 10
  • Add serialization tests for Reason property

    • Updated FunctionApprovalResponseContentTests.Serialization_Roundtrips to test with both null and custom reason
    • Ensures Reason property correctly serializes and deserializes through JSON
  • Add edge case tests for Reason property

    • Added RejectedApprovalResponsesWithEmptyOrWhitespaceReasonUsesDefaultMessageAsync test
    • Tests null, empty string, and whitespace-only values
    • Validates that IsNullOrWhiteSpace logic treats all as default message
  • Add optional reason parameter to FunctionApprovalRequestContent.CreateResponse

    • Added string? reason = null parameter to the method
    • Sets the Reason property on the created FunctionApprovalResponseContent
    • Added comprehensive tests covering both approved/rejected with/without reasons
    • Provides a convenient way to set the reason during response creation
  • Add API compatibility suppressions

    • Added suppressions in CompatibilitySuppressions.xml for the method signature change
    • Suppressed CP0002 errors for net462, net8.0, net9.0, and netstandard2.0 frameworks
    • Fixes CI build failures related to breaking API changes
  • Build and test validation

    • All builds passed successfully (Debug configuration)
    • All 22 FunctionInvokingChatClientApprovalsTests pass
    • All 6 FunctionApprovalResponseContentTests pass
    • All 11 FunctionApprovalRequestContentTests pass
    • API compatibility checks pass with suppressions
  • Code review feedback addressed

    • Simplified property documentation
    • Improved rejection message format to avoid ambiguity
    • Added comprehensive test coverage per reviewer suggestions
    • Added convenience method parameter for setting reason
    • Fixed CI build failures with proper API compatibility suppressions

Summary

All PR review feedback has been addressed. The rejection message now clearly indicates it's a rejection while allowing custom context to be appended. The implementation includes proper serialization support, comprehensive edge case testing, and a convenient way to set the reason via the CreateResponse method. API compatibility suppressions have been added to handle the method signature change.

Original prompt

This section details on the original issue you should resolve

<issue_title>Provide ability to specify reject message for FunctionInvokingChatClient</issue_title>
<issue_description>The reject message for FunctionApprovalResponseContent with Approved = false is currently a hard-coded string:

"Error: Tool call invocation was rejected by user."

However, this situation is not necessarily an error. It should be possible to provide a custom rejection message, allowing callers to specify the actual reason for rejection.

Relevant code:

rejections.ConvertAll(static m => (AIContent)new FunctionResultContent(m.Response.FunctionCall.CallId, "Error: Tool call invocation was rejected by user.")) :

</issue_description>

<agent_instructions>Please add a nullable string Reason get/set property to FunctionApprovalResponseContent. If such a reason is set, FunctionInvokingChatClient should factor that into the FunctionResultContent message it creates for rejected tool calls. The property can currently be ignored for Approval=true, though it's conceivable the message might be used for something in the future, and if nothing else, it'll show up in tracing/logging/etc. when the content is serialized.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Microsoft Reviewers: Open in CodeFlow

Copilot AI and others added 3 commits December 12, 2025 16:07
… rejection messages

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Copilot AI changed the title [WIP] Add ability to specify reject message for FunctionInvokingChatClient Add Reason property to FunctionApprovalResponseContent for custom rejection messages Dec 12, 2025
Copilot AI requested a review from stephentoub December 12, 2025 16:15
Copy link
Member

@stephentoub stephentoub left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

…ge format

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
@stephentoub
Copy link
Member

@NoofSaeidh, does this address your scenarios / concerns from #7139?

@stephentoub stephentoub marked this pull request as ready for review December 12, 2025 16:37
@stephentoub stephentoub requested a review from a team as a code owner December 12, 2025 16:37
Copilot AI review requested due to automatic review settings December 12, 2025 16:37
@stephentoub
Copy link
Member

cc: @rogerbarreto, @westey-m, @jozkee

@ericstj ericstj added the area-ai Microsoft.Extensions.AI libraries label Dec 12, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a nullable Reason property to FunctionApprovalResponseContent to enable custom rejection messages for function call approvals. Previously, rejected function calls used a hard-coded error message. Now, users can provide specific reasons for rejections that will be included in the function result content sent to the downstream client.

Key Changes:

  • Added Reason property (nullable string, get/set) to FunctionApprovalResponseContent with documentation indicating it's optional and can be used for both approval and rejection
  • Updated rejection message format in FunctionInvokingChatClient.GenerateRejectedFunctionResults from "Error: Tool call invocation was rejected by user." to "Tool call invocation rejected." with optional custom reason appended when provided
  • Updated all existing test expectations to match the new default rejection message format and added comprehensive tests for custom rejection reasons

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/FunctionApprovalResponseContent.cs Adds nullable Reason property to allow custom messages for approval/rejection responses
src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs Updates GenerateRejectedFunctionResults to use new rejection message format and append custom reason when provided using IsNullOrWhiteSpace check
test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientApprovalsTests.cs Updates existing test expectations for new default message format and adds two comprehensive tests (RejectedApprovalResponsesWithCustomReasonAsync and MixedApprovalResponsesWithCustomAndDefaultReasonsAsync) covering custom rejection reasons

@stephentoub
Copy link
Member

@copilot, please address the feedback

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
@NoofSaeidh
Copy link

@NoofSaeidh, does this address your scenarios / concerns from #7139?

Yes, thank you!

@stephentoub stephentoub enabled auto-merge (squash) December 15, 2025 04:35
@NoofSaeidh
Copy link

NoofSaeidh commented Dec 15, 2025

@stephentoub actually looks like it is worth to add Reason into the CreateResponse method in FunctionApprovalRequestContent

public FunctionApprovalResponseContent CreateResponse(bool approved) => new(Id, approved, FunctionCall);

@stephentoub
Copy link
Member

@stephentoub actually looks like it is worth to add Reason into the CreateResponse method in FunctionApprovalRequestContent

public FunctionApprovalResponseContent CreateResponse(bool approved) => new(Id, approved, FunctionCall);

Why not just set the property on the resulting object?

@NoofSaeidh
Copy link

NoofSaeidh commented Dec 16, 2025

@stephentoub it is of course would work. It just makes it easier to use, because setting property for resulting object doesn't support an object initializer patter and it requires to create variable first, which makes it ugly in certain situations.

For instance:

// with argument
messages.Add(new ChatMessage(ChatRole.User, [response.Messages.Last().Contents.OfType<FunctionApprovalRequestContent>().First().CreateResponse(false, reason: "reason")]));

// without argument
var approval = response.Messages.Last().Contents.OfType<FunctionApprovalRequestContent>().First().CreateResponse(false);
approval.Reason = "reason";
messages.Add(new ChatMessage(ChatRole.User, [approval]));

@stephentoub
Copy link
Member

@copilot, please add a string? reason = null optional parameter to FunctionApprovalRequestContent.CreateResponse (

public FunctionApprovalResponseContent CreateResponse(bool approved) => new(Id, approved, FunctionCall);
)

…eResponse

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
auto-merge was automatically disabled December 17, 2025 02:47

Head branch was pushed to by a user without write access

Copy link
Contributor Author

Copilot AI commented Dec 17, 2025

@copilot, please add a string? reason = null optional parameter to FunctionApprovalRequestContent.CreateResponse (https://github.com/dotnet/extensions/blob/72b108788bd3e098f277604a23ac826b9b62ce9d/s...

Done in commit fb45994. Added string? reason = null parameter to CreateResponse method which sets the Reason property on the returned FunctionApprovalResponseContent.

@jozkee
Copy link
Member

jozkee commented Dec 17, 2025

@copilot this is a breaking change, you need to update src/Libraries/Microsoft.Extensions.AI.Abstractions/CompatibilitySuppressions.xml to fix CI errors

/mnt/vss/_work/1/s/.dotnet/sdk/10.0.100/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.ApiCompat.ValidatePackage.targets(39,5): error : API compatibility errors between 'lib/net462/Microsoft.Extensions.AI.Abstractions.dll' (/mnt/vss/_work/1/s/.packages/microsoft.extensions.ai.abstractions/9.10.0/microsoft.extensions.ai.abstractions.9.10.0.nupkg) and 'lib/net462/Microsoft.Extensions.AI.Abstractions.dll' (/mnt/vss/_work/1/s/artifacts/packages/Release/Shipping/Microsoft.Extensions.AI.Abstractions.10.2.0-ci.nupkg): [/mnt/vss/_work/1/s/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.csproj]
/mnt/vss/_work/1/s/.dotnet/sdk/10.0.100/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.ApiCompat.ValidatePackage.targets(39,5): error CP0002: Member 'Microsoft.Extensions.AI.FunctionApprovalResponseContent Microsoft.Extensions.AI.FunctionApprovalRequestContent.CreateResponse(bool)' exists on [Baseline] lib/net462/Microsoft.Extensions.AI.Abstractions.dll but not on lib/net462/Microsoft.Extensions.AI.Abstractions.dll [/mnt/vss/_work/1/s/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.csproj]
/mnt/vss/_work/1/s/.dotnet/sdk/10.0.100/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.ApiCompat.ValidatePackage.targets(39,5): error : API compatibility errors between 'lib/net8.0/Microsoft.Extensions.AI.Abstractions.dll' (/mnt/vss/_work/1/s/.packages/microsoft.extensions.ai.abstractions/9.10.0/microsoft.extensions.ai.abstractions.9.10.0.nupkg) and 'lib/net8.0/Microsoft.Extensions.AI.Abstractions.dll' (/mnt/vss/_work/1/s/artifacts/packages/Release/Shipping/Microsoft.Extensions.AI.Abstractions.10.2.0-ci.nupkg): [/mnt/vss/_work/1/s/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.csproj]
/mnt/vss/_work/1/s/.dotnet/sdk/10.0.100/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.ApiCompat.ValidatePackage.targets(39,5): error CP0002: Member 'Microsoft.Extensions.AI.FunctionApprovalResponseContent Microsoft.Extensions.AI.FunctionApprovalRequestContent.CreateResponse(bool)' exists on [Baseline] lib/net8.0/Microsoft.Extensions.AI.Abstractions.dll but not on lib/net8.0/Microsoft.Extensions.AI.Abstractions.dll [/mnt/vss/_work/1/s/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.csproj]
/mnt/vss/_work/1/s/.dotnet/sdk/10.0.100/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.ApiCompat.ValidatePackage.targets(39,5): error : API compatibility errors between 'lib/net9.0/Microsoft.Extensions.AI.Abstractions.dll' (/mnt/vss/_work/1/s/.packages/microsoft.extensions.ai.abstractions/9.10.0/microsoft.extensions.ai.abstractions.9.10.0.nupkg) and 'lib/net9.0/Microsoft.Extensions.AI.Abstractions.dll' (/mnt/vss/_work/1/s/artifacts/packages/Release/Shipping/Microsoft.Extensions.AI.Abstractions.10.2.0-ci.nupkg): [/mnt/vss/_work/1/s/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.csproj]
/mnt/vss/_work/1/s/.dotnet/sdk/10.0.100/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.ApiCompat.ValidatePackage.targets(39,5): error CP0002: Member 'Microsoft.Extensions.AI.FunctionApprovalResponseContent Microsoft.Extensions.AI.FunctionApprovalRequestContent.CreateResponse(bool)' exists on [Baseline] lib/net9.0/Microsoft.Extensions.AI.Abstractions.dll but not on lib/net9.0/Microsoft.Extensions.AI.Abstractions.dll [/mnt/vss/_work/1/s/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.csproj]
/mnt/vss/_work/1/s/.dotnet/sdk/10.0.100/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.ApiCompat.ValidatePackage.targets(39,5): error : API compatibility errors between 'lib/netstandard2.0/Microsoft.Extensions.AI.Abstractions.dll' (/mnt/vss/_work/1/s/.packages/microsoft.extensions.ai.abstractions/9.10.0/microsoft.extensions.ai.abstractions.9.10.0.nupkg) and 'lib/netstandard2.0/Microsoft.Extensions.AI.Abstractions.dll' (/mnt/vss/_work/1/s/artifacts/packages/Release/Shipping/Microsoft.Extensions.AI.Abstractions.10.2.0-ci.nupkg): [/mnt/vss/_work/1/s/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.csproj]
/mnt/vss/_work/1/s/.dotnet/sdk/10.0.100/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.ApiCompat.ValidatePackage.targets(39,5): error CP0002: Member 'Microsoft.Extensions.AI.FunctionApprovalResponseContent Microsoft.Extensions.AI.FunctionApprovalRequestContent.CreateResponse(bool)' exists on [Baseline] lib/netstandard2.0/Microsoft.Extensions.AI.Abstractions.dll but not on lib/netstandard2.0/Microsoft.Extensions.AI.Abstractions.dll [/mnt/vss/_work/1/s/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.csproj]
/mnt/vss/_work/1/s/.dotnet/sdk/10.0.100/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.ApiCompat.ValidatePackage.targets(39,5): error : API breaking changes found. If those are intentional, the APICompat suppression file can be updated by rebuilding with '/p:ApiCompatGenerateSuppressionFile=true' [/mnt/vss/_work/1/s/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.csproj]

…e change

Co-authored-by: jozkee <16040868+jozkee@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Dec 17, 2025

@copilot this is a breaking change, you need to update src/Libraries/Microsoft.Extensions.AI.Abstractions/CompatibilitySuppressions.xml to fix CI errors

/mnt/vss/_work/1/s/.dotnet/sdk/10.0.100/S...

Done in commit 92bfbdb. Added CP0002 suppressions for the CreateResponse(bool) method signature change across all target frameworks (net462, net8.0, net9.0, netstandard2.0) in CompatibilitySuppressions.xml to fix the CI errors.

Copilot AI requested a review from jozkee December 17, 2025 17:26
@jozkee jozkee added the breaking-change Issue or PR that represents a breaking API or functional change over a previous release. label Dec 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-ai Microsoft.Extensions.AI libraries breaking-change Issue or PR that represents a breaking API or functional change over a previous release.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Provide ability to specify reject message for FunctionInvokingChatClient

6 participants