Skip to content

.Net: Allow Kernel to be mutable by AgentChatCompletions #12538

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
4 changes: 2 additions & 2 deletions dotnet/src/Agents/Core/ChatCompletionAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public override async IAsyncEnumerable<AgentResponseItem<ChatMessageContent>> In
() => new ChatHistoryAgentThread(),
cancellationToken).ConfigureAwait(false);

Kernel kernel = (options?.Kernel ?? this.Kernel).Clone();
Kernel kernel = options?.Kernel ?? this.Kernel;

// Get the context contributions from the AIContextProviders.
#pragma warning disable SKEXP0110, SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
Expand Down Expand Up @@ -168,7 +168,7 @@ public override async IAsyncEnumerable<AgentResponseItem<StreamingChatMessageCon
() => new ChatHistoryAgentThread(),
cancellationToken).ConfigureAwait(false);

Kernel kernel = (options?.Kernel ?? this.Kernel).Clone();
Kernel kernel = options?.Kernel ?? this.Kernel;

// Get the context contributions from the AIContextProviders.
#pragma warning disable SKEXP0110, SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
Expand Down
115 changes: 111 additions & 4 deletions dotnet/src/Agents/UnitTests/Core/ChatCompletionAgentTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,46 @@ public async Task VerifyChatCompletionAgentInvocationAsync()
Times.Once);
}

/// <summary>
/// Verify the invocation and response of <see cref="ChatCompletionAgent"/>.
/// </summary>
[Fact]
public async Task VerifyChatCompletionAgentInvocationsCanMutateProvidedKernelAsync()
{
// Arrange
Mock<IChatCompletionService> mockService = new();
mockService.Setup(
s => s.GetChatMessageContentsAsync(
It.IsAny<ChatHistory>(),
It.IsAny<PromptExecutionSettings>(),
It.IsAny<Kernel>(),
It.IsAny<CancellationToken>())).ReturnsAsync([new(AuthorRole.Assistant, "what?")]);

var kernel = CreateKernel(mockService.Object);
ChatCompletionAgent agent =
new()
{
Instructions = "test instructions",
Kernel = kernel,
Arguments = [],
};

// Act
AgentResponseItem<ChatMessageContent>[] result = await agent.InvokeAsync(Array.Empty<ChatMessageContent>() as ICollection<ChatMessageContent>).ToArrayAsync();

// Assert
Assert.Single(result);

mockService.Verify(
x =>
x.GetChatMessageContentsAsync(
It.IsAny<ChatHistory>(),
It.IsAny<PromptExecutionSettings>(),
kernel, // Use the same kernel instance
It.IsAny<CancellationToken>()),
Times.Once);
}

/// <summary>
/// Verify the invocation and response of <see cref="ChatCompletionAgent"/> using <see cref="IChatClient"/>.
/// </summary>
Expand All @@ -195,7 +235,7 @@ public async Task VerifyChatClientAgentInvocationAsync()
{
Instructions = "test instructions",
Kernel = CreateKernel(mockService.Object),
Arguments = [],
Arguments = new(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
};

// Act
Expand All @@ -208,7 +248,7 @@ public async Task VerifyChatClientAgentInvocationAsync()
x =>
x.GetResponseAsync(
It.IsAny<IEnumerable<ChatMessage>>(),
It.IsAny<ChatOptions>(),
It.Is<ChatOptions>(o => GetKernelFromChatOptions(o) == agent.Kernel),
It.IsAny<CancellationToken>()),
Times.Once);
}
Expand Down Expand Up @@ -258,6 +298,52 @@ public async Task VerifyChatCompletionAgentStreamingAsync()
Times.Once);
}

/// <summary>
/// Verify the streaming invocation and response of <see cref="ChatCompletionAgent"/>.
/// </summary>
[Fact]
public async Task VerifyChatCompletionAgentStreamingCanMutateProvidedKernelAsync()
{
// Arrange
StreamingChatMessageContent[] returnContent =
[
new(AuthorRole.Assistant, "wh"),
new(AuthorRole.Assistant, "at?"),
];

Mock<IChatCompletionService> mockService = new();
mockService.Setup(
s => s.GetStreamingChatMessageContentsAsync(
It.IsAny<ChatHistory>(),
It.IsAny<PromptExecutionSettings>(),
It.IsAny<Kernel>(),
It.IsAny<CancellationToken>())).Returns(returnContent.ToAsyncEnumerable());

var kernel = CreateKernel(mockService.Object);
ChatCompletionAgent agent =
new()
{
Instructions = "test instructions",
Kernel = kernel,
Arguments = [],
};

// Act
AgentResponseItem<StreamingChatMessageContent>[] result = await agent.InvokeStreamingAsync(Array.Empty<ChatMessageContent>() as ICollection<ChatMessageContent>).ToArrayAsync();

// Assert
Assert.Equal(2, result.Length);

mockService.Verify(
x =>
x.GetStreamingChatMessageContentsAsync(
It.IsAny<ChatHistory>(),
It.IsAny<PromptExecutionSettings>(),
kernel, // Use the same kernel instance
It.IsAny<CancellationToken>()),
Times.Once);
}

/// <summary>
/// Verify the streaming invocation and response of <see cref="ChatCompletionAgent"/> using <see cref="IChatClient"/>.
/// </summary>
Expand All @@ -283,7 +369,7 @@ public async Task VerifyChatClientAgentStreamingAsync()
{
Instructions = "test instructions",
Kernel = CreateKernel(mockService.Object),
Arguments = [],
Arguments = new(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
};

// Act
Expand All @@ -296,7 +382,7 @@ public async Task VerifyChatClientAgentStreamingAsync()
x =>
x.GetStreamingResponseAsync(
It.IsAny<IEnumerable<ChatMessage>>(),
It.IsAny<ChatOptions>(),
It.Is<ChatOptions>(o => GetKernelFromChatOptions(o) == agent.Kernel),
It.IsAny<CancellationToken>()),
Times.Once);
}
Expand Down Expand Up @@ -386,4 +472,25 @@ private static Kernel CreateKernel(IChatClient chatClient)
builder.Services.AddSingleton<IChatClient>(chatClient);
return builder.Build();
}

/// <summary>
/// Gets the Kernel property from ChatOptions using reflection.
/// </summary>
/// <param name="options">The ChatOptions instance to extract Kernel from.</param>
/// <returns>The Kernel instance if found; otherwise, null.</returns>
private static Kernel? GetKernelFromChatOptions(ChatOptions options)
{
// Use reflection to try to get the Kernel property
var kernelProperty = options.GetType().GetProperty("Kernel",
System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance);

if (kernelProperty != null)
{
return kernelProperty.GetValue(options) as Kernel;
}

return null;
}
}
Loading