diff --git a/NuGet.config b/NuGet.config
index 0fedd015e82..2aab6faa83e 100644
--- a/NuGet.config
+++ b/NuGet.config
@@ -17,6 +17,8 @@
+
+
@@ -39,6 +41,10 @@
+
+
+
+
diff --git a/eng/packages/General.props b/eng/packages/General.props
index d30eedcfefe..28206e5652b 100644
--- a/eng/packages/General.props
+++ b/eng/packages/General.props
@@ -17,7 +17,7 @@
-
+
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
index 38695ae16f0..80ece797929 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
@@ -17,7 +17,6 @@
using System.Threading.Tasks;
using Microsoft.Shared.DiagnosticIds;
using Microsoft.Shared.Diagnostics;
-using OpenAI;
using OpenAI.Responses;
#pragma warning disable S1226 // Method parameters, caught exceptions and foreach variables' initial values should not be ignored
@@ -464,6 +463,7 @@ ChatResponseUpdate CreateUpdate(AIContent? content = null) =>
case FunctionCallResponseItem fcri:
anyFunctions = true;
+ lastMessageId = outputItemAddedUpdate.Item.Id;
lastRole = ChatRole.Assistant;
break;
}
@@ -735,7 +735,9 @@ private static bool IsStoredOutputDisabled(CreateResponseOptions? options, Respo
case HostedToolSearchTool:
// Workaround: The OpenAI .NET SDK doesn't yet expose a ToolSearchTool type.
// See https://github.com/openai/openai-dotnet/issues/1053
- return ModelReaderWriter.Read(BinaryData.FromString("""{"type": "tool_search"}"""), ModelReaderWriterOptions.Json, OpenAIContext.Default)!;
+#pragma warning disable OPENAI001 // OpenAIResponsesContext is experimental
+ return ModelReaderWriter.Read(BinaryData.FromString("""{"type": "tool_search"}"""), ModelReaderWriterOptions.Json, OpenAIResponsesContext.Default)!;
+#pragma warning restore OPENAI001
case HostedWebSearchTool webSearchTool:
return new WebSearchTool
@@ -895,7 +897,9 @@ internal static ResponseTool ToNamespaceResponseTool(string name, string? descri
writer.WriteStartArray("tools"u8);
foreach (var namespacedTool in namespacedTools)
{
- var toolData = ModelReaderWriter.Write(namespacedTool, ModelReaderWriterOptions.Json, OpenAIContext.Default);
+#pragma warning disable OPENAI001 // OpenAIResponsesContext is experimental
+ var toolData = ModelReaderWriter.Write(namespacedTool, ModelReaderWriterOptions.Json, OpenAIResponsesContext.Default);
+#pragma warning restore OPENAI001
using var doc = JsonDocument.Parse(toolData);
doc.RootElement.WriteTo(writer);
}
@@ -904,7 +908,9 @@ internal static ResponseTool ToNamespaceResponseTool(string name, string? descri
writer.WriteEndObject();
}
- return ModelReaderWriter.Read(BinaryData.FromBytes(stream.ToArray()), ModelReaderWriterOptions.Json, OpenAIContext.Default)!;
+#pragma warning disable OPENAI001 // OpenAIResponsesContext is experimental
+ return ModelReaderWriter.Read(BinaryData.FromBytes(stream.ToArray()), ModelReaderWriterOptions.Json, OpenAIResponsesContext.Default)!;
+#pragma warning restore OPENAI001
}
/// Creates a from a .
diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs
index 90f8343d8f0..73297dfad23 100644
--- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs
@@ -5974,6 +5974,7 @@ public async Task StreamingResponseWithFunctionCallOutput_YieldsFunctionResultCo
var functionCallUpdate = updates.FirstOrDefault(u => u.Contents.OfType().Any());
Assert.NotNull(functionCallUpdate);
Assert.Equal(ChatRole.Assistant, functionCallUpdate.Role);
+ Assert.Equal("fc_001", functionCallUpdate.MessageId); // Verify MessageID is set for function calls (issue #7479)
var fcc = functionCallUpdate.Contents.OfType().Single();
Assert.Equal("call_123", fcc.CallId);
Assert.Equal("get_weather", fcc.Name);
@@ -5994,14 +5995,23 @@ public async Task StreamingResponseWithFunctionCallOutput_YieldsFunctionResultCo
var response = updates.ToChatResponse();
Assert.Equal("resp_001", response.ResponseId);
Assert.Equal("gpt-4o-mini", response.ModelId);
- Assert.Single(response.Messages);
- var message = response.Messages[0];
- Assert.Equal(ChatRole.Assistant, message.Role);
- // Message should contain: FunctionCallContent, FunctionResultContent, and TextContent
- Assert.Single(message.Contents.OfType());
- Assert.Single(message.Contents.OfType());
- var textContent = Assert.Single(message.Contents.OfType());
+ // With the MessageID fix, function calls now have their own MessageId ("fc_001") and
+ // the text message has a different MessageId ("msg_001"), resulting in 2 separate messages.
+ Assert.Equal(2, response.Messages.Count);
+
+ // First message contains the function call and result items (MessageId="fc_001")
+ var functionMessage = response.Messages[0];
+ Assert.Equal(ChatRole.Assistant, functionMessage.Role);
+ Assert.Equal("fc_001", functionMessage.MessageId);
+ Assert.Single(functionMessage.Contents.OfType());
+ Assert.Single(functionMessage.Contents.OfType());
+
+ // Second message contains the text response (MessageId="msg_001")
+ var textMessage = response.Messages[1];
+ Assert.Equal(ChatRole.Assistant, textMessage.Role);
+ Assert.Equal("msg_001", textMessage.MessageId);
+ var textContent = Assert.Single(textMessage.Contents.OfType());
Assert.Equal("It's 25°C and sunny in Paris.", textContent.Text);
// Verify usage