mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: [BREAKING] Add ability to mark the source of Agent request messages and use that for filtering (#3540)
* Add ability to mark the source of Agent request messages and use that for filtering * Add support for source, in addition to source type, and add unit tests for automatic stamping * Address PR comments. * Add merge fixes * Address PR comments
This commit is contained in:
+2
-2
@@ -62,7 +62,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, storeMessages)
|
||||
var invokedContext = new ChatHistoryProvider.InvokedContext(this, session, messages)
|
||||
{
|
||||
ResponseMessages = responseMessages
|
||||
};
|
||||
@@ -94,7 +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, storeMessages)
|
||||
var invokedContext = new ChatHistoryProvider.InvokedContext(this, session, messages)
|
||||
{
|
||||
ResponseMessages = responseMessages
|
||||
};
|
||||
|
||||
+2
-2
@@ -104,7 +104,7 @@ namespace SampleApp
|
||||
|
||||
public UserInfo UserInfo { get; set; }
|
||||
|
||||
public override async ValueTask InvokedAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
protected override async ValueTask InvokedCoreAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Try and extract the user name and age from the message if we don't have it already and it's a user message.
|
||||
if ((this.UserInfo.UserName is null || this.UserInfo.UserAge is null) && context.RequestMessages.Any(x => x.Role == ChatRole.User))
|
||||
@@ -122,7 +122,7 @@ namespace SampleApp
|
||||
}
|
||||
}
|
||||
|
||||
public override ValueTask<AIContext> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
protected override ValueTask<AIContext> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
StringBuilder instructions = new();
|
||||
|
||||
|
||||
+3
-3
@@ -89,7 +89,7 @@ namespace SampleApp
|
||||
|
||||
public string? SessionDbKey { get; private set; }
|
||||
|
||||
public override async ValueTask<IEnumerable<ChatMessage>> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
protected override async ValueTask<IEnumerable<ChatMessage>> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var collection = this._vectorStore.GetCollection<string, ChatHistoryItem>("ChatHistory");
|
||||
await collection.EnsureCollectionExistsAsync(cancellationToken);
|
||||
@@ -107,7 +107,7 @@ namespace SampleApp
|
||||
return messages;
|
||||
}
|
||||
|
||||
public override async ValueTask InvokedAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
protected override async ValueTask InvokedCoreAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Don't store messages if the request failed.
|
||||
if (context.InvokeException is not null)
|
||||
@@ -122,7 +122,7 @@ namespace SampleApp
|
||||
|
||||
// Add both request and response messages to the store
|
||||
// Optionally messages produced by the AIContextProvider can also be persisted (not shown).
|
||||
var allNewMessages = context.RequestMessages.Concat(context.AIContextProviderMessages ?? []).Concat(context.ResponseMessages ?? []);
|
||||
var allNewMessages = context.RequestMessages.Concat(context.ResponseMessages ?? []);
|
||||
|
||||
await collection.UpsertAsync(allNewMessages.Select(x => new ChatHistoryItem()
|
||||
{
|
||||
|
||||
@@ -92,7 +92,7 @@ namespace SampleApp
|
||||
}
|
||||
}
|
||||
|
||||
public override ValueTask<AIContext> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
protected override ValueTask<AIContext> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
StringBuilder outputMessageBuilder = new();
|
||||
outputMessageBuilder.AppendLine("Your todo list contains the following items:");
|
||||
@@ -132,7 +132,7 @@ namespace SampleApp
|
||||
/// </summary>
|
||||
internal sealed class CalendarSearchAIContextProvider(Func<Task<string[]>> loadNextThreeCalendarEvents) : AIContextProvider
|
||||
{
|
||||
public override async ValueTask<AIContext> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
protected override async ValueTask<AIContext> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var events = await loadNextThreeCalendarEvents();
|
||||
|
||||
@@ -179,7 +179,7 @@ namespace SampleApp
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public override async ValueTask<AIContext> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
protected override async ValueTask<AIContext> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Invoke all the sub providers.
|
||||
var tasks = this._providers.Select(provider => provider.InvokingAsync(context, cancellationToken).AsTask());
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -31,6 +32,25 @@ namespace Microsoft.Agents.AI;
|
||||
/// </remarks>
|
||||
public abstract class AIContextProvider
|
||||
{
|
||||
private readonly string _sourceName;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AIContextProvider"/> class.
|
||||
/// </summary>
|
||||
protected AIContextProvider()
|
||||
{
|
||||
this._sourceName = this.GetType().FullName!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AIContextProvider"/> class with the specified source name.
|
||||
/// </summary>
|
||||
/// <param name="sourceName">The source name to stamp on <see cref="ChatMessage.AdditionalProperties"/> for each messages produced by the <see cref="AIContextProvider"/>.</param>
|
||||
protected AIContextProvider(string sourceName)
|
||||
{
|
||||
this._sourceName = sourceName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called at the start of agent invocation to provide additional context.
|
||||
/// </summary>
|
||||
@@ -48,7 +68,57 @@ public abstract class AIContextProvider
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public abstract ValueTask<AIContext> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default);
|
||||
public async ValueTask<AIContext> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var aiContext = await this.InvokingCoreAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
if (aiContext.Messages is null)
|
||||
{
|
||||
return aiContext;
|
||||
}
|
||||
|
||||
aiContext.Messages = aiContext.Messages.Select(message =>
|
||||
{
|
||||
if (message.AdditionalProperties != null
|
||||
// Check if the message was already tagged with this provider's source type
|
||||
&& message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceType.AdditionalPropertiesKey, out var messageSourceType)
|
||||
&& messageSourceType is AgentRequestMessageSourceType typedMessageSourceType
|
||||
&& typedMessageSourceType == AgentRequestMessageSourceType.AIContextProvider
|
||||
// Check if the message was already tagged with this provider's source
|
||||
&& message.AdditionalProperties.TryGetValue(AgentRequestMessageSource.AdditionalPropertiesKey, out var messageSource)
|
||||
&& messageSource is string typedMessageSource
|
||||
&& typedMessageSource == this._sourceName)
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
message = message.Clone();
|
||||
message.AdditionalProperties ??= new();
|
||||
message.AdditionalProperties[AgentRequestMessageSourceType.AdditionalPropertiesKey] = AgentRequestMessageSourceType.AIContextProvider;
|
||||
message.AdditionalProperties[AgentRequestMessageSource.AdditionalPropertiesKey] = this._sourceName;
|
||||
return message;
|
||||
}).ToList();
|
||||
|
||||
return aiContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called at the start of agent invocation to provide additional context.
|
||||
/// </summary>
|
||||
/// <param name="context">Contains the request context including the caller provided messages that will be used by the agent for this invocation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains the <see cref="AIContext"/> with additional context to be used by the agent during this invocation.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Implementers can load any additional context required at this time, such as:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>Retrieving relevant information from knowledge bases</description></item>
|
||||
/// <item><description>Adding system instructions or prompts</description></item>
|
||||
/// <item><description>Providing function tools for the current invocation</description></item>
|
||||
/// <item><description>Injecting contextual messages from conversation history</description></item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
protected abstract ValueTask<AIContext> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Called at the end of the agent invocation to process the invocation results.
|
||||
@@ -71,7 +141,31 @@ public abstract class AIContextProvider
|
||||
/// To check if the invocation was successful, inspect the <see cref="InvokedContext.InvokeException"/> property.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public virtual ValueTask InvokedAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
public ValueTask InvokedAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
=> this.InvokedCoreAsync(context, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Called at the end of the agent invocation to process the invocation results.
|
||||
/// </summary>
|
||||
/// <param name="context">Contains the invocation context including request messages, response messages, and any exception that occurred.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Implementers can use the request and response messages in the provided <paramref name="context"/> to:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>Update internal state based on conversation outcomes</description></item>
|
||||
/// <item><description>Extract and store memories or preferences from user messages</description></item>
|
||||
/// <item><description>Log or audit conversation details</description></item>
|
||||
/// <item><description>Perform cleanup or finalization tasks</description></item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This method is called regardless of whether the invocation succeeded or failed.
|
||||
/// To check if the invocation was successful, inspect the <see cref="InvokedContext.InvokeException"/> property.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
protected virtual ValueTask InvokedCoreAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
=> default;
|
||||
|
||||
/// <summary>
|
||||
@@ -117,7 +211,7 @@ public abstract class AIContextProvider
|
||||
=> this.GetService(typeof(TService), serviceKey) is TService service ? service : default;
|
||||
|
||||
/// <summary>
|
||||
/// Contains the context information provided to <see cref="InvokingAsync(InvokingContext, CancellationToken)"/>.
|
||||
/// Contains the context information provided to <see cref="InvokingCoreAsync(InvokingContext, CancellationToken)"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class provides context about the invocation before the underlying AI model is invoked, including the messages
|
||||
@@ -163,7 +257,7 @@ public abstract class AIContextProvider
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains the context information provided to <see cref="InvokedAsync(InvokedContext, CancellationToken)"/>.
|
||||
/// Contains the context information provided to <see cref="InvokedCoreAsync(InvokedContext, CancellationToken)"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class provides context about a completed agent invocation, including both the
|
||||
@@ -178,18 +272,15 @@ public abstract class AIContextProvider
|
||||
/// <param name="agent">The agent being 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="aiContextProviderMessages">The messages provided by the <see cref="AIContextProvider"/> for this invocation, if any.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="requestMessages"/> is <see langword="null"/>.</exception>
|
||||
public InvokedContext(
|
||||
AIAgent agent,
|
||||
AgentSession? session,
|
||||
IEnumerable<ChatMessage> requestMessages,
|
||||
IEnumerable<ChatMessage>? aiContextProviderMessages)
|
||||
IEnumerable<ChatMessage> requestMessages)
|
||||
{
|
||||
this.Agent = Throw.IfNull(agent);
|
||||
this.Session = session;
|
||||
this.RequestMessages = Throw.IfNull(requestMessages);
|
||||
this.AIContextProviderMessages = aiContextProviderMessages;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -211,15 +302,6 @@ public abstract class AIContextProvider
|
||||
/// </value>
|
||||
public IEnumerable<ChatMessage> RequestMessages { get; set { field = Throw.IfNull(value); } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the messages provided by the <see cref="AIContextProvider"/> for this invocation, if any.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// A collection of <see cref="ChatMessage"/> instances that were provided by the <see cref="AIContextProvider"/>,
|
||||
/// and were used by the agent as part of the invocation.
|
||||
/// </value>
|
||||
public IEnumerable<ChatMessage>? AIContextProviderMessages { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of response messages generated during this invocation if the invocation succeeded.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace Microsoft.Agents.AI;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a constant for the key used to store the source of the agent request message.
|
||||
/// </summary>
|
||||
public static class AgentRequestMessageSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the key used in <see cref="ChatMessage.AdditionalProperties"/> to store the source of the agent request message.
|
||||
/// </summary>
|
||||
public static readonly string AdditionalPropertiesKey = "Agent.RequestMessageSource";
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Shared.Diagnostics;
|
||||
|
||||
namespace Microsoft.Agents.AI;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the source of an agent request message.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Input messages for a specific agent run can originate from various sources.
|
||||
/// This type helps to identify whether a message came from outside the agent pipeline,
|
||||
/// whether it was produced by middleware, or came from chat history.
|
||||
/// </remarks>
|
||||
public sealed class AgentRequestMessageSourceType : IEquatable<AgentRequestMessageSourceType>
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the key used in <see cref="ChatMessage.AdditionalProperties"/> to store the source type of the agent request message.
|
||||
/// </summary>
|
||||
public static readonly string AdditionalPropertiesKey = "Agent.RequestMessageSourceType";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AgentRequestMessageSourceType"/> class.
|
||||
/// </summary>
|
||||
/// <param name="value">The string value representing the source of the agent request message.</param>
|
||||
public AgentRequestMessageSourceType(string value) => this.Value = Throw.IfNullOrWhitespace(value);
|
||||
|
||||
/// <summary>
|
||||
/// Get the string value representing the source of the agent request message.
|
||||
/// </summary>
|
||||
public string Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The message came from outside the agent pipeline (e.g., user input).
|
||||
/// </summary>
|
||||
public static AgentRequestMessageSourceType External { get; } = new AgentRequestMessageSourceType(nameof(External));
|
||||
|
||||
/// <summary>
|
||||
/// The message was produced by middleware.
|
||||
/// </summary>
|
||||
public static AgentRequestMessageSourceType AIContextProvider { get; } = new AgentRequestMessageSourceType(nameof(AIContextProvider));
|
||||
|
||||
/// <summary>
|
||||
/// The message came from chat history.
|
||||
/// </summary>
|
||||
public static AgentRequestMessageSourceType ChatHistory { get; } = new AgentRequestMessageSourceType(nameof(ChatHistory));
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this instance and another specified <see cref="AgentRequestMessageSourceType"/> object have the same value.
|
||||
/// </summary>
|
||||
/// <param name="other">The <see cref="AgentRequestMessageSourceType"/> to compare to this instance.</param>
|
||||
/// <returns><see langword="true"/> if the value of the <paramref name="other"/> parameter is the same as the value of this instance; otherwise, <see langword="false"/>.</returns>
|
||||
public bool Equals(AgentRequestMessageSourceType? other)
|
||||
{
|
||||
if (other is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(this, other))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return string.Equals(this.Value, other.Value, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this instance and a specified object have the same value.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to compare to this instance.</param>
|
||||
/// <returns><see langword="true"/> if <paramref name="obj"/> is a <see cref="AgentRequestMessageSourceType"/> and its value is the same as this instance; otherwise, <see langword="false"/>.</returns>
|
||||
public override bool Equals(object? obj) => this.Equals(obj as AgentRequestMessageSourceType);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the hash code for this instance.
|
||||
/// </summary>
|
||||
/// <returns>A 32-bit signed integer hash code.</returns>
|
||||
public override int GetHashCode() => this.Value?.GetHashCode() ?? 0;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether two specified <see cref="AgentRequestMessageSourceType"/> objects have the same value.
|
||||
/// </summary>
|
||||
/// <param name="left">The first <see cref="AgentRequestMessageSourceType"/> to compare.</param>
|
||||
/// <param name="right">The second <see cref="AgentRequestMessageSourceType"/> to compare.</param>
|
||||
/// <returns><see langword="true"/> if the value of <paramref name="left"/> is the same as the value of <paramref name="right"/>; otherwise, <see langword="false"/>.</returns>
|
||||
public static bool operator ==(AgentRequestMessageSourceType? left, AgentRequestMessageSourceType? right)
|
||||
{
|
||||
if (left is null)
|
||||
{
|
||||
return right is null;
|
||||
}
|
||||
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether two specified <see cref="AgentRequestMessageSourceType"/> objects have different values.
|
||||
/// </summary>
|
||||
/// <param name="left">The first <see cref="AgentRequestMessageSourceType"/> to compare.</param>
|
||||
/// <param name="right">The second <see cref="AgentRequestMessageSourceType"/> to compare.</param>
|
||||
/// <returns><see langword="true"/> if the value of <paramref name="left"/> is different from the value of <paramref name="right"/>; otherwise, <see langword="false"/>.</returns>
|
||||
public static bool operator !=(AgentRequestMessageSourceType? left, AgentRequestMessageSourceType? right) => !(left == right);
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -36,6 +37,25 @@ namespace Microsoft.Agents.AI;
|
||||
/// </remarks>
|
||||
public abstract class ChatHistoryProvider
|
||||
{
|
||||
private readonly string _sourceName;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ChatHistoryProvider"/> class.
|
||||
/// </summary>
|
||||
protected ChatHistoryProvider()
|
||||
{
|
||||
this._sourceName = this.GetType().FullName!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ChatHistoryProvider"/> class with the specified source name.
|
||||
/// </summary>
|
||||
/// <param name="sourceName">The source name to stamp on <see cref="ChatMessage.AdditionalProperties"/> for each messages produced by the <see cref="ChatHistoryProvider"/>.</param>
|
||||
protected ChatHistoryProvider(string sourceName)
|
||||
{
|
||||
this._sourceName = sourceName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called at the start of agent invocation to provide messages from the chat history as context for the next agent invocation.
|
||||
/// </summary>
|
||||
@@ -65,7 +85,63 @@ public abstract class ChatHistoryProvider
|
||||
/// and context management.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public abstract ValueTask<IEnumerable<ChatMessage>> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default);
|
||||
public async ValueTask<IEnumerable<ChatMessage>> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var messages = await this.InvokingCoreAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return messages.Select(message =>
|
||||
{
|
||||
if (message.AdditionalProperties != null
|
||||
// Check if the message was already tagged with this provider's source type
|
||||
&& message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceType.AdditionalPropertiesKey, out var messageSourceType)
|
||||
&& messageSourceType is AgentRequestMessageSourceType typedMessageSourceType
|
||||
&& typedMessageSourceType == AgentRequestMessageSourceType.ChatHistory
|
||||
// Check if the message was already tagged with this provider's source
|
||||
&& message.AdditionalProperties.TryGetValue(AgentRequestMessageSource.AdditionalPropertiesKey, out var messageSource)
|
||||
&& messageSource is string typedMessageSource
|
||||
&& typedMessageSource == this._sourceName)
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
message = message.Clone();
|
||||
message.AdditionalProperties ??= new();
|
||||
message.AdditionalProperties[AgentRequestMessageSourceType.AdditionalPropertiesKey] = AgentRequestMessageSourceType.ChatHistory;
|
||||
message.AdditionalProperties[AgentRequestMessageSource.AdditionalPropertiesKey] = this._sourceName;
|
||||
return message;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called at the start of agent invocation to provide messages from the chat history as context for the next agent invocation.
|
||||
/// </summary>
|
||||
/// <param name="context">Contains the request context including the caller provided messages that will be used by the agent for this invocation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous operation. The task result contains a collection of <see cref="ChatMessage"/>
|
||||
/// instances in ascending chronological order (oldest first).
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Messages are returned in chronological order to maintain proper conversation flow and context for the agent.
|
||||
/// The oldest messages appear first in the collection, followed by more recent messages.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If the total message history becomes very large, implementations should apply appropriate strategies to manage
|
||||
/// storage constraints, such as:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>Truncating older messages while preserving recent context</description></item>
|
||||
/// <item><description>Summarizing message groups to maintain essential context</description></item>
|
||||
/// <item><description>Implementing sliding window approaches for message retention</description></item>
|
||||
/// <item><description>Archiving old messages while keeping active conversation context</description></item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Each <see cref="ChatHistoryProvider"/> instance should be associated with a single <see cref="AgentSession"/> to ensure proper message isolation
|
||||
/// and context management.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
protected abstract ValueTask<IEnumerable<ChatMessage>> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Called at the end of the agent invocation to add new messages to the chat history.
|
||||
@@ -77,7 +153,7 @@ public abstract class ChatHistoryProvider
|
||||
/// <para>
|
||||
/// Messages should be added in the order they were generated to maintain proper chronological sequence.
|
||||
/// The <see cref="ChatHistoryProvider"/> is responsible for preserving message ordering and ensuring that subsequent calls to
|
||||
/// <see cref="InvokingAsync"/> return messages in the correct chronological order.
|
||||
/// <see cref="InvokingCoreAsync"/> return messages in the correct chronological order.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Implementations may perform additional processing during message addition, such as:
|
||||
@@ -92,7 +168,35 @@ public abstract class ChatHistoryProvider
|
||||
/// To check if the invocation was successful, inspect the <see cref="InvokedContext.InvokeException"/> property.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public abstract ValueTask InvokedAsync(InvokedContext context, CancellationToken cancellationToken = default);
|
||||
public ValueTask InvokedAsync(InvokedContext context, CancellationToken cancellationToken = default) =>
|
||||
this.InvokedCoreAsync(context, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Called at the end of the agent invocation to add new messages to the chat history.
|
||||
/// </summary>
|
||||
/// <param name="context">Contains the invocation context including request messages, response messages, and any exception that occurred.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A task that represents the asynchronous add operation.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Messages should be added in the order they were generated to maintain proper chronological sequence.
|
||||
/// The <see cref="ChatHistoryProvider"/> is responsible for preserving message ordering and ensuring that subsequent calls to
|
||||
/// <see cref="InvokingCoreAsync"/> return messages in the correct chronological order.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Implementations may perform additional processing during message addition, such as:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>Validating message content and metadata</description></item>
|
||||
/// <item><description>Applying storage optimizations or compression</description></item>
|
||||
/// <item><description>Triggering background maintenance operations</description></item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This method is called regardless of whether the invocation succeeded or failed.
|
||||
/// To check if the invocation was successful, inspect the <see cref="InvokedContext.InvokeException"/> property.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
protected abstract ValueTask InvokedCoreAsync(InvokedContext context, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the current object's state to a <see cref="JsonElement"/> using the specified serialization options.
|
||||
@@ -131,7 +235,7 @@ public abstract class ChatHistoryProvider
|
||||
=> this.GetService(typeof(TService), serviceKey) is TService service ? service : default;
|
||||
|
||||
/// <summary>
|
||||
/// Contains the context information provided to <see cref="InvokingAsync(InvokingContext, CancellationToken)"/>.
|
||||
/// Contains the context information provided to <see cref="InvokingCoreAsync(InvokingContext, CancellationToken)"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class provides context about the invocation including the new messages that will be used.
|
||||
@@ -177,7 +281,7 @@ public abstract class ChatHistoryProvider
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains the context information provided to <see cref="InvokedAsync(InvokedContext, CancellationToken)"/>.
|
||||
/// Contains the context information provided to <see cref="InvokedCoreAsync(InvokedContext, CancellationToken)"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class provides context about a completed agent invocation, including both the
|
||||
@@ -192,18 +296,15 @@ public abstract class ChatHistoryProvider
|
||||
/// <param name="agent">The agent being 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="chatHistoryProviderMessages">The messages retrieved from the <see cref="ChatHistoryProvider"/> for this invocation.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="requestMessages"/> is <see langword="null"/>.</exception>
|
||||
public InvokedContext(
|
||||
AIAgent agent,
|
||||
AgentSession? session,
|
||||
IEnumerable<ChatMessage> requestMessages,
|
||||
IEnumerable<ChatMessage>? chatHistoryProviderMessages)
|
||||
IEnumerable<ChatMessage> requestMessages)
|
||||
{
|
||||
this.Agent = Throw.IfNull(agent);
|
||||
this.Session = session;
|
||||
this.RequestMessages = Throw.IfNull(requestMessages);
|
||||
this.ChatHistoryProviderMessages = chatHistoryProviderMessages;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -225,24 +326,6 @@ public abstract class ChatHistoryProvider
|
||||
/// </value>
|
||||
public IEnumerable<ChatMessage> RequestMessages { get; set { field = Throw.IfNull(value); } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the messages retrieved from the <see cref="ChatHistoryProvider"/> for this invocation, if any.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// A collection of <see cref="ChatMessage"/> instances that were retrieved from the <see cref="ChatHistoryProvider"/>,
|
||||
/// and were used by the agent as part of the invocation.
|
||||
/// </value>
|
||||
public IEnumerable<ChatMessage>? ChatHistoryProviderMessages { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the messages provided by the <see cref="AIContextProvider"/> for this invocation, if any.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// A collection of <see cref="ChatMessage"/> instances that were provided by the <see cref="AIContextProvider"/>,
|
||||
/// and were used by the agent as part of the invocation.
|
||||
/// </value>
|
||||
public IEnumerable<ChatMessage>? AIContextProviderMessages { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of response messages generated during this invocation if the invocation succeeded.
|
||||
/// </summary>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace Microsoft.Agents.AI;
|
||||
@@ -33,8 +34,8 @@ public static class ChatHistoryProviderExtensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decorates the provided chat message <see cref="ChatHistoryProvider"/> so that it does not add
|
||||
/// messages produced by any <see cref="AIContextProvider"/> to chat history.
|
||||
/// Decorates the provided <see cref="ChatHistoryProvider"/> so that it does not add
|
||||
/// messages with <see cref="AgentRequestMessageSourceType.AIContextProvider"/> to chat history.
|
||||
/// </summary>
|
||||
/// <param name="provider">The <see cref="ChatHistoryProvider"/> to add the message filter to.</param>
|
||||
/// <returns>A new <see cref="ChatHistoryProvider"/> instance that filters out <see cref="AIContextProvider"/> messages so they do not get added.</returns>
|
||||
@@ -44,7 +45,7 @@ public static class ChatHistoryProviderExtensions
|
||||
innerProvider: provider,
|
||||
invokedMessagesFilter: (ctx) =>
|
||||
{
|
||||
ctx.AIContextProviderMessages = null;
|
||||
ctx.RequestMessages = ctx.RequestMessages.Where(x => x.GetAgentRequestMessageSource() != AgentRequestMessageSourceType.AIContextProvider);
|
||||
return ctx;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -49,14 +49,14 @@ public sealed class ChatHistoryProviderMessageFilter : ChatHistoryProvider
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async ValueTask<IEnumerable<ChatMessage>> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
protected override async ValueTask<IEnumerable<ChatMessage>> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var messages = await this._innerProvider.InvokingAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
return this._invokingMessagesFilter != null ? this._invokingMessagesFilter(messages) : messages;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ValueTask InvokedAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
protected override ValueTask InvokedCoreAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (this._invokedMessagesFilter != null)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace Microsoft.Agents.AI;
|
||||
|
||||
/// <summary>
|
||||
/// Contains extension methods for <see cref="ChatMessage"/>
|
||||
/// </summary>
|
||||
public static class ChatMessageExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the source of the provided <see cref="ChatMessage"/> in the context of messages passed into an agent run.
|
||||
/// </summary>
|
||||
/// <param name="message">The <see cref="ChatMessage"/> for which we need the source.</param>
|
||||
/// <returns>An <see cref="AgentRequestMessageSourceType"/> value indicating the source of the <see cref="ChatMessage"/>. Defaults to <see
|
||||
/// cref="AgentRequestMessageSourceType.External"/> if no explicit source is defined.</returns>
|
||||
public static AgentRequestMessageSourceType GetAgentRequestMessageSource(this ChatMessage message)
|
||||
{
|
||||
if (message.AdditionalProperties?.TryGetValue(AgentRequestMessageSourceType.AdditionalPropertiesKey, out var source) is true && source is AgentRequestMessageSourceType typedSource)
|
||||
{
|
||||
return typedSource;
|
||||
}
|
||||
|
||||
return AgentRequestMessageSourceType.External;
|
||||
}
|
||||
}
|
||||
@@ -133,7 +133,7 @@ public sealed class InMemoryChatHistoryProvider : ChatHistoryProvider, IList<Cha
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async ValueTask<IEnumerable<ChatMessage>> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
protected override async ValueTask<IEnumerable<ChatMessage>> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_ = Throw.IfNull(context);
|
||||
|
||||
@@ -146,7 +146,7 @@ public sealed class InMemoryChatHistoryProvider : ChatHistoryProvider, IList<Cha
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async ValueTask InvokedAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
protected override async ValueTask InvokedCoreAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_ = Throw.IfNull(context);
|
||||
|
||||
@@ -155,8 +155,8 @@ public sealed class InMemoryChatHistoryProvider : ChatHistoryProvider, IList<Cha
|
||||
return;
|
||||
}
|
||||
|
||||
// Add request, AI context provider, and response messages to the provider
|
||||
var allNewMessages = context.RequestMessages.Concat(context.AIContextProviderMessages ?? []).Concat(context.ResponseMessages ?? []);
|
||||
// Add request and response messages to the provider
|
||||
var allNewMessages = context.RequestMessages.Concat(context.ResponseMessages ?? []);
|
||||
this._messages.AddRange(allNewMessages);
|
||||
|
||||
if (this.ReducerTriggerEvent is ChatReducerTriggerEvent.AfterMessageAdded && this.ChatReducer is not null)
|
||||
@@ -229,7 +229,7 @@ public sealed class InMemoryChatHistoryProvider : ChatHistoryProvider, IList<Cha
|
||||
{
|
||||
/// <summary>
|
||||
/// Trigger the reducer when a new message is added.
|
||||
/// <see cref="InvokedAsync(InvokedContext, CancellationToken)"/> will only complete when reducer processing is done.
|
||||
/// <see cref="InvokedCoreAsync(InvokedContext, CancellationToken)"/> will only complete when reducer processing is done.
|
||||
/// </summary>
|
||||
AfterMessageAdded,
|
||||
|
||||
|
||||
@@ -287,7 +287,7 @@ public sealed class CosmosChatHistoryProvider : ChatHistoryProvider, IDisposable
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async ValueTask<IEnumerable<ChatMessage>> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
protected override async ValueTask<IEnumerable<ChatMessage>> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
#pragma warning disable CA1513 // Use ObjectDisposedException.ThrowIf - not available on all target frameworks
|
||||
if (this._disposed)
|
||||
@@ -347,7 +347,7 @@ public sealed class CosmosChatHistoryProvider : ChatHistoryProvider, IDisposable
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async ValueTask InvokedAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
protected override async ValueTask InvokedCoreAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNull(context);
|
||||
|
||||
@@ -364,7 +364,7 @@ public sealed class CosmosChatHistoryProvider : ChatHistoryProvider, IDisposable
|
||||
}
|
||||
#pragma warning restore CA1513
|
||||
|
||||
var messageList = context.RequestMessages.Concat(context.AIContextProviderMessages ?? []).Concat(context.ResponseMessages ?? []).ToList();
|
||||
var messageList = context.RequestMessages.Concat(context.ResponseMessages ?? []).ToList();
|
||||
if (messageList.Count == 0)
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -131,13 +131,16 @@ public sealed class Mem0Provider : AIContextProvider
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async ValueTask<AIContext> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
protected override async ValueTask<AIContext> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Throw.IfNull(context);
|
||||
|
||||
string queryText = string.Join(
|
||||
Environment.NewLine,
|
||||
context.RequestMessages.Where(m => !string.IsNullOrWhiteSpace(m.Text)).Select(m => m.Text));
|
||||
context.RequestMessages
|
||||
.Where(m => m.GetAgentRequestMessageSource() == AgentRequestMessageSourceType.External)
|
||||
.Where(m => !string.IsNullOrWhiteSpace(m.Text))
|
||||
.Select(m => m.Text));
|
||||
|
||||
try
|
||||
{
|
||||
@@ -202,7 +205,7 @@ public sealed class Mem0Provider : AIContextProvider
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async ValueTask InvokedAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
protected override async ValueTask InvokedCoreAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (context.InvokeException is not null)
|
||||
{
|
||||
@@ -212,7 +215,11 @@ public sealed class Mem0Provider : AIContextProvider
|
||||
try
|
||||
{
|
||||
// Persist request and response messages after invocation.
|
||||
await this.PersistMessagesAsync(context.RequestMessages.Concat(context.ResponseMessages ?? []), cancellationToken).ConfigureAwait(false);
|
||||
await this.PersistMessagesAsync(
|
||||
context.RequestMessages
|
||||
.Where(m => m.GetAgentRequestMessageSource() == AgentRequestMessageSourceType.External)
|
||||
.Concat(context.ResponseMessages ?? []),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -46,17 +46,17 @@ internal sealed class WorkflowChatHistoryProvider : ChatHistoryProvider
|
||||
|
||||
internal void AddMessages(params IEnumerable<ChatMessage> messages) => this._chatMessages.AddRange(messages);
|
||||
|
||||
public override ValueTask<IEnumerable<ChatMessage>> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
protected override ValueTask<IEnumerable<ChatMessage>> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
=> new(this._chatMessages.AsReadOnly());
|
||||
|
||||
public override ValueTask InvokedAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
protected override ValueTask InvokedCoreAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (context.InvokeException is not null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
var allNewMessages = context.RequestMessages.Concat(context.AIContextProviderMessages ?? []).Concat(context.ResponseMessages ?? []);
|
||||
var allNewMessages = context.RequestMessages.Concat(context.ResponseMessages ?? []);
|
||||
this._chatMessages.AddRange(allNewMessages);
|
||||
|
||||
return default;
|
||||
|
||||
@@ -206,9 +206,8 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
|
||||
(ChatClientAgentSession safeSession,
|
||||
ChatOptions? chatOptions,
|
||||
List<ChatMessage> inputMessagesForProviders,
|
||||
List<ChatMessage> inputMessagesForChatClient,
|
||||
IList<ChatMessage>? aiContextProviderMessages,
|
||||
IList<ChatMessage>? chatHistoryProviderMessages,
|
||||
ChatClientAgentContinuationToken? continuationToken) =
|
||||
await this.PrepareSessionAndMessagesAsync(session, inputMessages, options, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -227,12 +226,12 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
try
|
||||
{
|
||||
// Using the enumerator to ensure we consider the case where no updates are returned for notification.
|
||||
responseUpdatesEnumerator = chatClient.GetStreamingResponseAsync(inputMessagesForChatClient, chatOptions, cancellationToken).GetAsyncEnumerator(cancellationToken);
|
||||
responseUpdatesEnumerator = chatClient.GetStreamingResponseAsync(inputMessagesForProviders, chatOptions, cancellationToken).GetAsyncEnumerator(cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await this.NotifyChatHistoryProviderOfFailureAsync(safeSession, ex, GetInputMessages(inputMessages, continuationToken), chatHistoryProviderMessages, aiContextProviderMessages, chatOptions, cancellationToken).ConfigureAwait(false);
|
||||
await this.NotifyAIContextProviderOfFailureAsync(safeSession, ex, GetInputMessages(inputMessages, continuationToken), aiContextProviderMessages, cancellationToken).ConfigureAwait(false);
|
||||
await this.NotifyChatHistoryProviderOfFailureAsync(safeSession, ex, GetInputMessages(inputMessagesForProviders, continuationToken), chatOptions, cancellationToken).ConfigureAwait(false);
|
||||
await this.NotifyAIContextProviderOfFailureAsync(safeSession, ex, GetInputMessages(inputMessagesForProviders, continuationToken), cancellationToken).ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
|
||||
@@ -246,8 +245,8 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await this.NotifyChatHistoryProviderOfFailureAsync(safeSession, ex, GetInputMessages(inputMessages, continuationToken), chatHistoryProviderMessages, aiContextProviderMessages, chatOptions, cancellationToken).ConfigureAwait(false);
|
||||
await this.NotifyAIContextProviderOfFailureAsync(safeSession, ex, GetInputMessages(inputMessages, continuationToken), aiContextProviderMessages, cancellationToken).ConfigureAwait(false);
|
||||
await this.NotifyChatHistoryProviderOfFailureAsync(safeSession, ex, GetInputMessages(inputMessagesForProviders, continuationToken), chatOptions, cancellationToken).ConfigureAwait(false);
|
||||
await this.NotifyAIContextProviderOfFailureAsync(safeSession, ex, GetInputMessages(inputMessagesForProviders, continuationToken), cancellationToken).ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
|
||||
@@ -273,8 +272,8 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await this.NotifyChatHistoryProviderOfFailureAsync(safeSession, ex, GetInputMessages(inputMessages, continuationToken), chatHistoryProviderMessages, aiContextProviderMessages, chatOptions, cancellationToken).ConfigureAwait(false);
|
||||
await this.NotifyAIContextProviderOfFailureAsync(safeSession, ex, GetInputMessages(inputMessages, continuationToken), aiContextProviderMessages, cancellationToken).ConfigureAwait(false);
|
||||
await this.NotifyChatHistoryProviderOfFailureAsync(safeSession, ex, GetInputMessages(inputMessagesForProviders, continuationToken), chatOptions, cancellationToken).ConfigureAwait(false);
|
||||
await this.NotifyAIContextProviderOfFailureAsync(safeSession, ex, GetInputMessages(inputMessagesForProviders, continuationToken), cancellationToken).ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -286,10 +285,10 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
await this.UpdateSessionWithTypeAndConversationIdAsync(safeSession, chatResponse.ConversationId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// To avoid inconsistent state we only notify the session of the input messages if no error occurs after the initial request.
|
||||
await this.NotifyChatHistoryProviderOfNewMessagesAsync(safeSession, GetInputMessages(inputMessages, continuationToken), chatHistoryProviderMessages, aiContextProviderMessages, chatResponse.Messages, chatOptions, cancellationToken).ConfigureAwait(false);
|
||||
await this.NotifyChatHistoryProviderOfNewMessagesAsync(safeSession, GetInputMessages(inputMessagesForProviders, continuationToken), chatResponse.Messages, chatOptions, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Notify the AIContextProvider of all new messages.
|
||||
await this.NotifyAIContextProviderOfSuccessAsync(safeSession, GetInputMessages(inputMessages, continuationToken), aiContextProviderMessages, chatResponse.Messages, cancellationToken).ConfigureAwait(false);
|
||||
await this.NotifyAIContextProviderOfSuccessAsync(safeSession, GetInputMessages(inputMessagesForProviders, continuationToken), chatResponse.Messages, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -433,9 +432,8 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
|
||||
(ChatClientAgentSession safeSession,
|
||||
ChatOptions? chatOptions,
|
||||
List<ChatMessage> inputMessagesForProviders,
|
||||
List<ChatMessage> inputMessagesForChatClient,
|
||||
IList<ChatMessage>? aiContextProviderMessages,
|
||||
IList<ChatMessage>? chatHistoryProviderMessages,
|
||||
ChatClientAgentContinuationToken? _) =
|
||||
await this.PrepareSessionAndMessagesAsync(session, inputMessages, options, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -455,8 +453,8 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await this.NotifyChatHistoryProviderOfFailureAsync(safeSession, ex, inputMessages, chatHistoryProviderMessages, aiContextProviderMessages, chatOptions, cancellationToken).ConfigureAwait(false);
|
||||
await this.NotifyAIContextProviderOfFailureAsync(safeSession, ex, inputMessages, aiContextProviderMessages, cancellationToken).ConfigureAwait(false);
|
||||
await this.NotifyChatHistoryProviderOfFailureAsync(safeSession, ex, inputMessagesForProviders, chatOptions, cancellationToken).ConfigureAwait(false);
|
||||
await this.NotifyAIContextProviderOfFailureAsync(safeSession, ex, inputMessagesForProviders, cancellationToken).ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
|
||||
@@ -473,10 +471,10 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
}
|
||||
|
||||
// Only notify the session of new messages if the chatResponse was successful to avoid inconsistent message state in the session.
|
||||
await this.NotifyChatHistoryProviderOfNewMessagesAsync(safeSession, inputMessages, chatHistoryProviderMessages, aiContextProviderMessages, chatResponse.Messages, chatOptions, cancellationToken).ConfigureAwait(false);
|
||||
await this.NotifyChatHistoryProviderOfNewMessagesAsync(safeSession, inputMessagesForProviders, chatResponse.Messages, chatOptions, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Notify the AIContextProvider of all new messages.
|
||||
await this.NotifyAIContextProviderOfSuccessAsync(safeSession, inputMessages, aiContextProviderMessages, chatResponse.Messages, cancellationToken).ConfigureAwait(false);
|
||||
await this.NotifyAIContextProviderOfSuccessAsync(safeSession, inputMessagesForProviders, chatResponse.Messages, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var agentResponse = agentResponseFactoryFunc(chatResponse);
|
||||
|
||||
@@ -491,13 +489,12 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
private async Task NotifyAIContextProviderOfSuccessAsync(
|
||||
ChatClientAgentSession session,
|
||||
IEnumerable<ChatMessage> inputMessages,
|
||||
IList<ChatMessage>? aiContextProviderMessages,
|
||||
IEnumerable<ChatMessage> responseMessages,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (session.AIContextProvider is not null)
|
||||
{
|
||||
await session.AIContextProvider.InvokedAsync(new(this, session, inputMessages, aiContextProviderMessages) { ResponseMessages = responseMessages },
|
||||
await session.AIContextProvider.InvokedAsync(new(this, session, inputMessages) { ResponseMessages = responseMessages },
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -509,12 +506,11 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
ChatClientAgentSession session,
|
||||
Exception ex,
|
||||
IEnumerable<ChatMessage> inputMessages,
|
||||
IList<ChatMessage>? aiContextProviderMessages,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (session.AIContextProvider is not null)
|
||||
{
|
||||
await session.AIContextProvider.InvokedAsync(new(this, session, inputMessages, aiContextProviderMessages) { InvokeException = ex },
|
||||
await session.AIContextProvider.InvokedAsync(new(this, session, inputMessages) { InvokeException = ex },
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -683,9 +679,8 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
<(
|
||||
ChatClientAgentSession AgentSession,
|
||||
ChatOptions? ChatOptions,
|
||||
List<ChatMessage> inputMessagesForProviders,
|
||||
List<ChatMessage> InputMessagesForChatClient,
|
||||
IList<ChatMessage>? AIContextProviderMessages,
|
||||
IList<ChatMessage>? ChatHistoryProviderMessages,
|
||||
ChatClientAgentContinuationToken? ContinuationToken
|
||||
)> PrepareSessionAndMessagesAsync(
|
||||
AgentSession? session,
|
||||
@@ -714,9 +709,8 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
throw new InvalidOperationException("Input messages are not allowed when continuing a background response using a continuation token.");
|
||||
}
|
||||
|
||||
List<ChatMessage> inputMessagesForProviders = [];
|
||||
List<ChatMessage> inputMessagesForChatClient = [];
|
||||
IList<ChatMessage>? aiContextProviderMessages = null;
|
||||
IList<ChatMessage>? chatHistoryProviderMessages = null;
|
||||
|
||||
// Populate the session messages only if we are not continuing an existing response as it's not allowed
|
||||
if (chatOptions?.ContinuationToken is null)
|
||||
@@ -729,10 +723,10 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
var invokingContext = new ChatHistoryProvider.InvokingContext(this, typedSession, inputMessages);
|
||||
var providerMessages = await chatHistoryProvider.InvokingAsync(invokingContext, cancellationToken).ConfigureAwait(false);
|
||||
inputMessagesForChatClient.AddRange(providerMessages);
|
||||
chatHistoryProviderMessages = providerMessages as IList<ChatMessage> ?? providerMessages.ToList();
|
||||
}
|
||||
|
||||
// Add the input messages before getting context from AIContextProvider.
|
||||
inputMessagesForProviders.AddRange(inputMessages);
|
||||
inputMessagesForChatClient.AddRange(inputMessages);
|
||||
|
||||
// If we have an AIContextProvider, we should get context from it, and update our
|
||||
@@ -743,8 +737,8 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
var aiContext = await typedSession.AIContextProvider.InvokingAsync(invokingContext, cancellationToken).ConfigureAwait(false);
|
||||
if (aiContext.Messages is { Count: > 0 })
|
||||
{
|
||||
inputMessagesForProviders.AddRange(aiContext.Messages);
|
||||
inputMessagesForChatClient.AddRange(aiContext.Messages);
|
||||
aiContextProviderMessages = aiContext.Messages;
|
||||
}
|
||||
|
||||
if (aiContext.Tools is { Count: > 0 })
|
||||
@@ -783,7 +777,7 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
chatOptions.ConversationId = typedSession.ConversationId;
|
||||
}
|
||||
|
||||
return (typedSession, chatOptions, inputMessagesForChatClient, aiContextProviderMessages, chatHistoryProviderMessages, continuationToken);
|
||||
return (typedSession, chatOptions, inputMessagesForProviders, inputMessagesForChatClient, continuationToken);
|
||||
}
|
||||
|
||||
private async Task UpdateSessionWithTypeAndConversationIdAsync(ChatClientAgentSession session, string? responseConversationId, CancellationToken cancellationToken)
|
||||
@@ -816,8 +810,6 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
ChatClientAgentSession session,
|
||||
Exception ex,
|
||||
IEnumerable<ChatMessage> requestMessages,
|
||||
IEnumerable<ChatMessage>? chatHistoryProviderMessages,
|
||||
IEnumerable<ChatMessage>? aiContextProviderMessages,
|
||||
ChatOptions? chatOptions,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -827,9 +819,8 @@ 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, chatHistoryProviderMessages!)
|
||||
var invokedContext = new ChatHistoryProvider.InvokedContext(this, session, requestMessages)
|
||||
{
|
||||
AIContextProviderMessages = aiContextProviderMessages,
|
||||
InvokeException = ex
|
||||
};
|
||||
|
||||
@@ -842,8 +833,6 @@ public sealed partial class ChatClientAgent : AIAgent
|
||||
private Task NotifyChatHistoryProviderOfNewMessagesAsync(
|
||||
ChatClientAgentSession session,
|
||||
IEnumerable<ChatMessage> requestMessages,
|
||||
IEnumerable<ChatMessage>? chatHistoryProviderMessages,
|
||||
IEnumerable<ChatMessage>? aiContextProviderMessages,
|
||||
IEnumerable<ChatMessage> responseMessages,
|
||||
ChatOptions? chatOptions,
|
||||
CancellationToken cancellationToken)
|
||||
@@ -854,9 +843,8 @@ 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, chatHistoryProviderMessages!)
|
||||
var invokedContext = new ChatHistoryProvider.InvokedContext(this, session, requestMessages)
|
||||
{
|
||||
AIContextProviderMessages = aiContextProviderMessages,
|
||||
ResponseMessages = responseMessages
|
||||
};
|
||||
return provider.InvokedAsync(invokedContext, cancellationToken).AsTask();
|
||||
|
||||
@@ -25,8 +25,8 @@ namespace Microsoft.Agents.AI;
|
||||
/// abstractions to work with any compatible vector store implementation.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Messages are stored during the <see cref="InvokedAsync"/> method and retrieved during the
|
||||
/// <see cref="InvokingAsync"/> method using semantic similarity search.
|
||||
/// Messages are stored during the <see cref="InvokedCoreAsync"/> method and retrieved during the
|
||||
/// <see cref="InvokingCoreAsync"/> method using semantic similarity search.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Behavior is configurable through <see cref="ChatHistoryMemoryProviderOptions"/>. When
|
||||
@@ -175,7 +175,7 @@ public sealed class ChatHistoryMemoryProvider : AIContextProvider, IDisposable
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async ValueTask<AIContext> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
protected override async ValueTask<AIContext> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_ = Throw.IfNull(context);
|
||||
|
||||
@@ -189,6 +189,7 @@ public sealed class ChatHistoryMemoryProvider : AIContextProvider, IDisposable
|
||||
{
|
||||
// Get the text from the current request messages
|
||||
var requestText = string.Join("\n", context.RequestMessages
|
||||
.Where(m => m.GetAgentRequestMessageSource() == AgentRequestMessageSourceType.External)
|
||||
.Where(m => m != null && !string.IsNullOrWhiteSpace(m.Text))
|
||||
.Select(m => m.Text));
|
||||
|
||||
@@ -228,7 +229,7 @@ public sealed class ChatHistoryMemoryProvider : AIContextProvider, IDisposable
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async ValueTask InvokedAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
protected override async ValueTask InvokedCoreAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_ = Throw.IfNull(context);
|
||||
|
||||
@@ -244,6 +245,7 @@ public sealed class ChatHistoryMemoryProvider : AIContextProvider, IDisposable
|
||||
var collection = await this.EnsureCollectionExistsAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
List<Dictionary<string, object?>> itemsToStore = context.RequestMessages
|
||||
.Where(m => m.GetAgentRequestMessageSource() == AgentRequestMessageSourceType.External)
|
||||
.Concat(context.ResponseMessages ?? [])
|
||||
.Select(message => new Dictionary<string, object?>
|
||||
{
|
||||
|
||||
@@ -107,7 +107,7 @@ public sealed class TextSearchProvider : AIContextProvider
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async ValueTask<AIContext> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
protected override async ValueTask<AIContext> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (this._searchTime != TextSearchProviderOptions.TextSearchBehavior.BeforeAIInvoke)
|
||||
{
|
||||
@@ -117,7 +117,9 @@ public sealed class TextSearchProvider : AIContextProvider
|
||||
|
||||
// Aggregate text from memory + current request messages.
|
||||
var sbInput = new StringBuilder();
|
||||
var requestMessagesText = context.RequestMessages.Where(x => !string.IsNullOrWhiteSpace(x?.Text)).Select(x => x.Text);
|
||||
var requestMessagesText = context.RequestMessages
|
||||
.Where(m => m.GetAgentRequestMessageSource() == AgentRequestMessageSourceType.External)
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x?.Text)).Select(x => x.Text);
|
||||
foreach (var messageText in this._recentMessagesText.Concat(requestMessagesText))
|
||||
{
|
||||
if (sbInput.Length > 0)
|
||||
@@ -166,7 +168,7 @@ public sealed class TextSearchProvider : AIContextProvider
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ValueTask InvokedAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
protected override ValueTask InvokedCoreAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
int limit = this._recentMessageMemoryLimit;
|
||||
if (limit <= 0)
|
||||
@@ -180,6 +182,7 @@ public sealed class TextSearchProvider : AIContextProvider
|
||||
}
|
||||
|
||||
var messagesText = context.RequestMessages
|
||||
.Where(m => m.GetAgentRequestMessageSource() == AgentRequestMessageSourceType.External)
|
||||
.Concat(context.ResponseMessages ?? [])
|
||||
.Where(m =>
|
||||
this._recentMessageRolesIncluded.Contains(m.Role) &&
|
||||
|
||||
+183
-27
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
@@ -15,35 +16,155 @@ public class AIContextProviderTests
|
||||
private static readonly AIAgent s_mockAgent = new Mock<AIAgent>().Object;
|
||||
private static readonly AgentSession s_mockSession = new Mock<AgentSession>().Object;
|
||||
|
||||
#region InvokingAsync Message Stamping Tests
|
||||
|
||||
[Fact]
|
||||
public async Task InvokingAsync_StampsMessagesWithSourceTypeAndSourceAsync()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestAIContextProviderWithMessages();
|
||||
var context = new AIContextProvider.InvokingContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Request")]);
|
||||
|
||||
// Act
|
||||
AIContext aiContext = await provider.InvokingAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(aiContext.Messages);
|
||||
ChatMessage message = aiContext.Messages.Single();
|
||||
Assert.NotNull(message.AdditionalProperties);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceType.AdditionalPropertiesKey, out object? sourceType));
|
||||
Assert.Equal(AgentRequestMessageSourceType.AIContextProvider, sourceType);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSource.AdditionalPropertiesKey, out object? source));
|
||||
Assert.Equal(typeof(TestAIContextProviderWithMessages).FullName, source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokingAsync_WithCustomSourceName_StampsMessagesWithCustomSourceAsync()
|
||||
{
|
||||
// Arrange
|
||||
const string CustomSourceName = "CustomContextSource";
|
||||
var provider = new TestAIContextProviderWithCustomSource(CustomSourceName);
|
||||
var context = new AIContextProvider.InvokingContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Request")]);
|
||||
|
||||
// Act
|
||||
AIContext aiContext = await provider.InvokingAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(aiContext.Messages);
|
||||
ChatMessage message = aiContext.Messages.Single();
|
||||
Assert.NotNull(message.AdditionalProperties);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceType.AdditionalPropertiesKey, out object? sourceType));
|
||||
Assert.Equal(AgentRequestMessageSourceType.AIContextProvider, sourceType);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSource.AdditionalPropertiesKey, out object? source));
|
||||
Assert.Equal(CustomSourceName, source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokingAsync_DoesNotReStampAlreadyStampedMessagesAsync()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestAIContextProviderWithPreStampedMessages();
|
||||
var context = new AIContextProvider.InvokingContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Request")]);
|
||||
|
||||
// Act
|
||||
AIContext aiContext = await provider.InvokingAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(aiContext.Messages);
|
||||
ChatMessage message = aiContext.Messages.Single();
|
||||
Assert.NotNull(message.AdditionalProperties);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceType.AdditionalPropertiesKey, out object? sourceType));
|
||||
Assert.Equal(AgentRequestMessageSourceType.AIContextProvider, sourceType);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSource.AdditionalPropertiesKey, out object? source));
|
||||
Assert.Equal(typeof(TestAIContextProviderWithPreStampedMessages).FullName, source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokingAsync_StampsMultipleMessagesAsync()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestAIContextProviderWithMultipleMessages();
|
||||
var context = new AIContextProvider.InvokingContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Request")]);
|
||||
|
||||
// Act
|
||||
AIContext aiContext = await provider.InvokingAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(aiContext.Messages);
|
||||
List<ChatMessage> messageList = aiContext.Messages.ToList();
|
||||
Assert.Equal(3, messageList.Count);
|
||||
|
||||
foreach (ChatMessage message in messageList)
|
||||
{
|
||||
Assert.NotNull(message.AdditionalProperties);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceType.AdditionalPropertiesKey, out object? sourceType));
|
||||
Assert.Equal(AgentRequestMessageSourceType.AIContextProvider, sourceType);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSource.AdditionalPropertiesKey, out object? source));
|
||||
Assert.Equal(typeof(TestAIContextProviderWithMultipleMessages).FullName, source);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokingAsync_WithNullMessages_ReturnsContextWithoutStampingAsync()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestAIContextProvider();
|
||||
var context = new AIContextProvider.InvokingContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Request")]);
|
||||
|
||||
// Act
|
||||
AIContext aiContext = await provider.InvokingAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(aiContext.Messages);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Basic Tests
|
||||
|
||||
[Fact]
|
||||
public async Task InvokedAsync_ReturnsCompletedTaskAsync()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestAIContextProvider();
|
||||
var messages = new ReadOnlyCollection<ChatMessage>([]);
|
||||
var task = provider.InvokedAsync(new(s_mockAgent, s_mockSession, messages, aiContextProviderMessages: null));
|
||||
|
||||
// Act
|
||||
ValueTask task = provider.InvokedAsync(new(s_mockAgent, s_mockSession, messages));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(default, task);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Serialize_ReturnsEmptyElement()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestAIContextProvider();
|
||||
|
||||
// Act
|
||||
var actual = provider.Serialize();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(default, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvokingContext_Constructor_ThrowsForNullMessages()
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => new AIContextProvider.InvokingContext(s_mockAgent, s_mockSession, null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvokedContext_Constructor_ThrowsForNullMessages()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, null!, aiContextProviderMessages: null));
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, null!));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetService Method Tests
|
||||
|
||||
/// <summary>
|
||||
@@ -246,7 +367,7 @@ public class AIContextProviderTests
|
||||
{
|
||||
// Arrange
|
||||
var messages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
|
||||
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, messages, aiContextProviderMessages: null);
|
||||
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, messages);
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => context.RequestMessages = null!);
|
||||
@@ -258,7 +379,7 @@ public class AIContextProviderTests
|
||||
// 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, aiContextProviderMessages: null);
|
||||
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, initialMessages);
|
||||
|
||||
// Act
|
||||
context.RequestMessages = newMessages;
|
||||
@@ -267,28 +388,13 @@ public class AIContextProviderTests
|
||||
Assert.Same(newMessages, context.RequestMessages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvokedContext_AIContextProviderMessages_Roundtrips()
|
||||
{
|
||||
// Arrange
|
||||
var requestMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
|
||||
var aiContextMessages = new List<ChatMessage> { new(ChatRole.System, "AI context message") };
|
||||
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, aiContextProviderMessages: null);
|
||||
|
||||
// Act
|
||||
context.AIContextProviderMessages = aiContextMessages;
|
||||
|
||||
// Assert
|
||||
Assert.Same(aiContextMessages, context.AIContextProviderMessages);
|
||||
}
|
||||
|
||||
[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, aiContextProviderMessages: null);
|
||||
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages);
|
||||
|
||||
// Act
|
||||
context.ResponseMessages = responseMessages;
|
||||
@@ -303,7 +409,7 @@ 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, aiContextProviderMessages: null);
|
||||
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages);
|
||||
|
||||
// Act
|
||||
context.InvokeException = exception;
|
||||
@@ -319,7 +425,7 @@ public class AIContextProviderTests
|
||||
var requestMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
|
||||
|
||||
// Act
|
||||
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, aiContextProviderMessages: null);
|
||||
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages);
|
||||
|
||||
// Assert
|
||||
Assert.Same(s_mockAgent, context.Agent);
|
||||
@@ -332,7 +438,7 @@ public class AIContextProviderTests
|
||||
var requestMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
|
||||
|
||||
// Act
|
||||
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, aiContextProviderMessages: null);
|
||||
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages);
|
||||
|
||||
// Assert
|
||||
Assert.Same(s_mockSession, context.Session);
|
||||
@@ -345,7 +451,7 @@ public class AIContextProviderTests
|
||||
var requestMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
|
||||
|
||||
// Act
|
||||
var context = new AIContextProvider.InvokedContext(s_mockAgent, null, requestMessages, aiContextProviderMessages: null);
|
||||
var context = new AIContextProvider.InvokedContext(s_mockAgent, null, requestMessages);
|
||||
|
||||
// Assert
|
||||
Assert.Null(context.Session);
|
||||
@@ -358,16 +464,66 @@ public class AIContextProviderTests
|
||||
var requestMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => new AIContextProvider.InvokedContext(null!, s_mockSession, requestMessages, aiContextProviderMessages: null));
|
||||
Assert.Throws<ArgumentNullException>(() => new AIContextProvider.InvokedContext(null!, s_mockSession, requestMessages));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private sealed class TestAIContextProvider : AIContextProvider
|
||||
{
|
||||
public override ValueTask<AIContext> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
protected override ValueTask<AIContext> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
=> new(new AIContext());
|
||||
}
|
||||
|
||||
private sealed class TestAIContextProviderWithMessages : AIContextProvider
|
||||
{
|
||||
protected override ValueTask<AIContext> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
=> new(new AIContext
|
||||
{
|
||||
Messages = [new ChatMessage(ChatRole.System, "Context Message")]
|
||||
});
|
||||
}
|
||||
|
||||
private sealed class TestAIContextProviderWithCustomSource : AIContextProvider
|
||||
{
|
||||
public TestAIContextProviderWithCustomSource(string sourceName) : base(sourceName)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
protected override ValueTask<AIContext> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
=> new(new AIContext
|
||||
{
|
||||
Messages = [new ChatMessage(ChatRole.System, "Context Message")]
|
||||
});
|
||||
}
|
||||
|
||||
private sealed class TestAIContextProviderWithPreStampedMessages : AIContextProvider
|
||||
{
|
||||
protected override ValueTask<AIContext> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var message = new ChatMessage(ChatRole.System, "Pre-stamped Message");
|
||||
message.AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
[AgentRequestMessageSourceType.AdditionalPropertiesKey] = AgentRequestMessageSourceType.AIContextProvider,
|
||||
[AgentRequestMessageSource.AdditionalPropertiesKey] = this.GetType().FullName!
|
||||
};
|
||||
return new(new AIContext
|
||||
{
|
||||
Messages = [message]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TestAIContextProviderWithMultipleMessages : AIContextProvider
|
||||
{
|
||||
protected override ValueTask<AIContext> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
=> new(new AIContext
|
||||
{
|
||||
Messages = [
|
||||
new ChatMessage(ChatRole.System, "Message 1"),
|
||||
new ChatMessage(ChatRole.User, "Message 2"),
|
||||
new ChatMessage(ChatRole.Assistant, "Message 3")
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+489
@@ -0,0 +1,489 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Agents.AI.Abstractions.UnitTests;
|
||||
|
||||
/// <summary>
|
||||
/// Contains tests for the <see cref="AgentRequestMessageSourceType"/> class.
|
||||
/// </summary>
|
||||
public sealed class AgentRequestMessageSourceTypeTests
|
||||
{
|
||||
#region Constructor Tests
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithValue_SetsValueProperty()
|
||||
{
|
||||
// Arrange
|
||||
const string ExpectedValue = "CustomSource";
|
||||
|
||||
// Act
|
||||
AgentRequestMessageSourceType source = new(ExpectedValue);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(ExpectedValue, source.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithNullValue_Throws()
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => new AgentRequestMessageSourceType(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithEmptyValue_Throws()
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentException>(() => new AgentRequestMessageSourceType(string.Empty));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static Properties Tests
|
||||
|
||||
[Fact]
|
||||
public void External_ReturnsInstanceWithExternalValue()
|
||||
{
|
||||
// Arrange & Act
|
||||
AgentRequestMessageSourceType source = AgentRequestMessageSourceType.External;
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(source);
|
||||
Assert.Equal("External", source.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AIContextProvider_ReturnsInstanceWithAIContextProviderValue()
|
||||
{
|
||||
// Arrange & Act
|
||||
AgentRequestMessageSourceType source = AgentRequestMessageSourceType.AIContextProvider;
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(source);
|
||||
Assert.Equal("AIContextProvider", source.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChatHistory_ReturnsInstanceWithChatHistoryValue()
|
||||
{
|
||||
// Arrange & Act
|
||||
AgentRequestMessageSourceType source = AgentRequestMessageSourceType.ChatHistory;
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(source);
|
||||
Assert.Equal("ChatHistory", source.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdditionalPropertiesKey_ReturnsExpectedValue()
|
||||
{
|
||||
// Arrange & Act
|
||||
string key = AgentRequestMessageSourceType.AdditionalPropertiesKey;
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Agent.RequestMessageSourceType", key);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StaticProperties_ReturnSameInstanceOnMultipleCalls()
|
||||
{
|
||||
// Arrange & Act
|
||||
AgentRequestMessageSourceType external1 = AgentRequestMessageSourceType.External;
|
||||
AgentRequestMessageSourceType external2 = AgentRequestMessageSourceType.External;
|
||||
AgentRequestMessageSourceType aiContextProvider1 = AgentRequestMessageSourceType.AIContextProvider;
|
||||
AgentRequestMessageSourceType aiContextProvider2 = AgentRequestMessageSourceType.AIContextProvider;
|
||||
AgentRequestMessageSourceType chatHistory1 = AgentRequestMessageSourceType.ChatHistory;
|
||||
AgentRequestMessageSourceType chatHistory2 = AgentRequestMessageSourceType.ChatHistory;
|
||||
|
||||
// Assert
|
||||
Assert.Same(external1, external2);
|
||||
Assert.Same(aiContextProvider1, aiContextProvider2);
|
||||
Assert.Same(chatHistory1, chatHistory2);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Equals Tests
|
||||
|
||||
[Fact]
|
||||
public void Equals_WithSameInstance_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType source = new("Test");
|
||||
|
||||
// Act
|
||||
bool result = source.Equals(source);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_WithEqualValue_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType source1 = new("Test");
|
||||
AgentRequestMessageSourceType source2 = new("Test");
|
||||
|
||||
// Act
|
||||
bool result = source1.Equals(source2);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_WithDifferentValue_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType source1 = new("Test1");
|
||||
AgentRequestMessageSourceType source2 = new("Test2");
|
||||
|
||||
// Act
|
||||
bool result = source1.Equals(source2);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_WithNull_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType source = new("Test");
|
||||
|
||||
// Act
|
||||
bool result = source.Equals(null);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_WithDifferentCase_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType source1 = new("Test");
|
||||
AgentRequestMessageSourceType source2 = new("test");
|
||||
|
||||
// Act
|
||||
bool result = source1.Equals(source2);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Equals_StaticExternalWithNewInstanceHavingSameValue_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType external = AgentRequestMessageSourceType.External;
|
||||
AgentRequestMessageSourceType newExternal = new("External");
|
||||
|
||||
// Act
|
||||
bool result = external.Equals(newExternal);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Object.Equals Tests
|
||||
|
||||
[Fact]
|
||||
public void ObjectEquals_WithEqualAgentRequestMessageSource_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType source1 = new("Test");
|
||||
object source2 = new AgentRequestMessageSourceType("Test");
|
||||
|
||||
// Act
|
||||
bool result = source1.Equals(source2);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ObjectEquals_WithDifferentType_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType source = new("Test");
|
||||
object other = "Test";
|
||||
|
||||
// Act
|
||||
bool result = source.Equals(other);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ObjectEquals_WithNullObject_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType source = new("Test");
|
||||
object? other = null;
|
||||
|
||||
// Act
|
||||
bool result = source.Equals(other);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetHashCode Tests
|
||||
|
||||
[Fact]
|
||||
public void GetHashCode_WithSameValue_ReturnsSameHashCode()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType source1 = new("Test");
|
||||
AgentRequestMessageSourceType source2 = new("Test");
|
||||
|
||||
// Act
|
||||
int hashCode1 = source1.GetHashCode();
|
||||
int hashCode2 = source2.GetHashCode();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(hashCode1, hashCode2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetHashCode_WithDifferentValue_ReturnsDifferentHashCode()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType source1 = new("Test1");
|
||||
AgentRequestMessageSourceType source2 = new("Test2");
|
||||
|
||||
// Act
|
||||
int hashCode1 = source1.GetHashCode();
|
||||
int hashCode2 = source2.GetHashCode();
|
||||
|
||||
// Assert
|
||||
Assert.NotEqual(hashCode1, hashCode2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetHashCode_ConsistentWithEquals()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType source1 = new("Test");
|
||||
AgentRequestMessageSourceType source2 = new("Test");
|
||||
|
||||
// Act & Assert
|
||||
// If two objects are equal, they must have the same hash code
|
||||
Assert.True(source1.Equals(source2));
|
||||
Assert.Equal(source1.GetHashCode(), source2.GetHashCode());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Equality Operator Tests
|
||||
|
||||
[Fact]
|
||||
public void EqualityOperator_WithEqualValues_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType source1 = new("Test");
|
||||
AgentRequestMessageSourceType source2 = new("Test");
|
||||
|
||||
// Act
|
||||
bool result = source1 == source2;
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualityOperator_WithDifferentValues_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType source1 = new("Test1");
|
||||
AgentRequestMessageSourceType source2 = new("Test2");
|
||||
|
||||
// Act
|
||||
bool result = source1 == source2;
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualityOperator_WithBothNull_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType? source1 = null;
|
||||
AgentRequestMessageSourceType? source2 = null;
|
||||
|
||||
// Act
|
||||
bool result = source1 == source2;
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualityOperator_WithLeftNull_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType? source1 = null;
|
||||
AgentRequestMessageSourceType source2 = new("Test");
|
||||
|
||||
// Act
|
||||
bool result = source1 == source2;
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualityOperator_WithRightNull_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType source1 = new("Test");
|
||||
AgentRequestMessageSourceType? source2 = null;
|
||||
|
||||
// Act
|
||||
bool result = source1 == source2;
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualityOperator_WithStaticInstances_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType external1 = AgentRequestMessageSourceType.External;
|
||||
AgentRequestMessageSourceType external2 = AgentRequestMessageSourceType.External;
|
||||
|
||||
// Act
|
||||
bool result = external1 == external2;
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualityOperator_StaticWithNewInstanceHavingSameValue_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType external = AgentRequestMessageSourceType.External;
|
||||
AgentRequestMessageSourceType newExternal = new("External");
|
||||
|
||||
// Act
|
||||
bool result = external == newExternal;
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Inequality Operator Tests
|
||||
|
||||
[Fact]
|
||||
public void InequalityOperator_WithEqualValues_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType source1 = new("Test");
|
||||
AgentRequestMessageSourceType source2 = new("Test");
|
||||
|
||||
// Act
|
||||
bool result = source1 != source2;
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalityOperator_WithDifferentValues_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType source1 = new("Test1");
|
||||
AgentRequestMessageSourceType source2 = new("Test2");
|
||||
|
||||
// Act
|
||||
bool result = source1 != source2;
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalityOperator_WithBothNull_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType? source1 = null;
|
||||
AgentRequestMessageSourceType? source2 = null;
|
||||
|
||||
// Act
|
||||
bool result = source1 != source2;
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalityOperator_WithLeftNull_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType? source1 = null;
|
||||
AgentRequestMessageSourceType source2 = new("Test");
|
||||
|
||||
// Act
|
||||
bool result = source1 != source2;
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalityOperator_WithRightNull_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType source1 = new("Test");
|
||||
AgentRequestMessageSourceType? source2 = null;
|
||||
|
||||
// Act
|
||||
bool result = source1 != source2;
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InequalityOperator_DifferentStaticInstances_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType external = AgentRequestMessageSourceType.External;
|
||||
AgentRequestMessageSourceType chatHistory = AgentRequestMessageSourceType.ChatHistory;
|
||||
|
||||
// Act
|
||||
bool result = external != chatHistory;
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IEquatable Tests
|
||||
|
||||
[Fact]
|
||||
public void IEquatable_ImplementedCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType source = new("Test");
|
||||
|
||||
// Act & Assert
|
||||
Assert.IsAssignableFrom<IEquatable<AgentRequestMessageSourceType>>(source);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
+23
-14
@@ -6,6 +6,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
|
||||
namespace Microsoft.Agents.AI.Abstractions.UnitTests;
|
||||
|
||||
@@ -41,7 +42,8 @@ public sealed class ChatHistoryProviderExtensionsTests
|
||||
ChatHistoryProvider.InvokingContext context = new(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Test")]);
|
||||
|
||||
providerMock
|
||||
.Setup(p => p.InvokingAsync(context, It.IsAny<CancellationToken>()))
|
||||
.Protected()
|
||||
.Setup<ValueTask<IEnumerable<ChatMessage>>>("InvokingCoreAsync", ItExpr.IsAny<ChatHistoryProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync(innerMessages);
|
||||
|
||||
ChatHistoryProvider filtered = providerMock.Object.WithMessageFilters(
|
||||
@@ -60,16 +62,20 @@ public sealed class ChatHistoryProviderExtensionsTests
|
||||
{
|
||||
// Arrange
|
||||
Mock<ChatHistoryProvider> providerMock = new();
|
||||
List<ChatMessage> requestMessages = [new(ChatRole.User, "Hello")];
|
||||
List<ChatMessage> chatHistoryProviderMessages = [new(ChatRole.System, "System")];
|
||||
ChatHistoryProvider.InvokedContext context = new(s_mockAgent, s_mockSession, requestMessages, chatHistoryProviderMessages)
|
||||
List<ChatMessage> requestMessages =
|
||||
[
|
||||
new(ChatRole.System, "System") { AdditionalProperties = new() { { AgentRequestMessageSourceType.AdditionalPropertiesKey, AgentRequestMessageSourceType.ChatHistory } } },
|
||||
new(ChatRole.User, "Hello")
|
||||
];
|
||||
ChatHistoryProvider.InvokedContext context = new(s_mockAgent, s_mockSession, requestMessages)
|
||||
{
|
||||
ResponseMessages = [new ChatMessage(ChatRole.Assistant, "Response")]
|
||||
};
|
||||
|
||||
ChatHistoryProvider.InvokedContext? capturedContext = null;
|
||||
providerMock
|
||||
.Setup(p => p.InvokedAsync(It.IsAny<ChatHistoryProvider.InvokedContext>(), It.IsAny<CancellationToken>()))
|
||||
.Protected()
|
||||
.Setup<ValueTask>("InvokedCoreAsync", ItExpr.IsAny<ChatHistoryProvider.InvokedContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.Callback<ChatHistoryProvider.InvokedContext, CancellationToken>((ctx, _) => capturedContext = ctx)
|
||||
.Returns(default(ValueTask));
|
||||
|
||||
@@ -106,17 +112,18 @@ public sealed class ChatHistoryProviderExtensionsTests
|
||||
{
|
||||
// Arrange
|
||||
Mock<ChatHistoryProvider> providerMock = new();
|
||||
List<ChatMessage> requestMessages = [new(ChatRole.User, "Hello")];
|
||||
List<ChatMessage> chatHistoryProviderMessages = [new(ChatRole.System, "System")];
|
||||
List<ChatMessage> aiContextProviderMessages = [new(ChatRole.System, "Context")];
|
||||
ChatHistoryProvider.InvokedContext context = new(s_mockAgent, s_mockSession, requestMessages, chatHistoryProviderMessages)
|
||||
{
|
||||
AIContextProviderMessages = aiContextProviderMessages
|
||||
};
|
||||
List<ChatMessage> requestMessages =
|
||||
[
|
||||
new(ChatRole.System, "System") { AdditionalProperties = new() { { AgentRequestMessageSourceType.AdditionalPropertiesKey, AgentRequestMessageSourceType.ChatHistory } } },
|
||||
new(ChatRole.User, "Hello"),
|
||||
new(ChatRole.System, "Context") { AdditionalProperties = new() { { AgentRequestMessageSourceType.AdditionalPropertiesKey, AgentRequestMessageSourceType.AIContextProvider } } }
|
||||
];
|
||||
ChatHistoryProvider.InvokedContext context = new(s_mockAgent, s_mockSession, requestMessages);
|
||||
|
||||
ChatHistoryProvider.InvokedContext? capturedContext = null;
|
||||
providerMock
|
||||
.Setup(p => p.InvokedAsync(It.IsAny<ChatHistoryProvider.InvokedContext>(), It.IsAny<CancellationToken>()))
|
||||
.Protected()
|
||||
.Setup<ValueTask>("InvokedCoreAsync", ItExpr.IsAny<ChatHistoryProvider.InvokedContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.Callback<ChatHistoryProvider.InvokedContext, CancellationToken>((ctx, _) => capturedContext = ctx)
|
||||
.Returns(default(ValueTask));
|
||||
|
||||
@@ -127,6 +134,8 @@ public sealed class ChatHistoryProviderExtensionsTests
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(capturedContext);
|
||||
Assert.Null(capturedContext.AIContextProviderMessages);
|
||||
Assert.Equal(2, capturedContext.RequestMessages.Count());
|
||||
Assert.Contains("System", capturedContext.RequestMessages.Select(x => x.Text));
|
||||
Assert.Contains("Hello", capturedContext.RequestMessages.Select(x => x.Text));
|
||||
}
|
||||
}
|
||||
|
||||
+26
-13
@@ -8,6 +8,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
|
||||
namespace Microsoft.Agents.AI.Abstractions.UnitTests;
|
||||
|
||||
@@ -65,7 +66,8 @@ public sealed class ChatHistoryProviderMessageFilterTests
|
||||
var context = new ChatHistoryProvider.InvokingContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Test")]);
|
||||
|
||||
innerProviderMock
|
||||
.Setup(s => s.InvokingAsync(context, It.IsAny<CancellationToken>()))
|
||||
.Protected()
|
||||
.Setup<ValueTask<IEnumerable<ChatMessage>>>("InvokingCoreAsync", ItExpr.IsAny<ChatHistoryProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync(expectedMessages);
|
||||
|
||||
var filter = new ChatHistoryProviderMessageFilter(innerProviderMock.Object, x => x, x => x);
|
||||
@@ -77,7 +79,9 @@ public sealed class ChatHistoryProviderMessageFilterTests
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Equal("Hello", result[0].Text);
|
||||
Assert.Equal("Hi there!", result[1].Text);
|
||||
innerProviderMock.Verify(s => s.InvokingAsync(context, It.IsAny<CancellationToken>()), Times.Once);
|
||||
innerProviderMock
|
||||
.Protected()
|
||||
.Verify<ValueTask<IEnumerable<ChatMessage>>>("InvokingCoreAsync", Times.Once(), ItExpr.IsAny<ChatHistoryProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -94,7 +98,8 @@ public sealed class ChatHistoryProviderMessageFilterTests
|
||||
var context = new ChatHistoryProvider.InvokingContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Test")]);
|
||||
|
||||
innerProviderMock
|
||||
.Setup(s => s.InvokingAsync(context, It.IsAny<CancellationToken>()))
|
||||
.Protected()
|
||||
.Setup<ValueTask<IEnumerable<ChatMessage>>>("InvokingCoreAsync", ItExpr.IsAny<ChatHistoryProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync(innerMessages);
|
||||
|
||||
// Filter to only user messages
|
||||
@@ -108,7 +113,9 @@ public sealed class ChatHistoryProviderMessageFilterTests
|
||||
// Assert
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.All(result, msg => Assert.Equal(ChatRole.User, msg.Role));
|
||||
innerProviderMock.Verify(s => s.InvokingAsync(context, It.IsAny<CancellationToken>()), Times.Once);
|
||||
innerProviderMock
|
||||
.Protected()
|
||||
.Verify<ValueTask<IEnumerable<ChatMessage>>>("InvokingCoreAsync", Times.Once(), ItExpr.IsAny<ChatHistoryProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -124,7 +131,8 @@ public sealed class ChatHistoryProviderMessageFilterTests
|
||||
var context = new ChatHistoryProvider.InvokingContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Test")]);
|
||||
|
||||
innerProviderMock
|
||||
.Setup(s => s.InvokingAsync(context, It.IsAny<CancellationToken>()))
|
||||
.Protected()
|
||||
.Setup<ValueTask<IEnumerable<ChatMessage>>>("InvokingCoreAsync", ItExpr.IsAny<ChatHistoryProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync(innerMessages);
|
||||
|
||||
// Filter that transforms messages
|
||||
@@ -147,28 +155,31 @@ public sealed class ChatHistoryProviderMessageFilterTests
|
||||
{
|
||||
// Arrange
|
||||
var innerProviderMock = new Mock<ChatHistoryProvider>();
|
||||
var requestMessages = new List<ChatMessage> { new(ChatRole.User, "Hello") };
|
||||
var chatHistoryProviderMessages = new List<ChatMessage> { new(ChatRole.System, "System") };
|
||||
List<ChatMessage> requestMessages =
|
||||
[
|
||||
new(ChatRole.System, "System") { AdditionalProperties = new() { { AgentRequestMessageSourceType.AdditionalPropertiesKey, AgentRequestMessageSourceType.ChatHistory } } },
|
||||
new(ChatRole.User, "Hello"),
|
||||
];
|
||||
var responseMessages = new List<ChatMessage> { new(ChatRole.Assistant, "Response") };
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, chatHistoryProviderMessages)
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages)
|
||||
{
|
||||
ResponseMessages = responseMessages
|
||||
};
|
||||
|
||||
ChatHistoryProvider.InvokedContext? capturedContext = null;
|
||||
innerProviderMock
|
||||
.Setup(s => s.InvokedAsync(It.IsAny<ChatHistoryProvider.InvokedContext>(), It.IsAny<CancellationToken>()))
|
||||
.Protected()
|
||||
.Setup<ValueTask>("InvokedCoreAsync", ItExpr.IsAny<ChatHistoryProvider.InvokedContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.Callback<ChatHistoryProvider.InvokedContext, CancellationToken>((ctx, ct) => capturedContext = ctx)
|
||||
.Returns(default(ValueTask));
|
||||
|
||||
// Filter that modifies the context
|
||||
ChatHistoryProvider.InvokedContext InvokedFilter(ChatHistoryProvider.InvokedContext ctx)
|
||||
{
|
||||
var modifiedRequestMessages = ctx.RequestMessages.Select(m => new ChatMessage(m.Role, $"[FILTERED] {m.Text}")).ToList();
|
||||
return new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, modifiedRequestMessages, ctx.ChatHistoryProviderMessages)
|
||||
var modifiedRequestMessages = ctx.RequestMessages.Where(x => x.GetAgentRequestMessageSource() == AgentRequestMessageSourceType.External).Select(m => new ChatMessage(m.Role, $"[FILTERED] {m.Text}")).ToList();
|
||||
return new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, modifiedRequestMessages)
|
||||
{
|
||||
ResponseMessages = ctx.ResponseMessages,
|
||||
AIContextProviderMessages = ctx.AIContextProviderMessages,
|
||||
InvokeException = ctx.InvokeException
|
||||
};
|
||||
}
|
||||
@@ -182,7 +193,9 @@ public sealed class ChatHistoryProviderMessageFilterTests
|
||||
Assert.NotNull(capturedContext);
|
||||
Assert.Single(capturedContext.RequestMessages);
|
||||
Assert.Equal("[FILTERED] Hello", capturedContext.RequestMessages.First().Text);
|
||||
innerProviderMock.Verify(s => s.InvokedAsync(It.IsAny<ChatHistoryProvider.InvokedContext>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
innerProviderMock
|
||||
.Protected()
|
||||
.Verify<ValueTask>("InvokedCoreAsync", Times.Once(), ItExpr.IsAny<ChatHistoryProvider.InvokedContext>(), ItExpr.IsAny<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
+151
-42
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -18,6 +19,92 @@ public class ChatHistoryProviderTests
|
||||
private static readonly AIAgent s_mockAgent = new Mock<AIAgent>().Object;
|
||||
private static readonly AgentSession s_mockSession = new Mock<AgentSession>().Object;
|
||||
|
||||
#region InvokingAsync Message Stamping Tests
|
||||
|
||||
[Fact]
|
||||
public async Task InvokingAsync_StampsMessagesWithSourceTypeAndSourceAsync()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestChatHistoryProvider();
|
||||
var context = new ChatHistoryProvider.InvokingContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Request")]);
|
||||
|
||||
// Act
|
||||
IEnumerable<ChatMessage> messages = await provider.InvokingAsync(context);
|
||||
|
||||
// Assert
|
||||
ChatMessage message = messages.Single();
|
||||
Assert.NotNull(message.AdditionalProperties);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceType.AdditionalPropertiesKey, out object? sourceType));
|
||||
Assert.Equal(AgentRequestMessageSourceType.ChatHistory, sourceType);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSource.AdditionalPropertiesKey, out object? source));
|
||||
Assert.Equal(typeof(TestChatHistoryProvider).FullName, source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokingAsync_WithCustomSourceName_StampsMessagesWithCustomSourceAsync()
|
||||
{
|
||||
// Arrange
|
||||
const string CustomSourceName = "CustomHistorySource";
|
||||
var provider = new TestChatHistoryProviderWithCustomSource(CustomSourceName);
|
||||
var context = new ChatHistoryProvider.InvokingContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Request")]);
|
||||
|
||||
// Act
|
||||
IEnumerable<ChatMessage> messages = await provider.InvokingAsync(context);
|
||||
|
||||
// Assert
|
||||
ChatMessage message = messages.Single();
|
||||
Assert.NotNull(message.AdditionalProperties);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceType.AdditionalPropertiesKey, out object? sourceType));
|
||||
Assert.Equal(AgentRequestMessageSourceType.ChatHistory, sourceType);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSource.AdditionalPropertiesKey, out object? source));
|
||||
Assert.Equal(CustomSourceName, source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokingAsync_DoesNotReStampAlreadyStampedMessagesAsync()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestChatHistoryProviderWithPreStampedMessages();
|
||||
var context = new ChatHistoryProvider.InvokingContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Request")]);
|
||||
|
||||
// Act
|
||||
IEnumerable<ChatMessage> messages = await provider.InvokingAsync(context);
|
||||
|
||||
// Assert
|
||||
ChatMessage message = messages.Single();
|
||||
Assert.NotNull(message.AdditionalProperties);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceType.AdditionalPropertiesKey, out object? sourceType));
|
||||
Assert.Equal(AgentRequestMessageSourceType.ChatHistory, sourceType);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSource.AdditionalPropertiesKey, out object? source));
|
||||
Assert.Equal(typeof(TestChatHistoryProviderWithPreStampedMessages).FullName, source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokingAsync_StampsMultipleMessagesAsync()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new TestChatHistoryProviderWithMultipleMessages();
|
||||
var context = new ChatHistoryProvider.InvokingContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Request")]);
|
||||
|
||||
// Act
|
||||
IEnumerable<ChatMessage> messages = await provider.InvokingAsync(context);
|
||||
|
||||
// Assert
|
||||
List<ChatMessage> messageList = messages.ToList();
|
||||
Assert.Equal(3, messageList.Count);
|
||||
|
||||
foreach (ChatMessage message in messageList)
|
||||
{
|
||||
Assert.NotNull(message.AdditionalProperties);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSourceType.AdditionalPropertiesKey, out object? sourceType));
|
||||
Assert.Equal(AgentRequestMessageSourceType.ChatHistory, sourceType);
|
||||
Assert.True(message.AdditionalProperties.TryGetValue(AgentRequestMessageSource.AdditionalPropertiesKey, out object? source));
|
||||
Assert.Equal(typeof(TestChatHistoryProviderWithMultipleMessages).FullName, source);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetService Method Tests
|
||||
|
||||
[Fact]
|
||||
@@ -172,7 +259,7 @@ public class ChatHistoryProviderTests
|
||||
public void InvokedContext_Constructor_ThrowsForNullRequestMessages()
|
||||
{
|
||||
// Arrange & Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, null!, []));
|
||||
Assert.Throws<ArgumentNullException>(() => new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -180,7 +267,7 @@ public class ChatHistoryProviderTests
|
||||
{
|
||||
// Arrange
|
||||
var requestMessages = new List<ChatMessage> { new(ChatRole.User, "Hello") };
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, []);
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages);
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => context.RequestMessages = null!);
|
||||
@@ -192,7 +279,7 @@ public class ChatHistoryProviderTests
|
||||
// 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, []);
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, initialMessages);
|
||||
|
||||
// Act
|
||||
context.RequestMessages = newMessages;
|
||||
@@ -201,43 +288,13 @@ public class ChatHistoryProviderTests
|
||||
Assert.Same(newMessages, context.RequestMessages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvokedContext_ChatHistoryProviderMessages_SetterRoundtrips()
|
||||
{
|
||||
// Arrange
|
||||
var requestMessages = new List<ChatMessage> { new(ChatRole.User, "Hello") };
|
||||
var newProviderMessages = new List<ChatMessage> { new(ChatRole.System, "System message") };
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, []);
|
||||
|
||||
// Act
|
||||
context.ChatHistoryProviderMessages = newProviderMessages;
|
||||
|
||||
// Assert
|
||||
Assert.Same(newProviderMessages, context.ChatHistoryProviderMessages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvokedContext_AIContextProviderMessages_Roundtrips()
|
||||
{
|
||||
// Arrange
|
||||
var requestMessages = new List<ChatMessage> { new(ChatRole.User, "Hello") };
|
||||
var aiContextMessages = new List<ChatMessage> { new(ChatRole.System, "AI context message") };
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, []);
|
||||
|
||||
// Act
|
||||
context.AIContextProviderMessages = aiContextMessages;
|
||||
|
||||
// Assert
|
||||
Assert.Same(aiContextMessages, context.AIContextProviderMessages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvokedContext_ResponseMessages_Roundtrips()
|
||||
{
|
||||
// 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, []);
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages);
|
||||
|
||||
// Act
|
||||
context.ResponseMessages = responseMessages;
|
||||
@@ -252,7 +309,7 @@ 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, []);
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages);
|
||||
|
||||
// Act
|
||||
context.InvokeException = exception;
|
||||
@@ -268,7 +325,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);
|
||||
@@ -281,7 +338,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);
|
||||
@@ -294,7 +351,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);
|
||||
@@ -307,17 +364,69 @@ 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));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private sealed class TestChatHistoryProvider : ChatHistoryProvider
|
||||
{
|
||||
public override ValueTask<IEnumerable<ChatMessage>> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
=> new(Array.Empty<ChatMessage>());
|
||||
protected override ValueTask<IEnumerable<ChatMessage>> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
=> new([new ChatMessage(ChatRole.User, "Test Message")]);
|
||||
|
||||
public override ValueTask InvokedAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
protected override ValueTask InvokedCoreAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
=> default;
|
||||
|
||||
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
=> default;
|
||||
}
|
||||
|
||||
private sealed class TestChatHistoryProviderWithCustomSource : ChatHistoryProvider
|
||||
{
|
||||
public TestChatHistoryProviderWithCustomSource(string sourceName) : base(sourceName)
|
||||
{
|
||||
}
|
||||
|
||||
protected override ValueTask<IEnumerable<ChatMessage>> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
=> new([new ChatMessage(ChatRole.User, "Test Message")]);
|
||||
|
||||
protected override ValueTask InvokedCoreAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
=> default;
|
||||
|
||||
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
=> default;
|
||||
}
|
||||
|
||||
private sealed class TestChatHistoryProviderWithPreStampedMessages : ChatHistoryProvider
|
||||
{
|
||||
protected override ValueTask<IEnumerable<ChatMessage>> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var message = new ChatMessage(ChatRole.User, "Pre-stamped Message");
|
||||
message.AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
[AgentRequestMessageSourceType.AdditionalPropertiesKey] = AgentRequestMessageSourceType.ChatHistory,
|
||||
[AgentRequestMessageSource.AdditionalPropertiesKey] = this.GetType().FullName!
|
||||
};
|
||||
return new([message]);
|
||||
}
|
||||
|
||||
protected override ValueTask InvokedCoreAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
=> default;
|
||||
|
||||
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
=> default;
|
||||
}
|
||||
|
||||
private sealed class TestChatHistoryProviderWithMultipleMessages : ChatHistoryProvider
|
||||
{
|
||||
protected override ValueTask<IEnumerable<ChatMessage>> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
=> new([
|
||||
new ChatMessage(ChatRole.User, "Message 1"),
|
||||
new ChatMessage(ChatRole.Assistant, "Message 2"),
|
||||
new ChatMessage(ChatRole.User, "Message 3")
|
||||
]);
|
||||
|
||||
protected override ValueTask InvokedCoreAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
=> default;
|
||||
|
||||
public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
|
||||
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Extensions.AI;
|
||||
|
||||
namespace Microsoft.Agents.AI.Abstractions.UnitTests;
|
||||
|
||||
/// <summary>
|
||||
/// Contains tests for the <see cref="ChatMessageExtensions"/> class.
|
||||
/// </summary>
|
||||
public sealed class ChatMessageExtensionsTests
|
||||
{
|
||||
#region GetAgentRequestMessageSource Tests
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSource_WithNoAdditionalProperties_ReturnsExternal()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello");
|
||||
|
||||
// Act
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSource();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(AgentRequestMessageSourceType.External, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSource_WithNullAdditionalProperties_ReturnsExternal()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = null
|
||||
};
|
||||
|
||||
// Act
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSource();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(AgentRequestMessageSourceType.External, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSource_WithEmptyAdditionalProperties_ReturnsExternal()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary()
|
||||
};
|
||||
|
||||
// Act
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSource();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(AgentRequestMessageSourceType.External, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSource_WithExternalSource_ReturnsExternal()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
{ AgentRequestMessageSourceType.AdditionalPropertiesKey, AgentRequestMessageSourceType.External }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSource();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(AgentRequestMessageSourceType.External, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSource_WithAIContextProviderSource_ReturnsAIContextProvider()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
{ AgentRequestMessageSourceType.AdditionalPropertiesKey, AgentRequestMessageSourceType.AIContextProvider }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSource();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(AgentRequestMessageSourceType.AIContextProvider, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSource_WithChatHistorySource_ReturnsChatHistory()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
{ AgentRequestMessageSourceType.AdditionalPropertiesKey, AgentRequestMessageSourceType.ChatHistory }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSource();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(AgentRequestMessageSourceType.ChatHistory, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSource_WithCustomSource_ReturnsCustomSource()
|
||||
{
|
||||
// Arrange
|
||||
AgentRequestMessageSourceType customSource = new("CustomSource");
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
{ AgentRequestMessageSourceType.AdditionalPropertiesKey, customSource }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSource();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(customSource, result);
|
||||
Assert.Equal("CustomSource", result.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSource_WithWrongKeyType_ReturnsExternal()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
{ AgentRequestMessageSourceType.AdditionalPropertiesKey, "NotAnAgentRequestMessageSource" }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSource();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(AgentRequestMessageSourceType.External, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSource_WithNullValue_ReturnsExternal()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
{ AgentRequestMessageSourceType.AdditionalPropertiesKey, null! }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSource();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(AgentRequestMessageSourceType.External, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAgentRequestMessageSource_WithMultipleProperties_ReturnsCorrectSource()
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage message = new(ChatRole.User, "Hello")
|
||||
{
|
||||
AdditionalProperties = new AdditionalPropertiesDictionary
|
||||
{
|
||||
{ "OtherProperty", "SomeValue" },
|
||||
{ AgentRequestMessageSourceType.AdditionalPropertiesKey, AgentRequestMessageSourceType.ChatHistory },
|
||||
{ "AnotherProperty", 123 }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
AgentRequestMessageSourceType result = message.GetAgentRequestMessageSource();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(AgentRequestMessageSourceType.ChatHistory, result);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
+8
-12
@@ -54,7 +54,8 @@ public class InMemoryChatHistoryProviderTests
|
||||
{
|
||||
var requestMessages = new List<ChatMessage>
|
||||
{
|
||||
new(ChatRole.User, "Hello")
|
||||
new(ChatRole.User, "Hello"),
|
||||
new(ChatRole.System, "additional context") { AdditionalProperties = new() { { AgentRequestMessageSourceType.AdditionalPropertiesKey, AgentRequestMessageSourceType.ChatHistory } } },
|
||||
};
|
||||
var responseMessages = new List<ChatMessage>
|
||||
{
|
||||
@@ -64,16 +65,11 @@ public class InMemoryChatHistoryProviderTests
|
||||
{
|
||||
new(ChatRole.System, "original instructions")
|
||||
};
|
||||
var aiContextProviderMessages = new List<ChatMessage>()
|
||||
{
|
||||
new(ChatRole.System, "additional context")
|
||||
};
|
||||
|
||||
var provider = new InMemoryChatHistoryProvider();
|
||||
provider.Add(providerMessages[0]);
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, providerMessages)
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages)
|
||||
{
|
||||
AIContextProviderMessages = aiContextProviderMessages,
|
||||
ResponseMessages = responseMessages
|
||||
};
|
||||
await provider.InvokedAsync(context, CancellationToken.None);
|
||||
@@ -90,7 +86,7 @@ public class InMemoryChatHistoryProviderTests
|
||||
{
|
||||
var provider = new InMemoryChatHistoryProvider();
|
||||
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, [], []);
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, []);
|
||||
await provider.InvokedAsync(context, CancellationToken.None);
|
||||
|
||||
Assert.Empty(provider);
|
||||
@@ -186,7 +182,7 @@ public class InMemoryChatHistoryProviderTests
|
||||
var provider = new InMemoryChatHistoryProvider();
|
||||
var messages = new List<ChatMessage>();
|
||||
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, messages, []);
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, messages);
|
||||
await provider.InvokedAsync(context, CancellationToken.None);
|
||||
|
||||
Assert.Empty(provider);
|
||||
@@ -523,7 +519,7 @@ public class InMemoryChatHistoryProviderTests
|
||||
var provider = new InMemoryChatHistoryProvider(reducerMock.Object, InMemoryChatHistoryProvider.ChatReducerTriggerEvent.AfterMessageAdded);
|
||||
|
||||
// Act
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, originalMessages, []);
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, originalMessages);
|
||||
await provider.InvokedAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
@@ -582,7 +578,7 @@ public class InMemoryChatHistoryProviderTests
|
||||
var provider = new InMemoryChatHistoryProvider(reducerMock.Object, InMemoryChatHistoryProvider.ChatReducerTriggerEvent.BeforeMessagesRetrieval);
|
||||
|
||||
// Act
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, originalMessages, []);
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, originalMessages);
|
||||
await provider.InvokedAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
@@ -630,7 +626,7 @@ public class InMemoryChatHistoryProviderTests
|
||||
{
|
||||
new(ChatRole.Assistant, "Hi there!")
|
||||
};
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, [])
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages)
|
||||
{
|
||||
ResponseMessages = responseMessages,
|
||||
InvokeException = new InvalidOperationException("Test exception")
|
||||
|
||||
+3
-3
@@ -3140,7 +3140,7 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
/// </summary>
|
||||
private sealed class TestAIContextProvider : AIContextProvider
|
||||
{
|
||||
public override ValueTask<AIContext> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
protected override ValueTask<AIContext> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return new ValueTask<AIContext>(new AIContext());
|
||||
}
|
||||
@@ -3151,12 +3151,12 @@ public sealed class AzureAIProjectChatClientExtensionsTests
|
||||
/// </summary>
|
||||
private sealed class TestChatHistoryProvider : ChatHistoryProvider
|
||||
{
|
||||
public override ValueTask<IEnumerable<ChatMessage>> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
protected override ValueTask<IEnumerable<ChatMessage>> InvokingCoreAsync(InvokingContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return new ValueTask<IEnumerable<ChatMessage>>(Array.Empty<ChatMessage>());
|
||||
}
|
||||
|
||||
public override ValueTask InvokedAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
protected override ValueTask InvokedCoreAsync(InvokedContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
+16
-20
@@ -217,7 +217,7 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
|
||||
using var provider = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, TestContainerId, conversationId);
|
||||
var message = new ChatMessage(ChatRole.User, "Hello, world!");
|
||||
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, [message], [])
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, [message])
|
||||
{
|
||||
ResponseMessages = []
|
||||
};
|
||||
@@ -285,20 +285,16 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
|
||||
{
|
||||
new ChatMessage(ChatRole.User, "First message"),
|
||||
new ChatMessage(ChatRole.Assistant, "Second message"),
|
||||
new ChatMessage(ChatRole.User, "Third message")
|
||||
};
|
||||
var aiContextProviderMessages = new[]
|
||||
{
|
||||
new ChatMessage(ChatRole.System, "System context message")
|
||||
new ChatMessage(ChatRole.User, "Third message"),
|
||||
new ChatMessage(ChatRole.System, "System context message") { AdditionalProperties = new() { { AgentRequestMessageSourceType.AdditionalPropertiesKey, AgentRequestMessageSourceType.AIContextProvider } } }
|
||||
};
|
||||
var responseMessages = new[]
|
||||
{
|
||||
new ChatMessage(ChatRole.Assistant, "Response message")
|
||||
};
|
||||
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, [])
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages)
|
||||
{
|
||||
AIContextProviderMessages = aiContextProviderMessages,
|
||||
ResponseMessages = responseMessages
|
||||
};
|
||||
|
||||
@@ -349,8 +345,8 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
|
||||
using var store1 = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, TestContainerId, conversation1);
|
||||
using var store2 = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, TestContainerId, conversation2);
|
||||
|
||||
var context1 = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Message for conversation 1")], []);
|
||||
var context2 = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Message for conversation 2")], []);
|
||||
var context1 = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Message for conversation 1")]);
|
||||
var context2 = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Message for conversation 2")]);
|
||||
|
||||
await store1.InvokedAsync(context1);
|
||||
await store2.InvokedAsync(context2);
|
||||
@@ -394,7 +390,7 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
|
||||
};
|
||||
|
||||
// Act 1: Add messages
|
||||
var invokedContext = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, messages, []);
|
||||
var invokedContext = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, messages);
|
||||
await originalStore.InvokedAsync(invokedContext);
|
||||
|
||||
// Act 2: Verify messages were added
|
||||
@@ -548,7 +544,7 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
|
||||
using var provider = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, TenantId, UserId, SessionId);
|
||||
var message = new ChatMessage(ChatRole.User, "Hello from hierarchical partitioning!");
|
||||
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, [message], []);
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, [message]);
|
||||
|
||||
// Act
|
||||
await provider.InvokedAsync(context);
|
||||
@@ -605,7 +601,7 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
|
||||
new ChatMessage(ChatRole.User, "Third hierarchical message")
|
||||
};
|
||||
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, messages, []);
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, messages);
|
||||
|
||||
// Act
|
||||
await provider.InvokedAsync(context);
|
||||
@@ -640,8 +636,8 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
|
||||
using var store2 = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, TenantId, UserId2, SessionId);
|
||||
|
||||
// Add messages to both stores
|
||||
var context1 = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Message from user 1")], []);
|
||||
var context2 = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Message from user 2")], []);
|
||||
var context1 = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Message from user 1")]);
|
||||
var context2 = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Message from user 2")]);
|
||||
|
||||
await store1.InvokedAsync(context1);
|
||||
await store2.InvokedAsync(context2);
|
||||
@@ -678,7 +674,7 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
|
||||
|
||||
using var originalStore = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, TenantId, UserId, SessionId);
|
||||
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Test serialization message")], []);
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Test serialization message")]);
|
||||
await originalStore.InvokedAsync(context);
|
||||
|
||||
// Act - Serialize the provider state
|
||||
@@ -720,8 +716,8 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
|
||||
using var hierarchicalProvider = new CosmosChatHistoryProvider(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, "tenant-coexist", "user-coexist", SessionId);
|
||||
|
||||
// Add messages to both
|
||||
var simpleContext = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Simple partitioning message")], []);
|
||||
var hierarchicalContext = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Hierarchical partitioning message")], []);
|
||||
var simpleContext = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Simple partitioning message")]);
|
||||
var hierarchicalContext = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "Hierarchical partitioning message")]);
|
||||
|
||||
await simpleProvider.InvokedAsync(simpleContext);
|
||||
await hierarchicalProvider.InvokedAsync(hierarchicalContext);
|
||||
@@ -763,7 +759,7 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
|
||||
await Task.Delay(10); // Small delay to ensure different timestamps
|
||||
}
|
||||
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, messages, []);
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, messages);
|
||||
await provider.InvokedAsync(context);
|
||||
|
||||
// Wait for eventual consistency
|
||||
@@ -801,7 +797,7 @@ public sealed class CosmosChatHistoryProviderTests : IAsyncLifetime, IDisposable
|
||||
messages.Add(new ChatMessage(ChatRole.User, $"Message {i}"));
|
||||
}
|
||||
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, messages, []);
|
||||
var context = new ChatHistoryProvider.InvokedContext(s_mockAgent, s_mockSession, messages);
|
||||
await provider.InvokedAsync(context);
|
||||
|
||||
// Wait for eventual consistency
|
||||
|
||||
@@ -56,7 +56,7 @@ public sealed class Mem0ProviderTests : IDisposable
|
||||
Assert.DoesNotContain("Caoimhe", ctxBefore.Messages?[0].Text ?? string.Empty);
|
||||
|
||||
// Act
|
||||
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, [input], aiContextProviderMessages: null));
|
||||
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, [input]));
|
||||
var ctxAfterAdding = await GetContextWithRetryAsync(sut, question);
|
||||
await sut.ClearStoredMemoriesAsync();
|
||||
var ctxAfterClearing = await sut.InvokingAsync(new AIContextProvider.InvokingContext(s_mockAgent, s_mockSession, [question]));
|
||||
@@ -80,7 +80,7 @@ public sealed class Mem0ProviderTests : IDisposable
|
||||
Assert.DoesNotContain("Caoimhe", ctxBefore.Messages?[0].Text ?? string.Empty);
|
||||
|
||||
// Act
|
||||
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, [assistantIntro], aiContextProviderMessages: null));
|
||||
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, [assistantIntro]));
|
||||
var ctxAfterAdding = await GetContextWithRetryAsync(sut, question);
|
||||
await sut.ClearStoredMemoriesAsync();
|
||||
var ctxAfterClearing = await sut.InvokingAsync(new AIContextProvider.InvokingContext(s_mockAgent, s_mockSession, [question]));
|
||||
@@ -108,7 +108,7 @@ public sealed class Mem0ProviderTests : IDisposable
|
||||
Assert.DoesNotContain("Caoimhe", ctxBefore2.Messages?[0].Text ?? string.Empty);
|
||||
|
||||
// Act
|
||||
await sut1.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, [assistantIntro], aiContextProviderMessages: null));
|
||||
await sut1.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, [assistantIntro]));
|
||||
var ctxAfterAdding1 = await GetContextWithRetryAsync(sut1, question);
|
||||
var ctxAfterAdding2 = await GetContextWithRetryAsync(sut2, question);
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@ public sealed class Mem0ProviderTests : IDisposable
|
||||
};
|
||||
|
||||
// Act
|
||||
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, aiContextProviderMessages: null) { ResponseMessages = responseMessages });
|
||||
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages) { ResponseMessages = responseMessages });
|
||||
|
||||
// Assert
|
||||
var memoryPosts = this._handler.Requests.Where(r => r.RequestMessage.RequestUri!.AbsolutePath == "/v1/memories/" && r.RequestMessage.Method == HttpMethod.Post).ToList();
|
||||
@@ -245,7 +245,7 @@ public sealed class Mem0ProviderTests : IDisposable
|
||||
};
|
||||
|
||||
// Act
|
||||
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, aiContextProviderMessages: null) { ResponseMessages = null, InvokeException = new InvalidOperationException("Request Failed") });
|
||||
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages) { ResponseMessages = null, InvokeException = new InvalidOperationException("Request Failed") });
|
||||
|
||||
// Assert
|
||||
Assert.Empty(this._handler.Requests);
|
||||
@@ -271,7 +271,7 @@ public sealed class Mem0ProviderTests : IDisposable
|
||||
};
|
||||
|
||||
// Act
|
||||
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, aiContextProviderMessages: null) { ResponseMessages = responseMessages });
|
||||
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages) { ResponseMessages = responseMessages });
|
||||
|
||||
// Assert
|
||||
this._loggerMock.Verify(
|
||||
@@ -321,7 +321,7 @@ public sealed class Mem0ProviderTests : IDisposable
|
||||
};
|
||||
|
||||
// Act
|
||||
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, aiContextProviderMessages: null) { ResponseMessages = responseMessages });
|
||||
await sut.InvokedAsync(new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages) { ResponseMessages = responseMessages });
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedLogCount, this._loggerMock.Invocations.Count);
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
|
||||
namespace Microsoft.Agents.AI.UnitTests;
|
||||
|
||||
@@ -342,7 +343,8 @@ public partial class ChatClientAgentTests
|
||||
|
||||
var mockProvider = new Mock<AIContextProvider>();
|
||||
mockProvider
|
||||
.Setup(p => p.InvokingAsync(It.IsAny<AIContextProvider.InvokingContext>(), It.IsAny<CancellationToken>()))
|
||||
.Protected()
|
||||
.Setup<ValueTask<AIContext>>("InvokingCoreAsync", ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync(new AIContext
|
||||
{
|
||||
Messages = aiContextProviderMessages,
|
||||
@@ -350,7 +352,8 @@ public partial class ChatClientAgentTests
|
||||
Tools = [AIFunctionFactory.Create(() => { }, "context provider function")]
|
||||
});
|
||||
mockProvider
|
||||
.Setup(p => p.InvokedAsync(It.IsAny<AIContextProvider.InvokedContext>(), It.IsAny<CancellationToken>()))
|
||||
.Protected()
|
||||
.Setup<ValueTask>("InvokedCoreAsync", ItExpr.IsAny<AIContextProvider.InvokedContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.Returns(new ValueTask());
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { AIContextProviderFactory = (_, _) => new(mockProvider.Object), ChatOptions = new() { Instructions = "base instructions", Tools = [AIFunctionFactory.Create(() => { }, "base function")] } });
|
||||
@@ -378,12 +381,15 @@ public partial class ChatClientAgentTests
|
||||
Assert.Equal("context provider message", chatHistoryProvider[1].Text);
|
||||
Assert.Equal("response", chatHistoryProvider[2].Text);
|
||||
|
||||
mockProvider.Verify(p => p.InvokingAsync(It.IsAny<AIContextProvider.InvokingContext>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
mockProvider.Verify(p => p.InvokedAsync(It.Is<AIContextProvider.InvokedContext>(x =>
|
||||
x.RequestMessages == requestMessages &&
|
||||
x.AIContextProviderMessages == aiContextProviderMessages &&
|
||||
x.ResponseMessages == responseMessages &&
|
||||
x.InvokeException == null), It.IsAny<CancellationToken>()), Times.Once);
|
||||
mockProvider
|
||||
.Protected()
|
||||
.Verify<ValueTask<AIContext>>("InvokingCoreAsync", Times.Once(), ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>());
|
||||
mockProvider
|
||||
.Protected()
|
||||
.Verify<ValueTask>("InvokedCoreAsync", Times.Once(), ItExpr.Is<AIContextProvider.InvokedContext>(x =>
|
||||
x.RequestMessages.Count() == requestMessages.Length + aiContextProviderMessages.Length &&
|
||||
x.ResponseMessages == responseMessages &&
|
||||
x.InvokeException == null), ItExpr.IsAny<CancellationToken>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -394,7 +400,6 @@ public partial class ChatClientAgentTests
|
||||
{
|
||||
// Arrange
|
||||
ChatMessage[] requestMessages = [new(ChatRole.User, "user message")];
|
||||
ChatMessage[] responseMessages = [new(ChatRole.Assistant, "response")];
|
||||
ChatMessage[] aiContextProviderMessages = [new(ChatRole.System, "context provider message")];
|
||||
Mock<IChatClient> mockService = new();
|
||||
mockService
|
||||
@@ -406,13 +411,15 @@ public partial class ChatClientAgentTests
|
||||
|
||||
var mockProvider = new Mock<AIContextProvider>();
|
||||
mockProvider
|
||||
.Setup(p => p.InvokingAsync(It.IsAny<AIContextProvider.InvokingContext>(), It.IsAny<CancellationToken>()))
|
||||
.Protected()
|
||||
.Setup<ValueTask<AIContext>>("InvokingCoreAsync", ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync(new AIContext
|
||||
{
|
||||
Messages = aiContextProviderMessages,
|
||||
});
|
||||
mockProvider
|
||||
.Setup(p => p.InvokedAsync(It.IsAny<AIContextProvider.InvokedContext>(), It.IsAny<CancellationToken>()))
|
||||
.Protected()
|
||||
.Setup<ValueTask>("InvokedCoreAsync", ItExpr.IsAny<AIContextProvider.InvokedContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.Returns(new ValueTask());
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { AIContextProviderFactory = (_, _) => new(mockProvider.Object), ChatOptions = new() { Instructions = "base instructions", Tools = [AIFunctionFactory.Create(() => { }, "base function")] } });
|
||||
@@ -421,12 +428,15 @@ public partial class ChatClientAgentTests
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => agent.RunAsync(requestMessages));
|
||||
|
||||
// Assert
|
||||
mockProvider.Verify(p => p.InvokingAsync(It.IsAny<AIContextProvider.InvokingContext>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
mockProvider.Verify(p => p.InvokedAsync(It.Is<AIContextProvider.InvokedContext>(x =>
|
||||
x.RequestMessages == requestMessages &&
|
||||
x.AIContextProviderMessages == aiContextProviderMessages &&
|
||||
x.ResponseMessages == null &&
|
||||
x.InvokeException is InvalidOperationException), It.IsAny<CancellationToken>()), Times.Once);
|
||||
mockProvider
|
||||
.Protected()
|
||||
.Verify<ValueTask<AIContext>>("InvokingCoreAsync", Times.Once(), ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>());
|
||||
mockProvider
|
||||
.Protected()
|
||||
.Verify<ValueTask>("InvokedCoreAsync", Times.Once(), ItExpr.Is<AIContextProvider.InvokedContext>(x =>
|
||||
x.RequestMessages.Count() == requestMessages.Length + aiContextProviderMessages.Length &&
|
||||
x.ResponseMessages == null &&
|
||||
x.InvokeException is InvalidOperationException), ItExpr.IsAny<CancellationToken>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -458,7 +468,8 @@ public partial class ChatClientAgentTests
|
||||
|
||||
var mockProvider = new Mock<AIContextProvider>();
|
||||
mockProvider
|
||||
.Setup(p => p.InvokingAsync(It.IsAny<AIContextProvider.InvokingContext>(), It.IsAny<CancellationToken>()))
|
||||
.Protected()
|
||||
.Setup<ValueTask<AIContext>>("InvokingCoreAsync", ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync(new AIContext());
|
||||
|
||||
ChatClientAgent agent = new(mockService.Object, options: new() { AIContextProviderFactory = (_, _) => new(mockProvider.Object), ChatOptions = new() { Instructions = "base instructions", Tools = [AIFunctionFactory.Create(() => { }, "base function")] } });
|
||||
@@ -474,7 +485,9 @@ public partial class ChatClientAgentTests
|
||||
Assert.Equal(ChatRole.User, capturedMessages[0].Role);
|
||||
Assert.Single(capturedTools);
|
||||
Assert.Contains(capturedTools, t => t.Name == "base function");
|
||||
mockProvider.Verify(p => p.InvokingAsync(It.IsAny<AIContextProvider.InvokingContext>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
mockProvider
|
||||
.Protected()
|
||||
.Verify<ValueTask<AIContext>>("InvokingCoreAsync", Times.Once(), ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>());
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -1371,7 +1384,8 @@ public partial class ChatClientAgentTests
|
||||
|
||||
var mockProvider = new Mock<AIContextProvider>();
|
||||
mockProvider
|
||||
.Setup(p => p.InvokingAsync(It.IsAny<AIContextProvider.InvokingContext>(), It.IsAny<CancellationToken>()))
|
||||
.Protected()
|
||||
.Setup<ValueTask<AIContext>>("InvokingCoreAsync", ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync(new AIContext
|
||||
{
|
||||
Messages = aiContextProviderMessages,
|
||||
@@ -1379,7 +1393,8 @@ public partial class ChatClientAgentTests
|
||||
Tools = [AIFunctionFactory.Create(() => { }, "context provider function")]
|
||||
});
|
||||
mockProvider
|
||||
.Setup(p => p.InvokedAsync(It.IsAny<AIContextProvider.InvokedContext>(), It.IsAny<CancellationToken>()))
|
||||
.Protected()
|
||||
.Setup<ValueTask>("InvokedCoreAsync", ItExpr.IsAny<AIContextProvider.InvokedContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.Returns(new ValueTask());
|
||||
|
||||
ChatClientAgent agent = new(
|
||||
@@ -1414,13 +1429,16 @@ public partial class ChatClientAgentTests
|
||||
Assert.Equal("context provider message", chatHistoryProvider[1].Text);
|
||||
Assert.Equal("response", chatHistoryProvider[2].Text);
|
||||
|
||||
mockProvider.Verify(p => p.InvokingAsync(It.IsAny<AIContextProvider.InvokingContext>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
mockProvider.Verify(p => p.InvokedAsync(It.Is<AIContextProvider.InvokedContext>(x =>
|
||||
x.RequestMessages == requestMessages &&
|
||||
x.AIContextProviderMessages == aiContextProviderMessages &&
|
||||
x.ResponseMessages!.Count() == 1 &&
|
||||
x.ResponseMessages!.ElementAt(0).Text == "response" &&
|
||||
x.InvokeException == null), It.IsAny<CancellationToken>()), Times.Once);
|
||||
mockProvider
|
||||
.Protected()
|
||||
.Verify<ValueTask<AIContext>>("InvokingCoreAsync", Times.Once(), ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>());
|
||||
mockProvider
|
||||
.Protected()
|
||||
.Verify<ValueTask>("InvokedCoreAsync", Times.Once(), ItExpr.Is<AIContextProvider.InvokedContext>(x =>
|
||||
x.RequestMessages.Count() == requestMessages.Length + aiContextProviderMessages.Length &&
|
||||
x.ResponseMessages!.Count() == 1 &&
|
||||
x.ResponseMessages!.ElementAt(0).Text == "response" &&
|
||||
x.InvokeException == null), ItExpr.IsAny<CancellationToken>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1442,13 +1460,15 @@ public partial class ChatClientAgentTests
|
||||
|
||||
var mockProvider = new Mock<AIContextProvider>();
|
||||
mockProvider
|
||||
.Setup(p => p.InvokingAsync(It.IsAny<AIContextProvider.InvokingContext>(), It.IsAny<CancellationToken>()))
|
||||
.Protected()
|
||||
.Setup<ValueTask<AIContext>>("InvokingCoreAsync", ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync(new AIContext
|
||||
{
|
||||
Messages = aiContextProviderMessages,
|
||||
});
|
||||
mockProvider
|
||||
.Setup(p => p.InvokedAsync(It.IsAny<AIContextProvider.InvokedContext>(), It.IsAny<CancellationToken>()))
|
||||
.Protected()
|
||||
.Setup<ValueTask>("InvokedCoreAsync", ItExpr.IsAny<AIContextProvider.InvokedContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.Returns(new ValueTask());
|
||||
|
||||
ChatClientAgent agent = new(
|
||||
@@ -1467,12 +1487,15 @@ public partial class ChatClientAgentTests
|
||||
});
|
||||
|
||||
// Assert
|
||||
mockProvider.Verify(p => p.InvokingAsync(It.IsAny<AIContextProvider.InvokingContext>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
mockProvider.Verify(p => p.InvokedAsync(It.Is<AIContextProvider.InvokedContext>(x =>
|
||||
x.RequestMessages == requestMessages &&
|
||||
x.AIContextProviderMessages == aiContextProviderMessages &&
|
||||
x.ResponseMessages == null &&
|
||||
x.InvokeException is InvalidOperationException), It.IsAny<CancellationToken>()), Times.Once);
|
||||
mockProvider
|
||||
.Protected()
|
||||
.Verify<ValueTask<AIContext>>("InvokingCoreAsync", Times.Once(), ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>());
|
||||
mockProvider
|
||||
.Protected()
|
||||
.Verify<ValueTask>("InvokedCoreAsync", Times.Once(), ItExpr.Is<AIContextProvider.InvokedContext>(x =>
|
||||
x.RequestMessages.Count() == requestMessages.Length + aiContextProviderMessages.Length &&
|
||||
x.ResponseMessages == null &&
|
||||
x.InvokeException is InvalidOperationException), ItExpr.IsAny<CancellationToken>());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
+41
-24
@@ -7,6 +7,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
|
||||
namespace Microsoft.Agents.AI.UnitTests;
|
||||
|
||||
@@ -339,13 +340,15 @@ public class ChatClientAgent_BackgroundResponsesTests
|
||||
// Create a mock chat history provider that would normally provide messages
|
||||
var mockChatHistoryProvider = new Mock<ChatHistoryProvider>();
|
||||
mockChatHistoryProvider
|
||||
.Setup(ms => ms.InvokingAsync(It.IsAny<ChatHistoryProvider.InvokingContext>(), It.IsAny<CancellationToken>()))
|
||||
.Protected()
|
||||
.Setup<ValueTask<IEnumerable<ChatMessage>>>("InvokingCoreAsync", ItExpr.IsAny<ChatHistoryProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync([new(ChatRole.User, "Message from chat history provider")]);
|
||||
|
||||
// Create a mock AI context provider that would normally provide context
|
||||
var mockContextProvider = new Mock<AIContextProvider>();
|
||||
mockContextProvider
|
||||
.Setup(p => p.InvokingAsync(It.IsAny<AIContextProvider.InvokingContext>(), It.IsAny<CancellationToken>()))
|
||||
.Protected()
|
||||
.Setup<ValueTask<AIContext>>("InvokingCoreAsync", ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync(new AIContext
|
||||
{
|
||||
Messages = [new(ChatRole.System, "Message from AI context")],
|
||||
@@ -385,14 +388,14 @@ public class ChatClientAgent_BackgroundResponsesTests
|
||||
Assert.Empty(capturedMessages);
|
||||
|
||||
// Verify that chat history provider was never called due to continuation token
|
||||
mockChatHistoryProvider.Verify(
|
||||
ms => ms.InvokingAsync(It.IsAny<ChatHistoryProvider.InvokingContext>(), It.IsAny<CancellationToken>()),
|
||||
Times.Never);
|
||||
mockChatHistoryProvider
|
||||
.Protected()
|
||||
.Verify<ValueTask<IEnumerable<ChatMessage>>>("InvokingCoreAsync", Times.Never(), ItExpr.IsAny<ChatHistoryProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>());
|
||||
|
||||
// Verify that AI context provider was never called due to continuation token
|
||||
mockContextProvider.Verify(
|
||||
p => p.InvokingAsync(It.IsAny<AIContextProvider.InvokingContext>(), It.IsAny<CancellationToken>()),
|
||||
Times.Never);
|
||||
mockContextProvider
|
||||
.Protected()
|
||||
.Verify<ValueTask<AIContext>>("InvokingCoreAsync", Times.Never(), ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -404,13 +407,15 @@ public class ChatClientAgent_BackgroundResponsesTests
|
||||
// Create a mock chat history provider that would normally provide messages
|
||||
var mockChatHistoryProvider = new Mock<ChatHistoryProvider>();
|
||||
mockChatHistoryProvider
|
||||
.Setup(ms => ms.InvokingAsync(It.IsAny<ChatHistoryProvider.InvokingContext>(), It.IsAny<CancellationToken>()))
|
||||
.Protected()
|
||||
.Setup<ValueTask<IEnumerable<ChatMessage>>>("InvokingCoreAsync", ItExpr.IsAny<ChatHistoryProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync([new(ChatRole.User, "Message from chat history provider")]);
|
||||
|
||||
// Create a mock AI context provider that would normally provide context
|
||||
var mockContextProvider = new Mock<AIContextProvider>();
|
||||
mockContextProvider
|
||||
.Setup(p => p.InvokingAsync(It.IsAny<AIContextProvider.InvokingContext>(), It.IsAny<CancellationToken>()))
|
||||
.Protected()
|
||||
.Setup<ValueTask<AIContext>>("InvokingCoreAsync", ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync(new AIContext
|
||||
{
|
||||
Messages = [new(ChatRole.System, "Message from AI context")],
|
||||
@@ -449,14 +454,14 @@ public class ChatClientAgent_BackgroundResponsesTests
|
||||
Assert.Empty(capturedMessages);
|
||||
|
||||
// Verify that chat history provider was never called due to continuation token
|
||||
mockChatHistoryProvider.Verify(
|
||||
ms => ms.InvokingAsync(It.IsAny<ChatHistoryProvider.InvokingContext>(), It.IsAny<CancellationToken>()),
|
||||
Times.Never);
|
||||
mockChatHistoryProvider
|
||||
.Protected()
|
||||
.Verify<ValueTask<IEnumerable<ChatMessage>>>("InvokingCoreAsync", Times.Never(), ItExpr.IsAny<ChatHistoryProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>());
|
||||
|
||||
// Verify that AI context provider was never called due to continuation token
|
||||
mockContextProvider.Verify(
|
||||
p => p.InvokingAsync(It.IsAny<AIContextProvider.InvokingContext>(), It.IsAny<CancellationToken>()),
|
||||
Times.Never);
|
||||
mockContextProvider
|
||||
.Protected()
|
||||
.Verify<ValueTask<AIContext>>("InvokingCoreAsync", Times.Never(), ItExpr.IsAny<AIContextProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -633,14 +638,16 @@ public class ChatClientAgent_BackgroundResponsesTests
|
||||
List<ChatMessage> capturedMessagesAddedToProvider = [];
|
||||
var mockChatHistoryProvider = new Mock<ChatHistoryProvider>();
|
||||
mockChatHistoryProvider
|
||||
.Setup(ms => ms.InvokedAsync(It.IsAny<ChatHistoryProvider.InvokedContext>(), It.IsAny<CancellationToken>()))
|
||||
.Protected()
|
||||
.Setup<ValueTask>("InvokedCoreAsync", ItExpr.IsAny<ChatHistoryProvider.InvokedContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.Callback<ChatHistoryProvider.InvokedContext, CancellationToken>((ctx, ct) => capturedMessagesAddedToProvider.AddRange(ctx.ResponseMessages ?? []))
|
||||
.Returns(new ValueTask());
|
||||
|
||||
AIContextProvider.InvokedContext? capturedInvokedContext = null;
|
||||
var mockContextProvider = new Mock<AIContextProvider>();
|
||||
mockContextProvider
|
||||
.Setup(cp => cp.InvokedAsync(It.IsAny<AIContextProvider.InvokedContext>(), It.IsAny<CancellationToken>()))
|
||||
.Protected()
|
||||
.Setup<ValueTask>("InvokedCoreAsync", ItExpr.IsAny<AIContextProvider.InvokedContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.Callback<AIContextProvider.InvokedContext, CancellationToken>((context, ct) => capturedInvokedContext = context)
|
||||
.Returns(new ValueTask());
|
||||
|
||||
@@ -662,11 +669,15 @@ public class ChatClientAgent_BackgroundResponsesTests
|
||||
await agent.RunStreamingAsync(session, options: runOptions).ToListAsync();
|
||||
|
||||
// Assert
|
||||
mockChatHistoryProvider.Verify(ms => ms.InvokedAsync(It.IsAny<ChatHistoryProvider.InvokedContext>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
mockChatHistoryProvider
|
||||
.Protected()
|
||||
.Verify<ValueTask>("InvokedCoreAsync", Times.Once(), ItExpr.IsAny<ChatHistoryProvider.InvokedContext>(), ItExpr.IsAny<CancellationToken>());
|
||||
Assert.Single(capturedMessagesAddedToProvider);
|
||||
Assert.Contains("once upon a time", capturedMessagesAddedToProvider[0].Text);
|
||||
|
||||
mockContextProvider.Verify(cp => cp.InvokedAsync(It.IsAny<AIContextProvider.InvokedContext>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
mockContextProvider
|
||||
.Protected()
|
||||
.Verify<ValueTask>("InvokedCoreAsync", Times.Once(), ItExpr.IsAny<AIContextProvider.InvokedContext>(), ItExpr.IsAny<CancellationToken>());
|
||||
Assert.NotNull(capturedInvokedContext?.ResponseMessages);
|
||||
Assert.Single(capturedInvokedContext.ResponseMessages);
|
||||
Assert.Contains("once upon a time", capturedInvokedContext.ResponseMessages.ElementAt(0).Text);
|
||||
@@ -689,14 +700,16 @@ public class ChatClientAgent_BackgroundResponsesTests
|
||||
List<ChatMessage> capturedMessagesAddedToProvider = [];
|
||||
var mockChatHistoryProvider = new Mock<ChatHistoryProvider>();
|
||||
mockChatHistoryProvider
|
||||
.Setup(ms => ms.InvokedAsync(It.IsAny<ChatHistoryProvider.InvokedContext>(), It.IsAny<CancellationToken>()))
|
||||
.Protected()
|
||||
.Setup<ValueTask>("InvokedCoreAsync", ItExpr.IsAny<ChatHistoryProvider.InvokedContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.Callback<ChatHistoryProvider.InvokedContext, CancellationToken>((ctx, ct) => capturedMessagesAddedToProvider.AddRange(ctx.RequestMessages))
|
||||
.Returns(new ValueTask());
|
||||
|
||||
AIContextProvider.InvokedContext? capturedInvokedContext = null;
|
||||
var mockContextProvider = new Mock<AIContextProvider>();
|
||||
mockContextProvider
|
||||
.Setup(cp => cp.InvokedAsync(It.IsAny<AIContextProvider.InvokedContext>(), It.IsAny<CancellationToken>()))
|
||||
.Protected()
|
||||
.Setup<ValueTask>("InvokedCoreAsync", ItExpr.IsAny<AIContextProvider.InvokedContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.Callback<AIContextProvider.InvokedContext, CancellationToken>((context, ct) => capturedInvokedContext = context)
|
||||
.Returns(new ValueTask());
|
||||
|
||||
@@ -718,11 +731,15 @@ public class ChatClientAgent_BackgroundResponsesTests
|
||||
await agent.RunStreamingAsync(session, options: runOptions).ToListAsync();
|
||||
|
||||
// Assert
|
||||
mockChatHistoryProvider.Verify(ms => ms.InvokedAsync(It.IsAny<ChatHistoryProvider.InvokedContext>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
mockChatHistoryProvider
|
||||
.Protected()
|
||||
.Verify<ValueTask>("InvokedCoreAsync", Times.Once(), ItExpr.IsAny<ChatHistoryProvider.InvokedContext>(), ItExpr.IsAny<CancellationToken>());
|
||||
Assert.Single(capturedMessagesAddedToProvider);
|
||||
Assert.Contains("Tell me a story", capturedMessagesAddedToProvider[0].Text);
|
||||
|
||||
mockContextProvider.Verify(cp => cp.InvokedAsync(It.IsAny<AIContextProvider.InvokedContext>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
mockContextProvider
|
||||
.Protected()
|
||||
.Verify<ValueTask>("InvokedCoreAsync", Times.Once(), ItExpr.IsAny<AIContextProvider.InvokedContext>(), ItExpr.IsAny<CancellationToken>());
|
||||
Assert.NotNull(capturedInvokedContext?.RequestMessages);
|
||||
Assert.Single(capturedInvokedContext.RequestMessages);
|
||||
Assert.Contains("Tell me a story", capturedInvokedContext.RequestMessages.ElementAt(0).Text);
|
||||
|
||||
+60
-46
@@ -7,6 +7,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Microsoft.Agents.AI.UnitTests;
|
||||
@@ -183,12 +184,14 @@ public class ChatClientAgent_ChatHistoryManagementTests
|
||||
It.IsAny<CancellationToken>())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]));
|
||||
|
||||
Mock<ChatHistoryProvider> mockChatHistoryProvider = new();
|
||||
mockChatHistoryProvider.Setup(s => s.InvokingAsync(
|
||||
It.IsAny<ChatHistoryProvider.InvokingContext>(),
|
||||
It.IsAny<CancellationToken>())).ReturnsAsync([new ChatMessage(ChatRole.User, "Existing Chat History")]);
|
||||
mockChatHistoryProvider.Setup(s => s.InvokedAsync(
|
||||
It.IsAny<ChatHistoryProvider.InvokedContext>(),
|
||||
It.IsAny<CancellationToken>())).Returns(new ValueTask());
|
||||
mockChatHistoryProvider
|
||||
.Protected()
|
||||
.Setup<ValueTask<IEnumerable<ChatMessage>>>("InvokingCoreAsync", ItExpr.IsAny<ChatHistoryProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync([new ChatMessage(ChatRole.User, "Existing Chat History")]);
|
||||
mockChatHistoryProvider
|
||||
.Protected()
|
||||
.Setup<ValueTask>("InvokedCoreAsync", ItExpr.IsAny<ChatHistoryProvider.InvokedContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.Returns(new ValueTask());
|
||||
|
||||
Mock<Func<ChatClientAgentOptions.ChatHistoryProviderFactoryContext, CancellationToken, ValueTask<ChatHistoryProvider>>> mockFactory = new();
|
||||
mockFactory.Setup(f => f(It.IsAny<ChatClientAgentOptions.ChatHistoryProviderFactoryContext>(), It.IsAny<CancellationToken>())).ReturnsAsync(mockChatHistoryProvider.Object);
|
||||
@@ -211,14 +214,16 @@ public class ChatClientAgent_ChatHistoryManagementTests
|
||||
It.IsAny<ChatOptions>(),
|
||||
It.IsAny<CancellationToken>()),
|
||||
Times.Once);
|
||||
mockChatHistoryProvider.Verify(s => s.InvokingAsync(
|
||||
It.Is<ChatHistoryProvider.InvokingContext>(x => x.RequestMessages.Count() == 1),
|
||||
It.IsAny<CancellationToken>()),
|
||||
Times.Once);
|
||||
mockChatHistoryProvider.Verify(s => s.InvokedAsync(
|
||||
It.Is<ChatHistoryProvider.InvokedContext>(x => x.RequestMessages.Count() == 1 && x.ChatHistoryProviderMessages != null && x.ChatHistoryProviderMessages.Count() == 1 && x.ResponseMessages!.Count() == 1),
|
||||
It.IsAny<CancellationToken>()),
|
||||
Times.Once);
|
||||
mockChatHistoryProvider
|
||||
.Protected()
|
||||
.Verify<ValueTask<IEnumerable<ChatMessage>>>("InvokingCoreAsync", Times.Once(),
|
||||
ItExpr.Is<ChatHistoryProvider.InvokingContext>(x => x.RequestMessages.Count() == 1),
|
||||
ItExpr.IsAny<CancellationToken>());
|
||||
mockChatHistoryProvider
|
||||
.Protected()
|
||||
.Verify<ValueTask>("InvokedCoreAsync", Times.Once(),
|
||||
ItExpr.Is<ChatHistoryProvider.InvokedContext>(x => x.RequestMessages.Count() == 1 && x.ResponseMessages!.Count() == 1),
|
||||
ItExpr.IsAny<CancellationToken>());
|
||||
mockFactory.Verify(f => f(It.IsAny<ChatClientAgentOptions.ChatHistoryProviderFactoryContext>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
@@ -253,10 +258,11 @@ public class ChatClientAgent_ChatHistoryManagementTests
|
||||
|
||||
// Assert
|
||||
Assert.IsType<ChatHistoryProvider>(session!.ChatHistoryProvider, exactMatch: false);
|
||||
mockChatHistoryProvider.Verify(s => s.InvokedAsync(
|
||||
It.Is<ChatHistoryProvider.InvokedContext>(x => x.RequestMessages.Count() == 1 && x.ResponseMessages == null && x.InvokeException!.Message == "Test Error"),
|
||||
It.IsAny<CancellationToken>()),
|
||||
Times.Once);
|
||||
mockChatHistoryProvider
|
||||
.Protected()
|
||||
.Verify<ValueTask>("InvokedCoreAsync", Times.Once(),
|
||||
ItExpr.Is<ChatHistoryProvider.InvokedContext>(x => x.RequestMessages.Count() == 1 && x.ResponseMessages == null && x.InvokeException!.Message == "Test Error"),
|
||||
ItExpr.IsAny<CancellationToken>());
|
||||
mockFactory.Verify(f => f(It.IsAny<ChatClientAgentOptions.ChatHistoryProviderFactoryContext>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
@@ -308,22 +314,26 @@ public class ChatClientAgent_ChatHistoryManagementTests
|
||||
|
||||
// Arrange a chat history provider to override the factory provided one.
|
||||
Mock<ChatHistoryProvider> mockOverrideChatHistoryProvider = new();
|
||||
mockOverrideChatHistoryProvider.Setup(s => s.InvokingAsync(
|
||||
It.IsAny<ChatHistoryProvider.InvokingContext>(),
|
||||
It.IsAny<CancellationToken>())).ReturnsAsync([new ChatMessage(ChatRole.User, "Existing Chat History")]);
|
||||
mockOverrideChatHistoryProvider.Setup(s => s.InvokedAsync(
|
||||
It.IsAny<ChatHistoryProvider.InvokedContext>(),
|
||||
It.IsAny<CancellationToken>())).Returns(new ValueTask());
|
||||
mockOverrideChatHistoryProvider
|
||||
.Protected()
|
||||
.Setup<ValueTask<IEnumerable<ChatMessage>>>("InvokingCoreAsync", ItExpr.IsAny<ChatHistoryProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync([new ChatMessage(ChatRole.User, "Existing Chat History")]);
|
||||
mockOverrideChatHistoryProvider
|
||||
.Protected()
|
||||
.Setup<ValueTask>("InvokedCoreAsync", ItExpr.IsAny<ChatHistoryProvider.InvokedContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.Returns(new ValueTask());
|
||||
|
||||
// Arrange a chat history provider to provide to the agent via a factory at construction time.
|
||||
// This one shouldn't be used since it is being overridden.
|
||||
Mock<ChatHistoryProvider> mockFactoryChatHistoryProvider = new();
|
||||
mockFactoryChatHistoryProvider.Setup(s => s.InvokingAsync(
|
||||
It.IsAny<ChatHistoryProvider.InvokingContext>(),
|
||||
It.IsAny<CancellationToken>())).ThrowsAsync(FailException.ForFailure("Base ChatHistoryProvider shouldn't be used."));
|
||||
mockFactoryChatHistoryProvider.Setup(s => s.InvokedAsync(
|
||||
It.IsAny<ChatHistoryProvider.InvokedContext>(),
|
||||
It.IsAny<CancellationToken>())).Throws(FailException.ForFailure("Base ChatHistoryProvider shouldn't be used."));
|
||||
mockFactoryChatHistoryProvider
|
||||
.Protected()
|
||||
.Setup<ValueTask<IEnumerable<ChatMessage>>>("InvokingCoreAsync", ItExpr.IsAny<ChatHistoryProvider.InvokingContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.ThrowsAsync(FailException.ForFailure("Base ChatHistoryProvider shouldn't be used."));
|
||||
mockFactoryChatHistoryProvider
|
||||
.Protected()
|
||||
.Setup<ValueTask>("InvokedCoreAsync", ItExpr.IsAny<ChatHistoryProvider.InvokedContext>(), ItExpr.IsAny<CancellationToken>())
|
||||
.Throws(FailException.ForFailure("Base ChatHistoryProvider shouldn't be used."));
|
||||
|
||||
Mock<Func<ChatClientAgentOptions.ChatHistoryProviderFactoryContext, CancellationToken, ValueTask<ChatHistoryProvider>>> mockFactory = new();
|
||||
mockFactory.Setup(f => f(It.IsAny<ChatClientAgentOptions.ChatHistoryProviderFactoryContext>(), It.IsAny<CancellationToken>())).ReturnsAsync(mockFactoryChatHistoryProvider.Object);
|
||||
@@ -348,23 +358,27 @@ public class ChatClientAgent_ChatHistoryManagementTests
|
||||
It.IsAny<ChatOptions>(),
|
||||
It.IsAny<CancellationToken>()),
|
||||
Times.Once);
|
||||
mockOverrideChatHistoryProvider.Verify(s => s.InvokingAsync(
|
||||
It.Is<ChatHistoryProvider.InvokingContext>(x => x.RequestMessages.Count() == 1),
|
||||
It.IsAny<CancellationToken>()),
|
||||
Times.Once);
|
||||
mockOverrideChatHistoryProvider.Verify(s => s.InvokedAsync(
|
||||
It.Is<ChatHistoryProvider.InvokedContext>(x => x.RequestMessages.Count() == 1 && x.ChatHistoryProviderMessages != null && x.ChatHistoryProviderMessages.Count() == 1 && x.ResponseMessages!.Count() == 1),
|
||||
It.IsAny<CancellationToken>()),
|
||||
Times.Once);
|
||||
mockOverrideChatHistoryProvider
|
||||
.Protected()
|
||||
.Verify<ValueTask<IEnumerable<ChatMessage>>>("InvokingCoreAsync", Times.Once(),
|
||||
ItExpr.Is<ChatHistoryProvider.InvokingContext>(x => x.RequestMessages.Count() == 1),
|
||||
ItExpr.IsAny<CancellationToken>());
|
||||
mockOverrideChatHistoryProvider
|
||||
.Protected()
|
||||
.Verify<ValueTask>("InvokedCoreAsync", Times.Once(),
|
||||
ItExpr.Is<ChatHistoryProvider.InvokedContext>(x => x.RequestMessages.Count() == 1 && x.ResponseMessages!.Count() == 1),
|
||||
ItExpr.IsAny<CancellationToken>());
|
||||
|
||||
mockFactoryChatHistoryProvider.Verify(s => s.InvokingAsync(
|
||||
It.IsAny<ChatHistoryProvider.InvokingContext>(),
|
||||
It.IsAny<CancellationToken>()),
|
||||
Times.Never);
|
||||
mockFactoryChatHistoryProvider.Verify(s => s.InvokedAsync(
|
||||
It.IsAny<ChatHistoryProvider.InvokedContext>(),
|
||||
It.IsAny<CancellationToken>()),
|
||||
Times.Never);
|
||||
mockFactoryChatHistoryProvider
|
||||
.Protected()
|
||||
.Verify<ValueTask<IEnumerable<ChatMessage>>>("InvokingCoreAsync", Times.Never(),
|
||||
ItExpr.IsAny<ChatHistoryProvider.InvokingContext>(),
|
||||
ItExpr.IsAny<CancellationToken>());
|
||||
mockFactoryChatHistoryProvider
|
||||
.Protected()
|
||||
.Verify<ValueTask>("InvokedCoreAsync", Times.Never(),
|
||||
ItExpr.IsAny<ChatHistoryProvider.InvokedContext>(),
|
||||
ItExpr.IsAny<CancellationToken>());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -345,7 +345,7 @@ public sealed class TextSearchProviderTests
|
||||
new ChatMessage(ChatRole.User, "C"),
|
||||
new ChatMessage(ChatRole.Assistant, "D"),
|
||||
};
|
||||
await provider.InvokedAsync(new(s_mockAgent, s_mockSession, initialMessages, aiContextProviderMessages: null) { InvokeException = new InvalidOperationException("Request Failed") });
|
||||
await provider.InvokedAsync(new(s_mockAgent, s_mockSession, initialMessages) { InvokeException = new InvalidOperationException("Request Failed") });
|
||||
|
||||
var invokingContext = new AIContextProvider.InvokingContext(
|
||||
s_mockAgent,
|
||||
@@ -387,7 +387,7 @@ public sealed class TextSearchProviderTests
|
||||
new ChatMessage(ChatRole.User, "C"),
|
||||
new ChatMessage(ChatRole.Assistant, "D"),
|
||||
};
|
||||
await provider.InvokedAsync(new(s_mockAgent, s_mockSession, initialMessages, aiContextProviderMessages: null));
|
||||
await provider.InvokedAsync(new(s_mockAgent, s_mockSession, initialMessages));
|
||||
|
||||
var invokingContext = new AIContextProvider.InvokingContext(
|
||||
s_mockAgent,
|
||||
@@ -423,22 +423,22 @@ public sealed class TextSearchProviderTests
|
||||
|
||||
// First memory update (A,B)
|
||||
await provider.InvokedAsync(new(
|
||||
s_mockAgent,
|
||||
s_mockSession,
|
||||
[
|
||||
new ChatMessage(ChatRole.User, "A"),
|
||||
new ChatMessage(ChatRole.Assistant, "B"),
|
||||
], aiContextProviderMessages: null));
|
||||
s_mockAgent,
|
||||
s_mockSession,
|
||||
[
|
||||
new ChatMessage(ChatRole.User, "A"),
|
||||
new ChatMessage(ChatRole.Assistant, "B"),
|
||||
]));
|
||||
|
||||
// Second memory update (C,D,E)
|
||||
await provider.InvokedAsync(new(
|
||||
s_mockAgent,
|
||||
s_mockSession,
|
||||
[
|
||||
new ChatMessage(ChatRole.User, "C"),
|
||||
new ChatMessage(ChatRole.Assistant, "D"),
|
||||
new ChatMessage(ChatRole.User, "E"),
|
||||
], aiContextProviderMessages: null));
|
||||
s_mockAgent,
|
||||
s_mockSession,
|
||||
[
|
||||
new ChatMessage(ChatRole.User, "C"),
|
||||
new ChatMessage(ChatRole.Assistant, "D"),
|
||||
new ChatMessage(ChatRole.User, "E"),
|
||||
]));
|
||||
|
||||
var invokingContext = new AIContextProvider.InvokingContext(s_mockAgent, s_mockSession, [new ChatMessage(ChatRole.User, "F")]);
|
||||
|
||||
@@ -475,7 +475,7 @@ public sealed class TextSearchProviderTests
|
||||
new ChatMessage(ChatRole.User, "U2"),
|
||||
new ChatMessage(ChatRole.Assistant, "A2"),
|
||||
};
|
||||
await provider.InvokedAsync(new(s_mockAgent, s_mockSession, initialMessages, null));
|
||||
await provider.InvokedAsync(new(s_mockAgent, s_mockSession, initialMessages));
|
||||
|
||||
var invokingContext = new AIContextProvider.InvokingContext(
|
||||
s_mockAgent,
|
||||
@@ -533,7 +533,7 @@ public sealed class TextSearchProviderTests
|
||||
};
|
||||
|
||||
// Act
|
||||
await provider.InvokedAsync(new(s_mockAgent, s_mockSession, messages, aiContextProviderMessages: null)); // Populate recent memory.
|
||||
await provider.InvokedAsync(new(s_mockAgent, s_mockSession, messages)); // Populate recent memory.
|
||||
var state = provider.Serialize();
|
||||
|
||||
// Assert
|
||||
@@ -562,7 +562,7 @@ public sealed class TextSearchProviderTests
|
||||
new ChatMessage(ChatRole.User, "C"),
|
||||
new ChatMessage(ChatRole.Assistant, "D"),
|
||||
};
|
||||
await provider.InvokedAsync(new(s_mockAgent, s_mockSession, messages, aiContextProviderMessages: null));
|
||||
await provider.InvokedAsync(new(s_mockAgent, s_mockSession, messages));
|
||||
|
||||
// Act
|
||||
var state = provider.Serialize();
|
||||
@@ -603,7 +603,7 @@ public sealed class TextSearchProviderTests
|
||||
new ChatMessage(ChatRole.Assistant, "L4"),
|
||||
new ChatMessage(ChatRole.User, "L5"),
|
||||
};
|
||||
await initialProvider.InvokedAsync(new(s_mockAgent, s_mockSession, messages, aiContextProviderMessages: null));
|
||||
await initialProvider.InvokedAsync(new(s_mockAgent, s_mockSession, messages));
|
||||
var state = initialProvider.Serialize();
|
||||
|
||||
string? capturedInput = null;
|
||||
|
||||
@@ -119,7 +119,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, s_mockSession, [requestMsgWithValues, requestMsgWithNulls], aiContextProviderMessages: null)
|
||||
var invokedContext = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, [requestMsgWithValues, requestMsgWithNulls])
|
||||
{
|
||||
ResponseMessages = [responseMsg]
|
||||
};
|
||||
@@ -177,7 +177,7 @@ public class ChatHistoryMemoryProviderTests
|
||||
1,
|
||||
new ChatHistoryMemoryProviderScope() { UserId = "UID" });
|
||||
var requestMsg = new ChatMessage(ChatRole.User, "request text") { MessageId = "req-1" };
|
||||
var invokedContext = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, [requestMsg], aiContextProviderMessages: null)
|
||||
var invokedContext = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, [requestMsg])
|
||||
{
|
||||
InvokeException = new InvalidOperationException("Invoke failed")
|
||||
};
|
||||
@@ -206,7 +206,7 @@ public class ChatHistoryMemoryProviderTests
|
||||
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, s_mockSession, [requestMsg], aiContextProviderMessages: null);
|
||||
var invokedContext = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, [requestMsg]);
|
||||
|
||||
// Act
|
||||
await provider.InvokedAsync(invokedContext, CancellationToken.None);
|
||||
@@ -257,7 +257,7 @@ public class ChatHistoryMemoryProviderTests
|
||||
loggerFactory: this._loggerFactoryMock.Object);
|
||||
|
||||
var requestMsg = new ChatMessage(ChatRole.User, "request text");
|
||||
var invokedContext = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, [requestMsg], aiContextProviderMessages: null);
|
||||
var invokedContext = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, [requestMsg]);
|
||||
|
||||
// Act
|
||||
await provider.InvokedAsync(invokedContext, CancellationToken.None);
|
||||
|
||||
Reference in New Issue
Block a user