Skip to content

Commit

Permalink
Merge branch 'issues/9989-amazon-warnings' of https://github.com/roge…
Browse files Browse the repository at this point in the history
…rbarreto/semantic-kernel into issues/9989-amazon-warnings
  • Loading branch information
RogerBarreto committed Jan 6, 2025
2 parents 804fae0 + f8b5bba commit 77db4e7
Show file tree
Hide file tree
Showing 20 changed files with 446 additions and 34 deletions.
8 changes: 4 additions & 4 deletions dotnet/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="AWSSDK.BedrockRuntime" Version="3.7.408.1" />
<PackageVersion Include="AWSSDK.BedrockRuntime" Version="3.7.411.5" />
<PackageVersion Include="AWSSDK.Extensions.NETCore.Setup" Version="3.7.301" />
<PackageVersion Include="AWSSDK.Core" Version="3.7.400.48" />
<PackageVersion Include="AWSSDK.Core" Version="3.7.400.64" />
<PackageVersion Include="Azure.AI.Inference" Version="1.0.0-beta.2" />
<PackageVersion Include="Dapr.Actors" Version="1.14.0" />
<PackageVersion Include="Dapr.Actors.AspNetCore" Version="1.14.0" />
Expand Down Expand Up @@ -91,7 +91,7 @@
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="8.0.0" />
<PackageVersion Include="xunit" Version="2.9.2" />
<PackageVersion Include="xunit.abstractions" Version="2.0.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.0" />
<PackageVersion Include="xretry" Version="1.9.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="Docker.DotNet" Version="3.125.15" />
Expand All @@ -106,7 +106,7 @@
<PackageVersion Include="Microsoft.OpenApi" Version="1.6.22" />
<PackageVersion Include="Microsoft.OpenApi.Readers" Version="1.6.22" />
<PackageVersion Include="Microsoft.OpenApi.ApiManifest" Version="0.5.6-preview" />
<PackageVersion Include="Microsoft.Plugins.Manifest" Version="1.0.0-rc2" />
<PackageVersion Include="Microsoft.Plugins.Manifest" Version="1.0.0-rc3" />
<PackageVersion Include="Google.Apis.CustomSearchAPI.v1" Version="1.60.0.3001" />
<PackageVersion Include="Grpc.Net.Client" Version="2.66.0" />
<PackageVersion Include="protobuf-net" Version="3.2.45" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.AzureAIInference;

namespace FunctionCalling;
public class AzureAIInference_FunctionCalling : BaseTest
{
private readonly LoggingHandler _handler;
private readonly HttpClient _httpClient;
private bool _isDisposed;

public AzureAIInference_FunctionCalling(ITestOutputHelper output) : base(output)
{
// Create a logging handler to output HTTP requests and responses
this._handler = new LoggingHandler(new HttpClientHandler(), this.Output);
this._httpClient = new(this._handler);
}

/// <summary>
/// This example demonstrates usage of <see cref="FunctionChoiceBehavior.Auto"/> that advertises all kernel functions to the AI model.
/// </summary>
[Fact]
public async Task FunctionCallingAsync()
{
var kernel = CreateKernel();
AzureAIInferencePromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
Console.WriteLine(await kernel.InvokePromptAsync("Given the current time of day and weather, what is the likely color of the sky in Boston?", new(settings)));
}

/// <summary>
/// This example demonstrates usage of <see cref="FunctionChoiceBehavior.Auto"/> that advertises all kernel functions to the AI model.
/// </summary>
[Fact]
public async Task FunctionCallingWithPromptExecutionSettingsAsync()
{
var kernel = CreateKernel();
PromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
Console.WriteLine(await kernel.InvokePromptAsync("Given the current time of day and weather, what is the likely color of the sky in Boston?", new(settings)));
}

protected override void Dispose(bool disposing)
{
if (!this._isDisposed)
{
if (disposing)
{
this._handler.Dispose();
this._httpClient.Dispose();
}

this._isDisposed = true;
}
base.Dispose(disposing);
}

private Kernel CreateKernel()
{
// Create kernel
var kernel = Kernel.CreateBuilder()
.AddAzureAIInferenceChatCompletion(
modelId: TestConfiguration.AzureAIInference.ChatModelId,
endpoint: new Uri(TestConfiguration.AzureAIInference.Endpoint),
apiKey: TestConfiguration.AzureAIInference.ApiKey,
httpClient: this._httpClient)
.Build();

// Add a plugin with some helper functions we want to allow the model to call.
kernel.ImportPluginFromFunctions("HelperFunctions",
[
kernel.CreateFunctionFromMethod(() => new List<string> { "Squirrel Steals Show", "Dog Wins Lottery" }, "GetLatestNewsTitles", "Retrieves latest news titles."),
kernel.CreateFunctionFromMethod(() => DateTime.UtcNow.ToString("R"), "GetCurrentUtcDateTime", "Retrieves the current date time in UTC."),
kernel.CreateFunctionFromMethod((string cityName, string currentDateTime) =>
cityName switch
{
"Boston" => "61 and rainy",
"London" => "55 and cloudy",
"Miami" => "80 and sunny",
"Paris" => "60 and rainy",
"Tokyo" => "50 and sunny",
"Sydney" => "75 and sunny",
"Tel Aviv" => "80 and sunny",
_ => "31 and snowing",
}, "GetWeatherForCity", "Gets the current weather for the specified city"),
]);

return kernel;
}
}
3 changes: 2 additions & 1 deletion dotnet/samples/Concepts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ dotnet test -l "console;verbosity=detailed" --filter "FullyQualifiedName=ChatCom

