From 7d47e907de628d9bed0ff98d00254de9e8bf2982 Mon Sep 17 00:00:00 2001 From: Gil LaHaye Date: Tue, 16 Jan 2024 14:44:42 -0800 Subject: [PATCH] =?UTF-8?q?.Net:=20Simplify=20example=2052=20to=20showcase?= =?UTF-8?q?=20using=20a=20custom=20OpenAIClient=20in=20genera=E2=80=A6=20(?= =?UTF-8?q?#4502)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …l instead of using one specifically for APIM ### Motivation and Context Setting up APIM and AAD is tedious and is not directly related to what we want to showcase: that we can customize OpenAIClient instances used with SK. resolves #3844 ### Description Simplify examples and its requirements. ### Contribution Checklist - [ ] The code builds clean without any errors or warnings - [ ] 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 - [ ] All unit tests pass, and I have added new tests where possible - [ ] I didn't break anyone :smile: --------- Co-authored-by: Chris <66376200+crickman@users.noreply.github.com> --- .../Example52_ApimAuth.cs | 99 ------------------- .../Example52_CustomOpenAIClient.cs | 58 +++++++++++ dotnet/samples/KernelSyntaxExamples/README.md | 7 -- 3 files changed, 58 insertions(+), 106 deletions(-) delete mode 100644 dotnet/samples/KernelSyntaxExamples/Example52_ApimAuth.cs create mode 100644 dotnet/samples/KernelSyntaxExamples/Example52_CustomOpenAIClient.cs diff --git a/dotnet/samples/KernelSyntaxExamples/Example52_ApimAuth.cs b/dotnet/samples/KernelSyntaxExamples/Example52_ApimAuth.cs deleted file mode 100644 index 6ac99961479e..000000000000 --- a/dotnet/samples/KernelSyntaxExamples/Example52_ApimAuth.cs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.IO; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Azure.AI.OpenAI; -using Azure.Core; -using Azure.Core.Pipeline; -using Azure.Identity; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.SemanticKernel; -using RepoUtils; -using Xunit.Abstractions; - -namespace Examples; - -public class Example52_ApimAuth : BaseTest -{ - public async Task RunAsync() - { - // Azure API Management details - // For more information see 'Protect your Azure OpenAI API keys with Azure API Management' here: https://learn.microsoft.com/en-us/semantic-kernel/deploy/ - var apimUri = new Uri(Env.Var("Apim__Endpoint")); - var subscriptionKey = Env.Var("Apim__SubscriptionKey"); - - // Use interactive browser login - string[] scopes = new string[] { "https://cognitiveservices.azure.com/.default" }; - var credential = new InteractiveBrowserCredential(); - var requestContext = new TokenRequestContext(scopes); - var accessToken = await credential.GetTokenAsync(requestContext); - - // Create HttpClient and include subscription key as a default header - var httpClient = new HttpClient(); - httpClient.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", subscriptionKey); - - // Configure OpenAIClient to use - // - Custom HttpClient with subscription key header - // - Diagnostics to log error response headers from APIM to aid problem determination - // - Authentication using BearerTokenCredential retrieved via interactive browser login - var clientOptions = new OpenAIClientOptions - { - Transport = new HttpClientTransport(httpClient), - Diagnostics = - { - LoggedHeaderNames = { "ErrorSource", "ErrorReason", "ErrorMessage", "ErrorScope", "ErrorSection", "ErrorStatusCode" }, - } - }; - var openAIClient = new OpenAIClient(apimUri, new BearerTokenCredential(accessToken), clientOptions); - - IKernelBuilder builder = Kernel.CreateBuilder(); - builder.Services.AddLogging(c => c.SetMinimumLevel(LogLevel.Warning).AddConsole()); - builder.AddAzureOpenAIChatCompletion( - deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName, - openAIClient: openAIClient); - Kernel kernel = builder.Build(); - - // Load semantic plugin defined with prompt templates - string folder = RepoFiles.SamplePluginsPath(); - - kernel.ImportPluginFromPromptDirectory(Path.Combine(folder, "FunPlugin")); - - // Run - var result = await kernel.InvokeAsync( - kernel.Plugins["FunPlugin"]["Excuses"], - new() { ["input"] = "I have no homework" } - ); - Console.WriteLine(result.GetValue()); - - httpClient.Dispose(); - } - - public Example52_ApimAuth(ITestOutputHelper output) : base(output) - { - } -} - -public class BearerTokenCredential : TokenCredential -{ - private readonly AccessToken _accessToken; - - // Constructor that takes a Bearer token string and its expiration date - public BearerTokenCredential(AccessToken accessToken) - { - this._accessToken = accessToken; - } - - public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) - { - return this._accessToken; - } - - public override ValueTask GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) - { - return new ValueTask(this._accessToken); - } -} diff --git a/dotnet/samples/KernelSyntaxExamples/Example52_CustomOpenAIClient.cs b/dotnet/samples/KernelSyntaxExamples/Example52_CustomOpenAIClient.cs new file mode 100644 index 000000000000..5512db6de839 --- /dev/null +++ b/dotnet/samples/KernelSyntaxExamples/Example52_CustomOpenAIClient.cs @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; +using Azure; +using Azure.AI.OpenAI; +using Azure.Core.Pipeline; +using Microsoft.SemanticKernel; +using RepoUtils; + +public static class Example52_CustomOpenAIClient +{ + public static async Task RunAsync() + { + Console.WriteLine("======== Using a custom OpenAI client ========"); + + string endpoint = TestConfiguration.AzureOpenAI.Endpoint; + string deploymentName = TestConfiguration.AzureOpenAI.ChatDeploymentName; + string apiKey = TestConfiguration.AzureOpenAI.ApiKey; + + if (endpoint is null || deploymentName is null || apiKey is null) + { + Console.WriteLine("Azure OpenAI credentials not found. Skipping example."); + return; + } + + // Create an HttpClient and include your custom header(s) + var httpClient = new HttpClient(); + httpClient.DefaultRequestHeaders.Add("x-my-custom-header", "My custom value"); + + // Configure OpenAIClient to use the customized HttpClient + var clientOptions = new OpenAIClientOptions + { + Transport = new HttpClientTransport(httpClient), + }; + var openAIClient = new OpenAIClient(new Uri(endpoint), new AzureKeyCredential(apiKey), clientOptions); + + IKernelBuilder builder = Kernel.CreateBuilder(); + builder.AddAzureOpenAIChatCompletion(deploymentName, openAIClient); + Kernel kernel = builder.Build(); + + // Load semantic plugin defined with prompt templates + string folder = RepoFiles.SamplePluginsPath(); + + kernel.ImportPluginFromPromptDirectory(Path.Combine(folder, "FunPlugin")); + + // Run + var result = await kernel.InvokeAsync( + kernel.Plugins["FunPlugin"]["Excuses"], + new() { ["input"] = "I have no homework" } + ); + Console.WriteLine(result.GetValue()); + + httpClient.Dispose(); + } +} diff --git a/dotnet/samples/KernelSyntaxExamples/README.md b/dotnet/samples/KernelSyntaxExamples/README.md index 4360c4b9b840..8c8996efa323 100644 --- a/dotnet/samples/KernelSyntaxExamples/README.md +++ b/dotnet/samples/KernelSyntaxExamples/README.md @@ -103,9 +103,6 @@ dotnet user-secrets set "Google:SearchEngineId" "..." dotnet user-secrets set "Github:PAT" "github_pat_..." -dotnet user-secrets set "Apim:Endpoint" "https://apim...azure-api.net/" -dotnet user-secrets set "Apim:SubscriptionKey" "..." - dotnet user-secrets set "Postgres:ConnectionString" "..." dotnet user-secrets set "Redis:Configuration" "..." dotnet user-secrets set "Kusto:ConnectionString" "..." @@ -173,10 +170,6 @@ Google__SearchEngineId # Github Github__PAT -# Azure API Management (APIM) -Apim__Endpoint -Apim__SubscriptionKey - # Other Postgres__ConnectionString Redis__Configuration