From b1d29ad39f1fc4ef83ac3932158e18f3ed32f718 Mon Sep 17 00:00:00 2001 From: westey <164392973+westey-m@users.noreply.github.com> Date: Thu, 19 Feb 2026 19:02:13 +0000 Subject: [PATCH] Replace inline string literals with constants in ChatHistoryMemoryProvider Extract 11 private const string fields for vector store property names (Key, Role, MessageId, AuthorName, ApplicationId, AgentId, UserId, SessionId, Content, CreatedAt, ContentEmbedding) and replace all inline usages across the collection definition, store dictionary, search result access, and filter expressions. Fixes #3801 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Memory/ChatHistoryMemoryProvider.cs | 66 +++++++++++-------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/dotnet/src/Microsoft.Agents.AI/Memory/ChatHistoryMemoryProvider.cs b/dotnet/src/Microsoft.Agents.AI/Memory/ChatHistoryMemoryProvider.cs index a93bfd2c67..c7cfdc0465 100644 --- a/dotnet/src/Microsoft.Agents.AI/Memory/ChatHistoryMemoryProvider.cs +++ b/dotnet/src/Microsoft.Agents.AI/Memory/ChatHistoryMemoryProvider.cs @@ -41,6 +41,18 @@ public sealed class ChatHistoryMemoryProvider : AIContextProvider, IDisposable private const string DefaultFunctionToolName = "Search"; private const string DefaultFunctionToolDescription = "Allows searching for related previous chat history to help answer the user question."; + private const string KeyField = "Key"; + private const string RoleField = "Role"; + private const string MessageIdField = "MessageId"; + private const string AuthorNameField = "AuthorName"; + private const string ApplicationIdField = "ApplicationId"; + private const string AgentIdField = "AgentId"; + private const string UserIdField = "UserId"; + private const string SessionIdField = "SessionId"; + private const string ContentField = "Content"; + private const string CreatedAtField = "CreatedAt"; + private const string ContentEmbeddingField = "ContentEmbedding"; + private readonly ProviderSessionState _sessionState; #pragma warning disable CA2213 // VectorStore is not owned by this class - caller is responsible for disposal @@ -98,17 +110,17 @@ public ChatHistoryMemoryProvider( { Properties = [ - new VectorStoreKeyProperty("Key", typeof(Guid)), - new VectorStoreDataProperty("Role", typeof(string)) { IsIndexed = true }, - new VectorStoreDataProperty("MessageId", typeof(string)) { IsIndexed = true }, - new VectorStoreDataProperty("AuthorName", typeof(string)), - new VectorStoreDataProperty("ApplicationId", typeof(string)) { IsIndexed = true }, - new VectorStoreDataProperty("AgentId", typeof(string)) { IsIndexed = true }, - new VectorStoreDataProperty("UserId", typeof(string)) { IsIndexed = true }, - new VectorStoreDataProperty("SessionId", typeof(string)) { IsIndexed = true }, - new VectorStoreDataProperty("Content", typeof(string)) { IsFullTextIndexed = true }, - new VectorStoreDataProperty("CreatedAt", typeof(string)) { IsIndexed = true }, - new VectorStoreVectorProperty("ContentEmbedding", typeof(string), Throw.IfLessThan(vectorDimensions, 1)) + new VectorStoreKeyProperty(KeyField, typeof(Guid)), + new VectorStoreDataProperty(RoleField, typeof(string)) { IsIndexed = true }, + new VectorStoreDataProperty(MessageIdField, typeof(string)) { IsIndexed = true }, + new VectorStoreDataProperty(AuthorNameField, typeof(string)), + new VectorStoreDataProperty(ApplicationIdField, typeof(string)) { IsIndexed = true }, + new VectorStoreDataProperty(AgentIdField, typeof(string)) { IsIndexed = true }, + new VectorStoreDataProperty(UserIdField, typeof(string)) { IsIndexed = true }, + new VectorStoreDataProperty(SessionIdField, typeof(string)) { IsIndexed = true }, + new VectorStoreDataProperty(ContentField, typeof(string)) { IsFullTextIndexed = true }, + new VectorStoreDataProperty(CreatedAtField, typeof(string)) { IsIndexed = true }, + new VectorStoreVectorProperty(ContentEmbeddingField, typeof(string), Throw.IfLessThan(vectorDimensions, 1)) ] }; @@ -207,17 +219,17 @@ protected override async ValueTask StoreAIContextAsync(InvokedContext context, C .Concat(context.ResponseMessages ?? []) .Select(message => new Dictionary { - ["Key"] = Guid.NewGuid(), - ["Role"] = message.Role.ToString(), - ["MessageId"] = message.MessageId, - ["AuthorName"] = message.AuthorName, - ["ApplicationId"] = storageScope.ApplicationId, - ["AgentId"] = storageScope.AgentId, - ["UserId"] = storageScope.UserId, - ["SessionId"] = storageScope.SessionId, - ["Content"] = message.Text, - ["CreatedAt"] = message.CreatedAt?.ToString("O") ?? DateTimeOffset.UtcNow.ToString("O"), - ["ContentEmbedding"] = message.Text, + [KeyField] = Guid.NewGuid(), + [RoleField] = message.Role.ToString(), + [MessageIdField] = message.MessageId, + [AuthorNameField] = message.AuthorName, + [ApplicationIdField] = storageScope.ApplicationId, + [AgentIdField] = storageScope.AgentId, + [UserIdField] = storageScope.UserId, + [SessionIdField] = storageScope.SessionId, + [ContentField] = message.Text, + [CreatedAtField] = message.CreatedAt?.ToString("O") ?? DateTimeOffset.UtcNow.ToString("O"), + [ContentEmbeddingField] = message.Text, }) .ToList(); @@ -262,7 +274,7 @@ private async Task SearchTextAsync(string userQuestion, ChatHistoryMemor } // Format the results as a single context message - var outputResultsText = string.Join("\n", results.Select(x => (string?)x["Content"]).Where(c => !string.IsNullOrWhiteSpace(c))); + var outputResultsText = string.Join("\n", results.Select(x => (string?)x[ContentField]).Where(c => !string.IsNullOrWhiteSpace(c))); if (string.IsNullOrWhiteSpace(outputResultsText)) { return string.Empty; @@ -314,12 +326,12 @@ private async Task SearchTextAsync(string userQuestion, ChatHistoryMemor Expression, bool>>? filter = null; if (applicationId != null) { - filter = x => (string?)x["ApplicationId"] == applicationId; + filter = x => (string?)x[ApplicationIdField] == applicationId; } if (agentId != null) { - Expression, bool>> agentIdFilter = x => (string?)x["AgentId"] == agentId; + Expression, bool>> agentIdFilter = x => (string?)x[AgentIdField] == agentId; filter = filter == null ? agentIdFilter : Expression.Lambda, bool>>( Expression.AndAlso(filter.Body, agentIdFilter.Body), filter.Parameters); @@ -327,7 +339,7 @@ private async Task SearchTextAsync(string userQuestion, ChatHistoryMemor if (userId != null) { - Expression, bool>> userIdFilter = x => (string?)x["UserId"] == userId; + Expression, bool>> userIdFilter = x => (string?)x[UserIdField] == userId; filter = filter == null ? userIdFilter : Expression.Lambda, bool>>( Expression.AndAlso(filter.Body, userIdFilter.Body), filter.Parameters); @@ -335,7 +347,7 @@ private async Task SearchTextAsync(string userQuestion, ChatHistoryMemor if (sessionId != null) { - Expression, bool>> sessionIdFilter = x => (string?)x["SessionId"] == sessionId; + Expression, bool>> sessionIdFilter = x => (string?)x[SessionIdField] == sessionId; filter = filter == null ? sessionIdFilter : Expression.Lambda, bool>>( Expression.AndAlso(filter.Body, sessionIdFilter.Body), filter.Parameters);