diff --git a/dotnet/SK-dotnet.sln b/dotnet/SK-dotnet.sln index bde158c3c1ac..1d7a9caf756d 100644 --- a/dotnet/SK-dotnet.sln +++ b/dotnet/SK-dotnet.sln @@ -150,6 +150,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Planning.StepwisePlanner", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationInsightsExample", "samples\ApplicationInsightsExample\ApplicationInsightsExample.csproj", "{C754950A-E16C-4F96-9CC7-9328E361B5AF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlayFabExamples", "samples\PlayFabExamples\PlayFabExamples.csproj", "{948F37AA-A437-4AFF-93C9-03C47A886B5D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -361,6 +363,12 @@ Global {C754950A-E16C-4F96-9CC7-9328E361B5AF}.Publish|Any CPU.ActiveCfg = Release|Any CPU {C754950A-E16C-4F96-9CC7-9328E361B5AF}.Release|Any CPU.ActiveCfg = Release|Any CPU {C754950A-E16C-4F96-9CC7-9328E361B5AF}.Release|Any CPU.Build.0 = Release|Any CPU + {948F37AA-A437-4AFF-93C9-03C47A886B5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {948F37AA-A437-4AFF-93C9-03C47A886B5D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {948F37AA-A437-4AFF-93C9-03C47A886B5D}.Publish|Any CPU.ActiveCfg = Debug|Any CPU + {948F37AA-A437-4AFF-93C9-03C47A886B5D}.Publish|Any CPU.Build.0 = Debug|Any CPU + {948F37AA-A437-4AFF-93C9-03C47A886B5D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {948F37AA-A437-4AFF-93C9-03C47A886B5D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -413,6 +421,7 @@ Global {677F1381-7830-4115-9C1A-58B282629DC6} = {0247C2C9-86C3-45BA-8873-28B0948EDC0C} {4762BCAF-E1C5-4714-B88D-E50FA333C50E} = {078F96B4-09E1-4E0E-B214-F71A4F4BF633} {C754950A-E16C-4F96-9CC7-9328E361B5AF} = {FA3720F1-C99A-49B2-9577-A940257098BF} + {948F37AA-A437-4AFF-93C9-03C47A886B5D} = {FA3720F1-C99A-49B2-9577-A940257098BF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FBDC56A3-86AD-4323-AA0F-201E59123B83} diff --git a/dotnet/samples/KernelSyntaxExamples/Program.cs b/dotnet/samples/KernelSyntaxExamples/Program.cs index 673da2c84acd..4e2ea73ef816 100644 --- a/dotnet/samples/KernelSyntaxExamples/Program.cs +++ b/dotnet/samples/KernelSyntaxExamples/Program.cs @@ -19,11 +19,6 @@ public static async Task Main() using CancellationTokenSource cancellationTokenSource = new(); CancellationToken cancelToken = cancellationTokenSource.ConsoleCancellationToken(); - // Run PlayFab Examples - await Example00_01_PlayFabDataQnA.RunAsync().SafeWaitAsync(cancelToken); - await Example00_02_PlayFabGenerative.RunAsync().SafeWaitAsync(cancelToken); - await Example_00_03_OpenApiSkill_PlayFab.RunAsync().SafeWaitAsync(cancelToken); - // Run examples await Example01_NativeFunctions.RunAsync().SafeWaitAsync(cancelToken); await Example02_Pipeline.RunAsync().SafeWaitAsync(cancelToken); diff --git a/dotnet/samples/PlayFabExamples/Common/Configuration/ConfigurationNotFoundException.cs b/dotnet/samples/PlayFabExamples/Common/Configuration/ConfigurationNotFoundException.cs new file mode 100644 index 000000000000..4de877dad55a --- /dev/null +++ b/dotnet/samples/PlayFabExamples/Common/Configuration/ConfigurationNotFoundException.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace PlayFabExamples.Common.Configuration; +public sealed class ConfigurationNotFoundException : Exception +{ + public string? Section { get; } + public string? Key { get; } + + public ConfigurationNotFoundException(string section, string key) + : base($"Configuration key '{section}:{key}' not found") + { + this.Section = section; + this.Key = key; + } + + public ConfigurationNotFoundException(string section) + : base($"Configuration section '{section}' not found") + { + this.Section = section; + } + + public ConfigurationNotFoundException() : base() + { + } + + public ConfigurationNotFoundException(string? message, Exception? innerException) : base(message, innerException) + { + } +} diff --git a/dotnet/samples/PlayFabExamples/Common/Configuration/Env.cs b/dotnet/samples/PlayFabExamples/Common/Configuration/Env.cs new file mode 100644 index 000000000000..5bf07044b76d --- /dev/null +++ b/dotnet/samples/PlayFabExamples/Common/Configuration/Env.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Extensions.Configuration; + +namespace PlayFabExamples.Common.Configuration; + +#pragma warning disable CA1812 // instantiated by AddUserSecrets +internal sealed class Env +#pragma warning restore CA1812 +{ + /// + /// Simple helper used to load env vars and secrets like credentials, + /// to avoid hard coding them in the sample code + /// + /// Secret name / Env var name + /// Value found in Secret Manager or Environment Variable + internal static string Var(string name) + { + var configuration = new ConfigurationBuilder() + .AddUserSecrets() + .Build(); + + var value = configuration[name]; + if (!string.IsNullOrEmpty(value)) + { + return value; + } + + value = Environment.GetEnvironmentVariable(name); + if (string.IsNullOrEmpty(value)) + { + throw new ConfigurationNotFoundException($"Secret / Env var not set: {name}"); + } + + return value; + } +} diff --git a/dotnet/samples/PlayFabExamples/Common/Configuration/TestConfiguration.cs b/dotnet/samples/PlayFabExamples/Common/Configuration/TestConfiguration.cs new file mode 100644 index 000000000000..75c9fd314e3d --- /dev/null +++ b/dotnet/samples/PlayFabExamples/Common/Configuration/TestConfiguration.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Runtime.CompilerServices; +using Microsoft.Extensions.Configuration; + +namespace PlayFabExamples.Common.Configuration; +public sealed class TestConfiguration +{ + private IConfigurationRoot _configRoot; + private static TestConfiguration? s_instance; + + private TestConfiguration(IConfigurationRoot configRoot) + { + this._configRoot = configRoot; + } + + public static void Initialize(IConfigurationRoot configRoot) + { + s_instance = new TestConfiguration(configRoot); + } + + public static AzureOpenAIConfig AzureOpenAI => LoadSection(); + + public static BingConfig Bing => LoadSection(); + public static PlayFabConfig PlayFab => LoadSection(); + + private static T LoadSection([CallerMemberName] string? caller = null) + { + if (s_instance == null) + { + throw new InvalidOperationException( + "TestConfiguration must be initialized with a call to Initialize(IConfigurationRoot) before accessing configuration values."); + } + + if (string.IsNullOrEmpty(caller)) + { + throw new ArgumentNullException(nameof(caller)); + } + return s_instance._configRoot.GetSection(caller).Get() ?? + throw new ConfigurationNotFoundException(section: caller); + } + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. + + public class AzureOpenAIConfig + { + public string ServiceId { get; set; } + public string DeploymentName { get; set; } + public string ChatDeploymentName { get; set; } + public string Endpoint { get; set; } + public string ApiKey { get; set; } + } + + public class BingConfig + { + public string ApiKey { get; set; } + } + + public class PlayFabConfig + { + public string Endpoint { get; set; } + public string TitleId { get; set; } + public string TitleSecretKey { get; set; } + public string SwaggerEndpoint { get; set; } + public string ReportsCosmosDBEndpoint { get; set; } + public string ReportsCosmosDBKey { get; set; } + public string ReportsCosmosDBDatabaseName { get; set; } + public string ReportsCosmosDBContainerName { get; set; } + } + +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. +} diff --git a/dotnet/samples/PlayFabExamples/Common/Logging/ConsoleLogger.cs b/dotnet/samples/PlayFabExamples/Common/Logging/ConsoleLogger.cs new file mode 100644 index 000000000000..ca47e2a71750 --- /dev/null +++ b/dotnet/samples/PlayFabExamples/Common/Logging/ConsoleLogger.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Extensions.Logging; + +namespace PlayFabExamples.Common.Logging; +/// +/// Basic logger printing to console +/// +internal static class ConsoleLogger +{ + internal static ILogger Logger => LogFactory.CreateLogger(); + + private static ILoggerFactory LogFactory => s_loggerFactory.Value; + + private static readonly Lazy s_loggerFactory = new(LogBuilder); + + private static ILoggerFactory LogBuilder() + { + return LoggerFactory.Create(builder => + { + builder.SetMinimumLevel(LogLevel.Warning); + + // builder.AddFilter("Microsoft", LogLevel.Trace); + // builder.AddFilter("Microsoft", LogLevel.Debug); + // builder.AddFilter("Microsoft", LogLevel.Information); + // builder.AddFilter("Microsoft", LogLevel.Warning); + // builder.AddFilter("Microsoft", LogLevel.Error); + + builder.AddFilter("Microsoft", LogLevel.Warning); + builder.AddFilter("System", LogLevel.Warning); + + builder.AddConsole(); + }); + } +} diff --git a/dotnet/samples/KernelSyntaxExamples/Example00_01_PlayFabDataQnA.cs b/dotnet/samples/PlayFabExamples/Example01_DataQnA/Example01_DataQnA.cs similarity index 99% rename from dotnet/samples/KernelSyntaxExamples/Example00_01_PlayFabDataQnA.cs rename to dotnet/samples/PlayFabExamples/Example01_DataQnA/Example01_DataQnA.cs index ec5416ce6ee4..26bbc41b394d 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example00_01_PlayFabDataQnA.cs +++ b/dotnet/samples/PlayFabExamples/Example01_DataQnA/Example01_DataQnA.cs @@ -1,10 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. -using System; using System.ComponentModel; using System.Diagnostics; using System.Text; -using System.Threading.Tasks; using Azure.AI.OpenAI; using Azure; using Microsoft.SemanticKernel; @@ -13,14 +11,13 @@ using Microsoft.SemanticKernel.Planning; using Microsoft.SemanticKernel.Reliability; using Microsoft.SemanticKernel.SkillDefinition; -using RepoUtils; using Microsoft.Azure.Cosmos; -using System.Threading; -using System.Collections.Generic; using Newtonsoft.Json.Linq; -using System.Linq; using Newtonsoft.Json; -using System.Text.Json.Serialization; +using PlayFabExamples.Common.Configuration; +using PlayFabExamples.Common.Logging; + +namespace PlayFabExamples.Example01_DataQnA; public enum PlannerType { @@ -30,7 +27,7 @@ public enum PlannerType } // ReSharper disable once InconsistentNaming -public static partial class Example00_01_PlayFabDataQnA +public static partial class Example01_DataQnA { public static Dictionary AllTitleReports = null; diff --git a/dotnet/samples/KernelSyntaxExamples/Example00_02_PlayFabGenerative.cs b/dotnet/samples/PlayFabExamples/Example02_Generative/Example02_Generative.cs similarity index 90% rename from dotnet/samples/KernelSyntaxExamples/Example00_02_PlayFabGenerative.cs rename to dotnet/samples/PlayFabExamples/Example02_Generative/Example02_Generative.cs index c4e7dfa0bfeb..f685f9ba535f 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example00_02_PlayFabGenerative.cs +++ b/dotnet/samples/PlayFabExamples/Example02_Generative/Example02_Generative.cs @@ -1,14 +1,13 @@ // Copyright (c) Microsoft. All rights reserved. -using System; -using System.Threading.Tasks; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Orchestration; using Microsoft.SemanticKernel.Planning; -using Microsoft.SemanticKernel.Skills.Core; -using RepoUtils; +using PlayFabExamples.Common.Configuration; +using PlayFabExamples.Common.Logging; -public static class Example00_02_PlayFabGenerative +namespace PlayFabExamples.Example02_Generative; +public static class Example02_Generative { public static async Task RunAsync() { @@ -42,7 +41,7 @@ private static async Task CreateSegmentExample(string goal) Console.WriteLine("======== Action Planner ========"); var kernel = new KernelBuilder() .WithLogger(ConsoleLogger.Logger) - .WithAzureTextCompletionService(TestConfiguration.AzureOpenAITextDavinci.DeploymentName, TestConfiguration.AzureOpenAITextDavinci.Endpoint, TestConfiguration.AzureOpenAITextDavinci.ApiKey) + .WithAzureTextCompletionService(TestConfiguration.AzureOpenAI.DeploymentName, TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey) .Build(); kernel.ImportSkill(new SegmentSkill(), "SegmentSkill"); diff --git a/dotnet/samples/KernelSyntaxExamples/Skills/SegmentSkill.cs b/dotnet/samples/PlayFabExamples/Example02_Generative/SegmentSkill.cs similarity index 96% rename from dotnet/samples/KernelSyntaxExamples/Skills/SegmentSkill.cs rename to dotnet/samples/PlayFabExamples/Example02_Generative/SegmentSkill.cs index 37e8f4b84960..d9771cac2b1b 100644 --- a/dotnet/samples/KernelSyntaxExamples/Skills/SegmentSkill.cs +++ b/dotnet/samples/PlayFabExamples/Example02_Generative/SegmentSkill.cs @@ -1,19 +1,16 @@ // Copyright (c) Microsoft. All rights reserved. -using System; -using System.Collections.Generic; -using System.Collections.Specialized; using System.ComponentModel; -using System.Net.Http; -using System.Threading.Tasks; +using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Orchestration; using Microsoft.SemanticKernel.SkillDefinition; using Microsoft.SemanticKernel.Skills.OpenAPI.Authentication; using Microsoft.SemanticKernel.Skills.OpenAPI.Extensions; using Newtonsoft.Json; -using RepoUtils; +using PlayFabExamples.Common.Configuration; +using PlayFabExamples.Common.Logging; -namespace Microsoft.SemanticKernel.Skills.Core; +namespace PlayFabExamples.Example02_Generative; /// /// Create a segment with given information. diff --git a/dotnet/samples/KernelSyntaxExamples/Example00_03_OpenApiSkill_PlayFab.cs b/dotnet/samples/PlayFabExamples/Example03_SegmentQuery/Example03_SegmentQuery.cs similarity index 94% rename from dotnet/samples/KernelSyntaxExamples/Example00_03_OpenApiSkill_PlayFab.cs rename to dotnet/samples/PlayFabExamples/Example03_SegmentQuery/Example03_SegmentQuery.cs index c26194bb2410..01510b2e3b52 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example00_03_OpenApiSkill_PlayFab.cs +++ b/dotnet/samples/PlayFabExamples/Example03_SegmentQuery/Example03_SegmentQuery.cs @@ -1,22 +1,21 @@ // Copyright (c) Microsoft. All rights reserved. -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading.Tasks; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Orchestration; using Microsoft.SemanticKernel.SkillDefinition; using Microsoft.SemanticKernel.Skills.OpenAPI.Authentication; using Microsoft.SemanticKernel.Skills.OpenAPI.Extensions; using Newtonsoft.Json; -using RepoUtils; +using PlayFabExamples.Common.Configuration; +using PlayFabExamples.Common.Logging; + +namespace PlayFabExamples.Example03_SegmentQuery; /// /// This example shows how to import PlayFab APIs as skills. /// // ReSharper disable once InconsistentNaming -public static class Example_00_03_OpenApiSkill_PlayFab +public static class Example03_SegmentQuery { public static async Task RunAsync() { diff --git a/dotnet/samples/PlayFabExamples/PlayFabExamples.csproj b/dotnet/samples/PlayFabExamples/PlayFabExamples.csproj new file mode 100644 index 000000000000..832c84ed7de9 --- /dev/null +++ b/dotnet/samples/PlayFabExamples/PlayFabExamples.csproj @@ -0,0 +1,37 @@ + + + + Exe + net7.0 + 11 + enable + enable + 4447f5a3-3bc4-4697-a289-b87c718828fc + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dotnet/samples/PlayFabExamples/Program.cs b/dotnet/samples/PlayFabExamples/Program.cs new file mode 100644 index 000000000000..01ce283c24b2 --- /dev/null +++ b/dotnet/samples/PlayFabExamples/Program.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Extensions.Configuration; +using PlayFabExamples.Common.Configuration; + +namespace PlayFabExamples; + +public static class Program +{ + public static async Task Main(string[] args) + { + // Load configuration from environment variables or user secrets. + LoadUserSecrets(); + + // Execution canceled if the user presses Ctrl+C. + using CancellationTokenSource cancellationTokenSource = new(); + CancellationToken cancelToken = cancellationTokenSource.ConsoleCancellationToken(); + + // Run PlayFab Examples + await PlayFabExamples.Example01_DataQnA.Example01_DataQnA.RunAsync().SafeWaitAsync(cancelToken); + await PlayFabExamples.Example02_Generative.Example02_Generative.RunAsync().SafeWaitAsync(cancelToken); + await PlayFabExamples.Example03_SegmentQuery.Example03_SegmentQuery.RunAsync().SafeWaitAsync(cancelToken); + } + + private static void LoadUserSecrets() + { + IConfigurationRoot configRoot = new ConfigurationBuilder() + .AddEnvironmentVariables() + .AddUserSecrets() + .Build(); + TestConfiguration.Initialize(configRoot); + } + + private static CancellationToken ConsoleCancellationToken(this CancellationTokenSource tokenSource) + { + Console.CancelKeyPress += (s, e) => + { + Console.WriteLine("Canceling..."); + tokenSource.Cancel(); + e.Cancel = true; + }; + + return tokenSource.Token; + } + + private static async Task SafeWaitAsync(this Task task, + CancellationToken cancellationToken = default) + { + try + { + await task.WaitAsync(cancellationToken); + Console.WriteLine("== DONE =="); + } + catch (ConfigurationNotFoundException ex) + { + Console.WriteLine($"{ex.Message}. Skipping example."); + } + + cancellationToken.ThrowIfCancellationRequested(); + } +}