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 @@ -528,14 +528,14 @@ public void LabelsFromPromptReturnsAsExpected()
var prompt = "prompt-example";
var executionSettings = new GeminiPromptExecutionSettings
{
Labels = "Key1:Value1"
Labels = new Dictionary<string, string> { { "key1", "value1" }, { "key2", "value2" } }
};

// Act
var request = GeminiRequest.FromPromptAndExecutionSettings(prompt, executionSettings);

// Assert
Assert.NotNull(request.Configuration);
Assert.NotNull(request.Labels);
Assert.Equal(executionSettings.Labels, request.Labels);
}

Expand Down Expand Up @@ -569,7 +569,7 @@ public void LabelsFromChatHistoryReturnsAsExpected()
chatHistory.AddUserMessage("user-message2");
var executionSettings = new GeminiPromptExecutionSettings
{
Labels = "Key1:Value1"
Labels = new Dictionary<string, string> { { "key1", "value1" }, { "key2", "value2" } }
};

// Act
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Text;
Expand Down Expand Up @@ -69,15 +70,13 @@ public async Task RequestCachedContentWorksCorrectlyAsync(string? cachedContent)
}
}

[Theory]
[InlineData(null)]
[InlineData("key:value")]
[InlineData("")]
public async Task RequestLabelsWorksCorrectlyAsync(string? labels)
[Fact]
public async Task RequestLabelsWorksCorrectlyAsync()
{
// Arrange
string model = "fake-model";
var sut = new GoogleAIGeminiChatCompletionService(model, "key", httpClient: this._httpClient);
var labels = new Dictionary<string, string> { { "key1", "value1" }, { "key2", "value2" } };

// Act
var result = await sut.GetChatMessageContentAsync("my prompt", new GeminiPromptExecutionSettings { Labels = labels });
Expand All @@ -87,19 +86,28 @@ public async Task RequestLabelsWorksCorrectlyAsync(string? labels)
Assert.NotNull(this._messageHandlerStub.RequestContent);

var requestBody = UTF8Encoding.UTF8.GetString(this._messageHandlerStub.RequestContent);
if (labels is not null)
{
Assert.Contains($"\"labels\":\"{labels}\"", requestBody);
}
else
{
// Then no quality is provided, it should not be included in the request body
Assert.DoesNotContain("labels", requestBody);
}
Assert.Contains("\"labels\":{\"key1\":\"value1\",\"key2\":\"value2\"}", requestBody);
}

[Fact]
public async Task RequestLabelsNullWorksCorrectlyAsync()
{
// Arrange
string model = "fake-model";
var sut = new GoogleAIGeminiChatCompletionService(model, "key", httpClient: this._httpClient);

// Act
var result = await sut.GetChatMessageContentAsync("my prompt", new GeminiPromptExecutionSettings { Labels = null });

// Assert
Assert.NotNull(result);
Assert.NotNull(this._messageHandlerStub.RequestContent);

var requestBody = UTF8Encoding.UTF8.GetString(this._messageHandlerStub.RequestContent);
Assert.DoesNotContain("labels", requestBody);
}

[Theory]
[InlineData(null, false)]
[InlineData(0, true)]
[InlineData(500, true)]
[InlineData(2048, true)]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Text;
Expand Down Expand Up @@ -80,15 +81,13 @@ public async Task RequestCachedContentWorksCorrectlyAsync(string? cachedContent)
}
}

[Theory]
[InlineData(null)]
[InlineData("key:value")]
[InlineData("")]
public async Task RequestLabelsWorksCorrectlyAsync(string? labels)
[Fact]
public async Task RequestLabelsWorksCorrectlyAsync()
{
// Arrange
string model = "fake-model";
var sut = new GoogleAIGeminiChatCompletionService(model, "key", httpClient: this._httpClient);
var sut = new VertexAIGeminiChatCompletionService(model, () => new ValueTask<string>("key"), "location", "project", httpClient: this._httpClient);
var labels = new Dictionary<string, string> { { "key1", "value1" }, { "key2", "value2" } };

// Act
var result = await sut.GetChatMessageContentAsync("my prompt", new GeminiPromptExecutionSettings { Labels = labels });
Expand All @@ -98,15 +97,25 @@ public async Task RequestLabelsWorksCorrectlyAsync(string? labels)
Assert.NotNull(this._messageHandlerStub.RequestContent);

var requestBody = UTF8Encoding.UTF8.GetString(this._messageHandlerStub.RequestContent);
if (labels is not null)
{
Assert.Contains($"\"labels\":\"{labels}\"", requestBody);
}
else
{
// Then no quality is provided, it should not be included in the request body
Assert.DoesNotContain("labels", requestBody);
}
Assert.Contains("\"labels\":{\"key1\":\"value1\",\"key2\":\"value2\"}", requestBody);
}