### FunctionCalling - Examples on `Function Calling` with function call capable models

- [Gemini_FunctionCalling](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/FunctionCalling/Gemini_FunctionCalling.cs)
- [FunctionCalling](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/FunctionCalling/FunctionCalling.cs)
- [Gemini_FunctionCalling](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/FunctionCalling/Gemini_FunctionCalling.cs)
- [AzureAIInference_FunctionCalling](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/FunctionCalling/AzureAIInference_FunctionCalling.cs)
- [NexusRaven_HuggingFaceTextGeneration](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/FunctionCalling/NexusRaven_FunctionCalling.cs)
- [MultipleFunctionsVsParameters](https://github.com/microsoft/semantic-kernel/blob/main/dotnet/samples/Concepts/FunctionCalling/MultipleFunctionsVsParameters.cs)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public async ValueTask ComputeAsync(KernelProcessStepContext context, string chu
{
Dictionary<string, int> counts = [];

string[] words = chunk.Split([' ', '\n', '\r', '.', ',', '’'], StringSplitOptions.RemoveEmptyEntries);
string[] words = chunk.Split([" ", "\n", "\r", ".", ",", "’"], StringSplitOptions.RemoveEmptyEntries);
foreach (string word in words)
{
if (s_notInteresting.Contains(word))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ private static string ConvertType(Type? type)
return "array";
}

if (type == typeof(DateTime) || type == typeof(DateTimeOffset))
{
return "date-time";
}

return Type.GetTypeCode(type) switch
{
TypeCode.SByte or TypeCode.Byte or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public void TestFunction1() { }
[KernelFunction]
[Description("test description")]
#pragma warning disable IDE0060 // Unused parameter for mock kernel function
public void TestFunction2(string p1, bool p2, int p3, string[] p4, ConsoleColor p5, OpenAIAssistantDefinition p6) { }
public void TestFunction2(string p1, bool p2, int p3, string[] p4, ConsoleColor p5, OpenAIAssistantDefinition p6, DateTime p7) { }
#pragma warning restore IDE0060 // Unused parameter
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ public void ItCreatesAzureAIInferenceExecutionSettingsWithCorrectDefaults()
Assert.Null(executionSettings.ResponseFormat);
Assert.Null(executionSettings.Seed);
Assert.Null(executionSettings.MaxTokens);
Assert.Null(executionSettings.Tools);
Assert.Null(executionSettings.StopSequences);
Assert.Empty(executionSettings.ExtensionData!);
Assert.Empty(executionSettings.Tools);
Assert.Empty(executionSettings.StopSequences!);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public object? ResponseFormat
/// <summary> A collection of textual sequences that will end completions generation. </summary>
[JsonPropertyName("stop")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public IList<string> StopSequences
public IList<string>? StopSequences
{
get => this._stopSequences;
set
Expand All @@ -170,7 +170,7 @@ public IList<string> StopSequences
/// </summary>
[JsonPropertyName("tools")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public IList<ChatCompletionsToolDefinition> Tools
public IList<ChatCompletionsToolDefinition>? Tools
{
get => this._tools;
set
Expand Down Expand Up @@ -229,8 +229,8 @@ public override PromptExecutionSettings Clone()
NucleusSamplingFactor = this.NucleusSamplingFactor,
MaxTokens = this.MaxTokens,
ResponseFormat = this.ResponseFormat,
StopSequences = new List<string>(this.StopSequences),
Tools = new List<ChatCompletionsToolDefinition>(this.Tools),
StopSequences = this.StopSequences is not null ? new List<string>(this.StopSequences) : null,
Tools = this.Tools is not null ? new List<ChatCompletionsToolDefinition>(this.Tools) : null,
Seed = this.Seed,
ExtensionData = this.ExtensionData is not null ? new Dictionary<string, object>(this.ExtensionData) : null,
};
Expand Down Expand Up @@ -273,8 +273,8 @@ public static AzureAIInferencePromptExecutionSettings FromExecutionSettings(Prom
private float? _nucleusSamplingFactor;
private int? _maxTokens;
private object? _responseFormat;
private IList<string> _stopSequences = [];
private IList<ChatCompletionsToolDefinition> _tools = [];
private IList<string>? _stopSequences;
private IList<ChatCompletionsToolDefinition>? _tools;
private long? _seed;

#endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,12 @@ private static List<RestApiParameter> GetParametersFromPayloadMetadata(RestApiOp

if (!property.Properties.Any())
{
// Assign an argument name (sanitized form of the property name) so that the parameter value look-up / resolution functionality in the RestApiOperationRunner
// class can find the value for the parameter by the argument name in the arguments dictionary. If the argument name is not assigned here, the resolution mechanism
// will try to find the parameter value by the parameter's original name. However, because the parameter was advertised with the sanitized name by the RestApiOperationExtensions.GetParameters
// method, no value will be found, and an exception will be thrown: "No argument is found for the '[email protected]' payload property."
property.ArgumentName ??= InvalidSymbolsRegex().Replace(parameterName, "_");

var parameter = new RestApiParameter(
name: parameterName,
type: property.Type,
Expand Down
42 changes: 32 additions & 10 deletions dotnet/src/Functions/Functions.OpenApi/RestApiOperationRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,34 +175,32 @@ public Task<RestApiOperationResponse> RunAsync(

var (Payload, Content) = this._payloadFactory?.Invoke(operation, arguments, this._enableDynamicPayload, this._enablePayloadNamespacing, options) ?? this.BuildOperationPayload(operation, arguments);

return this.SendAsync(url, operation.Method, headers, Payload, Content, operation.Responses.ToDictionary(item => item.Key, item => item.Value.Schema), options, cancellationToken);
return this.SendAsync(operation, url, headers, Payload, Content, options, cancellationToken);
}

#region private

/// <summary>
/// Sends an HTTP request.
/// </summary>
/// <param name="operation">The REST API operation.</param>
/// <param name="url">The url to send request to.</param>
/// <param name="method">The HTTP request method.</param>
/// <param name="headers">Headers to include into the HTTP request.</param>
/// <param name="payload">HTTP request payload.</param>
/// <param name="requestContent">HTTP request content.</param>
/// <param name="expectedSchemas">The dictionary of expected response schemas.</param>
/// <param name="options">Options for REST API operation run.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Response content and content type</returns>
private async Task<RestApiOperationResponse> SendAsync(
RestApiOperation operation,
Uri url,
HttpMethod method,
IDictionary<string, string>? headers = null,
object? payload = null,
HttpContent? requestContent = null,
IDictionary<string, KernelJsonSchema?>? expectedSchemas = null,
RestApiOperationRunOptions? options = null,
CancellationToken cancellationToken = default)
{
using var requestMessage = new HttpRequestMessage(method, url);
using var requestMessage = new HttpRequestMessage(operation.Method, url);

#if NET5_0_OR_GREATER
requestMessage.Options.Set(OpenApiKernelFunctionContext.KernelFunctionContextKey, new OpenApiKernelFunctionContext(options?.Kernel, options?.KernelFunction, options?.KernelArguments));
Expand Down Expand Up @@ -237,9 +235,7 @@ private async Task<RestApiOperationResponse> SendAsync(
{
responseMessage = await this._httpClient.SendWithSuccessCheckAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);

response = await this.ReadContentAndCreateOperationResponseAsync(requestMessage, responseMessage, payload, cancellationToken).ConfigureAwait(false);

response.ExpectedSchema ??= GetExpectedSchema(expectedSchemas, responseMessage.StatusCode);
response = await this.BuildResponseAsync(operation, requestMessage, responseMessage, payload, cancellationToken).ConfigureAwait(false);

return response;
}
Expand Down Expand Up @@ -352,7 +348,15 @@ private async Task<RestApiOperationResponse> ReadContentAndCreateOperationRespon
mediaType = mediaTypeFallback;
}

if (!this._payloadFactoryByMediaType.TryGetValue(mediaType!, out var payloadFactory))
// Remove media type parameters, such as x-api-version, from the "text/plain; x-api-version=2.0" media type string.
mediaType = mediaType!.Split(';').First();

// Normalize the media type to lowercase and remove trailing whitespaces.
#pragma warning disable CA1308 // Normalize strings to uppercase
mediaType = mediaType!.ToLowerInvariant().Trim();
#pragma warning restore CA1308 // Normalize strings to uppercase

if (!this._payloadFactoryByMediaType.TryGetValue(mediaType, out var payloadFactory))
{
throw new KernelException($"The media type {mediaType} of the {operation.Id} operation is not supported by {nameof(RestApiOperationRunner)}.");
}
Expand Down Expand Up @@ -562,5 +566,23 @@ private Uri BuildsOperationUrl(RestApiOperation operation, IDictionary<string, o
return content;
}

/// <summary>
/// Builds the operation response.
/// </summary>
/// <param name="operation">The REST API operation.</param>
/// <param name="requestMessage">The HTTP request message.</param>
/// <param name="responseMessage">The HTTP response message.</param>
/// <param name="payload">The payload sent in the HTTP request.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The operation response.</returns>
private async Task<RestApiOperationResponse> BuildResponseAsync(RestApiOperation operation, HttpRequestMessage requestMessage, HttpResponseMessage responseMessage, object? payload, CancellationToken cancellationToken)
{
var response = await this.ReadContentAndCreateOperationResponseAsync(requestMessage, responseMessage, payload, cancellationToken).ConfigureAwait(false);

response.ExpectedSchema ??= GetExpectedSchema(operation.Responses.ToDictionary(item => item.Key, item => item.Value.Schema), responseMessage.StatusCode);

return response;
}

#endregion
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public async Task ItCanParseMediaTypeAsync(string documentName)

// Assert.
Assert.NotNull(restApi.Operations);
Assert.Equal(7, restApi.Operations.Count);
Assert.Equal(8, restApi.Operations.Count);
var operation = restApi.Operations.Single(o => o.Id == "Joke");
Assert.NotNull(operation);
Assert.Equal("application/json; x-api-version=2.0", operation.Payload?.MediaType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ public async Task ItCanExtractAllPathsAsOperationsAsync()
var restApi = await this._sut.ParseAsync(this._openApiDocument);

// Assert
Assert.Equal(6, restApi.Operations.Count);
Assert.Equal(7, restApi.Operations.Count);
}

[Fact]
Expand Down Expand Up @@ -419,7 +419,7 @@ public async Task ItCanParsePropertiesOfObjectDataTypeAsync()
public async Task ItCanFilterOutSpecifiedOperationsAsync()
{
// Arrange
var operationsToExclude = new[] { "Excuses", "TestDefaultValues", "OpenApiExtensions", "TestParameterDataTypes" };
string[] operationsToExclude = ["Excuses", "TestDefaultValues", "OpenApiExtensions", "TestParameterDataTypes", "TestParameterNamesSanitization"];

var options = new OpenApiDocumentParserOptions
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ public async Task ItCanExtractAllPathsAsOperationsAsync()
var restApi = await this._sut.ParseAsync(this._openApiDocument);

// Assert
Assert.Equal(7, restApi.Operations.Count);
Assert.Equal(8, restApi.Operations.Count);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ public async Task ItCanExtractAllPathsAsOperationsAsync()
var restApi = await this._sut.ParseAsync(this._openApiDocument);

// Assert
Assert.Equal(7, restApi.Operations.Count);
Assert.Equal(8, restApi.Operations.Count);
}

[Fact]
Expand Down
Loading

0 comments on commit 77db4e7

Please sign in to comment.