Skip to content

Commit cb74902

Browse files
.Net: Allow Kernel to be mutable by AgentChatCompletions (#12538)
### Motivation and Context - Fixes #12534 - This regression seems to be part of #11689 where `kernel` instance was is cloned prior agent iterations, as I didn't captured any failing unit tests for the added `AIContext` providers considering this as a valid fix. - Added UT covering the expected `Kernel` mutability. --------- Co-authored-by: westey <[email protected]>
1 parent 3d21f17 commit cb74902

File tree

9 files changed

+1364
-23
lines changed

9 files changed

+1364
-23
lines changed

dotnet/src/Agents/Abstractions/Agent.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Diagnostics.CodeAnalysis;
55
using System.Threading;
66
using System.Threading.Tasks;
7+
using Microsoft.Extensions.AI;
78
using Microsoft.Extensions.Logging;
89
using Microsoft.Extensions.Logging.Abstractions;
910
using Microsoft.SemanticKernel.Arguments.Extensions;
@@ -66,6 +67,17 @@ public abstract class Agent
6667
/// </value>
6768
public Kernel Kernel { get; init; } = new();
6869

70+
/// <summary>
71+
/// This option forces the agent to clone the original kernel instance during invocation if <c>true</c>. Default is <c>false</c>.
72+
/// </summary>
73+
/// <remarks>
74+
/// <see cref="AIContextProvider"/> implementations that provide <see cref="AIFunction"/> instances require the
75+
/// kernel to be cloned during agent invocation, but cloning has the side affect of causing modifications to Kernel
76+
/// Data by plugins to be lost. Cloning is therefore opt-in.
77+
/// </remarks>
78+
[Experimental("SKEXP0130")]
79+
public bool UseImmutableKernel { get; set; } = false;
80+
6981
/// <summary>
7082
/// Gets or sets a prompt template based on the agent instructions.
7183
/// </summary>

dotnet/src/Agents/AzureAI/AzureAIAgent.cs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Copyright (c) Microsoft. All rights reserved.
22

3+
using System;
34
using System.Collections.Generic;
45
using System.Runtime.CompilerServices;
56
using System.Threading;
@@ -136,11 +137,22 @@ public async IAsyncEnumerable<AgentResponseItem<ChatMessageContent>> InvokeAsync
136137
() => new AzureAIAgentThread(this.Client),
137138
cancellationToken).ConfigureAwait(false);
138139

139-
Kernel kernel = (options?.Kernel ?? this.Kernel).Clone();
140+
Kernel kernel = this.GetKernel(options);
141+
#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.
142+
if (this.UseImmutableKernel)
143+
{
144+
kernel = kernel.Clone();
145+
}
140146

141147
// Get the context contributions from the AIContextProviders.
142-
#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.
143148
AIContext providersContext = await azureAIAgentThread.AIContextProviders.ModelInvokingAsync(messages, cancellationToken).ConfigureAwait(false);
149+
150+
// Check for compatibility AIContextProviders and the UseImmutableKernel setting.
151+
if (providersContext.AIFunctions is { Count: > 0 } && !this.UseImmutableKernel)
152+
{
153+
throw new InvalidOperationException("AIContextProviders with AIFunctions are not supported when Agent UseImmutableKernel setting is false.");
154+
}
155+
144156
kernel.Plugins.AddFromAIContext(providersContext, "Tools");
145157
#pragma warning restore SKEXP0110, SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
146158

@@ -228,11 +240,22 @@ public async IAsyncEnumerable<AgentResponseItem<StreamingChatMessageContent>> In
228240
() => new AzureAIAgentThread(this.Client),
229241
cancellationToken).ConfigureAwait(false);
230242

231-
var kernel = (options?.Kernel ?? this.Kernel).Clone();
243+
Kernel kernel = this.GetKernel(options);
244+
#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.
245+
if (this.UseImmutableKernel)
246+
{
247+
kernel = kernel.Clone();
248+
}
232249

233250
// Get the context contributions from the AIContextProviders.
234-
#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.
235251
AIContext providersContext = await azureAIAgentThread.AIContextProviders.ModelInvokingAsync(messages, cancellationToken).ConfigureAwait(false);
252+
253+
// Check for compatibility AIContextProviders and the UseImmutableKernel setting.
254+
if (providersContext.AIFunctions is { Count: > 0 } && !this.UseImmutableKernel)
255+
{
256+
throw new InvalidOperationException("AIContextProviders with AIFunctions are not supported when Agent UseImmutableKernel setting is false.");
257+
}
258+
236259
kernel.Plugins.AddFromAIContext(providersContext, "Tools");
237260
#pragma warning restore SKEXP0110, SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
238261

dotnet/src/Agents/Core/ChatCompletionAgent.cs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,22 @@ public override async IAsyncEnumerable<AgentResponseItem<ChatMessageContent>> In
7373
() => new ChatHistoryAgentThread(),
7474
cancellationToken).ConfigureAwait(false);
7575

76-
Kernel kernel = (options?.Kernel ?? this.Kernel).Clone();
76+
Kernel kernel = this.GetKernel(options);
77+
#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.
78+
if (this.UseImmutableKernel)
79+
{
80+
kernel = kernel.Clone();
81+
}
7782

7883
// Get the context contributions from the AIContextProviders.
79-
#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.
8084
AIContext providersContext = await chatHistoryAgentThread.AIContextProviders.ModelInvokingAsync(messages, cancellationToken).ConfigureAwait(false);
85+
86+
// Check for compatibility AIContextProviders and the UseImmutableKernel setting.
87+
if (providersContext.AIFunctions is { Count: > 0 } && !this.UseImmutableKernel)
88+
{
89+
throw new InvalidOperationException("AIContextProviders with AIFunctions are not supported when Agent UseImmutableKernel setting is false.");
90+
}
91+
8192
kernel.Plugins.AddFromAIContext(providersContext, "Tools");
8293
#pragma warning restore SKEXP0110, SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
8394