[Fact]
public async Task RequestLabelsNullWorksCorrectlyAsync()
{
// Arrange
string model = "fake-model";
var sut = new VertexAIGeminiChatCompletionService(model, () => new ValueTask<string>("key"), "location", "project", httpClient: this._httpClient);

// Act
var result = await sut.GetChatMessageContentAsync("my prompt", new GeminiPromptExecutionSettings { Labels = null });

// Assert
Assert.NotNull(result);
Assert.NotNull(this._messageHandlerStub.RequestContent);

var requestBody = UTF8Encoding.UTF8.GetString(this._messageHandlerStub.RequestContent);
Assert.DoesNotContain("labels", requestBody);
}

[Theory]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ internal sealed class GeminiRequest

[JsonPropertyName("labels")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Labels { get; set; }
public IDictionary<string, string>? Labels { get; set; }

public void AddFunction(GeminiFunction function)
{
Expand Down Expand Up @@ -450,7 +450,12 @@ private static void AddSafetySettings(GeminiPromptExecutionSettings executionSet
private static void AddAdditionalBodyFields(GeminiPromptExecutionSettings executionSettings, GeminiRequest request)
{
request.CachedContent = executionSettings.CachedContent;
request.Labels = executionSettings.Labels;

if (executionSettings.Labels is not null)
{
request.Labels = executionSettings.Labels;
}

if (executionSettings.ThinkingConfig is not null)
{
request.Configuration ??= new ConfigurationElement();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public sealed class GeminiPromptExecutionSettings : PromptExecutionSettings
private string? _responseMimeType;
private object? _responseSchema;
private string? _cachedContent;
private string? _labels;
private IDictionary<string, string>? _labels;
private IList<GeminiSafetySetting>? _safetySettings;
private GeminiToolCallBehavior? _toolCallBehavior;
private GeminiThinkingConfig? _thinkingConfig;
Expand Down Expand Up @@ -147,9 +147,12 @@ public IList<GeminiSafetySetting>? SafetySettings
/// Gets or sets the labels.
/// </summary>
/// <value>
/// Metadata that can be added to the API call in the format of key-value pairs.
/// The labels with user-defined metadata for the request. It is used for billing and reporting only.
/// label keys and values can be no longer than 63 characters (Unicode codepoints) and can only contain lowercase letters, numeric characters, underscores, and dashes. International characters are allowed. label values are optional. label keys must start with a letter.
/// </value>
public string? Labels
[JsonPropertyName("labels")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public IDictionary<string, string>? Labels
{
get => this._labels;
set
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
Expand Down Expand Up @@ -600,4 +601,34 @@ public async Task GoogleAIChatReturnsResponseWorksWithThinkingBudgetAsync()
Assert.NotNull(streamResponses[0].Content);
Assert.NotNull(responses[0].Content);
}

[RetryTheory(Skip = "This test is for manual verification.")]
[InlineData(ServiceType.VertexAI)] // GoogleAI does not support labels yet
public async Task GoogleAIChatReturnsResponseWorksWithLabelsAsync(ServiceType serviceType)
{
// Arrange
ChatHistory chatHistory = [];
chatHistory.AddUserMessage("Hello, I'm Brandon, how are you?");
chatHistory.AddAssistantMessage("I'm doing well, thanks for asking.");
chatHistory.AddUserMessage("Call me by my name and expand this abbreviation: LLM");

var sut = this.GetChatService(serviceType);

var settings = new GeminiPromptExecutionSettings
{
Labels = new Dictionary<string, string>()
{
["label1"] = "value1",
["label2"] = "value2"
}
};

// Act
var streamResponses = await sut.GetStreamingChatMessageContentsAsync(chatHistory, settings).ToListAsync();
var responses = await sut.GetChatMessageContentsAsync(chatHistory, settings);

// Assert
Assert.NotNull(streamResponses[0].Content);
Assert.NotNull(responses[0].Content);
}
}
Loading