From 72a863f4bf3c9bd0a1312ed02a8acd3474d23145 Mon Sep 17 00:00:00 2001
From: westey <164392973+westey-m@users.noreply.github.com>
Date: Thu, 12 Feb 2026 15:44:19 +0000
Subject: [PATCH] .NET: [BREAKING]Delay AIContext Materialization until the end
of the pipeline is reached. (#3883)
* Delay AIContext Materialization until the end of the pipeline is reached.
* Address PR comments.
* Address PR comments
---
.../Program.cs | 3 +-
.../AIContext.cs | 33 +++++++------
.../Microsoft.Agents.AI.Mem0/Mem0Provider.cs | 3 +-
.../ChatClient/ChatClientAgent.cs | 29 ++++++------
.../Memory/ChatHistoryMemoryProvider.cs | 5 +-
.../Microsoft.Agents.AI/TextSearchProvider.cs | 5 +-
.../AIContextTests.cs | 15 +++---
.../Mem0ProviderTests.cs | 23 ++++-----
.../Mem0ProviderTests.cs | 7 +--
.../ChatClient/ChatClientAgentTests.cs | 14 +++---
.../Data/TextSearchProviderTests.cs | 47 +++++++++++--------
.../Memory/ChatHistoryMemoryProviderTests.cs | 7 +--
12 files changed, 101 insertions(+), 90 deletions(-)
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step20_AdditionalAIContext/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step20_AdditionalAIContext/Program.cs
index ea9692610a..ba56d94e93 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step20_AdditionalAIContext/Program.cs
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step20_AdditionalAIContext/Program.cs
@@ -119,14 +119,13 @@ namespace SampleApp
{
AIFunctionFactory.Create((string item) => AddTodoItem(context.Session, item), "AddTodoItem", "Adds an item to the todo list."),
AIFunctionFactory.Create((int index) => RemoveTodoItem(context.Session, index), "RemoveTodoItem", "Removes an item from the todo list. Index is zero based.")
- }).ToList(),
+ }),
Messages =
(inputContext.Messages ?? [])
.Concat(
[
new MEAI.ChatMessage(ChatRole.User, outputMessageBuilder.ToString()).WithAgentRequestMessageSource(AgentRequestMessageSourceType.AIContextProvider, this.GetType().FullName!)
])
- .ToList()
});
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/AIContext.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/AIContext.cs
index b05992d93e..9ccfc3e905 100644
--- a/dotnet/src/Microsoft.Agents.AI.Abstractions/AIContext.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/AIContext.cs
@@ -56,41 +56,44 @@ public sealed class AIContext
public string? Instructions { get; set; }
///
- /// Gets or sets a collection of messages to add to the conversation history.
+ /// Gets or sets the sequence of messages to use for the current invocation.
///
///
- /// A list of instances to be permanently added to the conversation history,
- /// or if no messages should be added.
+ /// A sequence of instances to be used for the current invocation,
+ /// or if no messages should be used.
///
///
///
- /// Unlike and , messages added through this property become
- /// permanent additions to the conversation history. They will persist beyond the current invocation and
- /// will be available in future interactions within the same conversation thread.
+ /// Unlike and , messages added through this property may become
+ /// permanent additions to the conversation history.
+ /// If chat history is managed by the underlying AI service, these messages will become part of chat history.
+ /// If chat history is managed using a , these messages will be passed to the
+ /// method,
+ /// and the provider can choose which of these messages to permanently add to the conversation history.
///
///
/// This property is useful for:
///
- /// - Injecting relevant historical context or background information
+ /// - Injecting relevant historical context e.g. memories
+ /// - Injecting relevant background information e.g. via Retrieval Augmented Generation
/// - Adding system messages that provide ongoing context
- /// - Including retrieved information that should be part of the conversation record
- /// - Inserting contextual exchanges that inform the current conversation
///
///
///
- public IList? Messages { get; set; }
+ public IEnumerable? Messages { get; set; }
///
- /// Gets or sets a collection of tools or functions to make available to the AI model for the current invocation.
+ /// Gets or sets a sequence of tools or functions to make available to the AI model for the current invocation.
///
///
- /// A list of instances that will be available to the AI model during the current invocation,
+ /// A sequence of instances that will be available to the AI model during the current invocation,
/// or if no additional tools should be provided.
///
///
///
- /// These tools are transient and apply only to the current AI model invocation. They are combined with any
- /// tools already configured for the agent to provide an expanded set of capabilities for the specific interaction.
+ /// These tools are transient and apply only to the current AI model invocation. Any existing tools
+ /// are provided as input to the instances, so context providers can choose to modify or replace the existing tools
+ /// as needed based on the current context. The resulting set of tools is then passed to the underlying AI model, which may choose to utilize them when generating responses.
///
///
/// Context-specific tools enable:
@@ -102,5 +105,5 @@ public sealed class AIContext
///
///
///
- public IList? Tools { get; set; }
+ public IEnumerable? Tools { get; set; }
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Mem0/Mem0Provider.cs b/dotnet/src/Microsoft.Agents.AI.Mem0/Mem0Provider.cs
index 95126c725f..aaf2333553 100644
--- a/dotnet/src/Microsoft.Agents.AI.Mem0/Mem0Provider.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Mem0/Mem0Provider.cs
@@ -170,8 +170,7 @@ public sealed class Mem0Provider : AIContextProvider
Instructions = inputContext.Instructions,
Messages =
(inputContext.Messages ?? [])
- .Concat(outputMessage is not null ? [outputMessage] : [])
- .ToList(),
+ .Concat(outputMessage is not null ? [outputMessage] : []),
Tools = inputContext.Tools
};
}
diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs
index 2c840e0323..d43df626b4 100644
--- a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs
+++ b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs
@@ -677,7 +677,7 @@ public sealed partial class ChatClientAgent : AIAgent
throw new InvalidOperationException("Input messages are not allowed when continuing a background response using a continuation token.");
}
- List inputMessagesForChatClient = [];
+ IEnumerable inputMessagesForChatClient = inputMessages;
// Populate the session messages only if we are not continuing an existing response as it's not allowed
if (chatOptions?.ContinuationToken is null)
@@ -688,13 +688,8 @@ public sealed partial class ChatClientAgent : AIAgent
// The ChatHistoryProvider returns the merged result (history + input messages).
if (chatHistoryProvider is not null)
{
- var invokingContext = new ChatHistoryProvider.InvokingContext(this, typedSession, inputMessages);
- var providerMessages = await chatHistoryProvider.InvokingAsync(invokingContext, cancellationToken).ConfigureAwait(false);
- inputMessagesForChatClient.AddRange(providerMessages);
- }
- else
- {
- inputMessagesForChatClient.AddRange(inputMessages);
+ var invokingContext = new ChatHistoryProvider.InvokingContext(this, typedSession, inputMessagesForChatClient);
+ inputMessagesForChatClient = await chatHistoryProvider.InvokingAsync(invokingContext, cancellationToken).ConfigureAwait(false);
}
// If we have an AIContextProvider, we should get context from it, and update our
@@ -705,8 +700,8 @@ public sealed partial class ChatClientAgent : AIAgent
var aiContext = new AIContext
{
Instructions = chatOptions?.Instructions,
- Messages = inputMessagesForChatClient.ToList(),
- Tools = chatOptions?.Tools as List ?? chatOptions?.Tools?.ToList()
+ Messages = inputMessagesForChatClient,
+ Tools = chatOptions?.Tools
};
foreach (var aiContextProvider in aiContextProviders)
@@ -715,13 +710,14 @@ public sealed partial class ChatClientAgent : AIAgent
aiContext = await aiContextProvider.InvokingAsync(invokingContext, cancellationToken).ConfigureAwait(false);
}
- // Use the returned messages, tools and instructions directly since the provider accumulated them.
- inputMessagesForChatClient = aiContext.Messages as List ?? aiContext.Messages?.ToList() ?? [];
+ // Materialize the accumulated messages and tools once at the end of the provider pipeline.
+ inputMessagesForChatClient = aiContext.Messages ?? [];
- if (chatOptions?.Tools is { Count: > 0 } || aiContext.Tools is { Count: > 0 })
+ var tools = aiContext.Tools as IList ?? aiContext.Tools?.ToList();
+ if (chatOptions?.Tools is { Count: > 0 } || tools is { Count: > 0 })
{
chatOptions ??= new();
- chatOptions.Tools = aiContext.Tools;
+ chatOptions.Tools = tools;
}
if (chatOptions?.Instructions is not null || aiContext.Instructions is not null)
@@ -750,7 +746,10 @@ public sealed partial class ChatClientAgent : AIAgent
chatOptions.ConversationId = typedSession.ConversationId;
}
- return (typedSession, chatOptions, inputMessagesForChatClient, continuationToken);
+ // Materialize the accumulated messages once at the end of the provider pipeline, reusing the existing list if possible.
+ List messagesList = inputMessagesForChatClient as List ?? inputMessagesForChatClient.ToList();
+
+ return (typedSession, chatOptions, messagesList, continuationToken);
}
private void UpdateSessionConversationId(ChatClientAgentSession session, string? responseConversationId, CancellationToken cancellationToken)
diff --git a/dotnet/src/Microsoft.Agents.AI/Memory/ChatHistoryMemoryProvider.cs b/dotnet/src/Microsoft.Agents.AI/Memory/ChatHistoryMemoryProvider.cs
index dd2a88c96f..9d163f79cf 100644
--- a/dotnet/src/Microsoft.Agents.AI/Memory/ChatHistoryMemoryProvider.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Memory/ChatHistoryMemoryProvider.cs
@@ -171,7 +171,7 @@ public sealed class ChatHistoryMemoryProvider : AIContextProvider, IDisposable
{
Instructions = inputContext.Instructions,
Messages = inputContext.Messages,
- Tools = (inputContext.Tools ?? []).Concat(tools).ToList()
+ Tools = (inputContext.Tools ?? []).Concat(tools)
};
}
@@ -204,8 +204,7 @@ public sealed class ChatHistoryMemoryProvider : AIContextProvider, IDisposable
.Concat(
[
new ChatMessage(ChatRole.User, contextText).WithAgentRequestMessageSource(AgentRequestMessageSourceType.AIContextProvider, this.GetType().FullName!)
- ])
- .ToList(),
+ ]),
Tools = inputContext.Tools
};
}
diff --git a/dotnet/src/Microsoft.Agents.AI/TextSearchProvider.cs b/dotnet/src/Microsoft.Agents.AI/TextSearchProvider.cs
index 10c8750959..f038fa3c38 100644
--- a/dotnet/src/Microsoft.Agents.AI/TextSearchProvider.cs
+++ b/dotnet/src/Microsoft.Agents.AI/TextSearchProvider.cs
@@ -105,7 +105,7 @@ public sealed class TextSearchProvider : AIContextProvider
{
Instructions = inputContext.Instructions,
Messages = inputContext.Messages,
- Tools = (inputContext.Tools ?? []).Concat(this._tools).ToList()
+ Tools = (inputContext.Tools ?? []).Concat(this._tools)
};
}
@@ -161,8 +161,7 @@ public sealed class TextSearchProvider : AIContextProvider
.Concat(
[
new ChatMessage(ChatRole.User, formatted).WithAgentRequestMessageSource(AgentRequestMessageSourceType.AIContextProvider, this.GetType().FullName!)
- ])
- .ToList(),
+ ]),
Tools = inputContext.Tools
};
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIContextTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIContextTests.cs
index b1ba6060ea..c925f098b3 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIContextTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIContextTests.cs
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
+using System.Linq;
using Microsoft.Extensions.AI;
namespace Microsoft.Agents.AI.Abstractions.UnitTests;
@@ -33,9 +34,10 @@ public class AIContextTests
};
Assert.NotNull(context.Messages);
- Assert.Equal(2, context.Messages.Count);
- Assert.Equal("Hello", context.Messages[0].Text);
- Assert.Equal("Hi there!", context.Messages[1].Text);
+ var messages = context.Messages.ToList();
+ Assert.Equal(2, messages.Count);
+ Assert.Equal("Hello", messages[0].Text);
+ Assert.Equal("Hi there!", messages[1].Text);
}
[Fact]
@@ -51,8 +53,9 @@ public class AIContextTests
};
Assert.NotNull(context.Tools);
- Assert.Equal(2, context.Tools.Count);
- Assert.Equal("Function1", context.Tools[0].Name);
- Assert.Equal("Function2", context.Tools[1].Name);
+ var tools = context.Tools.ToList();
+ Assert.Equal(2, tools.Count);
+ Assert.Equal("Function1", tools[0].Name);
+ Assert.Equal("Function2", tools[1].Name);
}
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Mem0.IntegrationTests/Mem0ProviderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Mem0.IntegrationTests/Mem0ProviderTests.cs
index c713b9f9c9..e2355eafba 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Mem0.IntegrationTests/Mem0ProviderTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Mem0.IntegrationTests/Mem0ProviderTests.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
@@ -54,7 +55,7 @@ public sealed class Mem0ProviderTests : IDisposable
await sut.ClearStoredMemoriesAsync(mockSession);
var ctxBefore = await sut.InvokingAsync(new AIContextProvider.InvokingContext(s_mockAgent, mockSession, new AIContext { Messages = new List { question } }));
- Assert.DoesNotContain("Caoimhe", ctxBefore.Messages?[0].Text ?? string.Empty);
+ Assert.DoesNotContain("Caoimhe", ctxBefore.Messages?.LastOrDefault()?.Text ?? string.Empty);
// Act
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, mockSession, [input]));
@@ -63,8 +64,8 @@ public sealed class Mem0ProviderTests : IDisposable
var ctxAfterClearing = await sut.InvokingAsync(new AIContextProvider.InvokingContext(s_mockAgent, mockSession, new AIContext { Messages = new List { question } }));
// Assert
- Assert.Contains("Caoimhe", ctxAfterAdding.Messages?[0].Text ?? string.Empty);
- Assert.DoesNotContain("Caoimhe", ctxAfterClearing.Messages?[0].Text ?? string.Empty);
+ Assert.Contains("Caoimhe", ctxAfterAdding.Messages?.LastOrDefault()?.Text ?? string.Empty);
+ Assert.DoesNotContain("Caoimhe", ctxAfterClearing.Messages?.LastOrDefault()?.Text ?? string.Empty);
}
[Fact(Skip = SkipReason)]
@@ -79,7 +80,7 @@ public sealed class Mem0ProviderTests : IDisposable
await sut.ClearStoredMemoriesAsync(mockSession);
var ctxBefore = await sut.InvokingAsync(new AIContextProvider.InvokingContext(s_mockAgent, mockSession, new AIContext { Messages = new List { question } }));
- Assert.DoesNotContain("Caoimhe", ctxBefore.Messages?[0].Text ?? string.Empty);
+ Assert.DoesNotContain("Caoimhe", ctxBefore.Messages?.LastOrDefault()?.Text ?? string.Empty);
// Act
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, mockSession, [assistantIntro]));
@@ -88,8 +89,8 @@ public sealed class Mem0ProviderTests : IDisposable
var ctxAfterClearing = await sut.InvokingAsync(new AIContextProvider.InvokingContext(s_mockAgent, mockSession, new AIContext { Messages = new List { question } }));
// Assert
- Assert.Contains("Caoimhe", ctxAfterAdding.Messages?[0].Text ?? string.Empty);
- Assert.DoesNotContain("Caoimhe", ctxAfterClearing.Messages?[0].Text ?? string.Empty);
+ Assert.Contains("Caoimhe", ctxAfterAdding.Messages?.LastOrDefault()?.Text ?? string.Empty);
+ Assert.DoesNotContain("Caoimhe", ctxAfterClearing.Messages?.LastOrDefault()?.Text ?? string.Empty);
}
[Fact(Skip = SkipReason)]
@@ -110,8 +111,8 @@ public sealed class Mem0ProviderTests : IDisposable
var ctxBefore1 = await sut1.InvokingAsync(new AIContextProvider.InvokingContext(s_mockAgent, mockSession1, new AIContext { Messages = new List { question } }));
var ctxBefore2 = await sut2.InvokingAsync(new AIContextProvider.InvokingContext(s_mockAgent, mockSession2, new AIContext { Messages = new List { question } }));
- Assert.DoesNotContain("Caoimhe", ctxBefore1.Messages?[0].Text ?? string.Empty);
- Assert.DoesNotContain("Caoimhe", ctxBefore2.Messages?[0].Text ?? string.Empty);
+ Assert.DoesNotContain("Caoimhe", ctxBefore1.Messages?.LastOrDefault()?.Text ?? string.Empty);
+ Assert.DoesNotContain("Caoimhe", ctxBefore2.Messages?.LastOrDefault()?.Text ?? string.Empty);
// Act
await sut1.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, mockSession1, [assistantIntro]));
@@ -119,8 +120,8 @@ public sealed class Mem0ProviderTests : IDisposable
var ctxAfterAdding2 = await GetContextWithRetryAsync(sut2, mockSession2, question);
// Assert
- Assert.Contains("Caoimhe", ctxAfterAdding1.Messages?[0].Text ?? string.Empty);
- Assert.DoesNotContain("Caoimhe", ctxAfterAdding2.Messages?[0].Text ?? string.Empty);
+ Assert.Contains("Caoimhe", ctxAfterAdding1.Messages?.LastOrDefault()?.Text ?? string.Empty);
+ Assert.DoesNotContain("Caoimhe", ctxAfterAdding2.Messages?.LastOrDefault()?.Text ?? string.Empty);
// Cleanup
await sut1.ClearStoredMemoriesAsync(mockSession1);
@@ -133,7 +134,7 @@ public sealed class Mem0ProviderTests : IDisposable
for (int i = 0; i < attempts; i++)
{
ctx = await provider.InvokingAsync(new AIContextProvider.InvokingContext(s_mockAgent, session, new AIContext { Messages = new List { question } }), CancellationToken.None);
- var text = ctx.Messages?[0].Text;
+ var text = ctx.Messages?.LastOrDefault()?.Text;
if (!string.IsNullOrEmpty(text) && text.IndexOf("Caoimhe", StringComparison.OrdinalIgnoreCase) >= 0)
{
break;
diff --git a/dotnet/tests/Microsoft.Agents.AI.Mem0.UnitTests/Mem0ProviderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Mem0.UnitTests/Mem0ProviderTests.cs
index 83c9d0a89d..2883899640 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Mem0.UnitTests/Mem0ProviderTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Mem0.UnitTests/Mem0ProviderTests.cs
@@ -118,9 +118,10 @@ public sealed class Mem0ProviderTests : IDisposable
Assert.Equal("What is my name?", doc.RootElement.GetProperty("query").GetString());
Assert.NotNull(aiContext.Messages);
- Assert.Equal(2, aiContext.Messages.Count);
- Assert.Equal(AgentRequestMessageSourceType.External, aiContext.Messages[0].GetAgentRequestMessageSourceType());
- var contextMessage = aiContext.Messages[1];
+ var messages = aiContext.Messages.ToList();
+ Assert.Equal(2, messages.Count);
+ Assert.Equal(AgentRequestMessageSourceType.External, messages[0].GetAgentRequestMessageSourceType());
+ var contextMessage = messages[1];
Assert.Equal(ChatRole.User, contextMessage.Role);
Assert.Contains("Name is Caoimhe", contextMessage.Text);
Assert.Equal(AgentRequestMessageSourceType.AIContextProvider, contextMessage.GetAgentRequestMessageSourceType());
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs
index 1517b8c7fb..7b33cbbd5f 100644
--- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs
@@ -496,9 +496,9 @@ public partial class ChatClientAgentTests
.Returns((AIContextProvider.InvokingContext ctx, CancellationToken _) =>
new ValueTask(new AIContext
{
- Messages = (ctx.AIContext.Messages ?? []).Concat(aiContextProviderMessages).ToList(),
+ Messages = (ctx.AIContext.Messages ?? []).Concat(aiContextProviderMessages),
Instructions = ctx.AIContext.Instructions + "\ncontext provider instructions",
- Tools = (ctx.AIContext.Tools ?? []).Concat(new[] { AIFunctionFactory.Create(() => { }, "context provider function") }).ToList()
+ Tools = (ctx.AIContext.Tools ?? []).Concat(new[] { AIFunctionFactory.Create(() => { }, "context provider function") })
}));
mockProvider
.Protected()
@@ -567,7 +567,7 @@ public partial class ChatClientAgentTests
.Returns((AIContextProvider.InvokingContext ctx, CancellationToken _) =>
new ValueTask(new AIContext
{
- Messages = (ctx.AIContext.Messages ?? []).Concat(aiContextProviderMessages).ToList(),
+ Messages = (ctx.AIContext.Messages ?? []).Concat(aiContextProviderMessages),
}));
mockProvider
.Protected()
@@ -626,7 +626,7 @@ public partial class ChatClientAgentTests
new ValueTask(new AIContext
{
Instructions = ctx.AIContext.Instructions,
- Messages = ctx.AIContext.Messages?.ToList(),
+ Messages = ctx.AIContext.Messages,
Tools = ctx.AIContext.Tools
}));
@@ -1875,9 +1875,9 @@ public partial class ChatClientAgentTests
.Returns((AIContextProvider.InvokingContext ctx, CancellationToken _) =>
new ValueTask(new AIContext
{
- Messages = (ctx.AIContext.Messages ?? []).Concat(aiContextProviderMessages).ToList(),
+ Messages = (ctx.AIContext.Messages ?? []).Concat(aiContextProviderMessages),
Instructions = ctx.AIContext.Instructions + "\ncontext provider instructions",
- Tools = (ctx.AIContext.Tools ?? []).Concat(new[] { AIFunctionFactory.Create(() => { }, "context provider function") }).ToList()
+ Tools = (ctx.AIContext.Tools ?? []).Concat(new[] { AIFunctionFactory.Create(() => { }, "context provider function") })
}));
mockProvider
.Protected()
@@ -1954,7 +1954,7 @@ public partial class ChatClientAgentTests
.Returns((AIContextProvider.InvokingContext ctx, CancellationToken _) =>
new ValueTask(new AIContext
{
- Messages = (ctx.AIContext.Messages ?? []).Concat(aiContextProviderMessages).ToList(),
+ Messages = (ctx.AIContext.Messages ?? []).Concat(aiContextProviderMessages),
}));
mockProvider
.Protected()
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/Data/TextSearchProviderTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Data/TextSearchProviderTests.cs
index 98cb2ce85a..4b39c778d0 100644
--- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/Data/TextSearchProviderTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Data/TextSearchProviderTests.cs
@@ -106,12 +106,13 @@ public sealed class TextSearchProviderTests
Assert.Equal("Sample user question?\nAdditional part", capturedInput);
Assert.Null(aiContext.Instructions); // TextSearchProvider uses a user message for context injection.
Assert.NotNull(aiContext.Messages);
- Assert.Equal(3, aiContext.Messages!.Count); // 2 input messages + 1 search result message
- Assert.Equal("Sample user question?", aiContext.Messages![0].Text);
- Assert.Equal("Additional part", aiContext.Messages![1].Text);
- Assert.Equal(AgentRequestMessageSourceType.External, aiContext.Messages![0].GetAgentRequestMessageSourceType());
- Assert.Equal(AgentRequestMessageSourceType.External, aiContext.Messages![1].GetAgentRequestMessageSourceType());
- var message = aiContext.Messages!.Last();
+ var messages = aiContext.Messages!.ToList();
+ Assert.Equal(3, messages.Count); // 2 input messages + 1 search result message
+ Assert.Equal("Sample user question?", messages[0].Text);
+ Assert.Equal("Additional part", messages[1].Text);
+ Assert.Equal(AgentRequestMessageSourceType.External, messages[0].GetAgentRequestMessageSourceType());
+ Assert.Equal(AgentRequestMessageSourceType.External, messages[1].GetAgentRequestMessageSourceType());
+ var message = messages.Last();
Assert.Equal(ChatRole.User, message.Role);
Assert.Equal(AgentRequestMessageSourceType.AIContextProvider, message.GetAgentRequestMessageSourceType());
string text = message.Text!;
@@ -181,11 +182,13 @@ public sealed class TextSearchProviderTests
// Assert
Assert.NotNull(aiContext.Messages); // Input messages are preserved.
- Assert.Single(aiContext.Messages!);
- Assert.Equal("Q?", aiContext.Messages![0].Text);
+ var messages = aiContext.Messages!.ToList();
+ Assert.Single(messages);
+ Assert.Equal("Q?", messages[0].Text);
Assert.NotNull(aiContext.Tools);
- Assert.Single(aiContext.Tools);
- var tool = aiContext.Tools.Single();
+ var tools = aiContext.Tools!.ToList();
+ Assert.Single(tools);
+ var tool = tools[0];
Assert.Equal(expectedName, tool.Name);
Assert.Equal(expectedDescription, tool.Description);
}
@@ -202,8 +205,9 @@ public sealed class TextSearchProviderTests
// Assert
Assert.NotNull(aiContext.Messages); // Input messages are preserved on error.
- Assert.Single(aiContext.Messages!);
- Assert.Equal("Q?", aiContext.Messages![0].Text);
+ var messages = aiContext.Messages!.ToList();
+ Assert.Single(messages);
+ Assert.Equal("Q?", messages[0].Text);
Assert.Null(aiContext.Tools);
this._loggerMock.Verify(
l => l.Log(
@@ -297,9 +301,10 @@ public sealed class TextSearchProviderTests
// Assert
Assert.NotNull(aiContext.Messages);
- Assert.Equal(2, aiContext.Messages!.Count); // 1 input message + 1 formatted result message
- Assert.Equal("Q?", aiContext.Messages![0].Text);
- Assert.Equal("Custom formatted context with 2 results.", aiContext.Messages![1].Text);
+ var messages = aiContext.Messages!.ToList();
+ Assert.Equal(2, messages.Count); // 1 input message + 1 formatted result message
+ Assert.Equal("Q?", messages[0].Text);
+ Assert.Equal("Custom formatted context with 2 results.", messages[1].Text);
}
[Fact]
@@ -332,9 +337,10 @@ public sealed class TextSearchProviderTests
// Assert
Assert.NotNull(aiContext.Messages);
- Assert.Equal(2, aiContext.Messages!.Count); // 1 input message + 1 formatted result message
- Assert.Equal("Q?", aiContext.Messages![0].Text);
- Assert.Equal("R1,R2", aiContext.Messages![1].Text);
+ var messages = aiContext.Messages!.ToList();
+ Assert.Equal(2, messages.Count); // 1 input message + 1 formatted result message
+ Assert.Equal("Q?", messages[0].Text);
+ Assert.Equal("R1,R2", messages[1].Text);
}
[Fact]
@@ -350,8 +356,9 @@ public sealed class TextSearchProviderTests
// Assert
Assert.NotNull(aiContext.Messages); // Input messages are preserved when no results found.
- Assert.Single(aiContext.Messages!);
- Assert.Equal("Q?", aiContext.Messages![0].Text);
+ var messages = aiContext.Messages!.ToList();
+ Assert.Single(messages);
+ Assert.Equal("Q?", messages[0].Text);
Assert.Null(aiContext.Instructions);
Assert.Null(aiContext.Tools);
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/Memory/ChatHistoryMemoryProviderTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Memory/ChatHistoryMemoryProviderTests.cs
index 2d7d8a74c0..f7558c39a5 100644
--- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/Memory/ChatHistoryMemoryProviderTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/Memory/ChatHistoryMemoryProviderTests.cs
@@ -396,9 +396,10 @@ public class ChatHistoryMemoryProviderTests
Times.Once);
Assert.NotNull(aiContext.Messages);
- Assert.Equal(2, aiContext.Messages.Count);
- Assert.Equal(AgentRequestMessageSourceType.External, aiContext.Messages[0].GetAgentRequestMessageSourceType());
- Assert.Equal(AgentRequestMessageSourceType.AIContextProvider, aiContext.Messages[1].GetAgentRequestMessageSourceType());
+ var messages = aiContext.Messages.ToList();
+ Assert.Equal(2, messages.Count);
+ Assert.Equal(AgentRequestMessageSourceType.External, messages[0].GetAgentRequestMessageSourceType());
+ Assert.Equal(AgentRequestMessageSourceType.AIContextProvider, messages[1].GetAgentRequestMessageSourceType());
}
[Fact]