Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,7 @@ protected override async Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessag
List<ChatMessage> responseMessages = CloneAndToUpperCase(messages, this.Name).ToList();

// Notify the session of the input and output messages.
var invokedContext = new ChatHistoryProvider.InvokedContext(this, session, messages)
{
ResponseMessages = responseMessages
};
var invokedContext = new ChatHistoryProvider.InvokedContext(this, session, messages, responseMessages);
await this.ChatHistoryProvider.InvokedAsync(invokedContext, cancellationToken);

return new AgentResponse
Expand Down Expand Up @@ -97,10 +94,7 @@ protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingA
List<ChatMessage> responseMessages = CloneAndToUpperCase(messages, this.Name).ToList();

// Notify the session of the input and output messages.
var invokedContext = new ChatHistoryProvider.InvokedContext(this, session, messages)
{
ResponseMessages = responseMessages
};
var invokedContext = new ChatHistoryProvider.InvokedContext(this, session, messages, responseMessages);
await this.ChatHistoryProvider.InvokedAsync(invokedContext, cancellationToken);

foreach (var message in responseMessages)
Expand Down
39 changes: 31 additions & 8 deletions dotnet/src/Microsoft.Agents.AI.Abstractions/AIContextProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,20 +230,43 @@ public InvokingContext(
public sealed class InvokedContext
{
/// <summary>
/// Initializes a new instance of the <see cref="InvokedContext"/> class with the specified request messages.
/// Initializes a new instance of the <see cref="InvokedContext"/> class for a successful invocation.
/// </summary>
/// <param name="agent">The agent being invoked.</param>
/// <param name="agent">The agent that was invoked.</param>
/// <param name="session">The session associated with the agent invocation.</param>
/// <param name="requestMessages">The messages that were used by the agent for this invocation.</param>
/// <param name="responseMessages">The response messages generated during this invocation.</param>
/// <exception cref="ArgumentNullException"><paramref name="agent"/>, <paramref name="requestMessages"/>, or <paramref name="responseMessages"/> is <see langword="null"/>.</exception>
public InvokedContext(
AIAgent agent,
AgentSession? session,
IEnumerable<ChatMessage> requestMessages,
IEnumerable<ChatMessage> responseMessages)
{
this.Agent = Throw.IfNull(agent);
this.Session = session;
this.RequestMessages = Throw.IfNull(requestMessages);
this.ResponseMessages = Throw.IfNull(responseMessages);
}

/// <summary>
/// Initializes a new instance of the <see cref="InvokedContext"/> class for a failed invocation.
/// </summary>
/// <param name="agent">The agent that was invoked.</param>
/// <param name="session">The session associated with the agent invocation.</param>
/// <param name="requestMessages">The messages that were used by the agent for this invocation.</param>
/// <exception cref="ArgumentNullException"><paramref name="requestMessages"/> is <see langword="null"/>.</exception>
/// <param name="invokeException">The exception that caused the invocation to fail.</param>
/// <exception cref="ArgumentNullException"><paramref name="agent"/>, <paramref name="requestMessages"/>, or <paramref name="invokeException"/> is <see langword="null"/>.</exception>
public InvokedContext(
AIAgent agent,
AgentSession? session,
IEnumerable<ChatMessage> requestMessages)
IEnumerable<ChatMessage> requestMessages,
Exception invokeException)
{
this.Agent = Throw.IfNull(agent);
this.Session = session;
this.RequestMessages = Throw.IfNull(requestMessages);
this.InvokeException = Throw.IfNull(invokeException);
}

/// <summary>
Expand All @@ -262,23 +285,23 @@ public InvokedContext(
/// <value>
/// A collection of <see cref="ChatMessage"/> instances representing all messages that were used by the agent for this invocation.
/// </value>
public IEnumerable<ChatMessage> RequestMessages { get; set { field = Throw.IfNull(value); } }
public IEnumerable<ChatMessage> RequestMessages { get; }

/// <summary>
/// Gets the collection of response messages generated during this invocation if the invocation succeeded.
/// </summary>
/// <value>
/// A collection of <see cref="ChatMessage"/> instances representing the response,
/// or <see langword="null"/> if the invocation failed or did not produce response messages.
/// or <see langword="null"/> if the invocation failed.
/// </value>
public IEnumerable<ChatMessage>? ResponseMessages { get; set; }
public IEnumerable<ChatMessage>? ResponseMessages { get; }

/// <summary>
/// Gets the <see cref="Exception"/> that was thrown during the invocation, if the invocation failed.
/// </summary>
/// <value>
/// The exception that caused the invocation to fail, or <see langword="null"/> if the invocation succeeded.
/// </value>
public Exception? InvokeException { get; set; }
public Exception? InvokeException { get; }
}
}
39 changes: 31 additions & 8 deletions dotnet/src/Microsoft.Agents.AI.Abstractions/ChatHistoryProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,20 +260,43 @@ public InvokingContext(
public sealed class InvokedContext
{
/// <summary>
/// Initializes a new instance of the <see cref="InvokedContext"/> class with the specified request messages.
/// Initializes a new instance of the <see cref="InvokedContext"/> class for a successful invocation.
/// </summary>
/// <param name="agent">The agent being invoked.</param>
/// <param name="agent">The agent that was invoked.</param>
/// <param name="session">The session associated with the agent invocation.</param>
/// <param name="requestMessages">The caller provided messages that were used by the agent for this invocation.</param>
/// <exception cref="ArgumentNullException"><paramref name="requestMessages"/> is <see langword="null"/>.</exception>
/// <param name="responseMessages">The response messages generated during this invocation.</param>
/// <exception cref="ArgumentNullException"><paramref name="agent"/>, <paramref name="requestMessages"/>, or <paramref name="responseMessages"/> is <see langword="null"/>.</exception>
public InvokedContext(
AIAgent agent,
AgentSession? session,
IEnumerable<ChatMessage> requestMessages)
IEnumerable<ChatMessage> requestMessages,
IEnumerable<ChatMessage> responseMessages)
{
this.Agent = Throw.IfNull(agent);
this.Session = session;
this.RequestMessages = Throw.IfNull(requestMessages);
this.ResponseMessages = Throw.IfNull(responseMessages);
}

/// <summary>
/// Initializes a new instance of the <see cref="InvokedContext"/> class for a failed invocation.
/// </summary>
/// <param name="agent">The agent that was invoked.</param>
/// <param name="session">The session associated with the agent invocation.</param>
/// <param name="requestMessages">The caller provided messages that were used by the agent for this invocation.</param>
/// <param name="invokeException">The exception that caused the invocation to fail.</param>
/// <exception cref="ArgumentNullException"><paramref name="agent"/>, <paramref name="requestMessages"/>, or <paramref name="invokeException"/> is <see langword="null"/>.</exception>
public InvokedContext(
AIAgent agent,
AgentSession? session,
IEnumerable<ChatMessage> requestMessages,
Exception invokeException)
{
this.Agent = Throw.IfNull(agent);
this.Session = session;
this.RequestMessages = Throw.IfNull(requestMessages);
this.InvokeException = Throw.IfNull(invokeException);
}

/// <summary>
Expand All @@ -293,23 +316,23 @@ public InvokedContext(
/// A collection of <see cref="ChatMessage"/> instances representing new messages that were provided by the caller.
/// This does not include any <see cref="ChatHistoryProvider"/> supplied messages.
/// </value>
public IEnumerable<ChatMessage> RequestMessages { get; set { field = Throw.IfNull(value); } }
public IEnumerable<ChatMessage> RequestMessages { get; }

/// <summary>
/// Gets the collection of response messages generated during this invocation if the invocation succeeded.
/// </summary>
/// <value>
/// A collection of <see cref="ChatMessage"/> instances representing the response,
/// or <see langword="null"/> if the invocation failed or did not produce response messages.
/// or <see langword="null"/> if the invocation failed.
/// </value>
public IEnumerable<ChatMessage>? ResponseMessages { get; set; }
public IEnumerable<ChatMessage>? ResponseMessages { get; }

/// <summary>
/// Gets the <see cref="Exception"/> that was thrown during the invocation, if the invocation failed.
/// </summary>
/// <value>
/// The exception that caused the invocation to fail, or <see langword="null"/> if the invocation succeeded.
/// </value>
public Exception? InvokeException { get; set; }
public Exception? InvokeException { get; }
}
}
14 changes: 4 additions & 10 deletions dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ private async Task NotifyAIContextProviderOfSuccessAsync(
{
if (this.AIContextProviders is { Count: > 0 } contextProviders)
{
AIContextProvider.InvokedContext invokedContext = new(this, session, inputMessages) { ResponseMessages = responseMessages };
AIContextProvider.InvokedContext invokedContext = new(this, session, inputMessages, responseMessages);

foreach (var contextProvider in contextProviders)
{
Expand All @@ -475,7 +475,7 @@ private async Task NotifyAIContextProviderOfFailureAsync(
{
if (this.AIContextProviders is { Count: > 0 } contextProviders)
{
AIContextProvider.InvokedContext invokedContext = new(this, session, inputMessages) { InvokeException = ex };
AIContextProvider.InvokedContext invokedContext = new(this, session, inputMessages, ex);

foreach (var contextProvider in contextProviders)
{
Expand Down Expand Up @@ -798,10 +798,7 @@ private Task NotifyChatHistoryProviderOfFailureAsync(
// If we don't have one, it means that the chat history is service managed and the underlying service is responsible for storing messages.
if (provider is not null)
{
var invokedContext = new ChatHistoryProvider.InvokedContext(this, session, requestMessages)
{
InvokeException = ex
};
var invokedContext = new ChatHistoryProvider.InvokedContext(this, session, requestMessages, ex);

return provider.InvokedAsync(invokedContext, cancellationToken).AsTask();
}
Expand All @@ -822,10 +819,7 @@ private Task NotifyChatHistoryProviderOfNewMessagesAsync(
// If we don't have one, it means that the chat history is service managed and the underlying service is responsible for storing messages.
if (provider is not null)
{
var invokedContext = new ChatHistoryProvider.InvokedContext(this, session, requestMessages)
{
ResponseMessages = responseMessages
};
var invokedContext = new ChatHistoryProvider.InvokedContext(this, session, requestMessages, responseMessages);
return provider.InvokedAsync(invokedContext, cancellationToken).AsTask();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public async Task InvokedAsync_ReturnsCompletedTaskAsync()
var messages = new ReadOnlyCollection<ChatMessage>([]);

// Act
ValueTask task = provider.InvokedAsync(new(s_mockAgent, s_mockSession, messages));
ValueTask task = provider.InvokedAsync(new(s_mockAgent, s_mockSession, messages, []));

// Assert
Assert.Equal(default, task);
Expand All @@ -42,7 +42,7 @@ public void InvokingContext_Constructor_ThrowsForNullMessages()
public void InvokedContext_Constructor_ThrowsForNullMessages()
{
// Act & Assert
Assert.Throws<ArgumentNullException>(() => new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, null!));
Assert.Throws<ArgumentNullException>(() => new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, null!, []));
}

#endregion
Expand Down Expand Up @@ -238,42 +238,15 @@ public void InvokingContext_Constructor_ThrowsForNullAgent()

#region InvokedContext Tests

[Fact]
public void InvokedContext_RequestMessages_SetterThrowsForNull()
{
// Arrange
var messages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, messages);

// Act & Assert
Assert.Throws<ArgumentNullException>(() => context.RequestMessages = null!);
}

[Fact]
public void InvokedContext_RequestMessages_SetterRoundtrips()
{
// Arrange
var initialMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
var newMessages = new List<ChatMessage> { new(ChatRole.User, "New message") };
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, initialMessages);

// Act
context.RequestMessages = newMessages;

// Assert
Assert.Same(newMessages, context.RequestMessages);
}

[Fact]
public void InvokedContext_ResponseMessages_Roundtrips()
{
// Arrange
var requestMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
var responseMessages = new List<ChatMessage> { new(ChatRole.Assistant, "Response message") };
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages);

// Act
context.ResponseMessages = responseMessages;
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, responseMessages);

// Assert
Assert.Same(responseMessages, context.ResponseMessages);
Expand All @@ -285,10 +258,9 @@ public void InvokedContext_InvokeException_Roundtrips()
// Arrange
var requestMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);
var exception = new InvalidOperationException("Test exception");
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages);

// Act
context.InvokeException = exception;
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, exception);

// Assert
Assert.Same(exception, context.InvokeException);
Expand All @@ -301,7 +273,7 @@ public void InvokedContext_Agent_ReturnsConstructorValue()
var requestMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);

// Act
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages);
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, []);

