diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/FunctionCallContent.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/FunctionCallContent.cs
index 836d5a4110b..7c506a7845b 100644
--- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/FunctionCallContent.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/FunctionCallContent.cs
@@ -14,7 +14,7 @@ namespace Microsoft.Extensions.AI;
/// Represents a function call request.
///
[DebuggerDisplay("{DebuggerDisplay,nq}")]
-public sealed class FunctionCallContent : AIContent
+public class FunctionCallContent : AIContent
{
///
/// Initializes a new instance of the class.
diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/FunctionResultContent.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/FunctionResultContent.cs
index 46401347b40..d5eb4884709 100644
--- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/FunctionResultContent.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Contents/FunctionResultContent.cs
@@ -13,7 +13,7 @@ namespace Microsoft.Extensions.AI;
/// Represents the result of a function call.
///
[DebuggerDisplay("{DebuggerDisplay,nq}")]
-public sealed class FunctionResultContent : AIContent
+public class FunctionResultContent : AIContent
{
///
/// Initializes a new instance of the class.
diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json
index 6362fcaa00e..ef1f2ebb496 100644
--- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json
+++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/Microsoft.Extensions.AI.Abstractions.json
@@ -1792,7 +1792,7 @@
]
},
{
- "Type": "sealed class Microsoft.Extensions.AI.FunctionCallContent : Microsoft.Extensions.AI.AIContent",
+ "Type": "class Microsoft.Extensions.AI.FunctionCallContent : Microsoft.Extensions.AI.AIContent",
"Stage": "Stable",
"Methods": [
{
@@ -1824,7 +1824,7 @@
]
},
{
- "Type": "sealed class Microsoft.Extensions.AI.FunctionResultContent : Microsoft.Extensions.AI.AIContent",
+ "Type": "class Microsoft.Extensions.AI.FunctionResultContent : Microsoft.Extensions.AI.AIContent",
"Stage": "Stable",
"Methods": [
{
diff --git a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs
index fdc7d57b1ea..c88fe91fc17 100644
--- a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs
+++ b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs
@@ -1208,6 +1208,13 @@ FunctionResultContent CreateFunctionResultContent(FunctionInvocationResult resul
object? functionResult;
if (result.Status == FunctionInvocationStatus.RanToCompletion)
{
+ // If the result is already a FunctionResultContent with a matching CallId, use it directly.
+ if (result.Result is FunctionResultContent frc &&
+ frc.CallId == result.CallContent.CallId)
+ {
+ return frc;
+ }
+
functionResult = result.Result ?? "Success: Function completed.";
}
else
diff --git a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientTests.cs b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientTests.cs
index e9672275871..066fb5e3427 100644
--- a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientTests.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientTests.cs
@@ -354,6 +354,264 @@ public async Task FunctionInvokerDelegateOverridesHandlingAsync()
await InvokeAndAssertStreamingAsync(options, plan, configurePipeline: configure);
}
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task FunctionReturningFunctionResultContentWithMatchingCallId_UsesItDirectly(bool streaming)
+ {
+ FunctionResultContent? returnedFrc = null;
+
+ var options = new ChatOptions
+ {
+ Tools =
+ [
+ AIFunctionFactory.Create(() => "Result 1", "Func1"),
+ ]
+ };
+
+ using var innerClient = new TestChatClient
+ {
+ GetResponseAsyncCallback = (msgs, opts, ct) =>
+ {
+ var toolMessage = msgs.FirstOrDefault(m => m.Role == ChatRole.Tool);
+ if (toolMessage is null)
+ {
+ return Task.FromResult(new ChatResponse(
+ new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("callId1", "Func1")])));
+ }
+ else
+ {
+ return Task.FromResult(new ChatResponse(new ChatMessage(ChatRole.Assistant, "done")));
+ }
+ },
+ GetStreamingResponseAsyncCallback = (msgs, opts, ct) =>
+ {
+ var toolMessage = msgs.FirstOrDefault(m => m.Role == ChatRole.Tool);
+ if (toolMessage is null)
+ {
+ return YieldAsync(new ChatResponse(
+ new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("callId1", "Func1")])).ToChatResponseUpdates());
+ }
+ else
+ {
+ return YieldAsync(new ChatResponse(new ChatMessage(ChatRole.Assistant, "done")).ToChatResponseUpdates());
+ }
+ }
+ };
+
+ using var client = new FunctionInvokingChatClient(innerClient)
+ {
+ FunctionInvoker = (ctx, cancellationToken) =>
+ {
+ returnedFrc = new FunctionResultContent(ctx.CallContent.CallId, "Custom result from function")
+ {
+ RawRepresentation = "CustomRaw"
+ };
+ return new ValueTask