forked from microsoft/semantic-kernel
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathInvokeTests.cs
208 lines (175 loc) · 7.67 KB
/
InvokeTests.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.ChatCompletion;
using xRetry;
using Xunit;
namespace SemanticKernel.IntegrationTests.Agents.CommonInterfaceConformance.InvokeConformance;
/// <summary>
/// Base test class for testing the <see cref="Agent.InvokeAsync(ChatMessageContent, AgentThread?, AgentInvokeOptions?, System.Threading.CancellationToken)"/> method of agents.
/// Each agent type should have its own derived class.
/// </summary>
public abstract class InvokeTests(Func<AgentFixture> createAgentFixture) : IAsyncLifetime
{
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
private AgentFixture _agentFixture;
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
protected AgentFixture Fixture => this._agentFixture;
[RetryFact(3, 5000)]
public virtual async Task InvokeReturnsResultAsync()
{
// Arrange
var agent = this.Fixture.Agent;
// Act
var asyncResults = agent.InvokeAsync(new ChatMessageContent(AuthorRole.User, "What is the capital of France."), this.Fixture.AgentThread);
var results = await asyncResults.ToListAsync();
// Assert
Assert.Single(results);
var firstResult = results.First();
Assert.Contains("Paris", firstResult.Message.Content);
Assert.NotNull(firstResult.Thread);
}
[RetryFact(3, 5000)]
public virtual async Task InvokeWithoutThreadCreatesThreadAsync()
{
// Arrange
var agent = this.Fixture.Agent;
// Act
var asyncResults = agent.InvokeAsync(new ChatMessageContent(AuthorRole.User, "What is the capital of France."));
var results = await asyncResults.ToListAsync();
// Assert
Assert.Single(results);
var firstResult = results.First();
Assert.Contains("Paris", firstResult.Message.Content);
Assert.NotNull(firstResult.Thread);
// Cleanup
await this.Fixture.DeleteThread(firstResult.Thread);
}
[RetryFact(3, 5000)]
public virtual async Task InvokeWithoutMessageCreatesThreadAsync()
{
// Arrange
var agent = this.Fixture.Agent;
// Act
var asyncResults = agent.InvokeAsync();
var results = await asyncResults.ToListAsync();
// Assert
Assert.Single(results);
var firstResult = results.First();
Assert.NotNull(firstResult.Thread);
// Cleanup
await this.Fixture.DeleteThread(firstResult.Thread);
}
[RetryFact(3, 5000)]
public virtual async Task ConversationMaintainsHistoryAsync()
{
// Arrange
var q1 = "What is the capital of France.";
var q2 = "What is the capital of Austria.";
var agent = this.Fixture.Agent;
// Act
var asyncResults1 = agent.InvokeAsync(new ChatMessageContent(AuthorRole.User, q1), this.Fixture.AgentThread);
var result1 = await asyncResults1.FirstAsync();
var asyncResults2 = agent.InvokeAsync(new ChatMessageContent(AuthorRole.User, q2), result1.Thread);
var result2 = await asyncResults2.FirstAsync();
// Assert
Assert.Contains("Paris", result1.Message.Content);
Assert.Contains("Austria", result2.Message.Content);
var chatHistory = await this.Fixture.GetChatHistory();
Assert.Equal(4, chatHistory.Count);
Assert.Equal(2, chatHistory.Count(x => x.Role == AuthorRole.User));
Assert.Equal(2, chatHistory.Count(x => x.Role == AuthorRole.Assistant));
Assert.Equal(q1, chatHistory[0].Content);
Assert.Equal(q2, chatHistory[2].Content);
Assert.Contains("Paris", chatHistory[1].Content);
Assert.Contains("Vienna", chatHistory[3].Content);
}
/// <summary>
/// Verifies that the agent can invoke a plugin and respects the override
/// Kernel and KernelArguments provided in the options.
/// The step does multiple iterations to make sure that the agent
/// also manages the chat history correctly.
/// </summary>
[RetryFact(3, 5000)]
public virtual async Task MultiStepInvokeWithPluginAndArgOverridesAsync()
{
// Arrange
var questionsAndAnswers = new[]
{
("Hello", string.Empty),
("What is the special soup?", "Clam Chowder"),
("What is the special drink?", "Chai Tea"),
("What is the special salad?", "Cobb Salad"),
("Thank you", string.Empty)
};
var agent = this.Fixture.Agent;
var kernel = agent.Kernel.Clone();
kernel.Plugins.AddFromType<MenuPlugin>();
foreach (var questionAndAnswer in questionsAndAnswers)
{
// Act
var asyncResults = agent.InvokeAsync(
new ChatMessageContent(AuthorRole.User, questionAndAnswer.Item1),
this.Fixture.AgentThread,
options: new()
{
Kernel = kernel,
KernelArguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() })
});
// Assert
var result = await asyncResults.FirstAsync();
Assert.NotNull(result);
Assert.Contains(questionAndAnswer.Item2, result.Message.Content);
}
}
/// <summary>
/// Verifies that the agent notifies callers of all messages
/// including function calling ones when using invoke with a plugin.
/// </summary>
[Fact]
public virtual async Task InvokeWithPluginNotifiesForAllMessagesAsync()
{
// Arrange
var agent = this.Fixture.Agent;
agent.Kernel.Plugins.AddFromType<MenuPlugin>();
var notifiedMessages = new List<ChatMessageContent>();
// Act
var asyncResults = agent.InvokeAsync(
new ChatMessageContent(AuthorRole.User, "What is the special soup?"),
this.Fixture.AgentThread,
options: new()
{
KernelArguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }),
OnIntermediateMessage = (message) =>
{
notifiedMessages.Add(message);
return Task.CompletedTask;
}
});
// Assert
var results = await asyncResults.ToArrayAsync();
Assert.Single(results);
Assert.Contains("Clam Chowder", results[0].Message.Content);
Assert.Equal(3, notifiedMessages.Count);
Assert.Contains(notifiedMessages[0].Items, x => x is FunctionCallContent);
Assert.Contains(notifiedMessages[1].Items, x => x is FunctionResultContent);
var functionCallContent = notifiedMessages[0].Items.OfType<FunctionCallContent>().First();
var functionResultContent = notifiedMessages[1].Items.OfType<FunctionResultContent>().First();
Assert.Equal("GetSpecials", functionCallContent.FunctionName);
Assert.Equal("GetSpecials", functionResultContent.FunctionName);
Assert.Equal(results[0].Message.Content, notifiedMessages[2].Content);
}
public Task InitializeAsync()
{
this._agentFixture = createAgentFixture();
return this._agentFixture.InitializeAsync();
}
public Task DisposeAsync()
{
return this._agentFixture.DisposeAsync();
}
}