// Assert
Assert.Same(s_mockAgent, context.Agent);
Expand All @@ -314,7 +286,7 @@ public void InvokedContext_Session_ReturnsConstructorValue()
var requestMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);

// Act
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages);
var context = new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, []);

// Assert
Assert.Same(s_mockSession, context.Session);
Expand All @@ -327,7 +299,7 @@ public void InvokedContext_Session_CanBeNull()
var requestMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);

// Act
var context = new AIContextProvider.InvokedContext(s_mockAgent, null, requestMessages);
var context = new AIContextProvider.InvokedContext(s_mockAgent, null, requestMessages, []);

// Assert
Assert.Null(context.Session);
Expand All @@ -340,7 +312,27 @@ public void InvokedContext_Constructor_ThrowsForNullAgent()
var requestMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);

// Act & Assert
Assert.Throws<ArgumentNullException>(() => new AIContextProvider.InvokedContext(null!, s_mockSession, requestMessages));
Assert.Throws<ArgumentNullException>(() => new AIContextProvider.InvokedContext(null!, s_mockSession, requestMessages, []));
}

[Fact]
public void InvokedContext_SuccessConstructor_ThrowsForNullResponseMessages()
{
// Arrange
var requestMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);

// Act & Assert
Assert.Throws<ArgumentNullException>(() => new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, (IEnumerable<ChatMessage>)null!));
}

[Fact]
public void InvokedContext_FailureConstructor_ThrowsForNullException()
{
// Arrange
var requestMessages = new ReadOnlyCollection<ChatMessage>([new(ChatRole.User, "Hello")]);

// Act & Assert
Assert.Throws<ArgumentNullException>(() => new AIContextProvider.InvokedContext(s_mockAgent, s_mockSession, requestMessages, (Exception)null!));
}

#endregion
Expand Down
Loading
Loading