Skip to content

Commit 1b4d348

Browse files
authored
.Net: Gemini added support of system messages and removed messages order limitation (#7067)
### Motivation and Context <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> Finaly Google Gemini API is supporting system messages. ### Description <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> Added support one or multiple system messages. Removed messages order limitation because google API not permitting this anymore. (so Agents framework and handlebars planner now should work with Gemini! yeah!) ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [x] The code builds clean without any errors or warnings - [x] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [x] All unit tests pass, and I have added new tests where possible - [x] I didn't break anyone 😄 @rogerbarreto
1 parent b071168 commit 1b4d348

File tree

10 files changed

+245
-128
lines changed

10 files changed

+245
-128
lines changed

dotnet/samples/Concepts/ChatCompletion/Google_GeminiChatCompletion.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ private async Task SimpleChatAsync(Kernel kernel)
8989
{
9090
Console.WriteLine("======== Simple Chat ========");
9191

92-
var chatHistory = new ChatHistory();
92+
var chatHistory = new ChatHistory("You are an expert in the tool shop.");
9393
var chat = kernel.GetRequiredService<IChatCompletionService>();
9494

9595
// First user message

dotnet/samples/Concepts/ChatCompletion/Google_GeminiChatCompletionStreaming.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ private async Task StreamingChatAsync(Kernel kernel)
9090
{
9191
Console.WriteLine("======== Streaming Chat ========");
9292

93-
var chatHistory = new ChatHistory();
93+
var chatHistory = new ChatHistory("You are an expert in the tool shop.");
9494
var chat = kernel.GetRequiredService<IChatCompletionService>();
9595

9696
// First user message

dotnet/samples/Concepts/ChatCompletion/Google_GeminiVision.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public async Task GoogleAIAsync()
1414
Console.WriteLine("============= Google AI - Gemini Chat Completion with vision =============");
1515

1616
string geminiApiKey = TestConfiguration.GoogleAI.ApiKey;
17-
string geminiModelId = "gemini-pro-vision";
17+
string geminiModelId = TestConfiguration.GoogleAI.Gemini.ModelId;
1818

1919
if (geminiApiKey is null)
2020
{
@@ -28,7 +28,7 @@ public async Task GoogleAIAsync()
2828
apiKey: geminiApiKey)
2929
.Build();
3030

31-
var chatHistory = new ChatHistory();
31+
var chatHistory = new ChatHistory("Your job is describing images.");
3232
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
3333

3434
// Load the image from the resources
@@ -55,7 +55,7 @@ public async Task VertexAIAsync()
5555
Console.WriteLine("============= Vertex AI - Gemini Chat Completion with vision =============");
5656

5757
string geminiBearerKey = TestConfiguration.VertexAI.BearerKey;
58-
string geminiModelId = "gemini-pro-vision";
58+
string geminiModelId = TestConfiguration.VertexAI.Gemini.ModelId;
5959
string geminiLocation = TestConfiguration.VertexAI.Location;
6060
string geminiProject = TestConfiguration.VertexAI.ProjectId;
6161

@@ -96,7 +96,7 @@ public async Task VertexAIAsync()
9696
// location: TestConfiguration.VertexAI.Location,
9797
// projectId: TestConfiguration.VertexAI.ProjectId);
9898

99-
var chatHistory = new ChatHistory();
99+
var chatHistory = new ChatHistory("Your job is describing images.");
100100
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
101101

102102
// Load the image from the resources

dotnet/src/Connectors/Connectors.Google.UnitTests/Core/Gemini/Clients/GeminiChatGenerationTests.cs

Lines changed: 20 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -259,21 +259,7 @@ await Assert.ThrowsAsync<InvalidOperationException>(
259259
}
260260

261261
[Fact]
262-
public async Task ShouldThrowInvalidOperationExceptionIfChatHistoryContainsMoreThanOneSystemMessageAsync()
263-
{
264-
var client = this.CreateChatCompletionClient();
265-
var chatHistory = new ChatHistory("System message");
266-
chatHistory.AddSystemMessage("System message 2");
267-
chatHistory.AddSystemMessage("System message 3");
268-
chatHistory.AddUserMessage("hello");
269-
270-
// Act & Assert
271-
await Assert.ThrowsAsync<InvalidOperationException>(
272-
() => client.GenerateChatMessageAsync(chatHistory));
273-
}
274-
275-
[Fact]
276-
public async Task ShouldPassConvertedSystemMessageToUserMessageToRequestAsync()
262+
public async Task ShouldPassSystemMessageToRequestAsync()
277263
{
278264
// Arrange
279265
var client = this.CreateChatCompletionClient();
@@ -287,40 +273,35 @@ public async Task ShouldPassConvertedSystemMessageToUserMessageToRequestAsync()
287273
// Assert
288274
GeminiRequest? request = JsonSerializer.Deserialize<GeminiRequest>(this._messageHandlerStub.RequestContent);
289275
Assert.NotNull(request);
290-
var systemMessage = request.Contents[0].Parts![0].Text;
291-
var messageRole = request.Contents[0].Role;
292-
Assert.Equal(AuthorRole.User, messageRole);
276+
Assert.NotNull(request.SystemInstruction);
277+
var systemMessage = request.SystemInstruction.Parts![0].Text;
278+
Assert.Null(request.SystemInstruction.Role);
293279
Assert.Equal(message, systemMessage);
294280
}
295281

296282
[Fact]
297-
public async Task ShouldThrowNotSupportedIfChatHistoryHaveIncorrectOrderAsync()
283+
public async Task ShouldPassMultipleSystemMessagesToRequestAsync()
298284
{
299285
// Arrange
286+
string[] messages = ["System message 1", "System message 2", "System message 3"];
300287
var client = this.CreateChatCompletionClient();
301-
var chatHistory = new ChatHistory();
288+
var chatHistory = new ChatHistory(messages[0]);
289+
chatHistory.AddSystemMessage(messages[1]);
290+
chatHistory.AddSystemMessage(messages[2]);
302291
chatHistory.AddUserMessage("Hello");
303-
chatHistory.AddAssistantMessage("Hi");
304-
chatHistory.AddAssistantMessage("Hi me again");
305-
chatHistory.AddUserMessage("How are you?");
306292

307-
// Act & Assert
308-
await Assert.ThrowsAsync<NotSupportedException>(
309-
() => client.GenerateChatMessageAsync(chatHistory));
310-
}
311-
312-
[Fact]
313-
public async Task ShouldThrowNotSupportedIfChatHistoryNotEndWithUserMessageAsync()
314-
{
315-
// Arrange
316-
var client = this.CreateChatCompletionClient();
317-
var chatHistory = new ChatHistory();
318-
chatHistory.AddUserMessage("Hello");
319-
chatHistory.AddAssistantMessage("Hi");
293+
// Act
294+
await client.GenerateChatMessageAsync(chatHistory);
320295

321-
// Act & Assert
322-
await Assert.ThrowsAsync<NotSupportedException>(
323-
() => client.GenerateChatMessageAsync(chatHistory));
296+
// Assert
297+
GeminiRequest? request = JsonSerializer.Deserialize<GeminiRequest>(this._messageHandlerStub.RequestContent);
298+
Assert.NotNull(request);
299+
Assert.NotNull(request.SystemInstruction);
300+
Assert.Null(request.SystemInstruction.Role);
301+
Assert.Collection(request.SystemInstruction.Parts!,
302+
item => Assert.Equal(messages[0], item.Text),
303+
item => Assert.Equal(messages[1], item.Text),
304+
item => Assert.Equal(messages[2], item.Text));
324305
}
325306

326307
[Fact]

dotnet/src/Connectors/Connectors.Google.UnitTests/Core/Gemini/Clients/GeminiChatStreamingTests.cs

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ public async Task ShouldUsePromptExecutionSettingsAsync()
248248
}
249249

250250
[Fact]
251-
public async Task ShouldPassConvertedSystemMessageToUserMessageToRequestAsync()
251+
public async Task ShouldPassSystemMessageToRequestAsync()
252252
{
253253
// Arrange
254254
var client = this.CreateChatCompletionClient();
@@ -262,12 +262,37 @@ public async Task ShouldPassConvertedSystemMessageToUserMessageToRequestAsync()
262262
// Assert
263263
GeminiRequest? request = JsonSerializer.Deserialize<GeminiRequest>(this._messageHandlerStub.RequestContent);
264264
Assert.NotNull(request);
265-
var systemMessage = request.Contents[0].Parts![0].Text;
266-
var messageRole = request.Contents[0].Role;
267-
Assert.Equal(AuthorRole.User, messageRole);
265+
Assert.NotNull(request.SystemInstruction);
266+
var systemMessage = request.SystemInstruction.Parts![0].Text;
267+
Assert.Null(request.SystemInstruction.Role);
268268
Assert.Equal(message, systemMessage);
269269
}
270270

271+
[Fact]
272+
public async Task ShouldPassMultipleSystemMessagesToRequestAsync()
273+
{
274+
// Arrange
275+
string[] messages = ["System message 1", "System message 2", "System message 3"];
276+
var client = this.CreateChatCompletionClient();
277+
var chatHistory = new ChatHistory(messages[0]);
278+
chatHistory.AddSystemMessage(messages[1]);
279+
chatHistory.AddSystemMessage(messages[2]);
280+
chatHistory.AddUserMessage("Hello");
281+
282+
// Act
283+
await client.StreamGenerateChatMessageAsync(chatHistory).ToListAsync();
284+
285+
// Assert
286+
GeminiRequest? request = JsonSerializer.Deserialize<GeminiRequest>(this._messageHandlerStub.RequestContent);
287+
Assert.NotNull(request);
288+
Assert.NotNull(request.SystemInstruction);
289+
Assert.Null(request.SystemInstruction.Role);
290+
Assert.Collection(request.SystemInstruction.Parts!,
291+
item => Assert.Equal(messages[0], item.Text),
292+
item => Assert.Equal(messages[1], item.Text),
293+
item => Assert.Equal(messages[2], item.Text));
294+
}
295+
271296
[Theory]
272297
[InlineData(0)]
273298
[InlineData(-15)]

dotnet/src/Connectors/Connectors.Google.UnitTests/Core/Gemini/GeminiRequestTests.cs

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ namespace SemanticKernel.Connectors.Google.UnitTests.Core.Gemini;
1515
public sealed class GeminiRequestTests
1616
{
1717
[Fact]
18-
public void FromPromptItReturnsGeminiRequestWithConfiguration()
18+
public void FromPromptItReturnsWithConfiguration()
1919
{
2020
// Arrange
2121
var prompt = "prompt-example";
@@ -37,7 +37,7 @@ public void FromPromptItReturnsGeminiRequestWithConfiguration()
3737
}
3838

3939
[Fact]
40-
public void FromPromptItReturnsGeminiRequestWithSafetySettings()
40+
public void FromPromptItReturnsWithSafetySettings()
4141
{
4242
// Arrange
4343
var prompt = "prompt-example";
@@ -59,7 +59,7 @@ public void FromPromptItReturnsGeminiRequestWithSafetySettings()
5959
}
6060

6161
[Fact]
62-
public void FromPromptItReturnsGeminiRequestWithPrompt()
62+
public void FromPromptItReturnsWithPrompt()
6363
{
6464
// Arrange
6565
var prompt = "prompt-example";
@@ -73,7 +73,7 @@ public void FromPromptItReturnsGeminiRequestWithPrompt()
7373
}
7474

7575
[Fact]
76-
public void FromChatHistoryItReturnsGeminiRequestWithConfiguration()
76+
public void FromChatHistoryItReturnsWithConfiguration()
7777
{
7878
// Arrange
7979
ChatHistory chatHistory = [];
@@ -98,7 +98,7 @@ public void FromChatHistoryItReturnsGeminiRequestWithConfiguration()
9898
}
9999

100100
[Fact]
101-
public void FromChatHistoryItReturnsGeminiRequestWithSafetySettings()
101+
public void FromChatHistoryItReturnsWithSafetySettings()
102102
{
103103
// Arrange
104104
ChatHistory chatHistory = [];
@@ -123,10 +123,11 @@ public void FromChatHistoryItReturnsGeminiRequestWithSafetySettings()
123123
}
124124

125125
[Fact]
126-
public void FromChatHistoryItReturnsGeminiRequestWithChatHistory()
126+
public void FromChatHistoryItReturnsWithChatHistory()
127127
{
128128
// Arrange
129-
ChatHistory chatHistory = [];
129+
string systemMessage = "system-message";
130+
var chatHistory = new ChatHistory(systemMessage);
130131
chatHistory.AddUserMessage("user-message");
131132
chatHistory.AddAssistantMessage("assist-message");
132133
chatHistory.AddUserMessage("user-message2");
@@ -136,18 +137,41 @@ public void FromChatHistoryItReturnsGeminiRequestWithChatHistory()
136137
var request = GeminiRequest.FromChatHistoryAndExecutionSettings(chatHistory, executionSettings);
137138

138139
// Assert
140+
Assert.NotNull(request.SystemInstruction?.Parts);
141+
Assert.Single(request.SystemInstruction.Parts);
142+
Assert.Equal(request.SystemInstruction.Parts[0].Text, systemMessage);
139143
Assert.Collection(request.Contents,
140-
c => Assert.Equal(chatHistory[0].Content, c.Parts![0].Text),
141144
c => Assert.Equal(chatHistory[1].Content, c.Parts![0].Text),
142-
c => Assert.Equal(chatHistory[2].Content, c.Parts![0].Text));
145+
c => Assert.Equal(chatHistory[2].Content, c.Parts![0].Text),
146+
c => Assert.Equal(chatHistory[3].Content, c.Parts![0].Text));
143147
Assert.Collection(request.Contents,
144-
c => Assert.Equal(chatHistory[0].Role, c.Role),
145148
c => Assert.Equal(chatHistory[1].Role, c.Role),
146-
c => Assert.Equal(chatHistory[2].Role, c.Role));
149+
c => Assert.Equal(chatHistory[2].Role, c.Role),
150+
c => Assert.Equal(chatHistory[3].Role, c.Role));
151+
}
152+
153+
[Fact]
154+
public void FromChatHistoryMultipleSystemMessagesItReturnsWithSystemMessages()
155+
{
156+
// Arrange
157+
string[] systemMessages = ["system-message", "system-message2", "system-message3", "system-message4"];
158+
var chatHistory = new ChatHistory(systemMessages[0]);
159+
chatHistory.AddUserMessage("user-message");
160+
chatHistory.AddSystemMessage(systemMessages[1]);
161+
chatHistory.AddMessage(AuthorRole.System,
162+
[new TextContent(systemMessages[2]), new TextContent(systemMessages[3])]);
163+
var executionSettings = new GeminiPromptExecutionSettings();
164+
165+
// Act
166+
var request = GeminiRequest.FromChatHistoryAndExecutionSettings(chatHistory, executionSettings);
167+
168+
// Assert
169+
Assert.NotNull(request.SystemInstruction?.Parts);
170+
Assert.All(systemMessages, msg => Assert.Contains(request.SystemInstruction.Parts, p => p.Text == msg));
147171
}
148172

149173
[Fact]
150-
public void FromChatHistoryTextAsTextContentItReturnsGeminiRequestWithChatHistory()
174+
public void FromChatHistoryTextAsTextContentItReturnsWithChatHistory()
151175
{
152176
// Arrange
153177
ChatHistory chatHistory = [];
@@ -163,11 +187,11 @@ public void FromChatHistoryTextAsTextContentItReturnsGeminiRequestWithChatHistor
163187
Assert.Collection(request.Contents,
164188
c => Assert.Equal(chatHistory[0].Content, c.Parts![0].Text),
165189
c => Assert.Equal(chatHistory[1].Content, c.Parts![0].Text),
166-
c => Assert.Equal(chatHistory[2].Items!.Cast<TextContent>().Single().Text, c.Parts![0].Text));
190+
c => Assert.Equal(chatHistory[2].Items.Cast<TextContent>().Single().Text, c.Parts![0].Text));
167191
}
168192

