Modify InvokedContext to be immutable (#3888)

This commit is contained in:
westey
2026-02-12 19:30:29 +00:00
committed by GitHub
Unverified
parent 72a863f4bf
commit c071a13ab6
12 changed files with 174 additions and 199 deletions
@@ -65,10 +65,7 @@ namespace SampleApp
List<ChatMessage> responseMessages = CloneAndToUpperCase(messages, this.Name).ToList();
// Notify the session of the input and output messages.
var invokedContext = new ChatHistoryProvider.InvokedContext(this, session, messages)
{
ResponseMessages = responseMessages
};
var invokedContext = new ChatHistoryProvider.InvokedContext(this, session, messages, responseMessages);
await this.ChatHistoryProvider.InvokedAsync(invokedContext, cancellationToken);
return new AgentResponse
@@ -97,10 +94,7 @@ namespace SampleApp
List<ChatMessage> responseMessages = CloneAndToUpperCase(messages, this.Name).ToList();
// Notify the session of the input and output messages.
var invokedContext = new ChatHistoryProvider.InvokedContext(this, session, messages)
{
ResponseMessages = responseMessages
};
var invokedContext = new ChatHistoryProvider.InvokedContext(this, session, messages, responseMessages);
await this.ChatHistoryProvider.InvokedAsync(invokedContext, cancellationToken);
foreach (var message in responseMessages)
@@ -230,20 +230,43 @@ public abstract class AIContextProvider
public sealed class InvokedContext
{
/// <summary>
/// Initializes a new instance of the <see cref="InvokedContext"/> class with the specified request messages.
/// Initializes a new instance of the <see cref="InvokedContext"/> class for a successful invocation.
/// </summary>
/// <param name="agent">The agent being invoked.</param>
/// <param name="agent">The agent that was invoked.</param>
/// <param name="session">The session associated with the agent invocation.</param>
/// <param name="requestMessages">The messages that were used by the agent for this invocation.</param>
/// <exception cref="ArgumentNullException"><paramref name="requestMessages"/> is <see langword="null"/>.</exception>
/// <param name="responseMessages">The response messages generated during this invocation.</param>
/// <exception cref="ArgumentNullException"><paramref name="agent"/>, <paramref name="requestMessages"/>, or <paramref name="responseMessages"/> is <see langword="null"/>.</exception>
public InvokedContext(
AIAgent agent,
AgentSession? session,
IEnumerable<ChatMessage> requestMessages)
IEnumerable<ChatMessage> requestMessages,
IEnumerable<ChatMessage> responseMessages)
{
this.Agent = Throw.IfNull(agent);
this.Session = session;
this.RequestMessages = Throw.IfNull(requestMessages);
this.ResponseMessages = Throw.IfNull(responseMessages);
}
/// <summary>
/// Initializes a new instance of the <see cref="InvokedContext"/> class for a failed invocation.
/// </summary>
/// <param name="agent">The agent that was invoked.</param>
/// <param name="session">The session associated with the agent invocation.</param>
/// <param name="requestMessages">The messages that were used by the agent for this invocation.</param>
/// <param name="invokeException">The exception that caused the invocation to fail.</param>
/// <exception cref="ArgumentNullException"><paramref name="agent"/>, <paramref name="requestMessages"/>, or <paramref name="invokeException"/> is <see langword="null"/>.</exception>
public InvokedContext(
AIAgent agent,
AgentSession? session,
IEnumerable<ChatMessage> requestMessages,
Exception invokeException)
{
this.Agent = Throw.IfNull(agent);
this.Session = session;
this.RequestMessages = Throw.IfNull(requestMessages);
this.InvokeException = Throw.IfNull(invokeException);
}
/// <summary>
@@ -262,16 +285,16 @@ public abstract class AIContextProvider
/// <value>
/// A collection of <see cref="ChatMessage"/> instances representing all messages that were used by the agent for this invocation.
/// </value>
public IEnumerable<ChatMessage> RequestMessages { get; set { field = Throw.IfNull(value); } }
public IEnumerable<ChatMessage> RequestMessages { get; }
/// <summary>
/// Gets the collection of response messages generated during this invocation if the invocation succeeded.
/// </summary>
/// <value>
/// A collection of <see cref="ChatMessage"/> instances representing the response,
/// or <see langword="null"/> if the invocation failed or did not produce response messages.
/// or <see langword="null"/> if the invocation failed.
/// </value>
public IEnumerable<ChatMessage>? ResponseMessages { get; set; }
public IEnumerable<ChatMessage>? ResponseMessages { get; }
/// <summary>
/// Gets the <see cref="Exception"/> that was thrown during the invocation, if the invocation failed.
@@ -279,6 +302,6 @@ public abstract class AIContextProvider
/// <value>
/// The exception that caused the invocation to fail, or <see langword="null"/> if the invocation succeeded.
/// </value>
public Exception? InvokeException { get; set; }
public Exception? InvokeException { get; }
}
}
@@ -260,20 +260,43 @@ public abstract class ChatHistoryProvider
public sealed class InvokedContext
{
/// <summary>
/// Initializes a new instance of the <see cref="InvokedContext"/> class with the specified request messages.
/// Initializes a new instance of the <see cref="InvokedContext"/> class for a successful invocation.
/// </summary>
/// <param name="agent">The agent being invoked.</param>
/// <param name="agent">The agent that was invoked.</param>
/// <param name="session">The session associated with the agent invocation.</param>
/// <param name="requestMessages">The caller provided messages that were used by the agent for this invocation.</param>
/// <exception cref="ArgumentNullException"><paramref name="requestMessages"/> is <see langword="null"/>.</exception>
/// <param name="responseMessages">The response messages generated during this invocation.</param>
/// <exception cref="ArgumentNullException"><paramref name="agent"/>, <paramref name="requestMessages"/>, or <paramref name="responseMessages"/> is <see langword="null"/>.</exception>
public InvokedContext(
AIAgent agent,
AgentSession? session,
IEnumerable<ChatMessage> requestMessages)
IEnumerable<ChatMessage> requestMessages,
IEnumerable<ChatMessage> responseMessages)
{
this.Agent = Throw.IfNull(agent);
this.Session = session;
this.RequestMessages = Throw.IfNull(requestMessages);
this.ResponseMessages = Throw.IfNull(responseMessages);
}
/// <summary>
/// Initializes a new instance of the <see cref="InvokedContext"/> class for a failed invocation.
/// </summary>
/// <param name="agent">The agent that was invoked.</param>
/// <param name="session">The session associated with the agent invocation.</param>
/// <param name="requestMessages">The caller provided messages that were used by the agent for this invocation.</param>
/// <param name="invokeException">The exception that caused the invocation to fail.</param>
/// <exception cref="ArgumentNullException"><paramref name="agent"/>, <paramref name="requestMessages"/>, or <paramref name="invokeException"/> is <see langword="null"/>.</exception>
public InvokedContext(
AIAgent agent,
AgentSession? session,
IEnumerable<ChatMessage> requestMessages,
Exception invokeException)
{
this.Agent = Throw.IfNull(agent);
this.Session = session;
this.RequestMessages = Throw.IfNull(requestMessages);
this.InvokeException = Throw.IfNull(invokeException);
}
/// <summary>
@@ -293,16 +316,16 @@ public abstract class ChatHistoryProvider
/// A collection of <see cref="ChatMessage"/> instances representing new messages that were provided by the caller.
/// This does not include any <see cref="ChatHistoryProvider"/> supplied messages.
/// </value>
public IEnumerable<ChatMessage> RequestMessages { get; set { field = Throw.IfNull(value); } }
public IEnumerable<ChatMessage> RequestMessages { get; }
/// <summary>
/// Gets the collection of response messages generated during this invocation if the invocation succeeded.
/// </summary>
/// <value>
/// A collection of <see cref="ChatMessage"/> instances representing the response,
/// or <see langword="null"/> if the invocation failed or did not produce response messages.
/// or <see langword="null"/> if the invocation failed.
/// </value>
public IEnumerable<ChatMessage>? ResponseMessages { get; set; }
public IEnumerable<ChatMessage>? ResponseMessages { get; }
/// <summary>
/// Gets the <see cref="Exception"/> that was thrown during the invocation, if the invocation failed.
@@ -310,6 +333,6 @@ public abstract class ChatHistoryProvider
/// <value>
/// The exception that caused the invocation to fail, or <see langword="null"/> if the invocation succeeded.
/// </value>
public Exception? InvokeException { get; set; }
public Exception? InvokeException { get; }
}
}
@@ -455,7 +455,7 @@ public sealed partial class ChatClientAgent : AIAgent
{
if (this.AIContextProviders is { Count: > 0 } contextProviders)
{
AIContextProvider.InvokedContext invokedContext = new(this, session, inputMessages) { ResponseMessages = responseMessages };
AIContextProvider.InvokedContext invokedContext = new(this, session, inputMessages, responseMessages);
foreach (var contextProvider in contextProviders)
{
@@ -475,7 +475,7 @@ public sealed partial class ChatClientAgent : AIAgent
{
if (this.AIContextProviders is { Count: > 0 } contextProviders)
{
AIContextProvider.InvokedContext invokedContext = new(this, session, inputMessages) { InvokeException = ex };
AIContextProvider.InvokedContext invokedContext = new(this, session, inputMessages, ex);
foreach (var contextProvider in contextProviders)
{
@@ -797,10 +797,7 @@ public sealed partial class ChatClientAgent : AIAgent
// If we don't have one, it means that the chat history is service managed and the underlying service is responsible for storing messages.
if (provider is not null)
{
var invokedContext = new ChatHistoryProvider.InvokedContext(this, session, requestMessages)
{
InvokeException = ex
};
var invokedContext = new ChatHistoryProvider.InvokedContext(this, session, requestMessages, ex);
return provider.InvokedAsync(invokedContext, cancellationToken).AsTask();
}
@@ -821,10 +818,7 @@ public sealed partial class ChatClientAgent : AIAgent
// If we don't have one, it means that the chat history is service managed and the underlying service is responsible for storing messages.
if (provider is not null)
{
var invokedContext = new ChatHistoryProvider.InvokedContext(this, session, requestMessages)
{
ResponseMessages = responseMessages
};
var invokedContext = new ChatHistoryProvider.InvokedContext(this, session, requestMessages, responseMessages);
return provider.InvokedAsync(invokedContext, cancellationToken).AsTask();
}
@@ -25,7 +25,7 @@ public class AIContextProviderTests
var messages = new ReadOnlyCollection<ChatMessage>([]);
// Act
ValueTask task = provider.InvokedAsync(new(s_mockAgent, s_mockSession, messages));
ValueTask task = provider.InvokedAsync(new(s_mockAgent, s_mockSession, messages, []));
// Assert
Assert.Equal(default, task);
@@ -42,7 +42,7 @@ public class AIContextProviderTests
public void InvokedContext_Constructor_ThrowsForNullMessages()
{
// Act & Assert
Assert.Throws<ArgumentNullException>(() => new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, null!));
Assert.Throws<ArgumentNullException>(() => new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, null!, []));
}
#endregion
@@ -238,42 +238,15 @@ public class AIContextProviderTests
#region InvokedContext Tests
[Fact]
public void InvokedContext_RequestMessages_SetterThrowsForNull()
{
// Arrange
var messages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, messages);
// Act & Assert
Assert.Throws<ArgumentNullException>(() => context.RequestMessages = null!);
}
[Fact]
public void InvokedContext_RequestMessages_SetterRoundtrips()
{
// Arrange
var initialMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
var newMessages = new List<ChatMessage> { new(ChatRole.User, "New message") };
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, initialMessages);
// Act
context.RequestMessages = newMessages;
// Assert
Assert.Same(newMessages, context.RequestMessages);
}
[Fact]
public void InvokedContext_ResponseMessages_Roundtrips()
{
// Arrange
var requestMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
var responseMessages = new List<ChatMessage> { new(ChatRole.Assistant, "Response message") };
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages);
// Act
context.ResponseMessages = responseMessages;
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, responseMessages);
// Assert
Assert.Same(responseMessages, context.ResponseMessages);
@@ -285,10 +258,9 @@ public class AIContextProviderTests
// Arrange
var requestMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
var exception = new InvalidOperationException("Test exception");
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages);
// Act
context.InvokeException = exception;
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, exception);
// Assert
Assert.Same(exception, context.InvokeException);
@@ -301,7 +273,7 @@ public class AIContextProviderTests
var requestMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
// Act
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages);
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, []);
// Assert
Assert.Same(s_mockAgent, context.Agent);
@@ -314,7 +286,7 @@ public class AIContextProviderTests
var requestMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
// Act
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages);
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, []);
// Assert
Assert.Same(s_mockSession, context.Session);
@@ -327,7 +299,7 @@ public class AIContextProviderTests
var requestMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
// Act
var context = new AIContextProvider.InvokedContext(s_mockAgent, null, requestMessages);
var context = new AIContextProvider.InvokedContext(s_mockAgent, null, requestMessages, []);
// Assert
Assert.Null(context.Session);
@@ -340,7 +312,27 @@ public class AIContextProviderTests
var requestMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
// Act & Assert
Assert.Throws<ArgumentNullException>(() => new AIContextProvider.InvokedContext(null!, s_mockSession, requestMessages));
Assert.Throws<ArgumentNullException>(() => new AIContextProvider.InvokedContext(null!, s_mockSession, requestMessages, []));
}
[Fact]
public void InvokedContext_SuccessConstructor_ThrowsForNullResponseMessages()
{
// Arrange
var requestMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
// Act & Assert
Assert.Throws<ArgumentNullException>(() => new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, (IEnumerable<ChatMessage>)null!));
}
[Fact]
public void InvokedContext_FailureConstructor_ThrowsForNullException()
{
// Arrange
var requestMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
// Act & Assert
Assert.Throws<ArgumentNullException>(() => new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, (Exception)null!));
}
#endregion
@@ -172,33 +172,7 @@ public class ChatHistoryProviderTests
public void InvokedContext_Constructor_ThrowsForNullRequestMessages()
{
// Arrange & Act & Assert
Assert.Throws<ArgumentNullException>(() => new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, null!));
}
[Fact]
public void InvokedContext_RequestMessages_SetterThrowsForNull()
{
// Arrange
var requestMessages = new List<ChatMessage> { new(ChatRole.User, "Hello") };
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages);
// Act & Assert
Assert.Throws<ArgumentNullException>(() => context.RequestMessages = null!);
}
[Fact]
public void InvokedContext_RequestMessages_SetterRoundtrips()
{
// Arrange
var initialMessages = new List<ChatMessage> { new(ChatRole.User, "Hello") };
var newMessages = new List<ChatMessage> { new(ChatRole.User, "New message") };
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, initialMessages);
// Act
context.RequestMessages = newMessages;
// Assert
Assert.Same(newMessages, context.RequestMessages);
Assert.Throws<ArgumentNullException>(() => new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, null!, []));
}
[Fact]
@@ -207,10 +181,9 @@ public class ChatHistoryProviderTests
// Arrange
var requestMessages = new List<ChatMessage> { new(ChatRole.User, "Hello") };
var responseMessages = new List<ChatMessage> { new(ChatRole.Assistant, "Response message") };
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages);
// Act
context.ResponseMessages = responseMessages;
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, responseMessages);
// Assert
Assert.Same(responseMessages, context.ResponseMessages);
@@ -222,10 +195,9 @@ public class ChatHistoryProviderTests
// Arrange
var requestMessages = new List<ChatMessage> { new(ChatRole.User, "Hello") };
var exception = new InvalidOperationException("Test exception");
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages);
// Act
context.InvokeException = exception;
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, exception);
// Assert
Assert.Same(exception, context.InvokeException);
@@ -238,7 +210,7 @@ public class ChatHistoryProviderTests
var requestMessages = new List<ChatMessage> { new(ChatRole.User, "Hello") };
// Act
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages);
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, []);
// Assert
Assert.Same(s_mockAgent, context.Agent);
@@ -251,7 +223,7 @@ public class ChatHistoryProviderTests
var requestMessages = new List<ChatMessage> { new(ChatRole.User, "Hello") };
// Act
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages);
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, []);
// Assert
Assert.Same(s_mockSession, context.Session);
@@ -264,7 +236,7 @@ public class ChatHistoryProviderTests
var requestMessages = new List<ChatMessage> { new(ChatRole.User, "Hello") };
// Act
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, null, requestMessages);
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, null, requestMessages, []);
// Assert
Assert.Null(context.Session);
@@ -277,7 +249,27 @@ public class ChatHistoryProviderTests
var requestMessages = new List<ChatMessage> { new(ChatRole.User, "Hello") };
// Act & Assert
Assert.Throws<ArgumentNullException>(() => new ChatHistoryProvider.InvokedContext(null!, s_mockSession, requestMessages));
Assert.Throws<ArgumentNullException>(() => new ChatHistoryProvider.InvokedContext(null!, s_mockSession, requestMessages, []));
}
[Fact]
public void InvokedContext_SuccessConstructor_ThrowsForNullResponseMessages()
{
// Arrange
var requestMessages = new List<ChatMessage> { new(ChatRole.User, "Hello") };
// Act & Assert
Assert.Throws<ArgumentNullException>(() => new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, (IEnumerable<ChatMessage>)null!));
}
[Fact]
public void InvokedContext_FailureConstructor_ThrowsForNullException()
{
// Arrange
var requestMessages = new List<ChatMessage> { new(ChatRole.User, "Hello") };
// Act & Assert
Assert.Throws<ArgumentNullException>(() => new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, (Exception)null!));
}
#endregion
@@ -84,10 +84,7 @@ public class InMemoryChatHistoryProviderTests
var provider = new InMemoryChatHistoryProvider();
provider.SetMessages(session, providerMessages);
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, requestMessages)
{
ResponseMessages = responseMessages
};
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, requestMessages, responseMessages);
await provider.InvokedAsync(context, CancellationToken.None);
// Assert
@@ -107,9 +104,8 @@ public class InMemoryChatHistoryProviderTests
// Arrange
var provider = new InMemoryChatHistoryProvider();
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, []);
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, [], []);
await provider.InvokedAsync(context, CancellationToken.None);
// Assert
Assert.Empty(provider.GetMessages(session));
}
@@ -222,7 +218,7 @@ public class InMemoryChatHistoryProviderTests
var provider = new InMemoryChatHistoryProvider();
var messages = new List<ChatMessage>();
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, messages);
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, messages, []);
await provider.InvokedAsync(context, CancellationToken.None);
// Assert
@@ -263,7 +259,7 @@ public class InMemoryChatHistoryProviderTests
var provider = new InMemoryChatHistoryProvider(new() { ChatReducer = reducerMock.Object, ReducerTriggerEvent = InMemoryChatHistoryProviderOptions.ChatReducerTriggerEvent.AfterMessageAdded });
// Act
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, originalMessages);
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, originalMessages, []);
await provider.InvokedAsync(context, CancellationToken.None);
// Assert
@@ -323,7 +319,7 @@ public class InMemoryChatHistoryProviderTests
var provider = new InMemoryChatHistoryProvider(new() { ChatReducer = reducerMock.Object, ReducerTriggerEvent = InMemoryChatHistoryProviderOptions.ChatReducerTriggerEvent.BeforeMessagesRetrieval });
// Act
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, originalMessages);
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, originalMessages, []);
await provider.InvokedAsync(context, CancellationToken.None);
// Assert
@@ -370,15 +366,7 @@ public class InMemoryChatHistoryProviderTests
{
new(ChatRole.User, "Hello")
};
var responseMessages = new List<ChatMessage>
{
new(ChatRole.Assistant, "Hi there!")
};
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, requestMessages)
{
ResponseMessages = responseMessages,
InvokeException = new InvalidOperationException("Test exception")
};
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, requestMessages, new InvalidOperationException("Test exception"));
// Act
await provider.InvokedAsync(context, CancellationToken.None);
@@ -410,10 +398,7 @@ public class InMemoryChatHistoryProviderTests
new(ChatRole.System, "From context provider") { AdditionalProperties = new() { { AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.AIContextProvider, "ContextSource") } } },
};
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, requestMessages)
{
ResponseMessages = [new ChatMessage(ChatRole.Assistant, "Response")]
};
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, requestMessages, [new ChatMessage(ChatRole.Assistant, "Response")]);
// Act
await provider.InvokedAsync(context, CancellationToken.None);
@@ -442,10 +427,7 @@ public class InMemoryChatHistoryProviderTests
new(ChatRole.System, "From context provider") { AdditionalProperties = new() { { AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.AIContextProvider, "ContextSource") } } },
};
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, requestMessages)
{
ResponseMessages = [new ChatMessage(ChatRole.Assistant, "Response")]
};
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, requestMessages, [new ChatMessage(ChatRole.Assistant, "Response")]);
// Act
await provider.InvokedAsync(context, CancellationToken.None);
@@ -231,10 +231,7 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
_ => new CosmosChatHistoryProvider.State(conversationId));
var message = new ChatMessage(ChatRole.User, "Hello, world!");
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, [message])
{
ResponseMessages = []
};
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, [message], []);
// Act
await provider.InvokedAsync(context);
@@ -309,10 +306,7 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
new ChatMessage(ChatRole.Assistant, "Response message")
};
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, requestMessages)
{
ResponseMessages = responseMessages
};
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, requestMessages, responseMessages);
// Act
await provider.InvokedAsync(context);
@@ -367,8 +361,8 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
using var store2 = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, TestContainerId,
_ => new CosmosChatHistoryProvider.State(conversation2), stateKey: "conv2");
var context1 = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, [new ChatMessage(ChatRole.User, "Message for conversation 1")]);
var context2 = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, [new ChatMessage(ChatRole.User, "Message for conversation 2")]);
var context1 = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, [new ChatMessage(ChatRole.User, "Message for conversation 1")], []);
var context2 = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, [new ChatMessage(ChatRole.User, "Message for conversation 2")], []);
await store1.InvokedAsync(context1);
await store2.InvokedAsync(context2);
@@ -416,7 +410,7 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
};
// Act 1: Add messages
var invokedContext = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, messages);
var invokedContext = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, messages, []);
await originalStore.InvokedAsync(invokedContext);
// Act 2: Verify messages were added
@@ -562,7 +556,7 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
_ => new CosmosChatHistoryProvider.State(SessionId, TenantId, UserId));
var message = new ChatMessage(ChatRole.User, "Hello from hierarchical partitioning!");
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, [message]);
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, [message], []);
// Act
await provider.InvokedAsync(context);
@@ -621,7 +615,7 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
new ChatMessage(ChatRole.User, "Third hierarchical message")
};
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, messages);
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, messages, []);
// Act
await provider.InvokedAsync(context);
@@ -660,8 +654,8 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
_ => new CosmosChatHistoryProvider.State(SessionId, TenantId, UserId2), stateKey: "user2");
// Add messages to both stores
var context1 = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, [new ChatMessage(ChatRole.User, "Message from user 1")]);
var context2 = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, [new ChatMessage(ChatRole.User, "Message from user 2")]);
var context1 = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, [new ChatMessage(ChatRole.User, "Message from user 1")], []);
var context2 = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, [new ChatMessage(ChatRole.User, "Message from user 2")], []);
await store1.InvokedAsync(context1);
await store2.InvokedAsync(context2);
@@ -700,7 +694,7 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
using var originalStore = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId,
_ => new CosmosChatHistoryProvider.State(SessionId, TenantId, UserId));
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, [new ChatMessage(ChatRole.User, "Test serialization message")]);
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, [new ChatMessage(ChatRole.User, "Test serialization message")], []);
await originalStore.InvokedAsync(context);
// Wait a moment for eventual consistency
@@ -738,8 +732,8 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
_ => new CosmosChatHistoryProvider.State(SessionId, "tenant-coexist", "user-coexist"), stateKey: "hierarchical");
// Add messages to both
var simpleContext = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, [new ChatMessage(ChatRole.User, "Simple partitioning message")]);
var hierarchicalContext = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, [new ChatMessage(ChatRole.User, "Hierarchical partitioning message")]);
var simpleContext = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, [new ChatMessage(ChatRole.User, "Simple partitioning message")], []);
var hierarchicalContext = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, [new ChatMessage(ChatRole.User, "Hierarchical partitioning message")], []);
await simpleProvider.InvokedAsync(simpleContext);
await hierarchicalProvider.InvokedAsync(hierarchicalContext);
@@ -783,7 +777,7 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
await Task.Delay(10); // Small delay to ensure different timestamps
}
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, messages);
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, messages, []);
await provider.InvokedAsync(context);
// Wait for eventual consistency
@@ -823,7 +817,7 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
messages.Add(new ChatMessage(ChatRole.User, $"Message {i}"));
}
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, messages);
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, messages, []);
await provider.InvokedAsync(context);
// Wait for eventual consistency
@@ -862,10 +856,7 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
new ChatMessage(ChatRole.System, "From context provider") { AdditionalProperties = new() { { AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.AIContextProvider, "ContextSource") } } },
};
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, requestMessages)
{
ResponseMessages = [new ChatMessage(ChatRole.Assistant, "Response")]
};
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, requestMessages, [new ChatMessage(ChatRole.Assistant, "Response")]);
// Act
await provider.InvokedAsync(context);
@@ -904,10 +895,7 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
new ChatMessage(ChatRole.System, "From context provider") { AdditionalProperties = new() { { AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.AIContextProvider, "ContextSource") } } },
};
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, requestMessages)
{
ResponseMessages = [new ChatMessage(ChatRole.Assistant, "Response")]
};
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, requestMessages, [new ChatMessage(ChatRole.Assistant, "Response")]);
// Act
await provider.InvokedAsync(context);
@@ -944,10 +932,7 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
new ChatMessage(ChatRole.System, "System message"),
};
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, requestMessages)
{
ResponseMessages = [new ChatMessage(ChatRole.Assistant, "Assistant response")]
};
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, session, requestMessages, [new ChatMessage(ChatRole.Assistant, "Assistant response")]);
await provider.InvokedAsync(context);
@@ -58,7 +58,7 @@ public sealed class Mem0ProviderTests : IDisposable
Assert.DoesNotContain("Caoimhe", ctxBefore.Messages?.LastOrDefault()?.Text ?? string.Empty);
// Act
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, mockSession, [input]));
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, mockSession, [input], []));
var ctxAfterAdding = await GetContextWithRetryAsync(sut, mockSession, question);
await sut.ClearStoredMemoriesAsync(mockSession);
var ctxAfterClearing = await sut.InvokingAsync(new AIContextProvider.InvokingContext(s_mockAgent, mockSession, new AIContext { Messages = new List<ChatMessage> { question } }));
@@ -83,7 +83,7 @@ public sealed class Mem0ProviderTests : IDisposable
Assert.DoesNotContain("Caoimhe", ctxBefore.Messages?.LastOrDefault()?.Text ?? string.Empty);
// Act
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, mockSession, [assistantIntro]));
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, mockSession, [assistantIntro], []));
var ctxAfterAdding = await GetContextWithRetryAsync(sut, mockSession, question);
await sut.ClearStoredMemoriesAsync(mockSession);
var ctxAfterClearing = await sut.InvokingAsync(new AIContextProvider.InvokingContext(s_mockAgent, mockSession, new AIContext { Messages = new List<ChatMessage> { question } }));
@@ -115,7 +115,7 @@ public sealed class Mem0ProviderTests : IDisposable
Assert.DoesNotContain("Caoimhe", ctxBefore2.Messages?.LastOrDefault()?.Text ?? string.Empty);
// Act
await sut1.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, mockSession1, [assistantIntro]));
await sut1.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, mockSession1, [assistantIntro], []));
var ctxAfterAdding1 = await GetContextWithRetryAsync(sut1, mockSession1, question);
var ctxAfterAdding2 = await GetContextWithRetryAsync(sut2, mockSession2, question);
@@ -228,7 +228,7 @@ public sealed class Mem0ProviderTests : IDisposable
};
// Act
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, mockSession, requestMessages) { ResponseMessages = responseMessages });
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, mockSession, requestMessages, responseMessages));
// Assert
var memoryPosts = this._handler.Requests.Where(r => r.RequestMessage.RequestUri!.AbsolutePath == "/v1/memories/" && r.RequestMessage.Method == HttpMethod.Post).ToList();
@@ -256,7 +256,7 @@ public sealed class Mem0ProviderTests : IDisposable
};
// Act
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, mockSession, requestMessages) { ResponseMessages = null, InvokeException = new InvalidOperationException("Request Failed") });
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, mockSession, requestMessages, new InvalidOperationException("Request Failed")));
// Assert
Assert.Empty(this._handler.Requests);
@@ -283,7 +283,7 @@ public sealed class Mem0ProviderTests : IDisposable
};
// Act
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, mockSession, requestMessages) { ResponseMessages = responseMessages });
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, mockSession, requestMessages, responseMessages));
// Assert
this._loggerMock.Verify(
@@ -334,7 +334,7 @@ public sealed class Mem0ProviderTests : IDisposable
};
// Act
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, mockSession, requestMessages) { ResponseMessages = responseMessages });
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, mockSession, requestMessages, responseMessages));
// Assert
Assert.Equal(expectedLogCount, this._loggerMock.Invocations.Count);
@@ -511,7 +511,7 @@ public sealed class Mem0ProviderTests : IDisposable
};
// Act
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, mockSession, requestMessages));
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, mockSession, requestMessages, []));
// Assert - Only the External message should be persisted
var memoryPosts = this._handler.Requests.Where(r => r.RequestMessage.RequestUri!.AbsolutePath == "/v1/memories/" && r.RequestMessage.Method == HttpMethod.Post).ToList();
@@ -540,7 +540,7 @@ public sealed class Mem0ProviderTests : IDisposable
};
// Act
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, mockSession, requestMessages));
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, mockSession, requestMessages, []));
// Assert - Both messages should be persisted (identity filter overrides default)
var memoryPosts = this._handler.Requests.Where(r => r.RequestMessage.RequestUri!.AbsolutePath == "/v1/memories/" && r.RequestMessage.Method == HttpMethod.Post).ToList();
@@ -449,7 +449,7 @@ public sealed class TextSearchProviderTests
};
// Store messages via InvokedAsync
await provider.InvokedAsync(new(s_mockAgent, session, requestMessages));
await provider.InvokedAsync(new(s_mockAgent, session, requestMessages, []));
// Now invoke to read stored memory
var invokingContext = new AIContextProvider.InvokingContext(s_mockAgent, session, new AIContext { Messages = [new ChatMessage(ChatRole.User, "Next")] });
@@ -485,7 +485,7 @@ public sealed class TextSearchProviderTests
};
// Store messages via InvokedAsync
await provider.InvokedAsync(new(s_mockAgent, session, requestMessages));
await provider.InvokedAsync(new(s_mockAgent, session, requestMessages, []));
// Now invoke to read stored memory
var invokingContext = new AIContextProvider.InvokingContext(s_mockAgent, session, new AIContext { Messages = [new ChatMessage(ChatRole.User, "Next")] });
@@ -526,7 +526,7 @@ public sealed class TextSearchProviderTests
};
var session = new TestAgentSession();
await provider.InvokedAsync(new(s_mockAgent, session, initialMessages) { InvokeException = new InvalidOperationException("Request Failed") });
await provider.InvokedAsync(new(s_mockAgent, session, initialMessages, new InvalidOperationException("Request Failed")));
var invokingContext = new AIContextProvider.InvokingContext(
s_mockAgent,
@@ -567,7 +567,7 @@ public sealed class TextSearchProviderTests
new ChatMessage(ChatRole.User, "C"),
new ChatMessage(ChatRole.Assistant, "D"),
};
await provider.InvokedAsync(new(s_mockAgent, session, initialMessages));
await provider.InvokedAsync(new(s_mockAgent, session, initialMessages, []));
var invokingContext = new AIContextProvider.InvokingContext(
s_mockAgent,
@@ -607,7 +607,8 @@ public sealed class TextSearchProviderTests
[
new ChatMessage(ChatRole.User, "A"),
new ChatMessage(ChatRole.Assistant, "B"),
]));
],
[]));
// Second memory update (C,D,E)
await provider.InvokedAsync(new(
@@ -617,7 +618,8 @@ public sealed class TextSearchProviderTests
new ChatMessage(ChatRole.User, "C"),
new ChatMessage(ChatRole.Assistant, "D"),
new ChatMessage(ChatRole.User, "E"),
]));
],
[]));
var invokingContext = new AIContextProvider.InvokingContext(s_mockAgent, session, new AIContext { Messages = new List<ChatMessage> { new(ChatRole.User, "F") } });
@@ -655,7 +657,7 @@ public sealed class TextSearchProviderTests
new ChatMessage(ChatRole.User, "U2"),
new ChatMessage(ChatRole.Assistant, "A2"),
};
await provider.InvokedAsync(new(s_mockAgent, session, initialMessages));
await provider.InvokedAsync(new(s_mockAgent, session, initialMessages, []));
var invokingContext = new AIContextProvider.InvokingContext(
s_mockAgent,
@@ -693,7 +695,7 @@ public sealed class TextSearchProviderTests
};
// Act
await provider.InvokedAsync(new(s_mockAgent, session, messages)); // Populate recent memory.
await provider.InvokedAsync(new(s_mockAgent, session, messages, [])); // Populate recent memory.
// Assert - State should be in the session's StateBag
var stateBagSerialized = session.StateBag.Serialize();
@@ -724,7 +726,7 @@ public sealed class TextSearchProviderTests
new ChatMessage(ChatRole.User, "C"),
new ChatMessage(ChatRole.Assistant, "D"),
};
await provider.InvokedAsync(new(s_mockAgent, session, messages));
await provider.InvokedAsync(new(s_mockAgent, session, messages, []));
// Act - Serialize and deserialize the StateBag
var serializedStateBag = session.StateBag.Serialize();
@@ -170,10 +170,7 @@ public class ChatHistoryMemoryProviderTests
var requestMsgWithNulls = new ChatMessage(ChatRole.User, "request text nulls");
var responseMsg = new ChatMessage(ChatRole.Assistant, "response text") { MessageId = "resp-1", AuthorName = "assistant" };
var invokedContext = new AIContextProvider.InvokedContext(s_mockAgent, new TestAgentSession(), [requestMsgWithValues, requestMsgWithNulls])
{
ResponseMessages = [responseMsg]
};
var invokedContext = new AIContextProvider.InvokedContext(s_mockAgent, new TestAgentSession(), [requestMsgWithValues, requestMsgWithNulls], [responseMsg]);
// Act
await provider.InvokedAsync(invokedContext, CancellationToken.None);
@@ -228,10 +225,7 @@ public class ChatHistoryMemoryProviderTests
1,
_ => new ChatHistoryMemoryProvider.State(new ChatHistoryMemoryProviderScope { UserId = "UID" }));
var requestMsg = new ChatMessage(ChatRole.User, "request text") { MessageId = "req-1" };
var invokedContext = new AIContextProvider.InvokedContext(s_mockAgent, new TestAgentSession(), [requestMsg])
{
InvokeException = new InvalidOperationException("Invoke failed")
};
var invokedContext = new AIContextProvider.InvokedContext(s_mockAgent, new TestAgentSession(), [requestMsg], new InvalidOperationException("Invoke failed"));
// Act
await provider.InvokedAsync(invokedContext, CancellationToken.None);
@@ -257,7 +251,7 @@ public class ChatHistoryMemoryProviderTests
_ => new ChatHistoryMemoryProvider.State(new ChatHistoryMemoryProviderScope { UserId = "UID" }),
loggerFactory: this._loggerFactoryMock.Object);
var requestMsg = new ChatMessage(ChatRole.User, "request text") { MessageId = "req-1" };
var invokedContext = new AIContextProvider.InvokedContext(s_mockAgent, new TestAgentSession(), [requestMsg]);
var invokedContext = new AIContextProvider.InvokedContext(s_mockAgent, new TestAgentSession(), [requestMsg], []);
// Act
await provider.InvokedAsync(invokedContext, CancellationToken.None);
@@ -308,7 +302,7 @@ public class ChatHistoryMemoryProviderTests
loggerFactory: this._loggerFactoryMock.Object);
var requestMsg = new ChatMessage(ChatRole.User, "request text");
var invokedContext = new AIContextProvider.InvokedContext(s_mockAgent, new TestAgentSession(), [requestMsg]);
var invokedContext = new AIContextProvider.InvokedContext(s_mockAgent, new TestAgentSession(), [requestMsg], []);
// Act
await provider.InvokedAsync(invokedContext, CancellationToken.None);
@@ -658,10 +652,7 @@ public class ChatHistoryMemoryProviderTests
new(ChatRole.System, "From context provider") { AdditionalProperties = new() { { AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.AIContextProvider, "ContextSource") } } },
};
var invokedContext = new AIContextProvider.InvokedContext(s_mockAgent, new TestAgentSession(), requestMessages)
{
ResponseMessages = [new ChatMessage(ChatRole.Assistant, "Response")]
};
var invokedContext = new AIContextProvider.InvokedContext(s_mockAgent, new TestAgentSession(), requestMessages, [new ChatMessage(ChatRole.Assistant, "Response")]);
// Act
await provider.InvokedAsync(invokedContext, CancellationToken.None);
@@ -705,10 +696,7 @@ public class ChatHistoryMemoryProviderTests
new(ChatRole.System, "From history") { AdditionalProperties = new() { { AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.ChatHistory, "HistorySource") } } },
};
var invokedContext = new AIContextProvider.InvokedContext(s_mockAgent, new TestAgentSession(), requestMessages)
{
ResponseMessages = [new ChatMessage(ChatRole.Assistant, "Response")]
};
var invokedContext = new AIContextProvider.InvokedContext(s_mockAgent, new TestAgentSession(), requestMessages, [new ChatMessage(ChatRole.Assistant, "Response")]);
// Act
await provider.InvokedAsync(invokedContext, CancellationToken.None);