From 201096611286cb58b2138777279280eb58bba8d7 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 15 Apr 2025 18:22:38 +0100 Subject: [PATCH 1/3] In OpenAI responses client, use response ID as ChatThreadId --- .../OpenAIResponseChatClient.cs | 5 +++++ .../OpenAIResponseClientTests.cs | 2 ++ 2 files changed, 7 insertions(+) diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponseChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponseChatClient.cs index 70768f07caa..bd757d381fe 100644 --- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponseChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponseChatClient.cs @@ -87,6 +87,7 @@ public async Task GetResponseAsync( ChatResponse response = new() { ResponseId = openAIResponse.Id, + ChatThreadId = openAIResponse.Id, CreatedAt = openAIResponse.CreatedAt, FinishReason = ToFinishReason(openAIResponse.IncompleteStatusDetails?.Reason), Messages = [new(ChatRole.Assistant, [])], @@ -176,6 +177,7 @@ public async IAsyncEnumerable GetStreamingResponseAsync( Contents = ToUsageDetails(completedUpdate.Response) is { } usage ? [new UsageContent(usage)] : [], CreatedAt = createdAt, ResponseId = responseId, + ChatThreadId = responseId, FinishReason = ToFinishReason(completedUpdate.Response?.IncompleteStatusDetails?.Reason) ?? (functionCallInfos is not null ? ChatFinishReason.ToolCalls : ChatFinishReason.Stop), @@ -213,6 +215,7 @@ public async IAsyncEnumerable GetStreamingResponseAsync( MessageId = lastMessageId, ModelId = modelId, ResponseId = responseId, + ChatThreadId = responseId, }; break; @@ -246,6 +249,7 @@ public async IAsyncEnumerable GetStreamingResponseAsync( MessageId = lastMessageId, ModelId = modelId, ResponseId = responseId, + ChatThreadId = responseId, }; } @@ -259,6 +263,7 @@ public async IAsyncEnumerable GetStreamingResponseAsync( MessageId = lastMessageId, ModelId = modelId, ResponseId = responseId, + ChatThreadId = responseId, Contents = [ new ErrorContent(errorUpdate.Message) diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs index 717aae223f7..4dfad060f72 100644 --- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs @@ -157,6 +157,7 @@ public async Task BasicRequestResponse_NonStreaming() Assert.NotNull(response); Assert.Equal("resp_67d327649b288191aeb46a824e49dc40058a5e08c46a181d", response.ResponseId); + Assert.Equal("resp_67d327649b288191aeb46a824e49dc40058a5e08c46a181d", response.ChatThreadId); Assert.Equal("Hello! How can I assist you today?", response.Text); Assert.Single(response.Messages.Single().Contents); Assert.Equal(ChatRole.Assistant, response.Messages.Single().Role); @@ -265,6 +266,7 @@ public async Task BasicRequestResponse_Streaming() for (int i = 0; i < updates.Count; i++) { Assert.Equal("resp_67d329fbc87c81919f8952fe71dafc96029dabe3ee19bb77", updates[i].ResponseId); + Assert.Equal("resp_67d329fbc87c81919f8952fe71dafc96029dabe3ee19bb77", updates[i].ChatThreadId); Assert.Equal(createdAt, updates[i].CreatedAt); Assert.Equal("gpt-4o-mini-2024-07-18", updates[i].ModelId); Assert.Equal(ChatRole.Assistant, updates[i].Role); From 4ced8f2a0039ec151bfdd28490fdc143c6f7b978 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 15 Apr 2025 18:29:46 +0100 Subject: [PATCH 2/3] Rename ChatThreadId -> ConversationId --- .../ChatCompletion/ChatOptions.cs | 6 +++--- .../ChatCompletion/ChatResponse.cs | 14 ++++++++------ .../ChatCompletion/ChatResponseExtensions.cs | 4 ++-- .../ChatCompletion/ChatResponseUpdate.cs | 11 ++++++----- .../OpenAIResponseChatClient.cs | 12 ++++++------ .../ChatCompletion/CachingChatClient.cs | 2 +- .../ChatCompletion/ChatResponse{T}.cs | 2 +- .../ChatCompletion/FunctionInvokingChatClient.cs | 14 +++++++------- .../ChatCompletion/ChatOptionsTests.cs | 14 +++++++------- .../ChatResponseUpdateExtensionsTests.cs | 4 ++-- .../OpenAIResponseClientTests.cs | 4 ++-- .../FunctionInvokingChatClientTests.cs | 6 +++--- 12 files changed, 48 insertions(+), 45 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatOptions.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatOptions.cs index 071254182c2..52e000c915b 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatOptions.cs +++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatOptions.cs @@ -9,8 +9,8 @@ namespace Microsoft.Extensions.AI; /// Represents the options for a chat request. public class ChatOptions { - /// Gets or sets an optional identifier used to associate a request with an existing chat thread. - public string? ChatThreadId { get; set; } + /// Gets or sets an optional identifier used to associate a request with an existing conversation. + public string? ConversationId { get; set; } /// Gets or sets the temperature for generating chat responses. /// @@ -105,7 +105,7 @@ public virtual ChatOptions Clone() { ChatOptions options = new() { - ChatThreadId = ChatThreadId, + ConversationId = ConversationId, Temperature = Temperature, MaxOutputTokens = MaxOutputTokens, TopP = TopP, diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatResponse.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatResponse.cs index e0feaf164e0..b1f7f0d0c8a 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatResponse.cs +++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatResponse.cs @@ -63,15 +63,17 @@ public IList Messages /// Gets or sets the ID of the chat response. public string? ResponseId { get; set; } - /// Gets or sets the chat thread ID associated with this chat response. + /// Gets or sets an identifier for the state of the conversation. /// - /// Some implementations are capable of storing the state for a chat thread, such that + /// Some implementations are capable of storing the state for a conversation, such that /// the input messages supplied to need only be the additional messages beyond /// what's already stored. If this property is non-, it represents an identifier for that state, - /// and it should be used in a subsequent instead of supplying the same messages - /// (and this 's message) as part of the messages parameter. + /// and it should be used in a subsequent instead of supplying the same messages + /// (and this 's message) as part of the messages parameter. Note that the value may + /// or may not differ on every response, depending on whether the underlying provider uses a fixed ID for each conversation + /// or updates it for each message. /// - public string? ChatThreadId { get; set; } + public string? ConversationId { get; set; } /// Gets or sets the model ID used in the creation of the chat response. public string? ModelId { get; set; } @@ -127,7 +129,7 @@ public ChatResponseUpdate[] ToChatResponseUpdates() ChatMessage message = _messages![i]; updates[i] = new ChatResponseUpdate { - ChatThreadId = ChatThreadId, + ConversationId = ConversationId, AdditionalProperties = message.AdditionalProperties, AuthorName = message.AuthorName, diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatResponseExtensions.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatResponseExtensions.cs index bf73ab86934..01ce878e79c 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatResponseExtensions.cs +++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatResponseExtensions.cs @@ -320,9 +320,9 @@ private static void ProcessUpdate(ChatResponseUpdate update, ChatResponse respon response.ResponseId = update.ResponseId; } - if (update.ChatThreadId is not null) + if (update.ConversationId is not null) { - response.ChatThreadId = update.ChatThreadId; + response.ConversationId = update.ConversationId; } if (update.CreatedAt is not null) diff --git a/src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatResponseUpdate.cs b/src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatResponseUpdate.cs index a060b765401..63dbfbc0d7d 100644 --- a/src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatResponseUpdate.cs +++ b/src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatResponseUpdate.cs @@ -116,15 +116,16 @@ public IList Contents /// public string? MessageId { get; set; } - /// Gets or sets the chat thread ID associated with the chat response of which this update is a part. + /// Gets or sets an identifier for the state of the conversation of which this update is a part. /// - /// Some implementations are capable of storing the state for a chat thread, such that + /// Some implementations are capable of storing the state for a conversation, such that /// the input messages supplied to need only be the additional messages beyond /// what's already stored. If this property is non-, it represents an identifier for that state, - /// and it should be used in a subsequent instead of supplying the same messages - /// (and this streaming message) as part of the messages parameter. + /// and it should be used in a subsequent instead of supplying the same messages + /// (and this streaming message) as part of the messages parameter. Note that the value may or may not differ on every + /// response, depending on whether the underlying provider uses a fixed ID for each conversation or updates it for each message. /// - public string? ChatThreadId { get; set; } + public string? ConversationId { get; set; } /// Gets or sets a timestamp for the response update. public DateTimeOffset? CreatedAt { get; set; } diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponseChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponseChatClient.cs index bd757d381fe..b0498f55e5d 100644 --- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponseChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponseChatClient.cs @@ -87,7 +87,7 @@ public async Task GetResponseAsync( ChatResponse response = new() { ResponseId = openAIResponse.Id, - ChatThreadId = openAIResponse.Id, + ConversationId = openAIResponse.Id, CreatedAt = openAIResponse.CreatedAt, FinishReason = ToFinishReason(openAIResponse.IncompleteStatusDetails?.Reason), Messages = [new(ChatRole.Assistant, [])], @@ -177,7 +177,7 @@ public async IAsyncEnumerable GetStreamingResponseAsync( Contents = ToUsageDetails(completedUpdate.Response) is { } usage ? [new UsageContent(usage)] : [], CreatedAt = createdAt, ResponseId = responseId, - ChatThreadId = responseId, + ConversationId = responseId, FinishReason = ToFinishReason(completedUpdate.Response?.IncompleteStatusDetails?.Reason) ?? (functionCallInfos is not null ? ChatFinishReason.ToolCalls : ChatFinishReason.Stop), @@ -215,7 +215,7 @@ public async IAsyncEnumerable GetStreamingResponseAsync( MessageId = lastMessageId, ModelId = modelId, ResponseId = responseId, - ChatThreadId = responseId, + ConversationId = responseId, }; break; @@ -249,7 +249,7 @@ public async IAsyncEnumerable GetStreamingResponseAsync( MessageId = lastMessageId, ModelId = modelId, ResponseId = responseId, - ChatThreadId = responseId, + ConversationId = responseId, }; } @@ -263,7 +263,7 @@ public async IAsyncEnumerable GetStreamingResponseAsync( MessageId = lastMessageId, ModelId = modelId, ResponseId = responseId, - ChatThreadId = responseId, + ConversationId = responseId, Contents = [ new ErrorContent(errorUpdate.Message) @@ -309,7 +309,7 @@ private static ResponseCreationOptions ToOpenAIResponseCreationOptions(ChatOptio { // Handle strongly-typed properties. result.MaxOutputTokenCount = options.MaxOutputTokens; - result.PreviousResponseId = options.ChatThreadId; + result.PreviousResponseId = options.ConversationId; result.TopP = options.TopP; result.Temperature = options.Temperature; diff --git a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/CachingChatClient.cs b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/CachingChatClient.cs index 6fed2157b0b..e4d975f084f 100644 --- a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/CachingChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/CachingChatClient.cs @@ -108,7 +108,7 @@ public override async IAsyncEnumerable GetStreamingResponseA string? chatThreadId = null; foreach (var chunk in existingChunks) { - chatThreadId ??= chunk.ChatThreadId; + chatThreadId ??= chunk.ConversationId; yield return chunk; } } diff --git a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/ChatResponse{T}.cs b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/ChatResponse{T}.cs index 0348eb9b701..3756b255cc8 100644 --- a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/ChatResponse{T}.cs +++ b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/ChatResponse{T}.cs @@ -39,7 +39,7 @@ public ChatResponse(ChatResponse response, JsonSerializerOptions serializerOptio { _serializerOptions = Throw.IfNull(serializerOptions); AdditionalProperties = response.AdditionalProperties; - ChatThreadId = response.ChatThreadId; + ConversationId = response.ConversationId; CreatedAt = response.CreatedAt; FinishReason = response.FinishReason; ModelId = response.ModelId; diff --git a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs index ac8df9726c4..2af33213910 100644 --- a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs @@ -287,7 +287,7 @@ public override async Task GetResponseAsync( break; } - UpdateOptionsForNextIteration(ref options!, response.ChatThreadId); + UpdateOptionsForNextIteration(ref options!, response.ConversationId); } Debug.Assert(responseMessages is not null, "Expected to only be here if we have response messages."); @@ -390,7 +390,7 @@ public override async IAsyncEnumerable GetStreamingResponseA { AdditionalProperties = message.AdditionalProperties, AuthorName = message.AuthorName, - ChatThreadId = response.ChatThreadId, + ConversationId = response.ConversationId, CreatedAt = DateTimeOffset.UtcNow, Contents = message.Contents, RawRepresentation = message.RawRepresentation, @@ -408,7 +408,7 @@ public override async IAsyncEnumerable GetStreamingResponseA break; } - UpdateOptionsForNextIteration(ref options, response.ChatThreadId); + UpdateOptionsForNextIteration(ref options, response.ConversationId); } AddUsageTags(activity, totalUsage); @@ -448,7 +448,7 @@ private static void FixupHistories( { // We're now going to need to augment the history with function result contents. // That means we need a separate list to store the augmented history. - if (response.ChatThreadId is not null) + if (response.ConversationId is not null) { // The response indicates the inner client is tracking the history, so we don't want to send // anything we've already sent or received. @@ -533,14 +533,14 @@ private static void UpdateOptionsForNextIteration(ref ChatOptions options, strin // as otherwise we'll be in an infinite loop. options = options.Clone(); options.ToolMode = null; - options.ChatThreadId = chatThreadId; + options.ConversationId = chatThreadId; } - else if (options.ChatThreadId != chatThreadId) + else if (options.ConversationId != chatThreadId) { // As with the other modes, ensure we've propagated the chat thread ID to the options. // We only need to clone the options if we're actually mutating it. options = options.Clone(); - options.ChatThreadId = chatThreadId; + options.ConversationId = chatThreadId; } } diff --git a/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/ChatCompletion/ChatOptionsTests.cs b/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/ChatCompletion/ChatOptionsTests.cs index 498be7ecb1e..858e81a459f 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/ChatCompletion/ChatOptionsTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/ChatCompletion/ChatOptionsTests.cs @@ -13,7 +13,7 @@ public class ChatOptionsTests public void Constructor_Parameterless_PropsDefaulted() { ChatOptions options = new(); - Assert.Null(options.ChatThreadId); + Assert.Null(options.ConversationId); Assert.Null(options.Temperature); Assert.Null(options.MaxOutputTokens); Assert.Null(options.TopP); @@ -29,7 +29,7 @@ public void Constructor_Parameterless_PropsDefaulted() Assert.Null(options.AdditionalProperties); ChatOptions clone = options.Clone(); - Assert.Null(options.ChatThreadId); + Assert.Null(options.ConversationId); Assert.Null(clone.Temperature); Assert.Null(clone.MaxOutputTokens); Assert.Null(clone.TopP); @@ -67,7 +67,7 @@ public void Properties_Roundtrip() ["key"] = "value", }; - options.ChatThreadId = "12345"; + options.ConversationId = "12345"; options.Temperature = 0.1f; options.MaxOutputTokens = 2; options.TopP = 0.3f; @@ -82,7 +82,7 @@ public void Properties_Roundtrip() options.Tools = tools; options.AdditionalProperties = additionalProps; - Assert.Equal("12345", options.ChatThreadId); + Assert.Equal("12345", options.ConversationId); Assert.Equal(0.1f, options.Temperature); Assert.Equal(2, options.MaxOutputTokens); Assert.Equal(0.3f, options.TopP); @@ -98,7 +98,7 @@ public void Properties_Roundtrip() Assert.Same(additionalProps, options.AdditionalProperties); ChatOptions clone = options.Clone(); - Assert.Equal("12345", options.ChatThreadId); + Assert.Equal("12345", options.ConversationId); Assert.Equal(0.1f, clone.Temperature); Assert.Equal(2, clone.MaxOutputTokens); Assert.Equal(0.3f, clone.TopP); @@ -130,7 +130,7 @@ public void JsonSerialization_Roundtrips() ["key"] = "value", }; - options.ChatThreadId = "12345"; + options.ConversationId = "12345"; options.Temperature = 0.1f; options.MaxOutputTokens = 2; options.TopP = 0.3f; @@ -154,7 +154,7 @@ public void JsonSerialization_Roundtrips() ChatOptions? deserialized = JsonSerializer.Deserialize(json, TestJsonSerializerContext.Default.ChatOptions); Assert.NotNull(deserialized); - Assert.Equal("12345", deserialized.ChatThreadId); + Assert.Equal("12345", deserialized.ConversationId); Assert.Equal(0.1f, deserialized.Temperature); Assert.Equal(2, deserialized.MaxOutputTokens); Assert.Equal(0.3f, deserialized.TopP); diff --git a/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/ChatCompletion/ChatResponseUpdateExtensionsTests.cs b/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/ChatCompletion/ChatResponseUpdateExtensionsTests.cs index 50c4d136017..8b13d640ae1 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/ChatCompletion/ChatResponseUpdateExtensionsTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Abstractions.Tests/ChatCompletion/ChatResponseUpdateExtensionsTests.cs @@ -30,7 +30,7 @@ public async Task ToChatResponse_SuccessfullyCreatesResponse(bool useAsync) [ new(ChatRole.Assistant, "Hello") { ResponseId = "someResponse", MessageId = "12345", CreatedAt = new DateTimeOffset(1, 2, 3, 4, 5, 6, TimeSpan.Zero), ModelId = "model123" }, new(new("human"), ", ") { AuthorName = "Someone", AdditionalProperties = new() { ["a"] = "b" } }, - new(null, "world!") { CreatedAt = new DateTimeOffset(2, 2, 3, 4, 5, 6, TimeSpan.Zero), ChatThreadId = "123", AdditionalProperties = new() { ["c"] = "d" } }, + new(null, "world!") { CreatedAt = new DateTimeOffset(2, 2, 3, 4, 5, 6, TimeSpan.Zero), ConversationId = "123", AdditionalProperties = new() { ["c"] = "d" } }, new() { Contents = [new UsageContent(new() { InputTokenCount = 1, OutputTokenCount = 2 })] }, new() { Contents = [new UsageContent(new() { InputTokenCount = 4, OutputTokenCount = 5 })] }, @@ -49,7 +49,7 @@ public async Task ToChatResponse_SuccessfullyCreatesResponse(bool useAsync) Assert.Equal(new DateTimeOffset(2, 2, 3, 4, 5, 6, TimeSpan.Zero), response.CreatedAt); Assert.Equal("model123", response.ModelId); - Assert.Equal("123", response.ChatThreadId); + Assert.Equal("123", response.ConversationId); ChatMessage message = response.Messages.Single(); Assert.Equal("12345", message.MessageId); diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs index 4dfad060f72..8e4229937ee 100644 --- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs @@ -157,7 +157,7 @@ public async Task BasicRequestResponse_NonStreaming() Assert.NotNull(response); Assert.Equal("resp_67d327649b288191aeb46a824e49dc40058a5e08c46a181d", response.ResponseId); - Assert.Equal("resp_67d327649b288191aeb46a824e49dc40058a5e08c46a181d", response.ChatThreadId); + Assert.Equal("resp_67d327649b288191aeb46a824e49dc40058a5e08c46a181d", response.ConversationId); Assert.Equal("Hello! How can I assist you today?", response.Text); Assert.Single(response.Messages.Single().Contents); Assert.Equal(ChatRole.Assistant, response.Messages.Single().Role); @@ -266,7 +266,7 @@ public async Task BasicRequestResponse_Streaming() for (int i = 0; i < updates.Count; i++) { Assert.Equal("resp_67d329fbc87c81919f8952fe71dafc96029dabe3ee19bb77", updates[i].ResponseId); - Assert.Equal("resp_67d329fbc87c81919f8952fe71dafc96029dabe3ee19bb77", updates[i].ChatThreadId); + Assert.Equal("resp_67d329fbc87c81919f8952fe71dafc96029dabe3ee19bb77", updates[i].ConversationId); Assert.Equal(createdAt, updates[i].CreatedAt); Assert.Equal("gpt-4o-mini-2024-07-18", updates[i].ModelId); Assert.Equal(ChatRole.Assistant, updates[i].Role); diff --git a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientTests.cs b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientTests.cs index 67b2025b7de..ad25e91a042 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientTests.cs @@ -815,15 +815,15 @@ public async Task PropagatesResponseChatThreadIdToOptions() if (iteration == 1) { - Assert.Null(chatOptions?.ChatThreadId); + Assert.Null(chatOptions?.ConversationId); return new ChatResponse(new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("callId-abc", "Func1")])) { - ChatThreadId = "12345", + ConversationId = "12345", }; } else if (iteration == 2) { - Assert.Equal("12345", chatOptions?.ChatThreadId); + Assert.Equal("12345", chatOptions?.ConversationId); return new ChatResponse(new ChatMessage(ChatRole.Assistant, "done!")); } else From f1d8eca1fb38dae57a3068b5e60d68b36c3bc0d7 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 15 Apr 2025 18:31:58 +0100 Subject: [PATCH 3/3] Related renames --- .../ChatCompletion/CachingChatClient.cs | 4 +-- .../FunctionInvokingChatClient.cs | 28 +++++++++---------- .../FunctionInvokingChatClientTests.cs | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/CachingChatClient.cs b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/CachingChatClient.cs index e4d975f084f..5aa70e4b262 100644 --- a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/CachingChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/CachingChatClient.cs @@ -105,10 +105,10 @@ public override async IAsyncEnumerable GetStreamingResponseA if (await ReadCacheStreamingAsync(cacheKey, cancellationToken) is { } existingChunks) { // Yield all of the cached items. - string? chatThreadId = null; + string? conversationId = null; foreach (var chunk in existingChunks) { - chatThreadId ??= chunk.ConversationId; + conversationId ??= chunk.ConversationId; yield return chunk; } } diff --git a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs index 2af33213910..a73757ad03f 100644 --- a/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs +++ b/src/Libraries/Microsoft.Extensions.AI/ChatCompletion/FunctionInvokingChatClient.cs @@ -224,7 +224,7 @@ public override async Task GetResponseAsync( List? responseMessages = null; // tracked list of messages, across multiple turns, to be used for the final response UsageDetails? totalUsage = null; // tracked usage across all turns, to be used for the final response List? functionCallContents = null; // function call contents that need responding to in the current turn - bool lastIterationHadThreadId = false; // whether the last iteration's response had a ChatThreadId set + bool lastIterationHadConversationId = false; // whether the last iteration's response had a ConversationId set int consecutiveErrorCount = 0; for (int iteration = 0; ; iteration++) @@ -274,7 +274,7 @@ public override async Task GetResponseAsync( } // Prepare the history for the next iteration. - FixupHistories(originalMessages, ref messages, ref augmentedHistory, response, responseMessages, ref lastIterationHadThreadId); + FixupHistories(originalMessages, ref messages, ref augmentedHistory, response, responseMessages, ref lastIterationHadConversationId); // Add the responses from the function calls into the augmented history and also into the tracked // list of response messages. @@ -318,7 +318,7 @@ public override async IAsyncEnumerable GetStreamingResponseA List? augmentedHistory = null; // the actual history of messages sent on turns other than the first List? functionCallContents = null; // function call contents that need responding to in the current turn List? responseMessages = null; // tracked list of messages, across multiple turns, to be used in fallback cases to reconstitute history - bool lastIterationHadThreadId = false; // whether the last iteration's response had a ChatThreadId set + bool lastIterationHadConversationId = false; // whether the last iteration's response had a ConversationId set List updates = []; // updates from the current response int consecutiveErrorCount = 0; @@ -368,7 +368,7 @@ public override async IAsyncEnumerable GetStreamingResponseA (responseMessages ??= []).AddRange(response.Messages); // Prepare the history for the next iteration. - FixupHistories(originalMessages, ref messages, ref augmentedHistory, response, responseMessages, ref lastIterationHadThreadId); + FixupHistories(originalMessages, ref messages, ref augmentedHistory, response, responseMessages, ref lastIterationHadConversationId); // Process all of the functions, adding their results into the history. var modeAndMessages = await ProcessFunctionCallsAsync(augmentedHistory, options, functionCallContents, iteration, consecutiveErrorCount, cancellationToken); @@ -437,14 +437,14 @@ private static void AddUsageTags(Activity? activity, UsageDetails? usage) /// The augmented history containing all the messages to be sent. /// The most recent response being handled. /// A list of all response messages received up until this point. - /// Whether the previous iteration's response had a thread id. + /// Whether the previous iteration's response had a conversation id. private static void FixupHistories( IEnumerable originalMessages, ref IEnumerable messages, [NotNull] ref List? augmentedHistory, ChatResponse response, List allTurnsResponseMessages, - ref bool lastIterationHadThreadId) + ref bool lastIterationHadConversationId) { // We're now going to need to augment the history with function result contents. // That means we need a separate list to store the augmented history. @@ -461,9 +461,9 @@ private static void FixupHistories( augmentedHistory = []; } - lastIterationHadThreadId = true; + lastIterationHadConversationId = true; } - else if (lastIterationHadThreadId) + else if (lastIterationHadConversationId) { // In the very rare case where the inner client returned a response with a thread ID but then // returned a subsequent response without one, we want to reconstitue the full history. To do that, @@ -474,7 +474,7 @@ private static void FixupHistories( augmentedHistory.AddRange(originalMessages); augmentedHistory.AddRange(allTurnsResponseMessages); - lastIterationHadThreadId = false; + lastIterationHadConversationId = false; } else { @@ -486,7 +486,7 @@ private static void FixupHistories( // Now add the most recent response messages. augmentedHistory.AddMessages(response); - lastIterationHadThreadId = false; + lastIterationHadConversationId = false; } // Use the augmented history as the new set of messages to send. @@ -525,7 +525,7 @@ private static bool CopyFunctionCalls( return any; } - private static void UpdateOptionsForNextIteration(ref ChatOptions options, string? chatThreadId) + private static void UpdateOptionsForNextIteration(ref ChatOptions options, string? conversationId) { if (options.ToolMode is RequiredChatToolMode) { @@ -533,14 +533,14 @@ private static void UpdateOptionsForNextIteration(ref ChatOptions options, strin // as otherwise we'll be in an infinite loop. options = options.Clone(); options.ToolMode = null; - options.ConversationId = chatThreadId; + options.ConversationId = conversationId; } - else if (options.ConversationId != chatThreadId) + else if (options.ConversationId != conversationId) { // As with the other modes, ensure we've propagated the chat thread ID to the options. // We only need to clone the options if we're actually mutating it. options = options.Clone(); - options.ConversationId = chatThreadId; + options.ConversationId = conversationId; } } diff --git a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientTests.cs b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientTests.cs index ad25e91a042..f7c47ecd32a 100644 --- a/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientTests.cs +++ b/test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/FunctionInvokingChatClientTests.cs @@ -799,7 +799,7 @@ public async Task CanResumeFunctionCallingAfterTermination() } [Fact] - public async Task PropagatesResponseChatThreadIdToOptions() + public async Task PropagatesResponseConversationIdToOptions() { var options = new ChatOptions {