169193
[Fact]
170-
public void FromChatHistoryImageAsImageContentItReturnsGeminiRequestWithChatHistory()
194+
public void FromChatHistoryImageAsImageContentItReturnsWithChatHistory()
171195
{
172196
// Arrange
173197
ReadOnlyMemory<byte> imageAsBytes = new byte[] { 0x00, 0x01, 0x02, 0x03 };
@@ -187,7 +211,7 @@ public void FromChatHistoryImageAsImageContentItReturnsGeminiRequestWithChatHist
187211
Assert.Collection(request.Contents,
188212
c => Assert.Equal(chatHistory[0].Content, c.Parts![0].Text),
189213
c => Assert.Equal(chatHistory[1].Content, c.Parts![0].Text),
190-
c => Assert.Equal(chatHistory[2].Items!.Cast<ImageContent>().Single().Uri,
214+
c => Assert.Equal(chatHistory[2].Items.Cast<ImageContent>().Single().Uri,
191215
c.Parts![0].FileData!.FileUri),
192216
c => Assert.True(imageAsBytes.ToArray()
193217
.SequenceEqual(Convert.FromBase64String(c.Parts![0].InlineData!.InlineData))));
@@ -272,7 +296,7 @@ public void FromChatHistoryToolCallsNotNullAddsFunctionCalls()
272296
}
273297

274298
[Fact]
275-
public void AddFunctionItAddsFunctionToGeminiRequest()
299+
public void AddFunctionToGeminiRequest()
276300
{
277301
// Arrange
278302
var request = new GeminiRequest();
@@ -287,7 +311,7 @@ public void AddFunctionItAddsFunctionToGeminiRequest()
287311
}
288312

289313
[Fact]
290-
public void AddMultipleFunctionsItAddsFunctionsToGeminiRequest()
314+
public void AddMultipleFunctionsToGeminiRequest()
291315
{
292316
// Arrange
293317
var request = new GeminiRequest();
@@ -308,7 +332,7 @@ public void AddMultipleFunctionsItAddsFunctionsToGeminiRequest()
308332
}
309333

310334
[Fact]
311-
public void AddChatMessageToRequestItAddsChatMessageToGeminiRequest()
335+
public void AddChatMessageToRequest()
312336
{
313337
// Arrange
314338
ChatHistory chat = [];

0 commit comments

Comments
 (0)