From 4bee4dbf2a46bcae3fc1c6cb77049f83443994a6 Mon Sep 17 00:00:00 2001 From: Yufeng He <40085740+he-yufeng@users.noreply.github.com> Date: Thu, 28 May 2026 18:26:18 +0800 Subject: [PATCH] fix: avoid local history when service returns conversation id --- .../ChatClient/ChatClientAgent.cs | 14 ++++++++++++++ .../ChatClientAgent_ChatHistoryManagementTests.cs | 9 ++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs index 9e79ac5b780..4d8005c8e9e 100644 --- a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs @@ -244,6 +244,7 @@ protected override async Task RunCoreAsync( // so let's update it and set the conversation id for the service session case. var forceEndOfRunPersistence = chatOptions?.ContinuationToken is not null || chatOptions?.AllowBackgroundResponses is true; this.UpdateSessionConversationIdAtEndOfRun(safeSession, chatResponse.ConversationId, cancellationToken, forceUpdate: forceEndOfRunPersistence); + chatOptions = SetResponseConversationId(chatOptions, chatResponse.ConversationId); // Ensure that the author name is set for each message in the response. foreach (ChatMessage chatResponseMessage in chatResponse.Messages) @@ -389,6 +390,7 @@ protected override async IAsyncEnumerable RunCoreStreamingA // We can derive the type of supported session from whether we have a conversation id, // so let's update it and set the conversation id for the service session case. this.UpdateSessionConversationIdAtEndOfRun(safeSession, chatResponse.ConversationId, cancellationToken, forceUpdate: forceEndOfRunPersistence); + chatOptions = SetResponseConversationId(chatOptions, chatResponse.ConversationId); // Notify providers of all new messages unless persistence is handled per-service-call by the decorator. // When resuming from a continuation token or using background responses, force notification @@ -1017,6 +1019,18 @@ private void WarnOnMissingPerServiceCallChatHistoryPersistingChatClient() return provider; } + private static ChatOptions? SetResponseConversationId(ChatOptions? chatOptions, string? responseConversationId) + { + if (string.IsNullOrWhiteSpace(responseConversationId)) + { + return chatOptions; + } + + chatOptions ??= new(); + chatOptions.ConversationId ??= responseConversationId; + return chatOptions; + } + /// /// Loads chat history from the resolved and prepends it to the given messages. /// diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_ChatHistoryManagementTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_ChatHistoryManagementTests.cs index 410ee4edda1..3f92bd0fef2 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_ChatHistoryManagementTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_ChatHistoryManagementTests.cs @@ -385,8 +385,8 @@ public async Task RunAsync_Throws_WhenThrowEnabledRegardlessOfClearSettingAsync( } /// - /// Verify that RunAsync does not throw when no ChatHistoryProvider is configured on options, - /// even if the service returns a conversation id (default InMemoryChatHistoryProvider is used but not from options). + /// Verify that RunAsync does not throw or persist local history when no ChatHistoryProvider is configured on options, + /// even if the service returns a conversation id. /// [Fact] public async Task RunAsync_DoesNotThrow_WhenNoChatHistoryProviderInOptionsAndConversationIdReturnedAsync() @@ -407,8 +407,11 @@ public async Task RunAsync_DoesNotThrow_WhenNoChatHistoryProviderInOptionsAndCon ChatClientAgentSession? session = await agent.CreateSessionAsync() as ChatClientAgentSession; await agent.RunAsync([new(ChatRole.User, "test")], session); - // Assert - no exception, session gets the conversation id + // Assert - no exception, session gets the conversation id, and local history is not also persisted. Assert.Equal("ConvId", session!.ConversationId); + var inMemoryProvider = agent.ChatHistoryProvider as InMemoryChatHistoryProvider; + Assert.NotNull(inMemoryProvider); + Assert.Empty(inMemoryProvider.GetMessages(session)); } #endregion