@@ -168,11 +179,22 @@ public override async IAsyncEnumerable<AgentResponseItem<StreamingChatMessageCon
168179
() => new ChatHistoryAgentThread(),
169180
cancellationToken).ConfigureAwait(false);
170181

171-
Kernel kernel = (options?.Kernel ?? this.Kernel).Clone();
182+
Kernel kernel = this.GetKernel(options);
183+
#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.
184+
if (this.UseImmutableKernel)
185+
{
186+
kernel = kernel.Clone();
187+
}
172188

173189
// Get the context contributions from the AIContextProviders.
174-
#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.
175190
AIContext providersContext = await chatHistoryAgentThread.AIContextProviders.ModelInvokingAsync(messages, cancellationToken).ConfigureAwait(false);
191+
192+
// Check for compatibility AIContextProviders and the UseImmutableKernel setting.
193+
if (providersContext.AIFunctions is { Count: > 0 } && !this.UseImmutableKernel)
194+
{
195+
throw new InvalidOperationException("AIContextProviders with AIFunctions are not supported when Agent UseImmutableKernel setting is false.");
196+
}
197+
176198
kernel.Plugins.AddFromAIContext(providersContext, "Tools");
177199
#pragma warning restore SKEXP0110, SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
178200

dotnet/src/Agents/OpenAI/OpenAIAssistantAgent.cs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Copyright (c) Microsoft. All rights reserved.
22

3+
using System;
34
using System.Collections.Generic;
45
using System.Diagnostics.CodeAnalysis;
56
using System.Runtime.CompilerServices;
@@ -138,11 +139,22 @@ public async IAsyncEnumerable<AgentResponseItem<ChatMessageContent>> InvokeAsync
138139
AdditionalInstructions = options?.AdditionalInstructions,
139140
});
140141

141-
Kernel kernel = (options?.Kernel ?? this.Kernel).Clone();
142+
Kernel kernel = this.GetKernel(options);
143+
#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.
144+
if (this.UseImmutableKernel)
145+
{
146+
kernel = kernel.Clone();
147+
}
142148

143149
// Get the context contributions from the AIContextProviders.
144-
#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.
145150
AIContext providersContext = await openAIAssistantAgentThread.AIContextProviders.ModelInvokingAsync(messages, cancellationToken).ConfigureAwait(false);
151+
152+
// Check for compatibility AIContextProviders and the UseImmutableKernel setting.
153+
if (providersContext.AIFunctions is { Count: > 0 } && !this.UseImmutableKernel)
154+
{
155+
throw new InvalidOperationException("AIContextProviders with AIFunctions are not supported when Agent UseImmutableKernel setting is false.");
156+
}
157+
146158
kernel.Plugins.AddFromAIContext(providersContext, "Tools");
147159
#pragma warning restore SKEXP0110, SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
148160

@@ -226,11 +238,22 @@ public async IAsyncEnumerable<AgentResponseItem<StreamingChatMessageContent>> In
226238
() => new OpenAIAssistantAgentThread(this.Client),
227239
cancellationToken).ConfigureAwait(false);
228240

229-
Kernel kernel = (options?.Kernel ?? this.Kernel).Clone();
241+
Kernel kernel = this.GetKernel(options);
242+
#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.
243+
if (this.UseImmutableKernel)
244+
{
245+
kernel = kernel.Clone();
246+
}
230247

231248
// Get the context contributions from the AIContextProviders.
232-
#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.
233249
AIContext providersContext = await openAIAssistantAgentThread.AIContextProviders.ModelInvokingAsync(messages, cancellationToken).ConfigureAwait(false);
250+
251+
// Check for compatibility AIContextProviders and the UseImmutableKernel setting.
252+
if (providersContext.AIFunctions is { Count: > 0 } && !this.UseImmutableKernel)
253+
{
254+
throw new InvalidOperationException("AIContextProviders with AIFunctions are not supported when Agent UseImmutableKernel setting is false.");
255+
}
256+
234257
kernel.Plugins.AddFromAIContext(providersContext, "Tools");
235258
#pragma warning restore SKEXP0110, SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
236259

dotnet/src/Agents/OpenAI/OpenAIResponseAgent.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,22 @@ private async Task<AgentThread> EnsureThreadExistsWithMessagesAsync(ICollection<
140140

141141
private async Task<OpenAIAssistantAgentInvokeOptions> FinalizeInvokeOptionsAsync(ICollection<ChatMessageContent> messages, AgentInvokeOptions? options, AgentThread agentThread, CancellationToken cancellationToken)
142142
{
143-
Kernel kernel = this.GetKernel(options).Clone();
143+
Kernel kernel = this.GetKernel(options);
144+
#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.
145+
if (this.UseImmutableKernel)
146+
{
147+
kernel = kernel.Clone();
148+
}
144149

145-
#pragma warning disable SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
150+
// Get the AIContextProviders contributions to the kernel.
146151
AIContext providersContext = await agentThread.AIContextProviders.ModelInvokingAsync(messages, cancellationToken).ConfigureAwait(false);
152+
153+
// Check for compatibility AIContextProviders and the UseImmutableKernel setting.
154+
if (providersContext.AIFunctions is { Count: > 0 } && !this.UseImmutableKernel)
155+
{
156+
throw new InvalidOperationException("AIContextProviders with AIFunctions are not supported when Agent UseImmutableKernel setting is false.");
157+
}
158+
147159
kernel.Plugins.AddFromAIContext(providersContext, "Tools");
148160
#pragma warning restore SKEXP0130 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
149161

0 commit comments

Comments
 (0)