├── .gitignore ├── LICENSE ├── README.md ├── notebooks ├── 1_PropertyGraphRAG.ipynb ├── 2_PropertyGraphRAG.ipynb ├── data │ └── summaries.txt └── images │ └── summaries-entities-relations.jpg ├── samples └── demos │ ├── BostonAzure-June2024 │ ├── MvpWebApp │ │ ├── HttpClientHandlers.cs │ │ ├── MvpWebApp.csproj │ │ ├── Program.cs │ │ ├── Prompts │ │ │ └── RAG │ │ │ │ ├── config.json │ │ │ │ └── skprompt.txt │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── appsettings.json │ │ └── transcript.txt │ ├── MvpWebAppClient │ │ ├── MvpWebAppClient.csproj │ │ └── Program.cs │ ├── README.md │ └── demo-script.md │ ├── PropertyGraphRAG │ ├── PropertyGraph.Common │ │ ├── Configuration │ │ │ ├── Configuration.cs │ │ │ ├── Neo4jOptions.cs │ │ │ ├── OpenAIOptions.cs │ │ │ ├── PropertyGraphOptions.cs │ │ │ └── ServiceCollectionExtensions.cs │ │ ├── CypherStatements.cs │ │ ├── DataObjects.cs │ │ ├── Extensions.cs │ │ ├── IAppOptions.cs │ │ ├── KeywordExtractor.cs │ │ ├── Models │ │ │ └── Records.cs │ │ ├── Neo4jService.cs │ │ ├── Prompts │ │ │ ├── ExtractEntities │ │ │ │ ├── config.json │ │ │ │ └── skprompt.txt │ │ │ ├── ExtractKeywords │ │ │ │ ├── config.json │ │ │ │ └── skprompt.txt │ │ │ ├── RequestWithContext │ │ │ │ ├── config.json │ │ │ │ └── skprompt.txt │ │ │ └── RewriteQuery │ │ │ │ ├── config.json │ │ │ │ └── skprompt.txt │ │ ├── PropertyGraph.Common.csproj │ │ ├── PropertyGraphRetriever.cs │ │ ├── TripletsExtractor.cs │ │ └── Utilities.cs │ ├── PropertyGraphIngestor │ │ ├── CommandOptions.cs │ │ ├── Data │ │ │ └── summaries.txt │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── PropertyGraphIngestor.csproj │ │ └── appsettings.json │ └── PropertyGraphRAG │ │ ├── Program.cs │ │ ├── PropertyGraphRAG.csproj │ │ └── appsettings.json │ ├── QueryRewrite │ ├── Configuration │ │ ├── Configuration.cs │ │ ├── OpenAIOptions.cs │ │ └── ServiceCollectionExtensions.cs │ ├── Plugins │ │ └── BlogInfoPlugin.cs │ ├── Program.cs │ ├── QueryRewrite.csproj │ └── appsettings.json │ └── Text-to-Sql │ ├── TextToSql.Console │ ├── Configuration │ │ ├── Configuration.cs │ │ ├── OpenAIOptions.cs │ │ ├── PluginConfig.cs │ │ ├── ServiceCollectionExtensions.cs │ │ ├── SqlSchemaOptions.cs │ │ └── TextToSqlOptions.cs │ ├── Nl2Sql │ │ ├── SqlCommandExecutor.cs │ │ ├── SqlConnectionProvider.cs │ │ ├── SqlScemaLoader.cs │ │ └── SqlSchemaProvider.cs │ ├── Program.cs │ ├── Prompts │ │ ├── DescribeResults │ │ │ ├── config.json │ │ │ └── skprompt.txt │ │ ├── EvaluateIntent │ │ │ ├── config.json │ │ │ └── skprompt.txt │ │ ├── EvaluateResult │ │ │ ├── config.json │ │ │ └── skprompt.txt │ │ └── SqlGenerate │ │ │ ├── config.json │ │ │ └── skprompt.txt │ ├── TextToSql.Console.csproj │ └── appsettings.json │ └── nl2sql.library │ ├── Internal │ └── FunctionResultExtensions.cs │ ├── Nl2Sql.Library.csproj │ ├── Schema │ ├── ISchemaFormatter.cs │ ├── ISchemaFormatterExtensions.cs │ ├── SchemaColumn.cs │ ├── SchemaDefinition.cs │ ├── SchemaProvider.cs │ ├── SchemaSerializer.cs │ ├── SchemaTable.cs │ └── YamlSchemaFormatter.cs │ ├── SqlQueryGenerator.cs │ └── SqlQueryResult.cs └── src ├── .editorconfig ├── HelloWorld.Agent1.Console ├── BingConnector2.cs ├── Configuration │ ├── Configuration.cs │ ├── OpenAIOptions.cs │ ├── PluginConfig.cs │ └── ServiceCollectionExtensions.cs ├── HelloWorld.Agent1.Console.csproj ├── Program.cs └── appsettings.json ├── HelloWorld.Console ├── Configuration │ ├── Configuration.cs │ ├── OpenAIOptions.cs │ └── ServiceCollectionExtensions.cs ├── HelloWorld.Console.csproj ├── Program.cs └── appsettings.json ├── HelloWorld.Planner1.Console ├── Configuration │ ├── Configuration.cs │ ├── OpenAIOptions.cs │ └── ServiceCollectionExtensions.cs ├── Extensions │ └── HandlebarsPlannerExtensions.cs ├── HelloWorld.Planner1.Console.csproj ├── Plugins │ └── DailyFactPlugin.cs ├── Program.cs └── appsettings.json ├── HelloWorld.Planner2.Console ├── Configuration │ ├── Configuration.cs │ ├── OpenAIOptions.cs │ └── ServiceCollectionExtensions.cs ├── HelloWorld.Planner2.Console.csproj ├── Plugins │ └── DailyFactPlugin.cs ├── Program.cs └── appsettings.json ├── HelloWorld.Plugin1.Console ├── Configuration │ ├── Configuration.cs │ ├── OpenAIOptions.cs │ └── ServiceCollectionExtensions.cs ├── HelloWorld.Plugin1.Console.csproj ├── Program.cs ├── Prompts │ └── DailyFact │ │ ├── config.json │ │ └── skprompt.txt └── appsettings.json ├── HelloWorld.Plugin2.Console ├── Configuration │ ├── Configuration.cs │ ├── OpenAIOptions.cs │ └── ServiceCollectionExtensions.cs ├── HelloWorld.Plugin2.Console.csproj ├── Plugins │ └── DailyFactPlugin.cs ├── Program.cs └── appsettings.json ├── HelloWorld.Plugin3.Console ├── Configuration │ ├── Configuration.cs │ ├── OpenAIOptions.cs │ └── ServiceCollectionExtensions.cs ├── HelloWorld.Plugin3.Console.csproj ├── Plugins │ └── DailyFactPlugin.cs ├── Program.cs └── appsettings.json ├── HelloWorld.WebSearchEnginePlugin.Console ├── Configuration │ ├── Configuration.cs │ ├── OpenAIOptions.cs │ ├── PluginConfig.cs │ └── ServiceCollectionExtensions.cs ├── HelloWorld.WebSearchEnginePlugin.Console.csproj ├── Program.cs └── appsettings.json └── HelloWorld.sln /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Jason Haley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Semantic Kernel 2 | 3 | This repository contains the source code for serveral blog posts and presentations I have given in 2024. Most are available in the [semantic-kernel listing on my blog](https://jasonhaley.com/tags/semantic-kernel/) 4 | 5 | ## Blog Entries 6 | I have a couple of blog series that include Semantic Kernel. 7 | 8 | ### Semantic Kernel Hello World (Blog Series) 9 | This series is about individual features of Semantic Kernel. The source code is supposed to be reusable source code for future projects. 10 | 11 | #### [Semantic Kernel Hello World](https://jasonhaley.com/2024/03/30/semantic-kernel-hello-world/) 12 | (*March 30, 2024*) This is a very simple walk through of using Semantic Kernel to call an OpenAI chat completion endpoint. 13 | 14 | #### [Semantic Kernel Hello World Plugins Part 1](https://jasonhaley.com/2024/04/11/semantic-kernel-hello-world-plugin-part1/) 15 | (*April 11, 2024*) This walks through creating prompts in text files and using them with Semantic Kernel. 16 | 17 | #### [Semantic Kernel Hello World Plugins Part 2](https://jasonhaley.com/2024/04/26/semantic-kernel-hello-world-plugin-part2/) 18 | (*April 26, 2024*) This is about Native Functions in Semantic Kernel and converts the previous prompt into a native function. 19 | 20 | #### [Semantic Kernel Hello World Plugins Part 3](https://jasonhaley.com/2024/04/30/semantic-kernel-hello-world-plugin-part3/) 21 | (*April 30, 2024*) This builds on the native function entry and extends it to use Open AI function calling. 22 | 23 | #### [Semantic Kernel Hello World Planners Part 1](https://jasonhaley.com/2024/05/19/semantic-kernel-hello-world-planners-part1/) 24 | (*May 19, 2024*) This moves the same sample to using the Handlebars planner and analyzes what it does. 25 | 26 | #### [Semantic Kernel Hello World Planners Part 2](https://jasonhaley.com/2024/05/27/semantic-kernel-hello-world-planners-part2/) 27 | (*May 27, 2024*) This takes the same approach as the previous blog entry but uses the Function Calling Stepwise Planner. 28 | 29 | #### [Semantic Kernel Hello World WebSearchEnginePlugin](https://jasonhaley.com/2024/06/10/semantic-kernel-hello-world-websearchengineplugin/) 30 | (*June 10, 2024*) This takes a look at the usefulness of incorporating the ability to have the LLM call Bing search. 31 | 32 | ### Study Notes (Blog Series) 33 | This series is where I am putting resource created from specific topics like text-to-sql and graph RAG. 34 | 35 | #### [Study Notes: Text-to-SQL](https://jasonhaley.com/2024/07/05/study-notes-text-to-sql/) 36 | (*July 5, 2024*) This is the post describing what I learned focusing on Text-to-Sql for a week. 37 | 38 | #### [Study Notes: Text-to-SQL Code Sample](https://jasonhaley.com/2024/07/06/study-notes-text-to-sql-code-sample/) 39 | (*July 6, 2024*) This is the post about code that I put together to demonstrate how Text-to-Sql works. 40 | 41 | #### [Study Notes: Graph RAG - Property Graph RAG](https://jasonhaley.com/2024/08/05/study-notes-graph-rag1/) 42 | (*August 5, 2024*) This is the post describing what I learned focusing on graph RAG (specifically property graph RAG). 43 | 44 | #### [Study Notes: Graph RAG - Property Graph RAG (The Notebook)](https://jasonhaley.com/2024/08/06/study-notes-graph-rag1-code-sample-notebook/) 45 | (*August 6, 2024*) This is about the C# notebook I put together to highlight the steps and demonstrate property graph RAG. 46 | 47 | #### [Study Notes: Graph RAG - Property Graph RAG (The Projects)](https://jasonhaley.com/2024/08/16/study-notes-graph-rag1-code-sample-projects/) 48 | (*August 16, 2024*) This is about the C# projects I created for the ingestion and retrieval functionality of a property graph RAG system. 49 | 50 | ## Presentations 51 | 52 | #### [Boston Azure June 2024](https://jasonhaley.com/2024/06/25/boston-azure-june-2024/) 53 | (*June 25, 2024*) This highlights the demo code I used in the in person (no recording) Boston Azure meeting in June. 54 | -------------------------------------------------------------------------------- /notebooks/images/summaries-entities-relations.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonHaley/semantic-kernel-getting-started/c0f1f7266205a41a85f905561c5427b198a096f2/notebooks/images/summaries-entities-relations.jpg -------------------------------------------------------------------------------- /samples/demos/BostonAzure-June2024/MvpWebApp/HttpClientHandlers.cs: -------------------------------------------------------------------------------- 1 |  2 | // Found most of this implementation via: https://github.com/microsoft/semantic-kernel/issues/5107 3 | public class RequestAndResponseLoggingHttpClientHandler : HttpClientHandler 4 | { 5 | protected override async Task SendAsync( 6 | HttpRequestMessage request, CancellationToken cancellationToken) 7 | { 8 | string requestContent; 9 | if (request.Content is not null) 10 | { 11 | requestContent = await request.Content.ReadAsStringAsync(cancellationToken); 12 | System.Console.WriteLine("***********************************************"); 13 | System.Console.WriteLine("Request:"); 14 | System.Console.WriteLine(requestContent); 15 | System.Console.WriteLine("***********************************************"); 16 | } 17 | 18 | HttpResponseMessage result = null; 19 | string content = null; 20 | try 21 | { 22 | result = await base.SendAsync(request, cancellationToken); 23 | 24 | // Comment out since it is streaming and shows tokens as they come back - needs to rollback the stream or clone it 25 | // if (result.Content is not null) 26 | // { 27 | // content = await result.Content.ReadAsStringAsync(cancellationToken); 28 | // System.Console.WriteLine("***********************************************"); 29 | // System.Console.WriteLine("Response:"); 30 | // System.Console.WriteLine(content); 31 | // System.Console.WriteLine("***********************************************"); 32 | //} 33 | } 34 | catch (Exception ex) 35 | { 36 | System.Console.WriteLine($"Exception: {ex.Message}"); 37 | } 38 | 39 | return result; 40 | } 41 | } 42 | public class RequestLoggingHttpClientHandler : HttpClientHandler 43 | { 44 | protected override async Task SendAsync( 45 | HttpRequestMessage request, CancellationToken cancellationToken) 46 | { 47 | if (request.Content is not null) 48 | { 49 | var content = await request.Content.ReadAsStringAsync(cancellationToken); 50 | System.Console.WriteLine("***********************************************"); 51 | System.Console.WriteLine("Request:"); 52 | System.Console.WriteLine(content); 53 | System.Console.WriteLine("***********************************************"); 54 | } 55 | 56 | return await base.SendAsync(request, cancellationToken); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /samples/demos/BostonAzure-June2024/MvpWebApp/MvpWebApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | enable 6 | $(NoWarn);SKEXP0001;SKEXP0010;SKEXP0050;SKEXP0130; 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | PreserveNewest 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /samples/demos/BostonAzure-June2024/MvpWebApp/Program.cs: -------------------------------------------------------------------------------- 1 | var builder = WebApplication.CreateBuilder(args); 2 | var app = builder.Build(); 3 | 4 | app.MapGet("/copilot", (string question) => 5 | { 6 | return GetResponseAsync(question); 7 | }); 8 | 9 | app.Run(); 10 | 11 | static async IAsyncEnumerable GetResponseAsync(string question) 12 | { 13 | 14 | foreach (string word in question.Split(' ')) 15 | 16 | { 17 | 18 | await Task.Delay(250); 19 | 20 | yield return word + " "; 21 | 22 | } 23 | } -------------------------------------------------------------------------------- /samples/demos/BostonAzure-June2024/MvpWebApp/Prompts/RAG/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema": 1, 3 | "description": "Provides prompt with a question and context variables for RAG.", 4 | "execution_settings": { 5 | "default": { 6 | "max_tokens": 2000, 7 | "temperature": 0.3 8 | } 9 | }, 10 | "input_variables": [ 11 | { 12 | "name": "question", 13 | "description": "User's question'", 14 | "required": true 15 | }, 16 | { 17 | "name": "context", 18 | "description": "Context to user for answering the question", 19 | "required": true 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /samples/demos/BostonAzure-June2024/MvpWebApp/Prompts/RAG/skprompt.txt: -------------------------------------------------------------------------------- 1 | Please answer the question with only the context provided. 2 | ## Question: 3 | {{$question}} 4 | ## Context: 5 | {{$context}} 6 | -------------------------------------------------------------------------------- /samples/demos/BostonAzure-June2024/MvpWebApp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "http": { 4 | "commandName": "Project", 5 | "launchUrl": "copilot", 6 | "environmentVariables": { 7 | "ASPNETCORE_ENVIRONMENT": "Development" 8 | }, 9 | "dotnetRunMessages": true, 10 | "applicationUrl": "http://localhost:5171", 11 | "hotReloadEnabled": true 12 | } 13 | }, 14 | "$schema": "http://json.schemastore.org/launchsettings.json" 15 | } -------------------------------------------------------------------------------- /samples/demos/BostonAzure-June2024/MvpWebApp/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Trace" 5 | } 6 | }, 7 | "AllowedHosts": "*", 8 | "AI": { 9 | "OpenAI": { 10 | "APIKey": "" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/demos/BostonAzure-June2024/MvpWebAppClient/MvpWebAppClient.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /samples/demos/BostonAzure-June2024/MvpWebAppClient/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http.Json; 2 | 3 | using HttpClient hc = new() { BaseAddress = new("http://localhost:5171") }; 4 | while (true) 5 | { 6 | Console.WriteLine(""); 7 | Console.Write("Question: "); 8 | 9 | await foreach (var msg in hc.GetFromJsonAsAsyncEnumerable($"/copilot?question={Console.ReadLine()}")) 10 | Console.Write(msg); 11 | 12 | Console.WriteLine(); 13 | } -------------------------------------------------------------------------------- /samples/demos/BostonAzure-June2024/README.md: -------------------------------------------------------------------------------- 1 | # .NET AI Demo with Semantic Kernel 2 | 3 | This demo was started from a repo written by [James Montemagno](https://github.com/jamesmontemagno/AIDemo/blob/master/readme.md) 4 | 5 | My updated [demo-script](demo-script.md) walks through the stepts I took for my demo to discuss how to use Semantic Kernel to: 6 | 7 | * Connect to OpenAI 8 | * Chunk a text file into smaller pieces 9 | * Setup an in memory vector store and configure the embedding service 10 | * Add addition logging to see the network traffic 11 | * Search the vector store 12 | * Build a prompt to perform RAG and call Open AI 13 | 14 | 15 | For additional references 16 | * [Build your first AI Chat Bot with OPenAI and .NET in Minute](https://www.youtube.com/watch?v=NNPI4QQ8LhE) 17 | * [AI for .NET Developers documentation](https://learn.microsoft.com/dotnet/ai/) 18 | * [.NET AI Samples](https://github.com/dotnet/ai-samples) 19 | * [RAG with Azure and OpenAI Sample](https://github.com/Azure-Samples/azure-search-openai-demo-csharp) 20 | * [Semantic Kernel](https://learn.microsoft.com/semantic-kernel/overview/) 21 | 22 | 23 | ## Configuration 24 | 25 | 1. Create a developer account on [OpenAI](https://platform.openai.com/) and enable GPT-4 access (you may need to deposit $5) 26 | 2. Create a developer key and update **MvpWebApp/appsettings.json** 27 | 3. Open in Visual Studio or Visual Studio Code and enable multi project deployment to run both apps at the same time 28 | 4. The backend will run and a console window will be where we do our entry to call the API 29 | -------------------------------------------------------------------------------- /samples/demos/BostonAzure-June2024/demo-script.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonHaley/semantic-kernel-getting-started/c0f1f7266205a41a85f905561c5427b198a096f2/samples/demos/BostonAzure-June2024/demo-script.md -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraph.Common/Configuration/Configuration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace PropertyGraph.Common; 4 | 5 | public class Configuration 6 | { 7 | public static IConfigurationRoot ConfigureAppSettings() 8 | { 9 | var config = new ConfigurationBuilder() 10 | .SetBasePath(Directory.GetCurrentDirectory()) 11 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 12 | .AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true) 13 | .AddJsonFile("appsettings.Production.json", optional: true, reloadOnChange: true) 14 | #if DEBUG 15 | .AddJsonFile(GetUserJsonFilename(), optional: true, reloadOnChange: true) 16 | #endif 17 | .Build(); 18 | return config; 19 | } 20 | 21 | static string GetUserJsonFilename() 22 | { 23 | return $"appsettings.user_{Environment.UserName.ToLower()}.json"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraph.Common/Configuration/Neo4jOptions.cs: -------------------------------------------------------------------------------- 1 | namespace PropertyGraph.Common; 2 | 3 | public class Neo4jOptions 4 | { 5 | public const string Neo4j = "Neo4j"; 6 | 7 | public string User { get; set; } = string.Empty; 8 | public string Password { get; set; } = string.Empty; 9 | public string URI { get; set; } = string.Empty; 10 | } 11 | -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraph.Common/Configuration/OpenAIOptions.cs: -------------------------------------------------------------------------------- 1 | namespace PropertyGraph.Common; 2 | 3 | public class OpenAIOptions 4 | { 5 | public const string OpenAI = "OpenAI"; 6 | 7 | public string Source { get; set; } = string.Empty; 8 | public string ChatModelId { get; set; } = string.Empty; 9 | public string TextEmbeddingsModelId { get; set; } = string.Empty; 10 | public string ApiKey { get; set; } = string.Empty; 11 | public string ChatDeploymentName { get; set; } = string.Empty; 12 | public string TextEmbeddingsDeploymentName { get; set; } = string.Empty; 13 | public string Endpoint { get; set; } = string.Empty; 14 | public string Resource { get; set; } = string.Empty; // Used by Neo4j 15 | } 16 | -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraph.Common/Configuration/PropertyGraphOptions.cs: -------------------------------------------------------------------------------- 1 | namespace PropertyGraph.Common; 2 | 3 | public class PropertyGraphOptions 4 | { 5 | public const string PropertyGraph = "PropertyGraph"; 6 | 7 | public bool UseTokenSplitter { get; set; } = true; 8 | public int? ChunkSize { get; set; } 9 | public int? Overlap { get; set; } 10 | 11 | public string? EntityTypes { get; set; } 12 | public string? RelationshipTypes { get; set; } 13 | public string? EntityExtractonTemplatePreamble { get; set; } 14 | public string? DocumentChunkTypeLabel { get; set; } 15 | public int? MaxTripletsPerChunk { get; set; } 16 | 17 | public bool IncludeEntityTextSearch { get; set; } = true; 18 | public bool UseKeywords { get; set; } = true; 19 | public string TypeEntityTextOfSearch { get; set; } = "FULL_TEXT"; // "VECTOR" 20 | public bool IncludeTriplets { get; set; } = true; 21 | public int MaxTriplets { get; set; } = 30; 22 | public bool IncludeRelatedChunks { get; set; } = true; 23 | public int MaxRelatedChunks { get; set; } = 3; 24 | 25 | public bool IncludeChunkVectorSearch { get; set; } = true; 26 | public int MaxChunks { get; set; } = 5; 27 | } 28 | -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraph.Common/Configuration/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.SemanticKernel; 3 | using System.Text.Json; 4 | 5 | namespace PropertyGraph.Common; 6 | 7 | public static class ServiceCollectionExtensions 8 | { 9 | public static IServiceCollection AddChatCompletionService(this IServiceCollection serviceCollection, OpenAIOptions openAIOptions) 10 | { 11 | switch (openAIOptions.Source) 12 | { 13 | case "AzureOpenAI": 14 | serviceCollection = serviceCollection.AddAzureOpenAIChatCompletion(openAIOptions.ChatDeploymentName, endpoint: openAIOptions.Endpoint, 15 | apiKey: openAIOptions.ApiKey, serviceId: openAIOptions.ChatModelId); 16 | break; 17 | 18 | case "OpenAI": 19 | serviceCollection = serviceCollection.AddOpenAIChatCompletion(modelId: openAIOptions.ChatModelId, apiKey: openAIOptions.ApiKey); 20 | break; 21 | 22 | default: 23 | throw new ArgumentException($"Invalid source: {openAIOptions.Source}"); 24 | } 25 | 26 | return serviceCollection; 27 | } 28 | } 29 | public enum ApiLoggingLevel 30 | { 31 | None = 0, 32 | RequestOnly = 1, 33 | ResponseAndRequest = 2, 34 | } 35 | 36 | public static class IKernelBuilderExtensions 37 | { 38 | public static IKernelBuilder AddChatCompletionService(this IKernelBuilder kernelBuilder, OpenAIOptions openAIOptions, ApiLoggingLevel apiLoggingLevel = ApiLoggingLevel.None) 39 | { 40 | switch (openAIOptions.Source) 41 | { 42 | case "AzureOpenAI": 43 | { 44 | if (apiLoggingLevel == ApiLoggingLevel.None) 45 | { 46 | kernelBuilder = kernelBuilder.AddAzureOpenAIChatCompletion(openAIOptions.ChatDeploymentName, endpoint: openAIOptions.Endpoint, 47 | apiKey: openAIOptions.ApiKey, serviceId: openAIOptions.ChatModelId); 48 | } 49 | else 50 | { 51 | var client = CreateHttpClient(apiLoggingLevel); 52 | kernelBuilder.AddAzureOpenAIChatCompletion(openAIOptions.ChatDeploymentName, openAIOptions.Endpoint, openAIOptions.ApiKey, null, null, client); 53 | } 54 | break; 55 | } 56 | case "OpenAI": 57 | { 58 | if (apiLoggingLevel == ApiLoggingLevel.None) 59 | { 60 | kernelBuilder = kernelBuilder.AddOpenAIChatCompletion(modelId: openAIOptions.ChatModelId, apiKey: openAIOptions.ApiKey); 61 | break; 62 | } 63 | else 64 | { 65 | var client = CreateHttpClient(apiLoggingLevel); 66 | kernelBuilder.AddOpenAIChatCompletion(openAIOptions.ChatModelId, openAIOptions.ApiKey, null, null, client); 67 | } 68 | break; 69 | } 70 | default: 71 | throw new ArgumentException($"Invalid source: {openAIOptions.Source}"); 72 | } 73 | 74 | return kernelBuilder; 75 | } 76 | 77 | public static HttpClient CreateHttpClient(ApiLoggingLevel apiLoggingLevel) 78 | { 79 | HttpClientHandler httpClientHandler; 80 | if (apiLoggingLevel == ApiLoggingLevel.RequestOnly) 81 | { 82 | httpClientHandler = new RequestLoggingHttpClientHandler(); 83 | } 84 | else 85 | { 86 | httpClientHandler = new RequestAndResponseLoggingHttpClientHandler(); 87 | } 88 | var client = new HttpClient(httpClientHandler); 89 | return client; 90 | } 91 | } 92 | 93 | // Found most of this implementation via: https://github.com/microsoft/semantic-kernel/issues/5107 94 | public class RequestAndResponseLoggingHttpClientHandler : HttpClientHandler 95 | { 96 | protected override async Task SendAsync( 97 | HttpRequestMessage request, CancellationToken cancellationToken) 98 | { 99 | Console.WriteLine("***********************************************"); 100 | Console.WriteLine($"Request: {request.Method} {request.RequestUri}"); 101 | if (request.Content is not null) 102 | { 103 | var content = await request.Content.ReadAsStringAsync(cancellationToken); 104 | var json = JsonSerializer.Serialize(JsonSerializer.Deserialize(content), 105 | new JsonSerializerOptions { WriteIndented = true }); 106 | Console.WriteLine(json); 107 | } 108 | 109 | var result = await base.SendAsync(request, cancellationToken); 110 | 111 | if (result.Content is not null) 112 | { 113 | var content = await result.Content.ReadAsStringAsync(cancellationToken); 114 | //var json = JsonSerializer.Serialize(JsonSerializer.Deserialize(content), 115 | // new JsonSerializerOptions { WriteIndented = true }); 116 | Console.WriteLine("***********************************************"); 117 | Console.WriteLine("Response:"); 118 | //Console.WriteLine(json); 119 | Console.WriteLine(content); 120 | } 121 | 122 | return result; 123 | } 124 | } 125 | public class RequestLoggingHttpClientHandler : HttpClientHandler 126 | { 127 | protected override async Task SendAsync( 128 | HttpRequestMessage request, CancellationToken cancellationToken) 129 | { 130 | Console.WriteLine("***********************************************"); 131 | Console.WriteLine($"Request: {request.Method} {request.RequestUri}"); 132 | if (request.Content is not null) 133 | { 134 | var content = await request.Content.ReadAsStringAsync(cancellationToken); 135 | var json = JsonSerializer.Serialize(JsonSerializer.Deserialize(content), 136 | new JsonSerializerOptions { WriteIndented = true }); 137 | Console.WriteLine(json); 138 | } 139 | 140 | return await base.SendAsync(request, cancellationToken); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraph.Common/DataObjects.cs: -------------------------------------------------------------------------------- 1 | namespace PropertyGraph.Common; 2 | public record DocumentMetadata(string id, string source); 3 | public record ChunkMetadata(string id, string name, int sequence, string documentId, string text); 4 | public record TripletRow(string head, string head_type, string relation, string tail, string tail_type); 5 | public class EntityMetadata 6 | { 7 | public string name { get; set; } = string.Empty; 8 | public string type { get; set; } = string.Empty; 9 | public string id { get; set; } = string.Empty; 10 | public string text { get; set; } = string.Empty; 11 | public Dictionary mentionedInChunks { get; set; } = new Dictionary(); 12 | } 13 | 14 | public static class Defaults 15 | { 16 | public const int CHUNK_SIZE = 500; 17 | public const int OVERLAP = 100; 18 | public const string ENTITY_TYPES = "BLOG_POST,BOOK,MOVIE,PRESENTATION,EVENT,ORGANIZATION,PERSON,PLACE,PRODUCT,REVIEW,ACTION"; 19 | public const string RELATION_TYPES = "INTRODUCED,USED_FOR,WRITTEN_IN,PART_OF,LOCATED_IN,GIVEN,LIVES_IN,TRAVELED_TO"; 20 | public const string DOCUMENT_CHUNK_TYPE = "DOCUMENT_CHUNK"; 21 | public const int MAX_TRIPLETS_PER_CHUNK = 10; 22 | public const int MAX_KEYWORDS = 10; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraph.Common/Extensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.SemanticKernel; 2 | using Microsoft.SemanticKernel.ChatCompletion; 3 | using System.Text; 4 | using System.Text.Json; 5 | 6 | namespace PropertyGraph.Common; 7 | 8 | public static class StringExtensions 9 | { 10 | public static string CleanJsonText(this string jsonText) 11 | { 12 | return jsonText.Replace("```json", "").Replace("```", "").Replace("'", "").Trim(); 13 | } 14 | } 15 | 16 | public static class FunctionResultExtentions 17 | { 18 | public static string ToCleanString(this FunctionResult? result) 19 | { 20 | if (result == null) 21 | { 22 | return ""; 23 | } 24 | 25 | string jsonText = result.ToString(); 26 | return jsonText.CleanJsonText(); 27 | } 28 | 29 | public static T? As(this FunctionResult? result) 30 | { 31 | if (result == null) 32 | { 33 | return default; 34 | } 35 | 36 | return JsonSerializer.Deserialize(result.ToCleanString()); 37 | } 38 | } 39 | 40 | public static class KernelExtensions 41 | { 42 | public static async Task InvokePromptAsync(this Kernel kernel, KernelFunction plugin, KernelArguments kernelArguments) 43 | { 44 | var result = await kernel.InvokeAsync(plugin, kernelArguments); 45 | 46 | return result.As(); 47 | } 48 | } 49 | 50 | public static class ChatHistoryExtensions 51 | { 52 | 53 | public static async IAsyncEnumerable AddStreamingMessageAsync(this ChatHistory chatHistory, IAsyncEnumerable streamingMessageContents) 54 | { 55 | List messageContents = []; 56 | 57 | StringBuilder? contentBuilder = null; 58 | 59 | await foreach (var chatMessage in streamingMessageContents.ConfigureAwait(false)) 60 | { 61 | if (chatMessage.ToString() is { Length: > 0 } contentUpdate) 62 | { 63 | (contentBuilder ??= new()).Append(contentUpdate); 64 | } 65 | 66 | messageContents.Add(chatMessage); 67 | yield return chatMessage; 68 | } 69 | 70 | if (messageContents.Count != 0) 71 | { 72 | chatHistory.AddAssistantMessage(contentBuilder?.ToString() ?? string.Empty); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraph.Common/IAppOptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using Microsoft.SemanticKernel; 3 | 4 | namespace PropertyGraph.Common; 5 | 6 | public interface IAppOptions 7 | { 8 | Kernel Kernel { get; } 9 | OpenAIOptions OpenAI { get; } 10 | Neo4jOptions Neo4j { get; } 11 | PropertyGraphOptions PropertyGraph { get; } 12 | ILoggerFactory LoggerFactory { get; } 13 | } 14 | 15 | public record class DefaultOptions( 16 | Kernel Kernel, 17 | OpenAIOptions OpenAI, 18 | Neo4jOptions Neo4j, 19 | PropertyGraphOptions PropertyGraph, 20 | ILoggerFactory LoggerFactory) : IAppOptions; -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraph.Common/KeywordExtractor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using Microsoft.SemanticKernel; 3 | 4 | namespace PropertyGraph.Common; 5 | 6 | public class KeywordExtractor 7 | { 8 | private readonly IAppOptions _options; 9 | private readonly ILogger _logger; 10 | 11 | public KeywordExtractor(IAppOptions options) 12 | { 13 | _options = options; 14 | _logger = _options.LoggerFactory.CreateLogger(nameof(KeywordExtractor)); 15 | } 16 | 17 | public async Task> ExtractAsync(string text) 18 | { 19 | var prompts = _options.Kernel.CreatePluginFromPromptDirectory("Prompts"); 20 | 21 | var result = await _options.Kernel.InvokeAsync( 22 | prompts["ExtractKeywords"], 23 | new() { 24 | { "questionText", text }, 25 | }); 26 | 27 | _logger.LogTrace(result.ToString()); 28 | 29 | return result.ToString().Split("~"); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraph.Common/Models/Records.cs: -------------------------------------------------------------------------------- 1 | namespace PropertyGraph.Common.Models; 2 | 3 | public record TripletWithChunk(string triplet, string chunk, double score); -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraph.Common/Prompts/ExtractEntities/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema": 1, 3 | "description": "Extracts entities and relationships from text.", 4 | "execution_settings": { 5 | "default": { 6 | "max_tokens": 2500, 7 | "temperature": 0.1 8 | } 9 | }, 10 | "input_variables": [ 11 | { 12 | "name": "maxTripletsPerChunk", 13 | "description": "Maximum number of triplets to extract.", 14 | "required": true 15 | }, 16 | { 17 | "name": "entityTypes", 18 | "description": "Types of entities to start with.", 19 | "required": true 20 | }, 21 | { 22 | "name": "relationTypes", 23 | "description": "Types of relations to start with.", 24 | "required": true 25 | }, 26 | { 27 | "name": "text", 28 | "description": "Text to extract the entities and relationships from.", 29 | "required": true 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraph.Common/Prompts/ExtractEntities/skprompt.txt: -------------------------------------------------------------------------------- 1 | Please extract up to {{$maxTripletsPerChunk}} knowledge triplets from the provied text. 2 | {{$preamble}} 3 | Each triplet should be in the form of (head, relation, tail) with their respective types. 4 | ###################### 5 | ONTOLOGY: 6 | Entity Types: {{$entityTypes}} 7 | Relation Types: {{$relationTypes}} 8 | 9 | Use these entity types and relation types as a starting point, introduce new types if necessary based on the context. 10 | 11 | GUIDELINES: 12 | - Output in JSON format: [{""head"": """", ""head_type"": """", ""relation"": """", ""tail"": """", ""tail_type"": """"}] 13 | - Use the full form for entities (ie., 'Artificial Intelligence' instead of 'AI') 14 | - Keep entities and relation names concise (3-5 words max) 15 | - Break down complex phrases into multiple triplets 16 | - Ensure the knowledge graph is coherent and easily understandable 17 | ###################### 18 | EXAMPLE: 19 | Text: Jason Haley, chief engineer of Jason Haley Consulting, wrote a new blog post titled 'Study Notes: GraphRAG - Property Grids' about creating a property grid RAG system using Semantic Kernel. 20 | Output: 21 | [{""head"": ""Jason Haley"", ""head_type"": ""PERSON"", ""relation"": ""WORKS_FOR"", ""tail"": ""Jason Haley Consulting"", ""tail_type"": ""COMPANY""}, 22 | {""head"": ""Study Notes: GraphRAG - Property Grids"", ""head_type"": ""BLOG_POST"", ""relation"": ""WRITTEN_BY"", ""tail"": ""Jason Haley"", ""tail_type"": ""PERSON""}, 23 | {""head"": ""Study Notes: GraphRAG - Property Grids"", ""head_type"": ""BLOG_POST"", ""relation"": ""TOPIC"", ""tail"": ""Semantic Kernel"", ""tail_type"": ""TECHNOLOGY""}, 24 | {""head"": ""property grid RAG system"", ""head_type"": ""SOFTWARE_SYSTEM"", ""relation"": ""USES"", ""tail"": ""Semantic Kernel"", ""tail_type"": ""TECHNOLOGY""}] 25 | ###################### 26 | Text: {{$text}} 27 | ###################### 28 | Output: -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraph.Common/Prompts/ExtractKeywords/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema": 1, 3 | "description": "Extracts keywords and synonyms from text.", 4 | "execution_settings": { 5 | "default": { 6 | "max_tokens": 2500, 7 | "temperature": 0.1 8 | } 9 | }, 10 | "input_variables": [ 11 | { 12 | "name": "questionText", 13 | "description": "Text to extract keywords and synonyms from.", 14 | "required": true 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraph.Common/Prompts/ExtractKeywords/skprompt.txt: -------------------------------------------------------------------------------- 1 | Given a user question, pick or use 1 to 3 words to create a keyword to capture what the user is asking for'. 2 | 3 | QUERY: {{$questionText}} 4 | ###################### 5 | KEYWORDS: -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraph.Common/Prompts/RequestWithContext/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema": 1, 3 | "description": "Makes a request providing context for the LLM to use in answering the request.", 4 | "execution_settings": { 5 | "default": { 6 | "max_tokens": 2500, 7 | "temperature": 0.0 8 | } 9 | }, 10 | "input_variables": [ 11 | { 12 | "name": "context", 13 | "description": "The context to use to answer the question.", 14 | "required": true 15 | }, 16 | { 17 | "name": "questionText", 18 | "description": "User's question to answer.", 19 | "required": true 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraph.Common/Prompts/RequestWithContext/skprompt.txt: -------------------------------------------------------------------------------- 1 | Answer ONLY with the facts listed in the list of sources below. If there isn't enough information below, say you don't know. Do not generate answers that don't use the sources below. If asking a clarifying question to the user would help, ask the question. 2 | For tabular information return it as an html table. Do not return markdown format. If the question is not in English, answer in the language used in the question. 3 | 4 | To plan the response, begin by examining the Neo4j entity relations and their structured data to determine if the answer is present within. Follow these steps: 5 | 6 | 1. Analyze the provided Neo4j entity relations and their structured data: 7 | 2. Look at the nodes, relationships, and properties in the graph. 8 | 3. Identify the entities and their connections relevant to the question. 9 | 4. Identify relevant information: 10 | 5. Extract data points and relationships that are pertinent to the question. 11 | 6. Consider how these relationships influence the answer. 12 | 7. Explain how you got the answer in a section that starts with ""Explaination:" 13 | 8. Synthesize the identified information: 14 | 15 | Combine the extracted information logically. 16 | Formulate a coherent and comprehensive response. 17 | Here are some examples to guide the process: 18 | 19 | ###################### 20 | Example: 21 | (Semantic Kernel)-[:TOPIC]->(Blog Post Title 1) 22 | (Semantic Kernel)-[:HAS_TOPIC]->(Blog Post Title 2) 23 | (Semantic Kernel)-[:INCLUDES_TOPIC]->(Blog Post Title 3) 24 | (Jason)-[:PRESENTED]->(Presentation about Semantic Kernel) 25 | 26 | Question: 27 | What blog posts are about Semantic Kernel? 28 | 29 | Explaination: 30 | I used the notes that indicated they had a topic of Semantic Kernel to determine when blog posts were pertinent. 31 | 32 | Answer: 33 | Blog Post Title 1, Blog Post Title 2 and Blog Post Title 3 are about Semantic Kernel. 34 | 35 | ###################### 36 | Answer the question based solely on the following context: 37 | {{$context}} 38 | 39 | ###################### 40 | Question: {{$questionText}} 41 | ###################### 42 | Answer: -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraph.Common/Prompts/RewriteQuery/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema": 1, 3 | "description": "Has the LLM rewrite the original query.", 4 | "execution_settings": { 5 | "default": { 6 | "max_tokens": 2500, 7 | "temperature": 0.0 8 | } 9 | }, 10 | "input_variables": [ 11 | { 12 | "name": "questionText", 13 | "description": "User's question to answer.", 14 | "required": true 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraph.Common/Prompts/RewriteQuery/skprompt.txt: -------------------------------------------------------------------------------- 1 | Below is a history of the conversation so far, and a new question asked by the user that needs to be answered by searching a retriever system. 2 | Generate a search query based on the conversation and the new question. 3 | If you cannot generate a search query, return the original user question. 4 | DO NOT return anything besides the query. 5 | 6 | What does Jason blog about?"What does Jason blog about?"What is the name of his blog?What is the name of Jason's blog?Does he blog often?Does Jason blog often{{$questionText}} -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraph.Common/PropertyGraph.Common.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | SKEXP0050 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Always 24 | 25 | 26 | Always 27 | 28 | 29 | Always 30 | 31 | 32 | Always 33 | 34 | 35 | Always 36 | 37 | 38 | Always 39 | 40 | 41 | Always 42 | 43 | 44 | Always 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraph.Common/Utilities.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | using System.Text; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace PropertyGraph.Common; 6 | 7 | public class Utilities 8 | { 9 | public static string CreateId(string text) 10 | { 11 | using (SHA1 sha1 = SHA1.Create()) 12 | { 13 | byte[] inputBytes = Encoding.UTF8.GetBytes(text); 14 | byte[] hashBytes = sha1.ComputeHash(inputBytes); 15 | StringBuilder sb = new StringBuilder(); 16 | for (int i = 0; i < hashBytes.Length; i++) 17 | { 18 | sb.Append(hashBytes[i].ToString("X2").ToLower()); 19 | } 20 | return sb.ToString(); 21 | } 22 | } 23 | public static EntityMetadata PopulateEntityMetadata(ChunkMetadata chunkMetadata, TripletRow triplet, EntityMetadata entityMetadata, bool isHead = true) 24 | { 25 | if (isHead) 26 | { 27 | entityMetadata.name = CreateName(triplet.head); 28 | entityMetadata.type = triplet.head_type; 29 | entityMetadata.text = triplet.head; 30 | } 31 | else 32 | { 33 | entityMetadata.name = CreateName(triplet.tail); 34 | entityMetadata.type = triplet.tail_type; 35 | entityMetadata.text = triplet.tail; 36 | } 37 | 38 | entityMetadata.id = CreateId(entityMetadata.name); 39 | entityMetadata.mentionedInChunks.Add(chunkMetadata.id, chunkMetadata); 40 | 41 | return entityMetadata; 42 | } 43 | 44 | public static string CreateName(string text) 45 | { 46 | if (string.IsNullOrEmpty(text)) 47 | return text; 48 | 49 | // Split the text into words 50 | string[] words = text.Split(new[] { ' ', '-', '_' }, StringSplitOptions.RemoveEmptyEntries); 51 | 52 | StringBuilder nameText = new StringBuilder(); 53 | 54 | foreach (string word in words) 55 | { 56 | // Capitalize the first letter and make the rest lowercase 57 | var lword = word; 58 | if (char.IsDigit(word[0])) 59 | { 60 | lword = "_" + word; 61 | } 62 | 63 | nameText.Append(lword.ToLower()); 64 | } 65 | var textOnly = Regex.Replace(nameText.ToString(), "[^a-zA-Z0-9_]", ""); 66 | if (char.IsDigit(textOnly[0])) 67 | { 68 | textOnly = "_" + textOnly; 69 | } 70 | return textOnly; 71 | } 72 | 73 | public static List SplitPlainTextOnEmptyLine(string[] lines) 74 | { 75 | List allLines = new List(lines); 76 | List result = new List(); 77 | 78 | // Make sure there is an empty string as last line to split into paragraph 79 | var last = allLines.Last(); 80 | if (last.Length > 0) 81 | { 82 | allLines.Add(""); 83 | } 84 | 85 | StringBuilder paragraphBuilder = new StringBuilder(); 86 | foreach (string input in allLines) 87 | { 88 | if (input.Length == 0) 89 | { 90 | result.Add(paragraphBuilder.ToString()); 91 | paragraphBuilder.Clear(); 92 | } 93 | paragraphBuilder.Append($"{input} "); 94 | } 95 | 96 | return result; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraphIngestor/CommandOptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using Microsoft.SemanticKernel; 3 | using PropertyGraph.Common; 4 | using System.CommandLine; 5 | using System.CommandLine.Invocation; 6 | 7 | internal static class CommandOptions 8 | { 9 | internal static readonly Argument Files = new(name: "-f", description: "Files or directory to be processed"); 10 | 11 | internal static readonly Option Verbose = new(name: "-v", description: "Writes out verbose logging to console."); 12 | 13 | internal static readonly Option Remove = new(name: "-r", description: "Remove all nodes and relations for a specified file."); 14 | 15 | internal static readonly Option RemoveAll = new(name: "-ra", description: "Removes all nodes and relations in the database."); 16 | 17 | internal static readonly RootCommand RootCommand = new(description: """ 18 | Prepare documents by extracting knowledge graph triplets ( -> -> ) using OpenAI and 19 | inserting the entities and relations into Neo4j. 20 | """) 21 | { 22 | Files, 23 | Verbose, 24 | Remove, 25 | RemoveAll, 26 | }; 27 | internal static AppOptions GetParsedAppOptions(InvocationContext context, Kernel kernel, OpenAIOptions openAIOptions, Neo4jOptions neo4JOptions, PropertyGraphOptions propertyGraph, ILoggerFactory loggerFactory) => new( 28 | Files: context.ParseResult.GetValueForArgument(Files), 29 | Remove: context.ParseResult.GetValueForOption(Remove), 30 | RemoveAll: context.ParseResult.GetValueForOption(RemoveAll), 31 | Verbose: context.ParseResult.GetValueForOption(Verbose), 32 | Console: context.Console, 33 | Kernel: kernel, 34 | OpenAI: openAIOptions, 35 | Neo4j: neo4JOptions, 36 | PropertyGraph : propertyGraph, 37 | LoggerFactory: loggerFactory ); 38 | 39 | internal record class AppOptions( 40 | string Files, 41 | bool Verbose, 42 | bool Remove, 43 | bool RemoveAll, 44 | IConsole Console, 45 | Kernel Kernel, 46 | OpenAIOptions OpenAI, 47 | Neo4jOptions Neo4j, 48 | PropertyGraphOptions PropertyGraph, 49 | ILoggerFactory LoggerFactory) : AppConsole(Console), IAppOptions; 50 | 51 | internal record class AppConsole(IConsole Console); 52 | } 53 | -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraphIngestor/Data/summaries.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JasonHaley/semantic-kernel-getting-started/c0f1f7266205a41a85f905561c5427b198a096f2/samples/demos/PropertyGraphRAG/PropertyGraphIngestor/Data/summaries.txt -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraphIngestor/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.FileSystemGlobbing.Abstractions; 3 | using Microsoft.Extensions.FileSystemGlobbing; 4 | using Microsoft.Extensions.Logging; 5 | using System.CommandLine; 6 | using Microsoft.SemanticKernel; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using PropertyGraph.Common; 9 | using static CommandOptions; 10 | 11 | internal class Program 12 | { 13 | static void Main(string[] args) 14 | { 15 | MainAsync(args).Wait(); 16 | } 17 | 18 | static async Task MainAsync(string[] args) 19 | { 20 | var config = Configuration.ConfigureAppSettings(); 21 | 22 | // Get Settings (all this is just so I don't have hard coded config settings here) 23 | var openAiSettings = new OpenAIOptions(); 24 | config.GetSection(OpenAIOptions.OpenAI).Bind(openAiSettings); 25 | 26 | var neo4jSettings = new Neo4jOptions(); 27 | config.GetSection(Neo4jOptions.Neo4j).Bind(neo4jSettings); 28 | 29 | var propertyGraphSettings = new PropertyGraphOptions(); 30 | config.GetSection(PropertyGraphOptions.PropertyGraph).Bind(propertyGraphSettings); 31 | 32 | using var loggerFactory = LoggerFactory.Create(builder => 33 | { 34 | builder.SetMinimumLevel(LogLevel.Trace); 35 | 36 | builder.AddConfiguration(config); 37 | builder.AddConsole(); 38 | }); 39 | 40 | // Configure Semantic Kernel 41 | var builder = Kernel.CreateBuilder(); 42 | 43 | builder.Services.AddSingleton(loggerFactory); 44 | builder.Services.AddChatCompletionService(openAiSettings); 45 | 46 | Kernel kernel = builder.Build(); 47 | 48 | var rootCommand = CommandOptions.RootCommand; 49 | 50 | rootCommand.SetHandler( 51 | async (context) => 52 | { 53 | AppOptions options = CommandOptions.GetParsedAppOptions(context, kernel, openAiSettings, neo4jSettings, propertyGraphSettings, loggerFactory); 54 | if (options.RemoveAll) 55 | { 56 | await RemoveAllNodes(options); 57 | } 58 | else 59 | { 60 | Matcher matcher = new(); 61 | matcher.AddInclude(options.Files); 62 | 63 | var results = matcher.Execute( 64 | new DirectoryInfoWrapper( 65 | new DirectoryInfo(Directory.GetCurrentDirectory()))); 66 | 67 | var files = results.HasMatches 68 | ? results.Files.Select(f => f.Path).ToArray() 69 | : Array.Empty(); 70 | 71 | context.Console.WriteLine($"Processing {files.Length} files..."); 72 | 73 | var tasks = Enumerable.Range(0, files.Length) 74 | .Select(i => 75 | { 76 | var fileName = files[i]; 77 | return ProcessSingleFileAsync(options, fileName); 78 | }); 79 | 80 | await Task.WhenAll(tasks); 81 | } 82 | 83 | Console.WriteLine("Done."); 84 | }); 85 | return await rootCommand.InvokeAsync(args); 86 | } 87 | 88 | static async Task ProcessSingleFileAsync(AppOptions options, string fileName) 89 | { 90 | if (options.Verbose) 91 | { 92 | options.Console.WriteLine($"Processing '{fileName}'"); 93 | } 94 | 95 | var service = new Neo4jService(options); 96 | 97 | if (options.Remove) 98 | { 99 | Console.WriteLine($"Are you sure you want to remove all nodes connected to '{fileName}'? Y/N"); 100 | Console.WriteLine(""); 101 | 102 | var response = Console.ReadLine(); 103 | if (response != null && response.ToLower() == "y") 104 | { 105 | await service.RemoveNodesWithSource(fileName); 106 | 107 | options.Console.WriteLine($"Nodes connected to '{fileName}' removed"); 108 | } 109 | return; 110 | } 111 | 112 | await service.PopulateGraphFromDocumentAsync(fileName); 113 | 114 | if (options.Verbose) 115 | { 116 | // TODO: Add stopwatch timings 117 | options.Console.WriteLine($"'{fileName}' processing complete"); 118 | } 119 | } 120 | 121 | static async Task RemoveAllNodes(AppOptions options) 122 | { 123 | var service = new Neo4jService(options); 124 | var counts = await service.GetAllNodesAndRelationshipsCountsAsync(); 125 | 126 | Console.WriteLine($"There are {counts.Item1} nodes and {counts.Item2} relationships in the database."); 127 | Console.WriteLine(""); 128 | Console.WriteLine("Are you sure you want to remove all nodes? Y/N"); 129 | Console.WriteLine(""); 130 | var response = Console.ReadLine(); 131 | if (response != null && response.ToLower() == "y") 132 | { 133 | await service.RemoveAllNodesAsync(); 134 | } 135 | } 136 | 137 | } -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraphIngestor/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "PropertyGraphIngestor": { 4 | "commandName": "Project", 5 | "commandLineArgs": ".\\data\\summaries.txt -v" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraphIngestor/PropertyGraphIngestor.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | enable 8 | SKEXP0050 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | Always 20 | 21 | 22 | Always 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraphIngestor/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.SemanticKernel": "Information" 6 | }, 7 | "Console": { 8 | "LogLevel": { 9 | "Default": "Information" 10 | } 11 | } 12 | }, 13 | "OpenAI": { 14 | "Source": "OpenAI", // or "AzureOpenAI" 15 | "ChatModelId": "gpt-4", 16 | "TextEmbeddingsModelId": "text-embedding-ada-002", 17 | "ApiKey": "your-api-key", 18 | "ChatDeploymentName": "gpt4", 19 | "TextEmbeddingsDeploymentName": "text-embedding-ada-002", 20 | "Endpoint": "your-azure-endpoint", 21 | "Resource": "your-openai-resource-name" 22 | }, 23 | "PluginConfig": { 24 | "BingApiKey": "" // Add your Bing API key here 25 | }, 26 | "TextToSql": { 27 | "SchemaNames": "AdventureWorks", // comma delimited list 28 | "MinSchemaRelevance": 0.7 29 | }, 30 | "AdventureWorks": { 31 | "ConnectionName": "AdventureWorksDb", 32 | "Description": "Product, sales, and customer data for the AdentureWorks company." 33 | //"Tables": "SalesLT.Customer,SalesLT.Address" // commented out or otherwise comma delimited list of tables including schema (ie. dbo.Users) 34 | }, 35 | "ConnectionStrings": { 36 | "AdventureWorks": "your-database-connection-string" 37 | }, 38 | "PropertyGraph": { 39 | "UseTokenSplitter": false, 40 | "ChunkSize": 200, // tokens 41 | "Overlap": 0, 42 | "EntityTypes": "BLOG_POST,PRESENTATION,EVENT,ORGANIZATION,PERSON,PLACE,TECHNOLOGY,SOFTWARE_SYSTEM,REVIEW,ACTION", 43 | "RelationTypes": "WRITTEN_BY,PRESENTED_BY,PART_OF,LOCATED_IN,LIVES_IN,TRAVELED_TO", 44 | "EntityExtractonTemplatePreamble": "The given text document contains blog entry summaries with a Title, Author, Posted On date, Topics and Summary. Make sure to add the WRITTEN_BY relationship for the author.", 45 | "DocumentChunkTypeLabel": "DOCUMENT_CHUNK", 46 | "MaxTripletsPerChunk": 20, 47 | 48 | "IncludeEntityTextSearch": true, 49 | "UseKeywords": true, 50 | "TypeEntityTextOfSearch": "VECTOR", // "FULL_TEXT" 51 | "IncludeTriplets": true, 52 | "MaxTriplets": 50, 53 | "IncludeRelatedChunks": true, 54 | "MaxRelatedChunks": 5, 55 | 56 | "IncludeChunkVectorSearch": false, 57 | "MaxChunks": 5 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraphRAG/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.Logging; 3 | using Microsoft.SemanticKernel; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using PropertyGraph.Common; 6 | using Microsoft.SemanticKernel.ChatCompletion; 7 | 8 | internal class Program 9 | { 10 | private static string[] TEST_MESSAGES = new string[] 11 | { 12 | "What does Jason blog about?", 13 | "How many blogs has he written?", 14 | "How many blogs has Jason written?", 15 | "What blogs has Jason written?", 16 | "What has he written about Java?", 17 | "How about Python?", 18 | "What presentations has he given?", 19 | "Are all his blogs about AI in some way?", 20 | "What do you know about Code Camp?", 21 | "What has Jason mentioned about Boston Azure?", 22 | "Which blogs are about Semantic Kernel?", 23 | "What blogs are about LangChain?", 24 | }; 25 | 26 | static void Main(string[] args) 27 | { 28 | MainAsync(args).Wait(); 29 | } 30 | 31 | static async Task MainAsync(string[] args) 32 | { 33 | var config = Configuration.ConfigureAppSettings(); 34 | 35 | // Get Settings (all this is just so I don't have hard coded config settings here) 36 | var openAiSettings = new OpenAIOptions(); 37 | config.GetSection(OpenAIOptions.OpenAI).Bind(openAiSettings); 38 | 39 | var neo4jSettings = new Neo4jOptions(); 40 | config.GetSection(Neo4jOptions.Neo4j).Bind(neo4jSettings); 41 | 42 | var propertyGraphSettings = new PropertyGraphOptions(); 43 | config.GetSection(PropertyGraphOptions.PropertyGraph).Bind(propertyGraphSettings); 44 | 45 | using var loggerFactory = LoggerFactory.Create(builder => 46 | { 47 | builder.SetMinimumLevel(LogLevel.Warning); 48 | 49 | builder.AddConfiguration(config); 50 | builder.AddConsole(); 51 | }); 52 | 53 | // Configure Semantic Kernel 54 | var builder = Kernel.CreateBuilder(); 55 | 56 | builder.Services.AddSingleton(loggerFactory); 57 | builder.AddChatCompletionService(openAiSettings); 58 | //builder.AddChatCompletionService(openAiSettings, ApiLoggingLevel.ResponseAndRequest); // use this line to see the JSON between SK and OpenAI 59 | 60 | Kernel kernel = builder.Build(); 61 | 62 | var appOptions = new DefaultOptions(kernel, openAiSettings, neo4jSettings, propertyGraphSettings, loggerFactory); 63 | 64 | ChatHistory chatHistory = new ChatHistory(); 65 | PropertyGraphRetriever graphRAGRetriever = new PropertyGraphRetriever(appOptions); 66 | while (true) 67 | { 68 | Console.WriteLine("Enter User Message:"); 69 | Console.WriteLine(""); 70 | 71 | string? userMessage = Console.ReadLine(); 72 | if (string.IsNullOrEmpty(userMessage) || userMessage.ToLower() == "exit") 73 | { 74 | return; 75 | } 76 | 77 | if (userMessage.ToLower() == "run tests") 78 | { 79 | await RunTestMessagesInLoop(chatHistory, graphRAGRetriever); 80 | return; 81 | } 82 | 83 | if (userMessage.ToLower() == "clear") 84 | { 85 | chatHistory.Clear(); 86 | continue; 87 | } 88 | 89 | if (userMessage.ToLower().StartsWith("raw:")) 90 | { 91 | userMessage = userMessage.Substring(4); 92 | 93 | Console.WriteLine(userMessage); 94 | chatHistory.AddUserMessage(userMessage); 95 | await foreach (var update in kernel.InvokePromptStreamingAsync(userMessage)) 96 | { 97 | Console.Write(update); 98 | } 99 | } 100 | else 101 | { 102 | chatHistory.AddUserMessage(userMessage); 103 | await foreach (StreamingKernelContent update in chatHistory.AddStreamingMessageAsync(await graphRAGRetriever.RetrieveAsync(userMessage))) 104 | { 105 | Console.Write(update); 106 | } 107 | } 108 | 109 | Console.WriteLine(""); 110 | Console.WriteLine(""); 111 | } 112 | } 113 | 114 | private static async Task RunTestMessagesInLoop(ChatHistory chatHistory, PropertyGraphRetriever graphRAGRetriever) 115 | { 116 | 117 | foreach (var userMessage in TEST_MESSAGES) 118 | { 119 | Console.WriteLine(userMessage); 120 | chatHistory.AddUserMessage(userMessage); 121 | await foreach (StreamingKernelContent update in chatHistory.AddStreamingMessageAsync(await graphRAGRetriever.RetrieveAsync(userMessage))) 122 | { 123 | Console.Write(update); 124 | } 125 | 126 | Console.WriteLine(""); 127 | Console.WriteLine(""); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraphRAG/PropertyGraphRAG.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | enable 8 | SKEXP0010 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Always 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /samples/demos/PropertyGraphRAG/PropertyGraphRAG/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "OpenAI": { 3 | "Source": "OpenAI", // or "AzureOpenAI" 4 | "ChatModelId": "gpt-4", 5 | "TextEmbeddingsModelId": "text-embedding-ada-002", 6 | "ApiKey": "your-api-key", 7 | "ChatDeploymentName": "gpt4", 8 | "TextEmbeddingsDeploymentName": "text-embedding-ada-002", 9 | "Endpoint": "your-azure-endpoint", 10 | "Resource": "your-openai-resource-name" 11 | }, 12 | "PluginConfig": { 13 | "BingApiKey": "" // Add your Bing API key here 14 | }, 15 | "TextToSql": { 16 | "SchemaNames": "AdventureWorks", // comma delimited list 17 | "MinSchemaRelevance": 0.7 18 | }, 19 | "AdventureWorks": { 20 | "ConnectionName": "AdventureWorksDb", 21 | "Description": "Product, sales, and customer data for the AdentureWorks company." 22 | //"Tables": "SalesLT.Customer,SalesLT.Address" // commented out or otherwise comma delimited list of tables including schema (ie. dbo.Users) 23 | }, 24 | "ConnectionStrings": { 25 | "AdventureWorks": "your-database-connection-string" 26 | }, 27 | "PropertyGraph": { 28 | "UseTokenSplitter": false, 29 | "ChunkSize": 200, // tokens 30 | "Overlap": 0, 31 | "EntityTypes": "BLOG_POST,BOOK,MOVIE,PRESENTATION,EVENT,ORGANIZATION,PERSON,PLACE,PRODUCT,REVIEW,ACTION", 32 | "RelationTypes": "WRITTEN_BY,PRESENTED,USED_FOR,PART_OF,LOCATED_IN,GIVEN,LIVES_IN,TRAVELED_TO", 33 | "EntityExtractonTemplatePreamble": "The given text document contains blog entry summaries with a Title, Author, Posted On date, Topics and Summary. Make sure to add the WRITTEN_BY relationship for the author.", 34 | "DocumentChunkTypeLabel": "", 35 | "MaxTripletsPerChunk": 20, 36 | 37 | "IncludeEntityTextSearch": true, 38 | "TypeEntityTextOfSearch": "VECTOR", // FULL_TEXT 39 | "UseKeywords": true, 40 | "IncludeTriplets": true, 41 | "MaxTriplets": 30, 42 | "IncludeRelatedChunks": false, 43 | "MaxRelatedChunks": 5, 44 | 45 | "IncludeChunkVectorSearch": false, 46 | "MaxChunks": 5 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /samples/demos/QueryRewrite/Configuration/Configuration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace HelloWorld.Plugin2.Console.Configuration; 4 | 5 | internal class Configuration 6 | { 7 | public static IConfigurationRoot ConfigureAppSettings() 8 | { 9 | var config = new ConfigurationBuilder() 10 | .SetBasePath(Directory.GetCurrentDirectory()) 11 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 12 | .AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true) 13 | .AddJsonFile("appsettings.Production.json", optional: true, reloadOnChange: true) 14 | #if DEBUG 15 | .AddJsonFile(GetUserJsonFilename(), optional: true, reloadOnChange: true) 16 | #endif 17 | .Build(); 18 | return config; 19 | } 20 | 21 | static string GetUserJsonFilename() 22 | { 23 | return $"appsettings.user_{Environment.UserName.ToLower()}.json"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /samples/demos/QueryRewrite/Configuration/OpenAIOptions.cs: -------------------------------------------------------------------------------- 1 | namespace HelloWorld.Plugin2.Console.Configuration; 2 | internal class OpenAIOptions 3 | { 4 | public const string OpenAI = "OpenAI"; 5 | 6 | public string Source { get; set; } = string.Empty; 7 | public string ChatModelId { get; set; } = string.Empty; 8 | public string ApiKey { get; set; } = string.Empty; 9 | public string ChatDeploymentName { get; set; } = string.Empty; 10 | public string Endpoint { get; set; } = string.Empty; 11 | } 12 | -------------------------------------------------------------------------------- /samples/demos/QueryRewrite/Plugins/BlogInfoPlugin.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.SemanticKernel; 2 | using System.ComponentModel; 3 | using System.ComponentModel.DataAnnotations; 4 | 5 | namespace HelloWorld.Plugin.Console.Plugins; 6 | 7 | public class BlogInfoPlugin 8 | { 9 | private const string DESCRIPTION = "Provides answers to blog queries."; 10 | //private const string TEMPLATE = @"Tell me an interesting fact from world 11 | // about an event that took place on {{$today}}. 12 | // Be sure to mention the date in history for context."; 13 | private const string GET_DAILY_FACT_FUNC = "GetBlogInformation"; 14 | internal const string PLUGIN_NAME = "DailyFactPlugin"; 15 | internal const string GET_DAILY_FACT = "GetDailyFact"; 16 | 17 | //private readonly KernelFunction _dailyFact; 18 | private readonly KernelFunction _currentDay; 19 | 20 | public BlogInfoPlugin() 21 | { 22 | PromptExecutionSettings settings = new() 23 | { 24 | ExtensionData = new Dictionary() 25 | { 26 | { "Temperature", 0.0 }, 27 | { "MaxTokens", 250 } 28 | } 29 | 30 | }; 31 | 32 | //_dailyFact = KernelFunctionFactory.CreateFromPrompt(TEMPLATE, 33 | // functionName: GET_DAILY_FACT_FUNC, 34 | // executionSettings: settings); 35 | 36 | _currentDay = KernelFunctionFactory.CreateFromMethod(() => DateTime.Now.ToString("MMMM dd"), "GetCurrentDay"); 37 | } 38 | 39 | [KernelFunction, Description(DESCRIPTION)] 40 | public async Task GetBlogInformation([Description("Blog query"), Required] string query, Kernel kernel) 41 | { 42 | //var result = await _dailyFact.InvokeAsync(kernel, new() { ["today"] = today }).ConfigureAwait(false); 43 | 44 | return "Hello World"; 45 | } 46 | 47 | [KernelFunction, Description("Retrieves the current day.")] 48 | public async Task GetCurrentDay(Kernel kernel) 49 | { 50 | var today = await _currentDay.InvokeAsync(kernel); 51 | 52 | return today.ToString(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /samples/demos/QueryRewrite/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.SemanticKernel; 3 | using Microsoft.Extensions.Logging; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using OpenTelemetry.Metrics; 6 | using OpenTelemetry.Logs; 7 | using HelloWorld.Plugin.Console.Plugins; 8 | using HelloWorld.Plugin2.Console.Configuration; 9 | using Microsoft.SemanticKernel.Connectors.OpenAI; 10 | using Microsoft.SemanticKernel.ChatCompletion; 11 | using HandlebarsDotNet; 12 | using Microsoft.SemanticKernel.Embeddings; 13 | using Microsoft.SemanticKernel.Text; 14 | using System.ComponentModel; 15 | using System.Text.Json; 16 | 17 | internal class Program 18 | { 19 | private static string[] TEST_MESSAGES = new string[] 20 | { 21 | "What does Jason blog about?", 22 | "How many blogs has he written?", 23 | "How many blogs has Jason written?", 24 | "What blogs has Jason written?", 25 | "What has he written about Java?", 26 | "How about Python?", 27 | "What presentations has he given?", 28 | "Are all his blogs about AI in some way?", 29 | "What do you know about Code Camp?", 30 | "What has Jason mentioned about Boston Azure?", 31 | "Which blogs are about Semantic Kernel?", 32 | "What blogs are about LangChain?", 33 | }; 34 | static void Main(string[] args) 35 | { 36 | MainAsync(args).Wait(); 37 | } 38 | 39 | static async Task MainAsync(string[] args) 40 | { 41 | var config = Configuration.ConfigureAppSettings(); 42 | 43 | // Get Settings (all this is just so I don't have hard coded config settings here) 44 | var openAiSettings = new OpenAIOptions(); 45 | config.GetSection(OpenAIOptions.OpenAI).Bind(openAiSettings); 46 | 47 | using var loggerFactory = LoggerFactory.Create(builder => 48 | { 49 | builder.SetMinimumLevel(LogLevel.Warning); 50 | 51 | builder.AddConfiguration(config); 52 | builder.AddConsole(); 53 | }); 54 | 55 | // Configure Semantic Kernel 56 | var builder = Kernel.CreateBuilder(); 57 | 58 | builder.Services.AddSingleton(loggerFactory); 59 | builder.AddChatCompletionService(config.GetConnectionString("OpenAI")); 60 | //builder.AddChatCompletionService(config.GetConnectionString("OpenAI"), ApiLoggingLevel.RequestOnly); // use this line to see the JSON between SK and OpenAI); 61 | 62 | builder.Plugins.AddFromType(); 63 | 64 | OpenAIPromptExecutionSettings settings = new() 65 | { 66 | ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions, 67 | Temperature = 0.0f 68 | }; 69 | 70 | //var tools = new AssistantTools(); 71 | 72 | Kernel kernel = builder.Build(); 73 | //kernel.Plugins.AddFromObject(tools); 74 | 75 | IChatCompletionService chat = kernel.GetRequiredService(); 76 | 77 | var sysMessage = @"Below is a history of the conversation so far, and a new question asked by the user that needs to be answered by searching a retriever system. 78 | Generate a search query based on the conversation and the new question. 79 | If you cannot generate a search query, return the original user question. 80 | DO NOT return anything besides the query."; 81 | 82 | ChatHistory chatHistory = new ChatHistory(sysMessage); 83 | chatHistory.AddUserMessage("What does Jason blog about?"); 84 | chatHistory.AddAssistantMessage("Jason blogs about xyz."); 85 | chatHistory.AddUserMessage("What is the name of his blog?"); 86 | chatHistory.AddAssistantMessage("Jason's blog is named blog."); 87 | chatHistory.AddUserMessage("Does he blog often?"); 88 | chatHistory.AddAssistantMessage("Yes Jason blogs 2 or 3 times a month."); 89 | //while (true) 90 | //{ 91 | //Console.WriteLine("Enter User Message:"); 92 | //Console.WriteLine(""); 93 | 94 | //string? userMessage = Console.ReadLine(); 95 | //if (string.IsNullOrEmpty(userMessage) || userMessage.ToLower() == "exit") 96 | //{ 97 | // return; 98 | //} 99 | 100 | //if (userMessage.ToLower() == "clear") 101 | //{ 102 | // chatHistory.Clear(); 103 | // continue; 104 | //} 105 | 106 | //chatHistory.AddUserMessage(userMessage); 107 | foreach (var userMessage in TEST_MESSAGES) 108 | { 109 | Console.WriteLine(userMessage); 110 | chatHistory.AddUserMessage(userMessage); 111 | await foreach (StreamingKernelContent update in chat.GetStreamingChatMessageContentsAsync(chatHistory, settings, kernel)) 112 | { 113 | Console.Write(update); 114 | 115 | var json = JsonSerializer.Serialize(chatHistory, new JsonSerializerOptions { WriteIndented = true }); 116 | Console.WriteLine(json); 117 | } 118 | 119 | Console.WriteLine(""); 120 | Console.WriteLine(""); 121 | } 122 | //await foreach (StreamingKernelContent update in chat.GetStreamingChatMessageContentsAsync(chatHistory, settings, kernel)) 123 | //{ 124 | // Console.Write(update); 125 | //} 126 | 127 | Console.WriteLine(""); 128 | Console.WriteLine(""); 129 | //} 130 | 131 | } 132 | 133 | public class AssistantTools 134 | { 135 | [KernelFunction, Description("Provides answers to questions about blogs.")] 136 | public async Task SearchAsync([Description("User query")] string query) 137 | { 138 | return await Task.FromResult(query); 139 | } 140 | } 141 | } -------------------------------------------------------------------------------- /samples/demos/QueryRewrite/QueryRewrite.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | enable 8 | SKEXP0010 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Always 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /samples/demos/QueryRewrite/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.SemanticKernel": "Information" 6 | }, 7 | "Console": { 8 | "LogLevel": { 9 | "Default": "Information" 10 | } 11 | } 12 | }, 13 | "ConnectionStrings": { 14 | //"OpenAI": "Source=AzureOpenAI;Key=your-key;ChatDeploymentName=your-chat-deployment-name;Endpoint=your-endpoint" 15 | "OpenAI": "Source=OpenAI;ChatModelId=gpt-4o-2024-08-06;ApiKey=Your-OpenAI-API-Key" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/TextToSql.Console/Configuration/Configuration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace TextToSql.Console.Configuration; 4 | internal class Configuration 5 | { 6 | public static IConfigurationRoot ConfigureAppSettings() 7 | { 8 | var config = new ConfigurationBuilder() 9 | .SetBasePath(Directory.GetCurrentDirectory()) 10 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 11 | .AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true) 12 | .AddJsonFile("appsettings.Production.json", optional: true, reloadOnChange: true) 13 | #if DEBUG 14 | .AddJsonFile(GetUserJsonFilename(), optional: true, reloadOnChange: true) 15 | #endif 16 | .Build(); 17 | return config; 18 | } 19 | 20 | static string GetUserJsonFilename() 21 | { 22 | return $"appsettings.user_{Environment.UserName.ToLower()}.json"; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/TextToSql.Console/Configuration/OpenAIOptions.cs: -------------------------------------------------------------------------------- 1 | namespace TextToSql.Console.Configuration; 2 | internal class OpenAIOptions 3 | { 4 | public const string OpenAI = "OpenAI"; 5 | 6 | public string Source { get; set; } = string.Empty; 7 | public string ChatModelId { get; set; } = string.Empty; 8 | public string TextEmbeddingsModelId { get; set; } = string.Empty; 9 | public string ApiKey { get; set; } = string.Empty; 10 | public string ChatDeploymentName { get; set; } = string.Empty; 11 | public string TextEmbeddingsDeploymentName { get; set; } = string.Empty; 12 | public string Endpoint { get; set; } = string.Empty; 13 | } 14 | -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/TextToSql.Console/Configuration/PluginConfig.cs: -------------------------------------------------------------------------------- 1 | namespace TextToSql.Console.Configuration; 2 | public class PluginOptions 3 | { 4 | public const string PluginConfig = "PluginConfig"; 5 | 6 | public string BingApiKey { get; set; } = string.Empty; 7 | } 8 | -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/TextToSql.Console/Configuration/SqlSchemaOptions.cs: -------------------------------------------------------------------------------- 1 | namespace TextToSql.Console.Configuration; 2 | public class SqlSchemaOptions 3 | { 4 | public const string SqlSchemaConfig = "SqlSchemaConfig "; 5 | 6 | public string ConnectionName { get; set; } = string.Empty; 7 | public string Description { get; set; } = string.Empty; 8 | public string? Tables { get; set; } 9 | } 10 | -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/TextToSql.Console/Configuration/TextToSqlOptions.cs: -------------------------------------------------------------------------------- 1 | namespace TextToSql.Console.Configuration; 2 | public class TextToSqlOptions 3 | { 4 | public const string TextToSqlConfig = "TextToSql"; 5 | 6 | public string SchemaNames { get; set; } = string.Empty; 7 | public double MinSchemaRelevance { get; set; } = 0.7; 8 | } 9 | -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/TextToSql.Console/Nl2Sql/SqlCommandExecutor.cs: -------------------------------------------------------------------------------- 1 |  2 | using Microsoft.Data.SqlClient; 3 | using System.Data; 4 | 5 | namespace TextToSql.Console.Nl2Sql; 6 | public class SqlCommandExecutor 7 | { 8 | private readonly string _connectionString; 9 | 10 | public SqlCommandExecutor(string connectionString) 11 | { 12 | _connectionString = connectionString; 13 | } 14 | 15 | public async Task>> ExecuteAsync(string sql) 16 | { 17 | if (sql.IndexOf("SELECT") == -1) 18 | { 19 | return new List>(); 20 | } 21 | 22 | var rows = new List>(); 23 | try 24 | { 25 | using (var connection = new SqlConnection(_connectionString)) 26 | { 27 | await connection.OpenAsync(); 28 | using var command = connection.CreateCommand(); 29 | 30 | #pragma warning disable CA2100 // Review SQL queries for security vulnerabilities 31 | command.CommandText = sql; 32 | #pragma warning restore CA2100 // Review SQL queries for security vulnerabilities 33 | 34 | using var reader = await command.ExecuteReaderAsync(); 35 | 36 | bool headersAdded = false; 37 | while (reader.Read()) 38 | { 39 | var cols = new List(); 40 | var headerCols = new List(); 41 | if (!headersAdded) 42 | { 43 | for (int i = 0; i < reader.FieldCount; i++) 44 | { 45 | headerCols.Add(reader.GetName(i).ToString()); 46 | } 47 | headersAdded = true; 48 | rows.Add(headerCols); 49 | } 50 | 51 | for (int i = 0; i <= reader.FieldCount - 1; i++) 52 | { 53 | try 54 | { 55 | cols.Add(reader.GetValue(i).ToString()); 56 | } 57 | catch 58 | { 59 | cols.Add("DataTypeConversionError"); 60 | } 61 | } 62 | rows.Add(cols); 63 | } 64 | } 65 | } 66 | catch 67 | { 68 | throw; 69 | } 70 | return rows; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/TextToSql.Console/Nl2Sql/SqlConnectionProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using Microsoft.Data.SqlClient; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace SemanticKernel.Data.Nl2Sql; 8 | 9 | /// 10 | /// Responsible for producing a connection string for the requested schema. 11 | /// 12 | internal sealed class SqlConnectionProvider 13 | { 14 | private readonly IConfiguration _configuration; 15 | 16 | /// 17 | /// Factory method for 18 | /// 19 | public static Func Create(IConfiguration configuration) 20 | { 21 | return CreateProvider; 22 | 23 | SqlConnectionProvider CreateProvider(IServiceProvider provider) 24 | { 25 | return new SqlConnectionProvider(configuration); 26 | } 27 | } 28 | 29 | private SqlConnectionProvider(IConfiguration configuration) 30 | { 31 | this._configuration = configuration; 32 | } 33 | 34 | /// 35 | /// Factory method for producing a live SQL connection instance. 36 | /// 37 | /// The schema name (which should match a corresponding connectionstring setting). 38 | /// A instance in the "Open" state. 39 | /// 40 | /// Connection pooling enabled by default makes re-establishing connections 41 | /// relatively efficient. 42 | /// 43 | public async Task ConnectAsync(string schemaName) 44 | { 45 | var connectionString = 46 | this._configuration.GetConnectionString(schemaName) ?? 47 | throw new InvalidDataException($"Missing configuration for connection-string: {schemaName}"); 48 | 49 | var connection = new SqlConnection(connectionString); 50 | 51 | try 52 | { 53 | await connection.OpenAsync().ConfigureAwait(false); 54 | } 55 | catch 56 | { 57 | connection.Dispose(); 58 | throw; 59 | } 60 | 61 | return connection; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/TextToSql.Console/Nl2Sql/SqlScemaLoader.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Data.SqlClient; 2 | using Microsoft.Extensions.Configuration; 3 | using SemanticKernel.Data.Nl2Sql.Harness; 4 | using TextToSql.Console.Configuration; 5 | using SemanticKernel.Data.Nl2Sql.Library.Schema; 6 | using Microsoft.SemanticKernel.Memory; 7 | 8 | namespace TextToSql.Console.Nl2Sql; 9 | internal class SqlScemaLoader 10 | { 11 | private readonly IConfiguration _configuration; 12 | private TextToSqlOptions _textToSqlOptions = new TextToSqlOptions(); 13 | private readonly Dictionary _sqlSchemaOptionsMap = new Dictionary(); 14 | public SqlScemaLoader(IConfiguration confuration) 15 | { 16 | _configuration = confuration; 17 | Initialize(); 18 | } 19 | 20 | public IList SchemaNames { get { return _textToSqlOptions.SchemaNames.Split(','); } } 21 | public double MinSchemaRelevance { get { return _textToSqlOptions.MinSchemaRelevance; } } 22 | 23 | private void Initialize() 24 | { 25 | _configuration.GetSection(TextToSqlOptions.TextToSqlConfig).Bind(_textToSqlOptions); 26 | if (!string.IsNullOrEmpty(_textToSqlOptions.SchemaNames)) 27 | { 28 | foreach (var name in SchemaNames) 29 | { 30 | var schema = new SqlSchemaOptions(); 31 | _configuration.GetSection(name).Bind(schema); 32 | _sqlSchemaOptionsMap.Add(name, schema); 33 | } 34 | } 35 | } 36 | 37 | private bool HasSchemas() 38 | { 39 | return _sqlSchemaOptionsMap.Count > 0; 40 | } 41 | 42 | private bool SchemeFileExists(string schemaName) 43 | { 44 | // If the database schema doesn't exist, the create one 45 | var schemaFile = $"{schemaName}.json"; 46 | return File.Exists(schemaFile); 47 | } 48 | 49 | private async Task CreateSchemaFileAsync(string schemaName, SqlSchemaOptions options) 50 | { 51 | // If there is no ConnectionString, messages and exit 52 | var connectionString = GetConnectionString(schemaName); 53 | if (string.IsNullOrEmpty(connectionString)) 54 | { 55 | System.Console.WriteLine($"Please add a connection string for {schemaName} in the appsettings.json file before running"); 56 | return false; 57 | } 58 | 59 | // Connect to database and create schema 60 | using (var connection = new SqlConnection(connectionString)) 61 | { 62 | await connection.OpenAsync().ConfigureAwait(false); 63 | var provider = new SqlSchemaProvider(connection); 64 | 65 | SchemaDefinition schemaDef; 66 | string[] tableNames; 67 | if (options.Tables != null) 68 | { 69 | tableNames = options.Tables.Split(','); 70 | schemaDef = await provider.GetSchemaAsync(schemaName, options.Description, tableNames).ConfigureAwait(false); 71 | } 72 | else 73 | { 74 | schemaDef = await provider.GetSchemaAsync(schemaName, options.Description).ConfigureAwait(false); 75 | } 76 | 77 | await connection.CloseAsync().ConfigureAwait(false); 78 | 79 | using var streamCompact = new StreamWriter( 80 | $"{schemaName}.json", 81 | new FileStreamOptions 82 | { 83 | Access = FileAccess.Write, 84 | Mode = FileMode.Create, 85 | }); 86 | 87 | await streamCompact.WriteAsync(schemaDef.ToJson()).ConfigureAwait(false); 88 | } 89 | return true; 90 | } 91 | 92 | public string GetConnectionString(string schemaName) 93 | { 94 | return _configuration.GetConnectionString(schemaName) ?? ""; 95 | } 96 | public async Task TryLoadAsync(ISemanticTextMemory memory) 97 | { 98 | if (!HasSchemas()) 99 | { 100 | System.Console.WriteLine("No schemas configured in appsettings.json"); 101 | return false; 102 | } 103 | 104 | foreach (var schema in _sqlSchemaOptionsMap.Keys) 105 | { 106 | if (!SchemeFileExists(schema)) 107 | { 108 | await CreateSchemaFileAsync(schema, _sqlSchemaOptionsMap[schema]); 109 | } 110 | } 111 | 112 | await SchemaProvider.InitializeAsync(memory, _sqlSchemaOptionsMap.Keys.Select(s => $"{s}.json")).ConfigureAwait(false); 113 | 114 | return true; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/TextToSql.Console/Prompts/DescribeResults/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema": 1, 3 | "description": "Describes the data results", 4 | "execution_settings": { 5 | "default": { 6 | "temperature": 0.0 7 | } 8 | }, 9 | "input_variables": [ 10 | { 11 | "name": "data_result", 12 | "description": "Results from the query", 13 | "required": true 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/TextToSql.Console/Prompts/DescribeResults/skprompt.txt: -------------------------------------------------------------------------------- 1 | 2 | Given the following data set, please describe the contents. 3 | 4 | SQL RESULT: {{$data_result}} 5 | -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/TextToSql.Console/Prompts/EvaluateIntent/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema": 1, 3 | "description": "Determines if the user request is related to the described schema", 4 | "execution_settings": { 5 | "default": { 6 | "temperature": 0.0 7 | } 8 | }, 9 | "input_variables": [ 10 | { 11 | "name": "data_schema", 12 | "description": "Schema of the database tables", 13 | "required": true 14 | }, 15 | { 16 | "name": "data_objective", 17 | "description": "Request from the user", 18 | "required": true 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/TextToSql.Console/Prompts/EvaluateIntent/skprompt.txt: -------------------------------------------------------------------------------- 1 | 2 | Determine if the requested OBJECTIVE is related to the tables described in the YAML SCHEMA 3 | If so, ANSWER YES 4 | Otherwise, ANSWER NO 5 | 6 | 7 | [SCHEMA] 8 | description: historical record of concerts, stadiums and singers 9 | tables: 10 | - stadium: 11 | columns: 12 | Stadium_ID: 13 | Location: 14 | Name: 15 | Capacity: 16 | Highest: 17 | Lowest: 18 | Average: 19 | - singer: 20 | columns: 21 | Singer_ID: 22 | Name: 23 | Country: 24 | Song_Name: 25 | Song_release_year: 26 | Age: 27 | Is_male: 28 | - concert: 29 | columns: 30 | concert_ID: 31 | concert_Name: 32 | Theme: 33 | Stadium_ID: 34 | Year: 35 | - singer_in_concert: 36 | columns: 37 | concert_ID: 38 | Singer_ID: 39 | references: 40 | concert.Stadium_ID: stadium.Stadium_ID 41 | references: 42 | singer_in_concert.concert_ID: concert.concert_ID 43 | singer_in_concert.Singer_ID: singer.Singer_ID 44 | 45 | [OBJECTIVE] 46 | How many heads of the departments are older than 56 ? 47 | 48 | 49 | select count(*) department_head_count from head where age > 56 50 | 51 | 52 | SCHEMA: (same as previous) 53 | 54 | OBJECTIVE: List all tables? 55 | 56 | 57 | NO 58 | 59 | 60 | [SCHEMA] 61 | {{$data_schema}} 62 | 63 | [OBJECTIVE] 64 | {{$data_objective}} 65 | -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/TextToSql.Console/Prompts/EvaluateResult/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema": 1, 3 | "description": "Answers the user question for the given sql query and results", 4 | "execution_settings": { 5 | "default": { 6 | "temperature": 0.0 7 | } 8 | }, 9 | "input_variables": [ 10 | { 11 | "name": "data_objective", 12 | "description": "Request from the user", 13 | "required": true 14 | }, 15 | { 16 | "name": "data_query", 17 | "description": "SQL query", 18 | "required": true 19 | }, 20 | { 21 | "name": "data_result", 22 | "description": "Results from the query", 23 | "required": true 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/TextToSql.Console/Prompts/EvaluateResult/skprompt.txt: -------------------------------------------------------------------------------- 1 | 2 | Given the following objective, corresponding SQL query, and SQL result, answer the user question. 3 | 4 | OBJECTIVE: {{$data_objective}} 5 | SQL QUERY: {{$data_query}} 6 | SQL RESULT: {{$data_result}} 7 | ANSWER: 8 | -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/TextToSql.Console/Prompts/SqlGenerate/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema": 1, 3 | "description": "Creates valid SQL for a given user request.", 4 | "execution_settings": { 5 | "default": { 6 | "temperature": 0.0 7 | } 8 | }, 9 | "input_variables": [ 10 | { 11 | "name": "data_platform", 12 | "description": "SQL data platform type", 13 | "required": true 14 | }, 15 | { 16 | "name": "data_schema", 17 | "description": "Schema of the database tables", 18 | "required": true 19 | }, 20 | { 21 | "name": "data_objective", 22 | "description": "Request from the user", 23 | "required": true 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/TextToSql.Console/Prompts/SqlGenerate/skprompt.txt: -------------------------------------------------------------------------------- 1 | 2 | Generate a SQL SELECT query that is compatible with {{$data_platform}}, use aliases for all tables and reference those aliases when used and achieves the OBJECTIVE exclusively using only the tables and views described in "SCHEMA:". 3 | 4 | Only generate SQL if the OBJECTIVE can be answered by querying a database with tables described in SCHEMA. 5 | 6 | 7 | Respond with only with valid SQL 8 | 9 | 10 | SCHEMA: 11 | description: historical record of concerts, stadiums and singers 12 | tables: 13 | - stadium: 14 | columns: 15 | Stadium_ID: 16 | Location: 17 | Name: 18 | Capacity: 19 | Highest: 20 | Lowest: 21 | Average: 22 | - singer: 23 | columns: 24 | Singer_ID: 25 | Name: 26 | Country: 27 | Song_Name: 28 | Song_release_year: 29 | Age: 30 | Is_male: 31 | - concert: 32 | columns: 33 | concert_ID: 34 | concert_Name: 35 | Theme: 36 | Stadium_ID: 37 | Year: 38 | - singer_in_concert: 39 | columns: 40 | concert_ID: 41 | Singer_ID: 42 | references: 43 | concert.Stadium_ID: stadium.Stadium_ID 44 | references: 45 | singer_in_concert.concert_ID: concert.concert_ID 46 | singer_in_concert.Singer_ID: singer.Singer_ID 47 | 48 | OBJECTIVE: How many heads of the departments are older than 56 ? 49 | 50 | 51 | select count(*) department_head_count from head where age > 56 52 | 53 | 54 | SCHEMA: 55 | {{$data_schema}} 56 | 57 | OBJECTIVE: {{$data_objective}} 58 | 59 | -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/TextToSql.Console/TextToSql.Console.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | enable 8 | SKEXP0001,SKEXP0010,SKEXP0110,SKEXP0050 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Always 27 | 28 | 29 | Always 30 | 31 | 32 | Always 33 | 34 | 35 | Always 36 | 37 | 38 | Always 39 | 40 | 41 | Always 42 | 43 | 44 | Always 45 | 46 | 47 | Always 48 | 49 | 50 | Always 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/TextToSql.Console/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.SemanticKernel": "Information" 6 | }, 7 | "Console": { 8 | "LogLevel": { 9 | "Default": "Information" 10 | } 11 | } 12 | }, 13 | "OpenAI": { 14 | "Source": "OpenAI", // or "AzureOpenAI" 15 | "ChatModelId": "gpt-4", 16 | "TextEmbeddingsModelId": "text-embedding-ada-002", 17 | "ApiKey": "your-api-key", 18 | "ChatDeploymentName": "gpt4", 19 | "TextEmbeddingsDeploymentName": "text-embedding-ada-002", 20 | "Endpoint": "your-azure-endpoint" 21 | }, 22 | "PluginConfig": { 23 | "BingApiKey": "" // Add your Bing API key here 24 | }, 25 | "TextToSql": { 26 | "SchemaNames": "AdventureWorks", // comma delimited list 27 | "MinSchemaRelevance": 0.7 28 | }, 29 | "AdventureWorks": { 30 | "ConnectionName": "AdventureWorksDb", 31 | "Description": "Product, sales, and customer data for the AdentureWorks company." 32 | //"Tables": "SalesLT.Customer,SalesLT.Address" // commented out or otherwise comma delimited list of tables including schema (ie. dbo.Users) 33 | }, 34 | "ConnectionStrings": { 35 | "AdventureWorks": "your-database-connection-string" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/nl2sql.library/Internal/FunctionResultExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using Microsoft.SemanticKernel; 6 | 7 | namespace SemanticKernel.Data.Nl2Sql.Library.Internal; 8 | 9 | internal static class FunctionResultExtensions 10 | { 11 | private static readonly HashSet s_delimitersResult = new() { '\'', '"', '`' }; 12 | 13 | public static string ParseValue(this FunctionResult result, string? label = null) 14 | { 15 | if (result == null) 16 | { 17 | return string.Empty; 18 | } 19 | 20 | var resultText = TrimDelimiters(result.GetValue() ?? string.Empty); 21 | 22 | if (!string.IsNullOrWhiteSpace(label)) 23 | { 24 | // Trim out label, if present. 25 | int index = resultText.IndexOf($"{label}", StringComparison.OrdinalIgnoreCase); 26 | if (index == 0) 27 | { 28 | resultText = resultText.Substring(index + label.Length + 1).Trim(); 29 | } 30 | } 31 | 32 | return resultText; 33 | } 34 | 35 | private static string TrimDelimiters(string expression) 36 | { 37 | for (var index = 0; index < expression.Length; ++index) 38 | { 39 | if (s_delimitersResult.Contains(expression[index]) && 40 | s_delimitersResult.Contains(expression[expression.Length - index - 1])) 41 | { 42 | continue; 43 | } 44 | 45 | return expression.Substring(index, expression.Length - (index * 2)).Trim(); 46 | } 47 | 48 | return expression; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/nl2sql.library/Nl2Sql.Library.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | SemanticKernel.Data.Nl2Sql.Library 5 | nl2sql.library 6 | net9.0 7 | LatestMajor 8 | disable 9 | enable 10 | false 11 | $(NoWarn);CA1869,SKEXP0001 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/nl2sql.library/Schema/ISchemaFormatter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.IO; 4 | using System.Threading.Tasks; 5 | 6 | namespace SemanticKernel.Data.Nl2Sql.Library.Schema; 7 | 8 | internal interface ISchemaFormatter 9 | { 10 | Task WriteAsync(TextWriter writer, SchemaDefinition schema); 11 | } 12 | -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/nl2sql.library/Schema/ISchemaFormatterExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | using System.IO; 5 | using System.Threading.Tasks; 6 | 7 | namespace SemanticKernel.Data.Nl2Sql.Library.Schema; 8 | 9 | internal static class SchemaDefinitionExtensions 10 | { 11 | public static async Task FormatAsync(this SchemaDefinition schema, ISchemaFormatter formatter) 12 | { 13 | formatter = formatter ?? throw new ArgumentNullException(nameof(formatter)); 14 | 15 | using var writer = new StringWriter(); 16 | 17 | await formatter.WriteAsync(writer, schema).ConfigureAwait(false); 18 | 19 | return writer.ToString(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/nl2sql.library/Schema/SchemaColumn.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Text.Json.Serialization; 4 | 5 | namespace SemanticKernel.Data.Nl2Sql.Library.Schema; 6 | 7 | public sealed class SchemaColumn 8 | { 9 | public SchemaColumn( 10 | string name, 11 | string? description, 12 | string type, 13 | bool isPrimary, 14 | string? referencedTable = null, 15 | string? referencedColumn = null) 16 | { 17 | this.Name = name; 18 | this.Description = description; 19 | this.Type = type; 20 | this.IsPrimary = isPrimary; 21 | this.ReferencedTable = referencedTable; 22 | this.ReferencedColumn = referencedColumn; 23 | } 24 | 25 | public string Name { get; } 26 | 27 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] 28 | public string? Description { get; } 29 | 30 | public string Type { get; } 31 | 32 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] 33 | public bool IsPrimary { get; } 34 | 35 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] 36 | public string? ReferencedTable { get; } 37 | 38 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] 39 | public string? ReferencedColumn { get; } 40 | } 41 | -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/nl2sql.library/Schema/SchemaDefinition.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace SemanticKernel.Data.Nl2Sql.Library.Schema; 7 | 8 | public sealed class SchemaDefinition 9 | { 10 | public SchemaDefinition( 11 | string name, 12 | string platform, 13 | string? description, 14 | IEnumerable tables) 15 | { 16 | this.Name = name; 17 | this.Platform = platform; 18 | this.Description = description; 19 | this.Tables = tables ?? Array.Empty(); 20 | } 21 | 22 | public string Name { get; } 23 | 24 | public string? Description { get; } 25 | 26 | public string Platform { get; } 27 | 28 | public IEnumerable Tables { get; } 29 | } 30 | -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/nl2sql.library/Schema/SchemaProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | using Microsoft.SemanticKernel.Memory; 6 | 7 | namespace SemanticKernel.Data.Nl2Sql.Library.Schema; 8 | 9 | /// 10 | /// Responsible for loading the defined schemas into kernel memory. 11 | /// 12 | public static class SchemaProvider 13 | { 14 | public const string MemoryCollectionName = "data-schemas"; 15 | 16 | public static async Task InitializeAsync(ISemanticTextMemory memory, IEnumerable schemaPaths) 17 | { 18 | foreach (var schemaPath in schemaPaths) 19 | { 20 | var schema = await SchemaSerializer.ReadAsync(schemaPath).ConfigureAwait(false); 21 | 22 | var schemaText = await schema.FormatAsync(YamlSchemaFormatter.Instance).ConfigureAwait(false); 23 | 24 | await memory.SaveInformationAsync(MemoryCollectionName, schemaText, schema.Name, additionalMetadata: schema.Platform).ConfigureAwait(false); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/nl2sql.library/Schema/SchemaSerializer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.IO; 4 | using System.Text.Json; 5 | using System.Threading.Tasks; 6 | 7 | namespace SemanticKernel.Data.Nl2Sql.Library.Schema; 8 | 9 | public static class SchemaSerializer 10 | { 11 | public static async Task ReadAsync(string path) 12 | { 13 | using var stream = File.OpenRead(path); 14 | 15 | return await ReadAsync(stream).ConfigureAwait(false); 16 | } 17 | 18 | public static async Task ReadAsync(Stream stream) 19 | { 20 | return 21 | await JsonSerializer.DeserializeAsync( 22 | stream, 23 | new JsonSerializerOptions 24 | { 25 | PropertyNameCaseInsensitive = true 26 | }).ConfigureAwait(false) ?? 27 | throw new JsonException("Unable to read schema."); 28 | } 29 | 30 | public static string ToJson(this SchemaDefinition schema) 31 | { 32 | return JsonSerializer.Serialize(schema, new JsonSerializerOptions { WriteIndented = true }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/nl2sql.library/Schema/SchemaTable.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text.Json.Serialization; 6 | 7 | namespace SemanticKernel.Data.Nl2Sql.Library.Schema; 8 | 9 | public sealed class SchemaTable 10 | { 11 | public SchemaTable( 12 | string name, 13 | string? description = null, 14 | bool isView = false, 15 | IEnumerable? columns = null) 16 | { 17 | this.Name = name; 18 | this.Description = description; 19 | this.Columns = columns ?? Array.Empty(); 20 | this.IsView = isView; 21 | } 22 | 23 | public string Name { get; } 24 | 25 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] 26 | public string? Description { get; } 27 | 28 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] 29 | public bool IsView { get; } 30 | 31 | public IEnumerable Columns { get; } 32 | } 33 | -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/nl2sql.library/Schema/YamlSchemaFormatter.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace SemanticKernel.Data.Nl2Sql.Library.Schema; 8 | 9 | /// 10 | /// Format a object in YAML format. 11 | /// 12 | /// 13 | /// Turns out YAML is extremely efficient with token usage. 14 | /// 15 | internal sealed class YamlSchemaFormatter : ISchemaFormatter 16 | { 17 | public static YamlSchemaFormatter Instance { get; } = new YamlSchemaFormatter(); 18 | 19 | private YamlSchemaFormatter() { } 20 | 21 | async Task ISchemaFormatter.WriteAsync(TextWriter writer, SchemaDefinition schema) 22 | { 23 | if (!string.IsNullOrWhiteSpace(schema.Description)) 24 | { 25 | await writer.WriteLineAsync($"description: {schema.Description}").ConfigureAwait(false); 26 | } 27 | 28 | await writer.WriteLineAsync("tables:").ConfigureAwait(false); 29 | 30 | foreach (var table in schema.Tables) 31 | { 32 | await writer.WriteLineAsync($" - {table.Name}: {table.Description}").ConfigureAwait(false); 33 | await writer.WriteLineAsync(" columns:").ConfigureAwait(false); 34 | 35 | foreach (var column in table.Columns) 36 | { 37 | await writer.WriteLineAsync($" {column.Name}: {column.Description}").ConfigureAwait(false); 38 | } 39 | } 40 | 41 | await writer.WriteLineAsync("references:").ConfigureAwait(false); 42 | foreach (var (table, column) in schema.Tables.SelectMany(t => t.Columns.Where(c => !string.IsNullOrEmpty(c.ReferencedTable)).Select(c => (t.Name, c)))) 43 | { 44 | await writer.WriteLineAsync($" {table}.{column.Name}: {column.ReferencedTable}.{column.ReferencedColumn}").ConfigureAwait(false); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/nl2sql.library/SqlQueryGenerator.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Security.AccessControl; 8 | using System.Text.Json; 9 | using System.Threading.Tasks; 10 | using Microsoft.SemanticKernel; 11 | using Microsoft.SemanticKernel.Memory; 12 | using SemanticKernel.Data.Nl2Sql.Library.Internal; 13 | using SemanticKernel.Data.Nl2Sql.Library.Schema; 14 | 15 | namespace SemanticKernel.Data.Nl2Sql.Library; 16 | 17 | /// 18 | /// Generate SQL query targeting Microsoft SQL Server. 19 | /// 20 | public sealed class SqlQueryGenerator 21 | { 22 | public const double DefaultMinRelevance = 0.7D; 23 | 24 | public const string ContextParamObjective = "data_objective"; 25 | public const string ContextParamSchema = "data_schema"; 26 | public const string ContextParamSchemaId = "data_schema_id"; 27 | public const string ContextParamQuery = "data_query"; 28 | public const string ContextParamPlatform = "data_platform"; 29 | public const string ContextParamResult = "data_result"; 30 | public const string ContextParamError = "data_error"; 31 | 32 | private const string ContentLabelQuery = "sql"; 33 | private const string ContentLabelAnswer = "answer"; 34 | private const string ContentAffirmative = "yes"; 35 | 36 | private readonly double _minRelevanceScore; 37 | private readonly KernelFunction _promptEval; 38 | private readonly KernelFunction _promptGenerator; 39 | private readonly KernelFunction _promptResultEval; 40 | private readonly KernelFunction _promptDescribeResults; 41 | private readonly Kernel _kernel; 42 | private readonly ISemanticTextMemory _memory; 43 | 44 | public SqlQueryGenerator( 45 | Kernel kernel, 46 | ISemanticTextMemory memory, 47 | double minRelevanceScore = DefaultMinRelevance) 48 | { 49 | var prompts = kernel.CreatePluginFromPromptDirectory("Prompts"); 50 | 51 | this._promptEval = prompts["EvaluateIntent"]; 52 | this._promptGenerator = prompts["SqlGenerate"]; 53 | this._promptResultEval = prompts["EvaluateResult"]; 54 | this._promptDescribeResults = prompts["DescribeResults"]; 55 | this._kernel = kernel; 56 | this._memory = memory; 57 | this._minRelevanceScore = minRelevanceScore; 58 | } 59 | 60 | /// 61 | /// Attempt to produce a query for the given objective based on the registered schemas. 62 | /// 63 | /// A natural language objective 64 | /// A object 65 | /// A SQL query (or null if not able) 66 | public async Task SolveObjectiveAsync(string objective) 67 | { 68 | // Search for schema with best similarity match to the objective 69 | var recall = 70 | await this._memory.SearchAsync( 71 | SchemaProvider.MemoryCollectionName, 72 | objective, 73 | limit: 1, // Take top result with maximum relevance (> minRelevanceScore) 74 | minRelevanceScore: this._minRelevanceScore, 75 | withEmbeddings: true).ToArrayAsync().ConfigureAwait(false); 76 | 77 | var best = recall.FirstOrDefault(); 78 | if (best == null) 79 | { 80 | return null; // No schema / no query 81 | } 82 | 83 | var schemaName = best.Metadata.Id; 84 | var schemaText = best.Metadata.Text; 85 | 86 | var arguments = new KernelArguments(); 87 | arguments[ContextParamSchema] = schemaText; 88 | arguments[ContextParamObjective] = objective; 89 | 90 | // Screen objective to determine if it can be solved with the selected schema. 91 | if (!await this.ScreenObjectiveAsync(arguments).ConfigureAwait(false)) 92 | { 93 | return null; // Objective doesn't pass screen 94 | } 95 | 96 | var sqlPlatform = best.Metadata.AdditionalMetadata; 97 | arguments[ContextParamPlatform] = sqlPlatform; 98 | 99 | // Generate query 100 | var result = await this._promptGenerator.InvokeAsync(this._kernel, arguments).ConfigureAwait(false); 101 | 102 | // Parse result to handle 103 | string query = result.ParseValue(ContentLabelQuery); 104 | 105 | return new SqlQueryResult(schemaName, query); 106 | } 107 | 108 | public async Task ProcessResultAsync(string objective, string query, List> dataResult) 109 | { 110 | var arguments = new KernelArguments(); 111 | arguments[ContextParamObjective] = objective; 112 | arguments[ContextParamQuery] = query; 113 | arguments[ContextParamResult] = JsonSerializer.Serialize(dataResult); 114 | 115 | 116 | // Generate query 117 | var result = await this._promptResultEval.InvokeAsync(this._kernel, arguments).ConfigureAwait(false); 118 | var answer = result.ToString(); 119 | 120 | return answer; 121 | } 122 | 123 | public async Task DescribeResultsAsync(List> dataResult) 124 | { 125 | var arguments = new KernelArguments(); 126 | arguments[ContextParamResult] = JsonSerializer.Serialize(dataResult.Take(10)); 127 | 128 | 129 | // Generate query 130 | var result = await this._promptDescribeResults.InvokeAsync(this._kernel, arguments).ConfigureAwait(false); 131 | var answer = result.ToString(); 132 | 133 | return answer; 134 | } 135 | 136 | private async Task ScreenObjectiveAsync(KernelArguments context) 137 | { 138 | var result = await this._promptEval.InvokeAsync(this._kernel, context).ConfigureAwait(false); 139 | 140 | var answer = result.ParseValue(ContentLabelAnswer); 141 | return answer.Equals(ContentAffirmative, StringComparison.OrdinalIgnoreCase); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /samples/demos/Text-to-Sql/nl2sql.library/SqlQueryResult.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 |  3 | namespace SemanticKernel.Data.Nl2Sql.Library; 4 | 5 | public record SqlQueryResult(string Schema, string Query) 6 | { 7 | // No specialization 8 | } 9 | -------------------------------------------------------------------------------- /src/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # SKEXP0055: Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. 4 | dotnet_diagnostic.SKEXP0055.severity = none 5 | -------------------------------------------------------------------------------- /src/HelloWorld.Agent1.Console/BingConnector2.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | 3 | using System.Text.Json; 4 | using Microsoft.Extensions.Logging; 5 | using Microsoft.Extensions.Logging.Abstractions; 6 | 7 | namespace Microsoft.SemanticKernel.Plugins.Web.Bing; 8 | 9 | /// 10 | /// Bing API connector2. 11 | /// 12 | public sealed class BingConnector2: IWebSearchEngineConnector 13 | { 14 | private readonly ILogger _logger; 15 | private readonly HttpClient _httpClient; 16 | private readonly string? _apiKey; 17 | private readonly Uri? _uri = null; 18 | private const string DefaultUri = "https://api.bing.microsoft.com/v7.0/search?q"; 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | /// The API key to authenticate the connector. 24 | /// The HTTP client to use for making requests. 25 | /// The URI of the Bing Search instance. Defaults to "https://api.bing.microsoft.com/v7.0/search?q". 26 | /// The to use for logging. If null, no logging will be performed. 27 | public BingConnector2(string apiKey, HttpClient httpClient, Uri? uri = null, ILoggerFactory? loggerFactory = null) 28 | { 29 | 30 | this._apiKey = apiKey; 31 | this._logger = loggerFactory?.CreateLogger(typeof(BingConnector)) ?? NullLogger.Instance; 32 | this._httpClient = httpClient; 33 | this._uri = uri ?? new Uri(DefaultUri); 34 | } 35 | 36 | /// 37 | public async Task> SearchAsync(string query, int count = 1, int offset = 0, CancellationToken cancellationToken = default) 38 | { 39 | if (count is <= 0 or >= 50) 40 | { 41 | throw new ArgumentOutOfRangeException(nameof(count), count, $"{nameof(count)} value must be greater than 0 and less than 50."); 42 | } 43 | 44 | Uri uri = new($"{this._uri}={Uri.EscapeDataString(query.Trim())}&count={count}&offset={offset}"); 45 | 46 | this._logger.LogDebug("Sending request: {Uri}", uri); 47 | 48 | using HttpResponseMessage response = await this.SendGetRequestAsync(uri, cancellationToken).ConfigureAwait(false); 49 | 50 | this._logger.LogDebug("Response received: {StatusCode}", response.StatusCode); 51 | 52 | string json = await response.Content.ReadAsStringAsync().ConfigureAwait(false); 53 | 54 | // Sensitive data, logging as trace, disabled by default 55 | this._logger.LogTrace("Response content received: {Data}", json); 56 | 57 | WebSearchResponse? data = JsonSerializer.Deserialize(json); 58 | 59 | List? returnValues = null; 60 | if (data?.WebPages?.Value is not null) 61 | { 62 | if (typeof(T) == typeof(string)) 63 | { 64 | WebPage[]? results = data?.WebPages?.Value; 65 | var item = results?.GetRandomItem(); 66 | returnValues = new string[] { @$"Source URL: {item?.Url} 67 | {item?.Snippet}" }.ToList() as List; 68 | } 69 | else if (typeof(T) == typeof(WebPage)) 70 | { 71 | List? webPages = [.. data.WebPages.Value]; 72 | returnValues = webPages.Take(count).ToList() as List; 73 | } 74 | else 75 | { 76 | throw new NotSupportedException($"Type {typeof(T)} is not supported."); 77 | } 78 | } 79 | 80 | return 81 | returnValues is null ? [] : 82 | returnValues.Count <= count ? returnValues : 83 | returnValues.Take(count); 84 | } 85 | 86 | /// 87 | /// Sends a GET request to the specified URI. 88 | /// 89 | /// The URI to send the request to. 90 | /// A cancellation token to cancel the request. 91 | /// A representing the response from the request. 92 | private async Task SendGetRequestAsync(Uri uri, CancellationToken cancellationToken = default) 93 | { 94 | using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, uri); 95 | 96 | if (!string.IsNullOrEmpty(this._apiKey)) 97 | { 98 | httpRequestMessage.Headers.Add("Ocp-Apim-Subscription-Key", this._apiKey); 99 | } 100 | 101 | return await this._httpClient.SendAsync(httpRequestMessage, cancellationToken).ConfigureAwait(false); 102 | } 103 | } 104 | public static class EnumerableExtensions 105 | { 106 | private static readonly Random _random = new Random(); 107 | 108 | public static T GetRandomItem(this IEnumerable source) 109 | { 110 | if (source == null) 111 | { 112 | throw new ArgumentNullException(nameof(source)); 113 | } 114 | 115 | var list = source.ToList(); 116 | if (!list.Any()) 117 | { 118 | throw new InvalidOperationException("Sequence contains no elements"); 119 | } 120 | 121 | int randomIndex = _random.Next(list.Count); 122 | return list[randomIndex]; 123 | } 124 | } -------------------------------------------------------------------------------- /src/HelloWorld.Agent1.Console/Configuration/Configuration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace HelloWorld.Configuration; 4 | 5 | internal class Configuration 6 | { 7 | public static IConfigurationRoot ConfigureAppSettings() 8 | { 9 | var config = new ConfigurationBuilder() 10 | .SetBasePath(Directory.GetCurrentDirectory()) 11 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 12 | .AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true) 13 | .AddJsonFile("appsettings.Production.json", optional: true, reloadOnChange: true) 14 | #if DEBUG 15 | .AddJsonFile(GetUserJsonFilename(), optional: true, reloadOnChange: true) 16 | #endif 17 | .Build(); 18 | return config; 19 | } 20 | 21 | static string GetUserJsonFilename() 22 | { 23 | return $"appsettings.user_{Environment.UserName.ToLower()}.json"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/HelloWorld.Agent1.Console/Configuration/OpenAIOptions.cs: -------------------------------------------------------------------------------- 1 | namespace HelloWorld.Configuration; 2 | internal class OpenAIOptions 3 | { 4 | public const string OpenAI = "OpenAI"; 5 | 6 | public string Source { get; set; } = string.Empty; 7 | public string ChatModelId { get; set; } = string.Empty; 8 | public string ApiKey { get; set; } = string.Empty; 9 | public string ChatDeploymentName { get; set; } = string.Empty; 10 | public string Endpoint { get; set; } = string.Empty; 11 | } 12 | -------------------------------------------------------------------------------- /src/HelloWorld.Agent1.Console/Configuration/PluginConfig.cs: -------------------------------------------------------------------------------- 1 | namespace HelloWorld.Configuration; 2 | 3 | public class PluginOptions 4 | { 5 | public const string PluginConfig = "PluginConfig"; 6 | 7 | public string BingApiKey { get; set; } = string.Empty; 8 | } 9 | -------------------------------------------------------------------------------- /src/HelloWorld.Agent1.Console/HelloWorld.Agent1.Console.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | enable 8 | SKEXP0001,SKEXP0010,SKEXP0110,SKEXP0050 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Always 19 | 20 | 21 | Always 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/HelloWorld.Agent1.Console/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.SemanticKernel": "Information" 6 | }, 7 | "Console": { 8 | "LogLevel": { 9 | "Default": "Information" 10 | } 11 | } 12 | }, 13 | "OpenAI": { 14 | "Source": "AzureOpenAI", // or "AzureOpenAI" 15 | "ChatModelId": "gpt-35-gpt", 16 | "ApiKey": "Your-OpenAI-API-Key" 17 | 18 | // Add these settings if you are using AzureOpenAI 19 | // "ChatDeploymentName": "your-chat-deployment-name", 20 | // "Endpoint": "your-endpoint" 21 | }, 22 | "PluginConfig": { 23 | "BingApiKey": "" // Add your Bing API key here 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/HelloWorld.Console/Configuration/Configuration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace HelloWorld.Console.Configuration; 4 | 5 | internal class Configuration 6 | { 7 | public static IConfigurationRoot ConfigureAppSettings() 8 | { 9 | var config = new ConfigurationBuilder() 10 | .SetBasePath(Directory.GetCurrentDirectory()) 11 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 12 | .AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true) 13 | .AddJsonFile("appsettings.Production.json", optional: true, reloadOnChange: true) 14 | #if DEBUG 15 | .AddJsonFile(GetUserJsonFilename(), optional: true, reloadOnChange: true) 16 | #endif 17 | .Build(); 18 | return config; 19 | } 20 | 21 | static string GetUserJsonFilename() 22 | { 23 | return $"appsettings.user_{Environment.UserName.ToLower()}.json"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/HelloWorld.Console/Configuration/OpenAIOptions.cs: -------------------------------------------------------------------------------- 1 | namespace HelloWorld.Console.Configuration; 2 | internal class OpenAIOptions 3 | { 4 | public const string OpenAI = "OpenAI"; 5 | 6 | public string Source { get; set; } = string.Empty; 7 | public string ChatModelId { get; set; } = string.Empty; 8 | public string ApiKey { get; set; } = string.Empty; 9 | public string ChatDeploymentName { get; set; } = string.Empty; 10 | public string Endpoint { get; set; } = string.Empty; 11 | } 12 | -------------------------------------------------------------------------------- /src/HelloWorld.Console/Configuration/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.SemanticKernel; 3 | 4 | namespace HelloWorld.Console.Configuration; 5 | 6 | internal static class ServiceCollectionExtensions 7 | { 8 | internal static IServiceCollection AddChatCompletionService(this IServiceCollection serviceCollection, OpenAIOptions openAIOptions) 9 | { 10 | switch (openAIOptions.Source) 11 | { 12 | case "AzureOpenAI": 13 | serviceCollection = serviceCollection.AddAzureOpenAIChatCompletion(openAIOptions.ChatDeploymentName, endpoint: openAIOptions.Endpoint, 14 | apiKey: openAIOptions.ApiKey, serviceId: openAIOptions.ChatModelId); 15 | break; 16 | 17 | case "OpenAI": 18 | serviceCollection = serviceCollection.AddOpenAIChatCompletion(modelId: openAIOptions.ChatModelId, apiKey: openAIOptions.ApiKey); 19 | break; 20 | 21 | default: 22 | throw new ArgumentException($"Invalid source: {openAIOptions.Source}"); 23 | } 24 | 25 | return serviceCollection; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/HelloWorld.Console/HelloWorld.Console.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Always 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/HelloWorld.Console/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.SemanticKernel; 3 | using Microsoft.Extensions.Logging; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.SemanticKernel.Connectors.OpenAI; 6 | using Microsoft.SemanticKernel.ChatCompletion; 7 | using OpenTelemetry.Metrics; 8 | using OpenTelemetry.Logs; 9 | using HelloWorld.Console.Configuration; 10 | 11 | internal class Program 12 | { 13 | static void Main(string[] args) 14 | { 15 | MainAsync(args).Wait(); 16 | } 17 | 18 | static async Task MainAsync(string[] args) 19 | { 20 | var config = Configuration.ConfigureAppSettings(); 21 | 22 | // Get Settings (all this is just so I don't have hard coded config settings here) 23 | var openAiSettings = new OpenAIOptions(); 24 | config.GetSection(OpenAIOptions.OpenAI).Bind(openAiSettings); 25 | 26 | #region OpenTelemetry Logging Provider 27 | 28 | //// Uncomment to add OpenTelemetry as a logging provider 29 | //using var meterProvider = Sdk.CreateMeterProviderBuilder() 30 | // .AddMeter("Microsoft.SemanticKernel*") 31 | // .AddConsoleExporter() 32 | // .Build(); 33 | 34 | #endregion 35 | 36 | using var loggerFactory = LoggerFactory.Create(builder => 37 | { 38 | builder.SetMinimumLevel(LogLevel.Information); 39 | 40 | #region OpenTelemetry Logging Provider 41 | 42 | // Uncomment to add OpenTelemetry as a logging provider 43 | //builder.AddOpenTelemetry(options => 44 | //{ 45 | // options.AddConsoleExporter(); 46 | // options.IncludeFormattedMessage = true; 47 | //}); 48 | 49 | #endregion 50 | 51 | builder.AddConfiguration(config); 52 | builder.AddConsole(); 53 | }); 54 | 55 | // Configure Semantic Kernel 56 | var builder = Kernel.CreateBuilder(); 57 | 58 | builder.Services.AddSingleton(loggerFactory); 59 | builder.Services.AddChatCompletionService(openAiSettings); 60 | 61 | Kernel kernel = builder.Build(); 62 | 63 | // -------------------------------------------------------------------------------------- 64 | // Exercise from Virtual Boston Azure for creating a prompt 65 | // -------------------------------------------------------------------------------------- 66 | 67 | // output today's date just for fun 68 | WriteLine($"\n----------------- DEBUG INFO -----------------"); 69 | var today = DateTime.Now.ToString("MMMM dd"); 70 | WriteLine($"Today is {today}"); 71 | WriteLine("----------------------------------------------"); 72 | 73 | IChatCompletionService chatCompletionService = kernel.GetRequiredService(); 74 | 75 | // TODO: CHALLENGE 1: does the AI respond accurately to this prompt? How to fix? 76 | var prompt = $"Tell me an interesting fact from world about an event " + 77 | $"that took place on {today}. " + 78 | $"Be sure to mention the date in history for context."; 79 | 80 | OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new() 81 | { 82 | Temperature = 0.7f, 83 | MaxTokens = 250 84 | }; 85 | 86 | var result = await chatCompletionService.GetChatMessageContentsAsync(prompt, openAIPromptExecutionSettings, kernel); 87 | 88 | WriteLine($"\nPROMPT: \n\n{prompt}"); 89 | 90 | // Write out the result 91 | foreach (var content in result) 92 | { 93 | WriteLine($"\nRESPONSE:\n{content}"); 94 | } 95 | } 96 | 97 | static void WriteLine(string message) 98 | { 99 | var currentColor = Console.ForegroundColor; 100 | Console.ForegroundColor = ConsoleColor.Green; 101 | 102 | Console.WriteLine(message); 103 | 104 | Console.ForegroundColor = currentColor; 105 | } 106 | } -------------------------------------------------------------------------------- /src/HelloWorld.Console/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.SemanticKernel": "Information" 6 | }, 7 | "Console": { 8 | "LogLevel": { 9 | "Default": "Information" 10 | } 11 | } 12 | }, 13 | "OpenAI": { 14 | "Source": "OpenAI", // or "AzureOpenAI" 15 | "ChatModelId": "gpt-35-gpt", 16 | "ApiKey": "Your-OpenAI-API-Key" 17 | 18 | // Add these settings if you are using AzureOpenAI 19 | // "ChatDeploymentName": "your-chat-deployment-name", 20 | // "Endpoint": "your-endpoint" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/HelloWorld.Planner1.Console/Configuration/Configuration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace HelloWorld.Plugin2.Console.Configuration; 4 | 5 | internal class Configuration 6 | { 7 | public static IConfigurationRoot ConfigureAppSettings() 8 | { 9 | var config = new ConfigurationBuilder() 10 | .SetBasePath(Directory.GetCurrentDirectory()) 11 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 12 | .AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true) 13 | .AddJsonFile("appsettings.Production.json", optional: true, reloadOnChange: true) 14 | #if DEBUG 15 | .AddJsonFile(GetUserJsonFilename(), optional: true, reloadOnChange: true) 16 | #endif 17 | .Build(); 18 | return config; 19 | } 20 | 21 | static string GetUserJsonFilename() 22 | { 23 | return $"appsettings.user_{Environment.UserName.ToLower()}.json"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/HelloWorld.Planner1.Console/Configuration/OpenAIOptions.cs: -------------------------------------------------------------------------------- 1 | namespace HelloWorld.Plugin2.Console.Configuration; 2 | internal class OpenAIOptions 3 | { 4 | public const string OpenAI = "OpenAI"; 5 | 6 | public string Source { get; set; } = string.Empty; 7 | public string ChatModelId { get; set; } = string.Empty; 8 | public string ApiKey { get; set; } = string.Empty; 9 | public string ChatDeploymentName { get; set; } = string.Empty; 10 | public string Endpoint { get; set; } = string.Empty; 11 | } 12 | -------------------------------------------------------------------------------- /src/HelloWorld.Planner1.Console/Configuration/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.SemanticKernel; 3 | using System.Text.Json; 4 | 5 | namespace HelloWorld.Plugin2.Console.Configuration; 6 | 7 | internal static class ServiceCollectionExtensions 8 | { 9 | internal static IServiceCollection AddChatCompletionService(this IServiceCollection serviceCollection, OpenAIOptions openAIOptions) 10 | { 11 | switch (openAIOptions.Source) 12 | { 13 | case "AzureOpenAI": 14 | serviceCollection = serviceCollection.AddAzureOpenAIChatCompletion(openAIOptions.ChatDeploymentName, endpoint: openAIOptions.Endpoint, 15 | apiKey: openAIOptions.ApiKey, serviceId: openAIOptions.ChatModelId); 16 | break; 17 | 18 | case "OpenAI": 19 | serviceCollection = serviceCollection.AddOpenAIChatCompletion(modelId: openAIOptions.ChatModelId, apiKey: openAIOptions.ApiKey); 20 | break; 21 | 22 | default: 23 | throw new ArgumentException($"Invalid source: {openAIOptions.Source}"); 24 | } 25 | 26 | return serviceCollection; 27 | } 28 | } 29 | public enum ApiLoggingLevel 30 | { 31 | None = 0, 32 | RequestOnly = 1, 33 | ResponseAndRequest = 2, 34 | } 35 | 36 | internal static class IKernelBuilderExtensions 37 | { 38 | internal static IKernelBuilder AddChatCompletionService(this IKernelBuilder kernelBuilder, OpenAIOptions openAIOptions, ApiLoggingLevel apiLoggingLevel = ApiLoggingLevel.None) 39 | { 40 | switch (openAIOptions.Source) 41 | { 42 | case "AzureOpenAI": 43 | { 44 | if (apiLoggingLevel == ApiLoggingLevel.None) 45 | { 46 | kernelBuilder = kernelBuilder.AddAzureOpenAIChatCompletion(openAIOptions.ChatDeploymentName, endpoint: openAIOptions.Endpoint, 47 | apiKey: openAIOptions.ApiKey, serviceId: openAIOptions.ChatModelId); 48 | } 49 | else 50 | { 51 | var client = CreateHttpClient(apiLoggingLevel); 52 | kernelBuilder.AddAzureOpenAIChatCompletion(openAIOptions.ChatDeploymentName, openAIOptions.Endpoint, openAIOptions.ApiKey, null, null, client); 53 | } 54 | break; 55 | } 56 | case "OpenAI": 57 | { 58 | if (apiLoggingLevel == ApiLoggingLevel.None) 59 | { 60 | kernelBuilder = kernelBuilder.AddOpenAIChatCompletion(modelId: openAIOptions.ChatModelId, apiKey: openAIOptions.ApiKey); 61 | break; 62 | } 63 | else 64 | { 65 | var client = CreateHttpClient(apiLoggingLevel); 66 | kernelBuilder.AddOpenAIChatCompletion(openAIOptions.ChatModelId, openAIOptions.ApiKey, null, null, client); 67 | } 68 | break; 69 | } 70 | default: 71 | throw new ArgumentException($"Invalid source: {openAIOptions.Source}"); 72 | } 73 | 74 | return kernelBuilder; 75 | } 76 | 77 | private static HttpClient CreateHttpClient(ApiLoggingLevel apiLoggingLevel) 78 | { 79 | HttpClientHandler httpClientHandler; 80 | if (apiLoggingLevel == ApiLoggingLevel.RequestOnly) 81 | { 82 | httpClientHandler = new RequestLoggingHttpClientHandler(); 83 | } 84 | else 85 | { 86 | httpClientHandler = new RequestAndResponseLoggingHttpClientHandler(); 87 | } 88 | var client = new HttpClient(httpClientHandler); 89 | return client; 90 | } 91 | } 92 | 93 | // Found most of this implementation via: https://github.com/microsoft/semantic-kernel/issues/5107 94 | public class RequestAndResponseLoggingHttpClientHandler : HttpClientHandler 95 | { 96 | protected override async Task SendAsync( 97 | HttpRequestMessage request, CancellationToken cancellationToken) 98 | { 99 | if (request.Content is not null) 100 | { 101 | var content = await request.Content.ReadAsStringAsync(cancellationToken); 102 | var json = JsonSerializer.Serialize(JsonSerializer.Deserialize(content), 103 | new JsonSerializerOptions { WriteIndented = true }); 104 | System.Console.WriteLine("***********************************************"); 105 | System.Console.WriteLine("Request:"); 106 | System.Console.WriteLine(json); 107 | } 108 | 109 | var result = await base.SendAsync(request, cancellationToken); 110 | 111 | if (result.Content is not null) 112 | { 113 | var content = await result.Content.ReadAsStringAsync(cancellationToken); 114 | var json = JsonSerializer.Serialize(JsonSerializer.Deserialize(content), 115 | new JsonSerializerOptions { WriteIndented = true }); 116 | System.Console.WriteLine("***********************************************"); 117 | System.Console.WriteLine("Response:"); 118 | System.Console.WriteLine(json); 119 | } 120 | 121 | return result; 122 | } 123 | } 124 | public class RequestLoggingHttpClientHandler : HttpClientHandler 125 | { 126 | protected override async Task SendAsync( 127 | HttpRequestMessage request, CancellationToken cancellationToken) 128 | { 129 | if (request.Content is not null) 130 | { 131 | var content = await request.Content.ReadAsStringAsync(cancellationToken); 132 | var json = JsonSerializer.Serialize(JsonSerializer.Deserialize(content), 133 | new JsonSerializerOptions { WriteIndented = true }); 134 | System.Console.WriteLine("***********************************************"); 135 | System.Console.WriteLine("Request:"); 136 | System.Console.WriteLine(json); 137 | } 138 | 139 | return await base.SendAsync(request, cancellationToken); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/HelloWorld.Planner1.Console/Extensions/HandlebarsPlannerExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.SemanticKernel; 2 | using Microsoft.SemanticKernel.Planning.Handlebars; 3 | 4 | namespace HelloWorld.Planner1.Console; 5 | 6 | public static class HandlebarsPlannerExtensions 7 | { 8 | // Load from an existing file or create a new plan and save it 9 | public async static Task GetOrCreatePlanAsync(this HandlebarsPlanner planner, string filename, 10 | Kernel kernel, string goal, KernelArguments? arguments = null) 11 | { 12 | if (Exists(filename)) 13 | { 14 | return planner.Load(filename); 15 | } 16 | else 17 | { 18 | return await planner.CreateAndSavePlanAsync(filename, kernel, goal, arguments); 19 | } 20 | } 21 | // Create a new plan then save it 22 | public async static Task CreateAndSavePlanAsync(this HandlebarsPlanner planner, string filename, 23 | Kernel kernel, string goal, KernelArguments? arguments = null) 24 | { 25 | var plan = await planner.CreatePlanAsync(kernel, goal, arguments); 26 | 27 | plan.Save(filename); 28 | 29 | return plan; 30 | } 31 | 32 | public static bool Exists(string filename) 33 | { 34 | return File.Exists(filename); 35 | } 36 | 37 | public static HandlebarsPlan Load(this HandlebarsPlanner planner, string filename) 38 | { 39 | // Load the saved plan 40 | var savedPlan = File.ReadAllText(filename); 41 | 42 | // Populate intance 43 | return new HandlebarsPlan(savedPlan); 44 | } 45 | 46 | public static void Save(this HandlebarsPlan plan, string filename) 47 | { 48 | File.WriteAllText(filename, plan.ToString()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/HelloWorld.Planner1.Console/HelloWorld.Planner1.Console.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | enable 8 | SKEXP0060 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Always 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/HelloWorld.Planner1.Console/Plugins/DailyFactPlugin.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.SemanticKernel; 2 | using System.ComponentModel; 3 | using System.ComponentModel.DataAnnotations; 4 | 5 | namespace HelloWorld.Plugin.Console.Plugins; 6 | 7 | public class DailyFactPlugin 8 | { 9 | private const string DESCRIPTION = "Provides interesting historic facts for the current date."; 10 | private const string TEMPLATE = @"Tell me an interesting fact from world 11 | about an event that took place on {{$today}}. 12 | Be sure to mention the date in history for context."; 13 | private const string GET_DAILY_FACT_FUNC = "GetDailyFactFunc"; 14 | internal const string PLUGIN_NAME = "DailyFactPlugin"; 15 | internal const string GET_DAILY_FACT = "GetDailyFact"; 16 | 17 | private readonly KernelFunction _dailyFact; 18 | private readonly KernelFunction _currentDay; 19 | 20 | public DailyFactPlugin() 21 | { 22 | PromptExecutionSettings settings = new() 23 | { 24 | ExtensionData = new Dictionary() 25 | { 26 | { "Temperature", 0.7 }, 27 | { "MaxTokens", 250 } 28 | } 29 | 30 | }; 31 | 32 | _dailyFact = KernelFunctionFactory.CreateFromPrompt(TEMPLATE, 33 | functionName: GET_DAILY_FACT_FUNC, 34 | executionSettings: settings); 35 | 36 | _currentDay = KernelFunctionFactory.CreateFromMethod(() => DateTime.Now.ToString("MMMM dd"), "GetCurrentDay"); 37 | } 38 | 39 | [KernelFunction, Description(DESCRIPTION)] 40 | public async Task GetDailyFact([Description("Current day"), Required] string today, Kernel kernel) 41 | { 42 | var result = await _dailyFact.InvokeAsync(kernel, new() { ["today"] = today }).ConfigureAwait(false); 43 | 44 | return result.ToString(); 45 | } 46 | 47 | [KernelFunction, Description("Retrieves the current day.")] 48 | public async Task GetCurrentDay(Kernel kernel) 49 | { 50 | var today = await _currentDay.InvokeAsync(kernel); 51 | 52 | return today.ToString(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/HelloWorld.Planner1.Console/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.SemanticKernel; 3 | using Microsoft.Extensions.Logging; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using OpenTelemetry.Metrics; 6 | using OpenTelemetry.Logs; 7 | using HelloWorld.Plugin.Console.Plugins; 8 | using HelloWorld.Plugin2.Console.Configuration; 9 | using Microsoft.SemanticKernel.Planning.Handlebars; 10 | using HelloWorld.Planner1.Console; 11 | 12 | internal class Program 13 | { 14 | static void Main(string[] args) 15 | { 16 | MainAsync(args).Wait(); 17 | } 18 | 19 | static async Task MainAsync(string[] args) 20 | { 21 | var config = Configuration.ConfigureAppSettings(); 22 | 23 | // Get Settings (all this is just so I don't have hard coded config settings here) 24 | var openAiSettings = new OpenAIOptions(); 25 | config.GetSection(OpenAIOptions.OpenAI).Bind(openAiSettings); 26 | 27 | using var loggerFactory = LoggerFactory.Create(builder => 28 | { 29 | builder.SetMinimumLevel(LogLevel.Information); 30 | 31 | builder.AddConfiguration(config); 32 | builder.AddConsole(); 33 | }); 34 | 35 | // Configure Semantic Kernel 36 | var builder = Kernel.CreateBuilder(); 37 | 38 | builder.Services.AddSingleton(loggerFactory); 39 | builder.AddChatCompletionService(openAiSettings); 40 | //builder.AddChatCompletionService(openAiSettings, ApiLoggingLevel.ResponseAndRequest); // use this line to see the JSON between SK and OpenAI 41 | 42 | // -------------------------------------------------------------------------------------- 43 | // Exercise from Virtual Boston Azure for creating a prompt 44 | // -------------------------------------------------------------------------------------- 45 | 46 | builder.Plugins.AddFromType(); 47 | 48 | Kernel kernel = builder.Build(); 49 | 50 | // TODO: CHALLENGE 1: does the AI respond accurately to this prompt? How to fix? 51 | var prompt = $"Tell me an interesting fact from world about an event " + 52 | $"that took place on today's date. " + 53 | $"Be sure to mention the date in history for context."; 54 | 55 | var planner = new HandlebarsPlanner(new HandlebarsPlannerOptions() { AllowLoops = true }); 56 | 57 | var plan = await planner.GetOrCreatePlanAsync("SavedPlan.hbs", kernel, prompt); 58 | 59 | WriteLine($"\nPLAN: \n\n{plan}"); 60 | 61 | var result = await plan.InvokeAsync(kernel); 62 | 63 | WriteLine($"\nRESPONSE: \n\n{result}"); 64 | } 65 | 66 | static void WriteLine(string message) 67 | { 68 | Console.WriteLine("----------------------------------------------"); 69 | 70 | Console.WriteLine(message); 71 | 72 | Console.WriteLine("----------------------------------------------"); 73 | } 74 | } -------------------------------------------------------------------------------- /src/HelloWorld.Planner1.Console/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.SemanticKernel": "Information" 6 | }, 7 | "Console": { 8 | "LogLevel": { 9 | "Default": "Information" 10 | } 11 | } 12 | }, 13 | "OpenAI": { 14 | "Source": "OpenAI", // or "AzureOpenAI" 15 | "ChatModelId": "gpt-35-gpt", 16 | "ApiKey": "Your-OpenAI-API-Key" 17 | 18 | // Add these settings if you are using AzureOpenAI 19 | // "ChatDeploymentName": "your-chat-deployment-name", 20 | // "Endpoint": "your-endpoint" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/HelloWorld.Planner2.Console/Configuration/Configuration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace HelloWorld.Plugin2.Console.Configuration; 4 | 5 | internal class Configuration 6 | { 7 | public static IConfigurationRoot ConfigureAppSettings() 8 | { 9 | var config = new ConfigurationBuilder() 10 | .SetBasePath(Directory.GetCurrentDirectory()) 11 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 12 | .AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true) 13 | .AddJsonFile("appsettings.Production.json", optional: true, reloadOnChange: true) 14 | #if DEBUG 15 | .AddJsonFile(GetUserJsonFilename(), optional: true, reloadOnChange: true) 16 | #endif 17 | .Build(); 18 | return config; 19 | } 20 | 21 | static string GetUserJsonFilename() 22 | { 23 | return $"appsettings.user_{Environment.UserName.ToLower()}.json"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/HelloWorld.Planner2.Console/Configuration/OpenAIOptions.cs: -------------------------------------------------------------------------------- 1 | namespace HelloWorld.Plugin2.Console.Configuration; 2 | internal class OpenAIOptions 3 | { 4 | public const string OpenAI = "OpenAI"; 5 | 6 | public string Source { get; set; } = string.Empty; 7 | public string ChatModelId { get; set; } = string.Empty; 8 | public string ApiKey { get; set; } = string.Empty; 9 | public string ChatDeploymentName { get; set; } = string.Empty; 10 | public string Endpoint { get; set; } = string.Empty; 11 | } 12 | -------------------------------------------------------------------------------- /src/HelloWorld.Planner2.Console/Configuration/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.SemanticKernel; 3 | using System.Text.Json; 4 | 5 | namespace HelloWorld.Plugin2.Console.Configuration; 6 | 7 | internal static class ServiceCollectionExtensions 8 | { 9 | internal static IServiceCollection AddChatCompletionService(this IServiceCollection serviceCollection, OpenAIOptions openAIOptions) 10 | { 11 | switch (openAIOptions.Source) 12 | { 13 | case "AzureOpenAI": 14 | serviceCollection = serviceCollection.AddAzureOpenAIChatCompletion(openAIOptions.ChatDeploymentName, endpoint: openAIOptions.Endpoint, 15 | apiKey: openAIOptions.ApiKey, serviceId: openAIOptions.ChatModelId); 16 | break; 17 | 18 | case "OpenAI": 19 | serviceCollection = serviceCollection.AddOpenAIChatCompletion(modelId: openAIOptions.ChatModelId, apiKey: openAIOptions.ApiKey); 20 | break; 21 | 22 | default: 23 | throw new ArgumentException($"Invalid source: {openAIOptions.Source}"); 24 | } 25 | 26 | return serviceCollection; 27 | } 28 | } 29 | public enum ApiLoggingLevel 30 | { 31 | None = 0, 32 | RequestOnly = 1, 33 | ResponseAndRequest = 2, 34 | } 35 | 36 | internal static class IKernelBuilderExtensions 37 | { 38 | internal static IKernelBuilder AddChatCompletionService(this IKernelBuilder kernelBuilder, OpenAIOptions openAIOptions, ApiLoggingLevel apiLoggingLevel = ApiLoggingLevel.None) 39 | { 40 | switch (openAIOptions.Source) 41 | { 42 | case "AzureOpenAI": 43 | { 44 | if (apiLoggingLevel == ApiLoggingLevel.None) 45 | { 46 | kernelBuilder = kernelBuilder.AddAzureOpenAIChatCompletion(openAIOptions.ChatDeploymentName, endpoint: openAIOptions.Endpoint, 47 | apiKey: openAIOptions.ApiKey, serviceId: openAIOptions.ChatModelId); 48 | } 49 | else 50 | { 51 | var client = CreateHttpClient(apiLoggingLevel); 52 | kernelBuilder.AddAzureOpenAIChatCompletion(openAIOptions.ChatDeploymentName, openAIOptions.Endpoint, openAIOptions.ApiKey, null, null, client); 53 | } 54 | break; 55 | } 56 | case "OpenAI": 57 | { 58 | if (apiLoggingLevel == ApiLoggingLevel.None) 59 | { 60 | kernelBuilder = kernelBuilder.AddOpenAIChatCompletion(modelId: openAIOptions.ChatModelId, apiKey: openAIOptions.ApiKey); 61 | break; 62 | } 63 | else 64 | { 65 | var client = CreateHttpClient(apiLoggingLevel); 66 | kernelBuilder.AddOpenAIChatCompletion(openAIOptions.ChatModelId, openAIOptions.ApiKey, null, null, client); 67 | } 68 | break; 69 | } 70 | default: 71 | throw new ArgumentException($"Invalid source: {openAIOptions.Source}"); 72 | } 73 | 74 | return kernelBuilder; 75 | } 76 | 77 | private static HttpClient CreateHttpClient(ApiLoggingLevel apiLoggingLevel) 78 | { 79 | HttpClientHandler httpClientHandler; 80 | if (apiLoggingLevel == ApiLoggingLevel.RequestOnly) 81 | { 82 | httpClientHandler = new RequestLoggingHttpClientHandler(); 83 | } 84 | else 85 | { 86 | httpClientHandler = new RequestAndResponseLoggingHttpClientHandler(); 87 | } 88 | var client = new HttpClient(httpClientHandler); 89 | return client; 90 | } 91 | } 92 | 93 | // Found most of this implementation via: https://github.com/microsoft/semantic-kernel/issues/5107 94 | public class RequestAndResponseLoggingHttpClientHandler : HttpClientHandler 95 | { 96 | protected override async Task SendAsync( 97 | HttpRequestMessage request, CancellationToken cancellationToken) 98 | { 99 | if (request.Content is not null) 100 | { 101 | var content = await request.Content.ReadAsStringAsync(cancellationToken); 102 | var json = JsonSerializer.Serialize(JsonSerializer.Deserialize(content), 103 | new JsonSerializerOptions { WriteIndented = true }); 104 | System.Console.WriteLine("***********************************************"); 105 | System.Console.WriteLine("Request:"); 106 | System.Console.WriteLine(json); 107 | } 108 | 109 | var result = await base.SendAsync(request, cancellationToken); 110 | 111 | if (result.Content is not null) 112 | { 113 | var content = await result.Content.ReadAsStringAsync(cancellationToken); 114 | var json = JsonSerializer.Serialize(JsonSerializer.Deserialize(content), 115 | new JsonSerializerOptions { WriteIndented = true }); 116 | System.Console.WriteLine("***********************************************"); 117 | System.Console.WriteLine("Response:"); 118 | System.Console.WriteLine(json); 119 | } 120 | 121 | return result; 122 | } 123 | } 124 | public class RequestLoggingHttpClientHandler : HttpClientHandler 125 | { 126 | protected override async Task SendAsync( 127 | HttpRequestMessage request, CancellationToken cancellationToken) 128 | { 129 | if (request.Content is not null) 130 | { 131 | var content = await request.Content.ReadAsStringAsync(cancellationToken); 132 | var json = JsonSerializer.Serialize(JsonSerializer.Deserialize(content), 133 | new JsonSerializerOptions { WriteIndented = true }); 134 | System.Console.WriteLine("***********************************************"); 135 | System.Console.WriteLine("Request:"); 136 | System.Console.WriteLine(json); 137 | } 138 | 139 | return await base.SendAsync(request, cancellationToken); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/HelloWorld.Planner2.Console/HelloWorld.Planner2.Console.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | enable 8 | SKEXP0060 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Always 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/HelloWorld.Planner2.Console/Plugins/DailyFactPlugin.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.SemanticKernel; 2 | using System.ComponentModel; 3 | using System.ComponentModel.DataAnnotations; 4 | 5 | namespace HelloWorld.Plugin.Console.Plugins; 6 | 7 | public class DailyFactPlugin 8 | { 9 | private const string DESCRIPTION = "Provides interesting historic facts for the current date."; 10 | private const string TEMPLATE = @"Tell me an interesting fact from world 11 | about an event that took place on {{$today}}. 12 | Be sure to mention the date in history for context."; 13 | private const string GET_DAILY_FACT_FUNC = "GetDailyFactFunc"; 14 | internal const string PLUGIN_NAME = "DailyFactPlugin"; 15 | internal const string GET_DAILY_FACT = "GetDailyFact"; 16 | 17 | private readonly KernelFunction _dailyFact; 18 | private readonly KernelFunction _currentDay; 19 | 20 | public DailyFactPlugin() 21 | { 22 | PromptExecutionSettings settings = new() 23 | { 24 | ExtensionData = new Dictionary() 25 | { 26 | { "Temperature", 0.7 }, 27 | { "MaxTokens", 250 } 28 | } 29 | 30 | }; 31 | 32 | _dailyFact = KernelFunctionFactory.CreateFromPrompt(TEMPLATE, 33 | functionName: GET_DAILY_FACT_FUNC, 34 | executionSettings: settings); 35 | 36 | _currentDay = KernelFunctionFactory.CreateFromMethod(() => DateTime.Now.ToString("MMMM dd"), "GetCurrentDay"); 37 | } 38 | 39 | [KernelFunction, Description(DESCRIPTION)] 40 | public async Task GetDailyFact([Description("Current day"), Required] string today, Kernel kernel) 41 | { 42 | var result = await _dailyFact.InvokeAsync(kernel, new() { ["today"] = today }).ConfigureAwait(false); 43 | 44 | return result.ToString(); 45 | } 46 | 47 | [KernelFunction, Description("Retrieves the current day.")] 48 | public async Task GetCurrentDay(Kernel kernel) 49 | { 50 | var today = await _currentDay.InvokeAsync(kernel); 51 | 52 | return today.ToString(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/HelloWorld.Planner2.Console/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.SemanticKernel; 3 | using Microsoft.Extensions.Logging; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using OpenTelemetry.Metrics; 6 | using OpenTelemetry.Logs; 7 | using HelloWorld.Plugin.Console.Plugins; 8 | using HelloWorld.Plugin2.Console.Configuration; 9 | using Microsoft.SemanticKernel.Planning; 10 | using System.Text.Json; 11 | 12 | internal class Program 13 | { 14 | static void Main(string[] args) 15 | { 16 | MainAsync(args).Wait(); 17 | } 18 | 19 | static async Task MainAsync(string[] args) 20 | { 21 | 22 | var config = Configuration.ConfigureAppSettings(); 23 | 24 | // Get Settings (all this is just so I don't have hard coded config settings here) 25 | var openAiSettings = new OpenAIOptions(); 26 | config.GetSection(OpenAIOptions.OpenAI).Bind(openAiSettings); 27 | 28 | using var loggerFactory = LoggerFactory.Create(builder => 29 | { 30 | builder.SetMinimumLevel(LogLevel.Information); 31 | 32 | builder.AddConfiguration(config); 33 | builder.AddConsole(); 34 | }); 35 | 36 | // Configure Semantic Kernel 37 | var builder = Kernel.CreateBuilder(); 38 | 39 | builder.Services.AddSingleton(loggerFactory); 40 | builder.AddChatCompletionService(openAiSettings); 41 | //builder.AddChatCompletionService(openAiSettings, ApiLoggingLevel.ResponseAndRequest); // use this line to see the JSON between SK and OpenAI 42 | 43 | // -------------------------------------------------------------------------------------- 44 | // Exercise from Virtual Boston Azure for creating a prompt 45 | // -------------------------------------------------------------------------------------- 46 | 47 | builder.Plugins.AddFromType(); 48 | 49 | Kernel kernel = builder.Build(); 50 | 51 | // TODO: CHALLENGE 1: does the AI respond accurately to this prompt? How to fix? 52 | var prompt = $"Tell me an interesting fact from world about an event " + 53 | $"that took place on today's date. " + 54 | $"Be sure to mention the date in history for context."; 55 | 56 | var planner = new FunctionCallingStepwisePlanner(new FunctionCallingStepwisePlannerOptions 57 | { 58 | MaxIterations = 15, 59 | MaxTokens = 2500 60 | }); 61 | 62 | var result = await planner.ExecuteAsync(kernel, prompt); 63 | 64 | // Uncomment to see the chat history 65 | //WriteLine($"HISTORY:\n{JsonSerializer.Serialize(result.ChatHistory, new JsonSerializerOptions() 66 | //{ 67 | // WriteIndented = true 68 | //})}"); 69 | 70 | WriteLine($"\nRESPONSE: \n\n{result.FinalAnswer}"); 71 | 72 | } 73 | 74 | static void WriteLine(string message) 75 | { 76 | Console.WriteLine("----------------------------------------------"); 77 | 78 | Console.WriteLine(message); 79 | 80 | Console.WriteLine("----------------------------------------------"); 81 | } 82 | } -------------------------------------------------------------------------------- /src/HelloWorld.Planner2.Console/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.SemanticKernel": "Information" 6 | }, 7 | "Console": { 8 | "LogLevel": { 9 | "Default": "Information" 10 | } 11 | } 12 | }, 13 | "OpenAI": { 14 | "Source": "OpenAI", // or "AzureOpenAI" 15 | "ChatModelId": "gpt-35-gpt", 16 | "ApiKey": "Your-OpenAI-API-Key" 17 | 18 | // Add these settings if you are using AzureOpenAI 19 | // "ChatDeploymentName": "your-chat-deployment-name", 20 | // "Endpoint": "your-endpoint" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/HelloWorld.Plugin1.Console/Configuration/Configuration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace HelloWorld.Plugin1.Console.Configuration; 4 | 5 | internal class Configuration 6 | { 7 | public static IConfigurationRoot ConfigureAppSettings() 8 | { 9 | var config = new ConfigurationBuilder() 10 | .SetBasePath(Directory.GetCurrentDirectory()) 11 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 12 | .AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true) 13 | .AddJsonFile("appsettings.Production.json", optional: true, reloadOnChange: true) 14 | #if DEBUG 15 | .AddJsonFile(GetUserJsonFilename(), optional: true, reloadOnChange: true) 16 | #endif 17 | .Build(); 18 | return config; 19 | } 20 | 21 | static string GetUserJsonFilename() 22 | { 23 | return $"appsettings.user_{Environment.UserName.ToLower()}.json"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/HelloWorld.Plugin1.Console/Configuration/OpenAIOptions.cs: -------------------------------------------------------------------------------- 1 | namespace HelloWorld.Plugin1.Console.Configuration; 2 | internal class OpenAIOptions 3 | { 4 | public const string OpenAI = "OpenAI"; 5 | 6 | public string Source { get; set; } = string.Empty; 7 | public string ChatModelId { get; set; } = string.Empty; 8 | public string ApiKey { get; set; } = string.Empty; 9 | public string ChatDeploymentName { get; set; } = string.Empty; 10 | public string Endpoint { get; set; } = string.Empty; 11 | } 12 | -------------------------------------------------------------------------------- /src/HelloWorld.Plugin1.Console/Configuration/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.SemanticKernel; 3 | 4 | namespace HelloWorld.Plugin1.Console.Configuration; 5 | 6 | internal static class ServiceCollectionExtensions 7 | { 8 | internal static IServiceCollection AddChatCompletionService(this IServiceCollection serviceCollection, OpenAIOptions openAIOptions) 9 | { 10 | switch (openAIOptions.Source) 11 | { 12 | case "AzureOpenAI": 13 | serviceCollection = serviceCollection.AddAzureOpenAIChatCompletion(openAIOptions.ChatDeploymentName, endpoint: openAIOptions.Endpoint, 14 | apiKey: openAIOptions.ApiKey, serviceId: openAIOptions.ChatModelId); 15 | break; 16 | 17 | case "OpenAI": 18 | serviceCollection = serviceCollection.AddOpenAIChatCompletion(modelId: openAIOptions.ChatModelId, apiKey: openAIOptions.ApiKey); 19 | break; 20 | 21 | default: 22 | throw new ArgumentException($"Invalid source: {openAIOptions.Source}"); 23 | } 24 | 25 | return serviceCollection; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/HelloWorld.Plugin1.Console/HelloWorld.Plugin1.Console.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | Always 20 | 21 | 22 | Always 23 | 24 | 25 | Always 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/HelloWorld.Plugin1.Console/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.SemanticKernel; 3 | using Microsoft.Extensions.Logging; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Azure.AI.OpenAI; 6 | using OpenTelemetry; 7 | using OpenTelemetry.Metrics; 8 | using OpenTelemetry.Logs; 9 | using HelloWorld.Plugin1.Console.Configuration; 10 | 11 | internal class Program 12 | { 13 | static void Main(string[] args) 14 | { 15 | MainAsync(args).Wait(); 16 | } 17 | 18 | static async Task MainAsync(string[] args) 19 | { 20 | var config = Configuration.ConfigureAppSettings(); 21 | 22 | // Get Settings (all this is just so I don't have hard coded config settings here) 23 | var openAiSettings = new OpenAIOptions(); 24 | config.GetSection(OpenAIOptions.OpenAI).Bind(openAiSettings); 25 | 26 | #region OpenTelemetry Logging Provider 27 | 28 | // Uncomment to add OpenTelemetry as a logging provider 29 | //using var meterProvider = Sdk.CreateMeterProviderBuilder() 30 | // .AddMeter("Microsoft.SemanticKernel*") 31 | // .AddConsoleExporter() 32 | // .Build(); 33 | 34 | #endregion 35 | 36 | using var loggerFactory = LoggerFactory.Create(builder => 37 | { 38 | builder.SetMinimumLevel(LogLevel.Trace); 39 | 40 | #region OpenTelemetry Logging Provider 41 | 42 | // Uncomment to add OpenTelemetry as a logging provider 43 | //builder.AddOpenTelemetry(options => 44 | //{ 45 | // options.AddConsoleExporter(); 46 | // options.IncludeFormattedMessage = true; 47 | //}); 48 | 49 | #endregion 50 | 51 | builder.AddConfiguration(config); 52 | builder.AddConsole(); 53 | }); 54 | 55 | // Configure Semantic Kernel 56 | var builder = Kernel.CreateBuilder(); 57 | 58 | builder.Services.AddSingleton(loggerFactory); 59 | builder.Services.AddChatCompletionService(openAiSettings); 60 | 61 | // -------------------------------------------------------------------------------------- 62 | // Exercise from Virtual Boston Azure for creating a prompt 63 | // -------------------------------------------------------------------------------------- 64 | 65 | Kernel kernel = builder.Build(); 66 | 67 | // output today's date just for fun 68 | var today = DateTime.Now.ToString("MMMM dd"); 69 | WriteLine($"Today is {today}"); 70 | 71 | // Using a Prompt ----------------------------------------- 72 | var prompts = kernel.CreatePluginFromPromptDirectory("Prompts"); 73 | 74 | var result = await kernel.InvokeAsync( 75 | prompts["DailyFact"], 76 | new() { 77 | { "today", today }, 78 | } 79 | ); 80 | 81 | WriteLine($"\nRESPONSE: \n\n{result}"); 82 | } 83 | 84 | static void WriteLine(string message) 85 | { 86 | Console.WriteLine("----------------------------------------------"); 87 | 88 | Console.WriteLine(message); 89 | 90 | Console.WriteLine("----------------------------------------------"); 91 | } 92 | } -------------------------------------------------------------------------------- /src/HelloWorld.Plugin1.Console/Prompts/DailyFact/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema": 1, 3 | "description": "Provides interesting historic facts for the current date.", 4 | "execution_settings": { 5 | "default": { 6 | "max_tokens": 250, 7 | "temperature": 0.7 8 | } 9 | }, 10 | "input_variables": [ 11 | { 12 | "name": "today", 13 | "description": "Current date", 14 | "required": true 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /src/HelloWorld.Plugin1.Console/Prompts/DailyFact/skprompt.txt: -------------------------------------------------------------------------------- 1 | Tell me an interesting fact from world 2 | about an event that took place on {{$today}}. 3 | Be sure to mention the date in history for context. -------------------------------------------------------------------------------- /src/HelloWorld.Plugin1.Console/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.SemanticKernel": "Information" 6 | }, 7 | "Console": { 8 | "LogLevel": { 9 | "Default": "Information" 10 | } 11 | } 12 | }, 13 | "OpenAI": { 14 | "Source": "OpenAI", // or "AzureOpenAI" 15 | "ChatModelId": "gpt-35-gpt", 16 | "ApiKey": "Your-OpenAI-API-Key" 17 | 18 | // Add these settings if you are using AzureOpenAI 19 | // "ChatDeploymentName": "your-chat-deployment-name", 20 | // "Endpoint": "your-endpoint" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/HelloWorld.Plugin2.Console/Configuration/Configuration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace HelloWorld.Plugin2.Console.Configuration; 4 | 5 | internal class Configuration 6 | { 7 | public static IConfigurationRoot ConfigureAppSettings() 8 | { 9 | var config = new ConfigurationBuilder() 10 | .SetBasePath(Directory.GetCurrentDirectory()) 11 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 12 | .AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true) 13 | .AddJsonFile("appsettings.Production.json", optional: true, reloadOnChange: true) 14 | #if DEBUG 15 | .AddJsonFile(GetUserJsonFilename(), optional: true, reloadOnChange: true) 16 | #endif 17 | .Build(); 18 | return config; 19 | } 20 | 21 | static string GetUserJsonFilename() 22 | { 23 | return $"appsettings.user_{Environment.UserName.ToLower()}.json"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/HelloWorld.Plugin2.Console/Configuration/OpenAIOptions.cs: -------------------------------------------------------------------------------- 1 | namespace HelloWorld.Plugin2.Console.Configuration; 2 | internal class OpenAIOptions 3 | { 4 | public const string OpenAI = "OpenAI"; 5 | 6 | public string Source { get; set; } = string.Empty; 7 | public string ChatModelId { get; set; } = string.Empty; 8 | public string ApiKey { get; set; } = string.Empty; 9 | public string ChatDeploymentName { get; set; } = string.Empty; 10 | public string Endpoint { get; set; } = string.Empty; 11 | } 12 | -------------------------------------------------------------------------------- /src/HelloWorld.Plugin2.Console/Configuration/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.SemanticKernel; 3 | using System.Text.Json; 4 | 5 | 6 | namespace HelloWorld.Plugin2.Console.Configuration; 7 | 8 | internal static class ServiceCollectionExtensions 9 | { 10 | internal static IServiceCollection AddChatCompletionService(this IServiceCollection serviceCollection, OpenAIOptions openAIOptions) 11 | { 12 | switch (openAIOptions.Source) 13 | { 14 | case "AzureOpenAI": 15 | serviceCollection = serviceCollection.AddAzureOpenAIChatCompletion(openAIOptions.ChatDeploymentName, endpoint: openAIOptions.Endpoint, 16 | apiKey: openAIOptions.ApiKey, serviceId: openAIOptions.ChatModelId); 17 | break; 18 | 19 | case "OpenAI": 20 | serviceCollection = serviceCollection.AddOpenAIChatCompletion(modelId: openAIOptions.ChatModelId, apiKey: openAIOptions.ApiKey); 21 | break; 22 | 23 | default: 24 | throw new ArgumentException($"Invalid source: {openAIOptions.Source}"); 25 | } 26 | 27 | return serviceCollection; 28 | } 29 | } 30 | 31 | // Found most of this implementation via: https://github.com/microsoft/semantic-kernel/issues/5107 32 | public class RequestAndResponseLoggingHttpClientHandler : HttpClientHandler 33 | { 34 | protected override async Task SendAsync( 35 | HttpRequestMessage request, CancellationToken cancellationToken) 36 | { 37 | if (request.Content is not null) 38 | { 39 | var content = await request.Content.ReadAsStringAsync(cancellationToken); 40 | var json = JsonSerializer.Serialize(JsonSerializer.Deserialize(content), 41 | new JsonSerializerOptions { WriteIndented = true }); 42 | System.Console.WriteLine("Request:"); 43 | System.Console.WriteLine(json); 44 | } 45 | 46 | var result = await base.SendAsync(request, cancellationToken); 47 | 48 | if (result.Content is not null) 49 | { 50 | var content = await result.Content.ReadAsStringAsync(cancellationToken); 51 | var json = JsonSerializer.Serialize(JsonSerializer.Deserialize(content), 52 | new JsonSerializerOptions { WriteIndented = true }); 53 | System.Console.WriteLine("Response:"); 54 | System.Console.WriteLine(json); 55 | } 56 | 57 | return result; 58 | } 59 | } 60 | public class RequestLoggingHttpClientHandler : HttpClientHandler 61 | { 62 | protected override async Task SendAsync( 63 | HttpRequestMessage request, CancellationToken cancellationToken) 64 | { 65 | if (request.Content is not null) 66 | { 67 | var content = await request.Content.ReadAsStringAsync(cancellationToken); 68 | var json = JsonSerializer.Serialize(JsonSerializer.Deserialize(content), 69 | new JsonSerializerOptions { WriteIndented = true }); 70 | System.Console.WriteLine("Request:"); 71 | System.Console.WriteLine(json); 72 | } 73 | 74 | return await base.SendAsync(request, cancellationToken); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/HelloWorld.Plugin2.Console/HelloWorld.Plugin2.Console.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Always 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/HelloWorld.Plugin2.Console/Plugins/DailyFactPlugin.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.SemanticKernel; 2 | using System.ComponentModel; 3 | 4 | namespace HelloWorld.Plugin.Console.Plugins; 5 | 6 | public class DailyFactPlugin 7 | { 8 | private const string DESCRIPTION = "Provides interesting historic facts for the current date."; 9 | private const string TEMPLATE = @"Tell me an interesting fact from world 10 | about an event that took place on {{$today}}. 11 | Be sure to mention the date in history for context."; 12 | private const string GET_DAILY_FACT_FUNC = "GetDailyFactFunc"; 13 | internal const string PLUGIN_NAME = "DailyFactPlugin"; 14 | internal const string GET_DAILY_FACT = "GetDailyFact"; 15 | 16 | private readonly KernelFunction _dailyFact; 17 | 18 | public DailyFactPlugin() 19 | { 20 | PromptExecutionSettings settings = new() 21 | { 22 | ExtensionData = new Dictionary() 23 | { 24 | { "Temperature", 0.7 }, 25 | { "MaxTokens", 250 } 26 | } 27 | 28 | }; 29 | 30 | _dailyFact = KernelFunctionFactory.CreateFromPrompt(TEMPLATE, 31 | functionName: GET_DAILY_FACT_FUNC, 32 | description: DESCRIPTION, 33 | executionSettings: settings); 34 | } 35 | 36 | [KernelFunction] 37 | public async Task GetDailyFact([Description("Current date")] string today, Kernel kernel) 38 | { 39 | var result = await _dailyFact.InvokeAsync(kernel, new() { ["today"] = today }).ConfigureAwait(false); 40 | 41 | return result.ToString(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/HelloWorld.Plugin2.Console/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.SemanticKernel; 3 | using Microsoft.Extensions.Logging; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using OpenTelemetry.Metrics; 6 | using OpenTelemetry.Logs; 7 | using HelloWorld.Plugin.Console.Plugins; 8 | using HelloWorld.Plugin2.Console.Configuration; 9 | 10 | internal class Program 11 | { 12 | static void Main(string[] args) 13 | { 14 | MainAsync(args).Wait(); 15 | } 16 | 17 | static async Task MainAsync(string[] args) 18 | { 19 | var config = Configuration.ConfigureAppSettings(); 20 | 21 | // Get Settings (all this is just so I don't have hard coded config settings here) 22 | var openAiSettings = new OpenAIOptions(); 23 | config.GetSection(OpenAIOptions.OpenAI).Bind(openAiSettings); 24 | 25 | using var loggerFactory = LoggerFactory.Create(builder => 26 | { 27 | builder.SetMinimumLevel(LogLevel.Information); 28 | builder.AddConfiguration(config); 29 | builder.AddConsole(); 30 | }); 31 | 32 | // Configure Semantic Kernel 33 | var builder = Kernel.CreateBuilder(); 34 | 35 | builder.Services.AddSingleton(loggerFactory); 36 | builder.Services.AddChatCompletionService(openAiSettings); 37 | 38 | // -------------------------------------------------------------------------------------- 39 | // Exercise from Virtual Boston Azure for creating a prompt 40 | // -------------------------------------------------------------------------------------- 41 | 42 | builder.Plugins.AddFromType(); 43 | 44 | Kernel kernel = builder.Build(); 45 | 46 | // output today's date just for fun 47 | var today = DateTime.Now.ToString("MMMM dd"); 48 | WriteLine($"Today is {today}"); 49 | 50 | // Using a function with a parameter ----------------------------- 51 | var funcargs = new KernelArguments { ["today"] = today }; 52 | 53 | var funcresult = await kernel.InvokeAsync( 54 | DailyFactPlugin.PLUGIN_NAME, 55 | DailyFactPlugin.GET_DAILY_FACT, 56 | funcargs 57 | ); 58 | 59 | WriteLine($"\nRESPONSE: \n\n{funcresult}"); 60 | } 61 | 62 | static void WriteLine(string message) 63 | { 64 | Console.WriteLine("----------------------------------------------"); 65 | 66 | Console.WriteLine(message); 67 | 68 | Console.WriteLine("----------------------------------------------"); 69 | } 70 | } -------------------------------------------------------------------------------- /src/HelloWorld.Plugin2.Console/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.SemanticKernel": "Information" 6 | }, 7 | "Console": { 8 | "LogLevel": { 9 | "Default": "Information" 10 | } 11 | } 12 | }, 13 | "OpenAI": { 14 | "Source": "OpenAI", // or "AzureOpenAI" 15 | "ChatModelId": "gpt-35-gpt", 16 | "ApiKey": "Your-OpenAI-API-Key" 17 | 18 | // Add these settings if you are using AzureOpenAI 19 | // "ChatDeploymentName": "your-chat-deployment-name", 20 | // "Endpoint": "your-endpoint" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/HelloWorld.Plugin3.Console/Configuration/Configuration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace HelloWorld.Plugin2.Console.Configuration; 4 | 5 | internal class Configuration 6 | { 7 | public static IConfigurationRoot ConfigureAppSettings() 8 | { 9 | var config = new ConfigurationBuilder() 10 | .SetBasePath(Directory.GetCurrentDirectory()) 11 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 12 | .AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true) 13 | .AddJsonFile("appsettings.Production.json", optional: true, reloadOnChange: true) 14 | #if DEBUG 15 | .AddJsonFile(GetUserJsonFilename(), optional: true, reloadOnChange: true) 16 | #endif 17 | .Build(); 18 | return config; 19 | } 20 | 21 | static string GetUserJsonFilename() 22 | { 23 | return $"appsettings.user_{Environment.UserName.ToLower()}.json"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/HelloWorld.Plugin3.Console/Configuration/OpenAIOptions.cs: -------------------------------------------------------------------------------- 1 | namespace HelloWorld.Plugin2.Console.Configuration; 2 | internal class OpenAIOptions 3 | { 4 | public const string OpenAI = "OpenAI"; 5 | 6 | public string Source { get; set; } = string.Empty; 7 | public string ChatModelId { get; set; } = string.Empty; 8 | public string ApiKey { get; set; } = string.Empty; 9 | public string ChatDeploymentName { get; set; } = string.Empty; 10 | public string Endpoint { get; set; } = string.Empty; 11 | } 12 | -------------------------------------------------------------------------------- /src/HelloWorld.Plugin3.Console/HelloWorld.Plugin3.Console.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Always 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/HelloWorld.Plugin3.Console/Plugins/DailyFactPlugin.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.SemanticKernel; 2 | using System.ComponentModel; 3 | using System.ComponentModel.DataAnnotations; 4 | 5 | namespace HelloWorld.Plugin.Console.Plugins; 6 | 7 | public class DailyFactPlugin 8 | { 9 | private const string DESCRIPTION = "Provides interesting historic facts for the current date."; 10 | private const string TEMPLATE = @"Tell me an interesting fact from world 11 | about an event that took place on {{$today}}. 12 | Be sure to mention the date in history for context."; 13 | private const string GET_DAILY_FACT_FUNC = "GetDailyFactFunc"; 14 | internal const string PLUGIN_NAME = "DailyFactPlugin"; 15 | internal const string GET_DAILY_FACT = "GetDailyFact"; 16 | 17 | private readonly KernelFunction _dailyFact; 18 | private readonly KernelFunction _currentDay; 19 | 20 | public DailyFactPlugin() 21 | { 22 | PromptExecutionSettings settings = new() 23 | { 24 | ExtensionData = new Dictionary() 25 | { 26 | { "Temperature", 0.7 }, 27 | { "MaxTokens", 250 } 28 | } 29 | 30 | }; 31 | 32 | _dailyFact = KernelFunctionFactory.CreateFromPrompt(TEMPLATE, 33 | functionName: GET_DAILY_FACT_FUNC, 34 | executionSettings: settings); 35 | 36 | _currentDay = KernelFunctionFactory.CreateFromMethod(() => DateTime.Now.ToString("MMMM dd"), "GetCurrentDay"); 37 | } 38 | 39 | [KernelFunction, Description(DESCRIPTION)] 40 | public async Task GetDailyFact([Description("Current day"), Required] string today, Kernel kernel) 41 | { 42 | var result = await _dailyFact.InvokeAsync(kernel, new() { ["today"] = today }).ConfigureAwait(false); 43 | 44 | return result.ToString(); 45 | } 46 | 47 | [KernelFunction, Description("Retrieves the current day.")] 48 | public async Task GetCurrentDay(Kernel kernel) 49 | { 50 | var today = await _currentDay.InvokeAsync(kernel); 51 | 52 | return today.ToString(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/HelloWorld.Plugin3.Console/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.SemanticKernel; 3 | using Microsoft.Extensions.Logging; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using OpenTelemetry.Metrics; 6 | using OpenTelemetry.Logs; 7 | using HelloWorld.Plugin.Console.Plugins; 8 | using HelloWorld.Plugin2.Console.Configuration; 9 | using Microsoft.SemanticKernel.Connectors.OpenAI; 10 | 11 | internal class Program 12 | { 13 | static void Main(string[] args) 14 | { 15 | MainAsync(args).Wait(); 16 | } 17 | 18 | static async Task MainAsync(string[] args) 19 | { 20 | var config = Configuration.ConfigureAppSettings(); 21 | 22 | // Get Settings (all this is just so I don't have hard coded config settings here) 23 | var openAiSettings = new OpenAIOptions(); 24 | config.GetSection(OpenAIOptions.OpenAI).Bind(openAiSettings); 25 | 26 | using var loggerFactory = LoggerFactory.Create(builder => 27 | { 28 | builder.SetMinimumLevel(LogLevel.Warning); 29 | 30 | builder.AddConfiguration(config); 31 | builder.AddConsole(); 32 | }); 33 | 34 | // Configure Semantic Kernel 35 | var builder = Kernel.CreateBuilder(); 36 | 37 | builder.Services.AddSingleton(loggerFactory); 38 | builder.AddChatCompletionService(config.GetConnectionString("OpenAI")); 39 | //builder.AddChatCompletionService(openAiSettings); 40 | //builder.AddChatCompletionService(openAiSettings, ApiLoggingLevel.ResponseAndRequest); // use this line to see the JSON between SK and OpenAI 41 | 42 | // -------------------------------------------------------------------------------------- 43 | // Exercise from Virtual Boston Azure for creating a prompt 44 | // -------------------------------------------------------------------------------------- 45 | 46 | builder.Plugins.AddFromType(); 47 | 48 | Kernel kernel = builder.Build(); 49 | 50 | // TODO: CHALLENGE 1: does the AI respond accurately to this prompt? How to fix? 51 | var prompt = $"Tell me an interesting fact from world about an event " + 52 | $"that took place on today's date. " + 53 | $"Be sure to mention the date in history for context."; 54 | 55 | OpenAIPromptExecutionSettings settings = new() 56 | { 57 | ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions, 58 | Temperature = 0.7f, 59 | MaxTokens = 250 60 | }; 61 | 62 | var funcresult = await kernel.InvokePromptAsync(prompt, new KernelArguments(settings)); 63 | 64 | WriteLine($"\nRESPONSE: \n\n{funcresult}"); 65 | } 66 | 67 | static void WriteLine(string message) 68 | { 69 | Console.WriteLine("----------------------------------------------"); 70 | 71 | Console.WriteLine(message); 72 | 73 | Console.WriteLine("----------------------------------------------"); 74 | } 75 | } -------------------------------------------------------------------------------- /src/HelloWorld.Plugin3.Console/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.SemanticKernel": "Information" 6 | }, 7 | "Console": { 8 | "LogLevel": { 9 | "Default": "Information" 10 | } 11 | } 12 | }, 13 | "ConnectionStrings": { 14 | //"OpenAI": "Source=AzureOpenAI;Key=your-key;ChatDeploymentName=your-chat-deployment-name;Endpoint=your-endpoint" 15 | "OpenAI": "Source=OpenAI;ChatModelId=gpt-4o-2024-08-06;ApiKey=Your-OpenAI-API-Key" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/HelloWorld.WebSearchEnginePlugin.Console/Configuration/Configuration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace HelloWorld.Configuration; 4 | 5 | internal class Configuration 6 | { 7 | public static IConfigurationRoot ConfigureAppSettings() 8 | { 9 | var config = new ConfigurationBuilder() 10 | .SetBasePath(Directory.GetCurrentDirectory()) 11 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 12 | .AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true) 13 | .AddJsonFile("appsettings.Production.json", optional: true, reloadOnChange: true) 14 | #if DEBUG 15 | .AddJsonFile(GetUserJsonFilename(), optional: true, reloadOnChange: true) 16 | #endif 17 | .Build(); 18 | return config; 19 | } 20 | 21 | static string GetUserJsonFilename() 22 | { 23 | return $"appsettings.user_{Environment.UserName.ToLower()}.json"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/HelloWorld.WebSearchEnginePlugin.Console/Configuration/OpenAIOptions.cs: -------------------------------------------------------------------------------- 1 | namespace HelloWorld.Configuration; 2 | internal class OpenAIOptions 3 | { 4 | public const string OpenAI = "OpenAI"; 5 | 6 | public string Source { get; set; } = string.Empty; 7 | public string ChatModelId { get; set; } = string.Empty; 8 | public string ApiKey { get; set; } = string.Empty; 9 | public string ChatDeploymentName { get; set; } = string.Empty; 10 | public string Endpoint { get; set; } = string.Empty; 11 | } 12 | -------------------------------------------------------------------------------- /src/HelloWorld.WebSearchEnginePlugin.Console/Configuration/PluginConfig.cs: -------------------------------------------------------------------------------- 1 | namespace HelloWorld.Configuration; 2 | 3 | public class PluginOptions 4 | { 5 | public const string PluginConfig = "PluginConfig"; 6 | 7 | public string BraveApiKey { get; set; } = string.Empty; 8 | } 9 | -------------------------------------------------------------------------------- /src/HelloWorld.WebSearchEnginePlugin.Console/Configuration/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.SemanticKernel; 3 | using Microsoft.SemanticKernel.Plugins.Web; 4 | using Microsoft.SemanticKernel.Plugins.Web.Brave; 5 | using System.Text.Json; 6 | 7 | namespace HelloWorld.Configuration; 8 | 9 | internal static class ServiceCollectionExtensions 10 | { 11 | internal static IServiceCollection AddChatCompletionService(this IServiceCollection serviceCollection, OpenAIOptions openAIOptions) 12 | { 13 | switch (openAIOptions.Source) 14 | { 15 | case "AzureOpenAI": 16 | serviceCollection = serviceCollection.AddAzureOpenAIChatCompletion(openAIOptions.ChatDeploymentName, endpoint: openAIOptions.Endpoint, 17 | apiKey: openAIOptions.ApiKey, serviceId: openAIOptions.ChatModelId); 18 | break; 19 | 20 | case "OpenAI": 21 | serviceCollection = serviceCollection.AddOpenAIChatCompletion(modelId: openAIOptions.ChatModelId, apiKey: openAIOptions.ApiKey); 22 | break; 23 | 24 | default: 25 | throw new ArgumentException($"Invalid source: {openAIOptions.Source}"); 26 | } 27 | 28 | return serviceCollection; 29 | } 30 | } 31 | public enum ApiLoggingLevel 32 | { 33 | None = 0, 34 | RequestOnly = 1, 35 | ResponseAndRequest = 2, 36 | } 37 | 38 | internal static class IKernelBuilderExtensions 39 | { 40 | internal static IKernelBuilder AddBraveConnector(this IKernelBuilder kernelBuilder, PluginOptions pluginOptions, ApiLoggingLevel apiLoggingLevel = ApiLoggingLevel.None) 41 | { 42 | if (apiLoggingLevel == ApiLoggingLevel.None) 43 | { 44 | kernelBuilder.Services.AddSingleton(new BraveConnector(pluginOptions.BraveApiKey)); 45 | } 46 | else 47 | { 48 | var client = CreateHttpClient(apiLoggingLevel); 49 | kernelBuilder.Services.AddSingleton(new BraveConnector(pluginOptions.BraveApiKey, client)); 50 | } 51 | 52 | return kernelBuilder; 53 | } 54 | 55 | internal static IKernelBuilder AddChatCompletionService(this IKernelBuilder kernelBuilder, OpenAIOptions openAIOptions, ApiLoggingLevel apiLoggingLevel = ApiLoggingLevel.None) 56 | { 57 | switch (openAIOptions.Source) 58 | { 59 | case "AzureOpenAI": 60 | { 61 | if (apiLoggingLevel == ApiLoggingLevel.None) 62 | { 63 | kernelBuilder = kernelBuilder.AddAzureOpenAIChatCompletion(openAIOptions.ChatDeploymentName, endpoint: openAIOptions.Endpoint, 64 | apiKey: openAIOptions.ApiKey, serviceId: openAIOptions.ChatModelId); 65 | } 66 | else 67 | { 68 | var client = CreateHttpClient(apiLoggingLevel); 69 | kernelBuilder.AddAzureOpenAIChatCompletion(openAIOptions.ChatDeploymentName, openAIOptions.Endpoint, openAIOptions.ApiKey, null, null, client); 70 | } 71 | break; 72 | } 73 | case "OpenAI": 74 | { 75 | if (apiLoggingLevel == ApiLoggingLevel.None) 76 | { 77 | kernelBuilder = kernelBuilder.AddOpenAIChatCompletion(modelId: openAIOptions.ChatModelId, apiKey: openAIOptions.ApiKey); 78 | break; 79 | } 80 | else 81 | { 82 | var client = CreateHttpClient(apiLoggingLevel); 83 | kernelBuilder.AddOpenAIChatCompletion(openAIOptions.ChatModelId, openAIOptions.ApiKey, null, null, client); 84 | } 85 | break; 86 | } 87 | default: 88 | throw new ArgumentException($"Invalid source: {openAIOptions.Source}"); 89 | } 90 | 91 | return kernelBuilder; 92 | } 93 | 94 | public static HttpClient CreateHttpClient(ApiLoggingLevel apiLoggingLevel) 95 | { 96 | HttpClientHandler httpClientHandler; 97 | if (apiLoggingLevel == ApiLoggingLevel.RequestOnly) 98 | { 99 | httpClientHandler = new RequestLoggingHttpClientHandler(); 100 | } 101 | else 102 | { 103 | httpClientHandler = new RequestAndResponseLoggingHttpClientHandler(); 104 | } 105 | var client = new HttpClient(httpClientHandler); 106 | return client; 107 | } 108 | } 109 | 110 | // Found most of this implementation via: https://github.com/microsoft/semantic-kernel/issues/5107 111 | public class RequestAndResponseLoggingHttpClientHandler : HttpClientHandler 112 | { 113 | protected override async Task SendAsync( 114 | HttpRequestMessage request, CancellationToken cancellationToken) 115 | { 116 | if (request.Content is not null) 117 | { 118 | var content = await request.Content.ReadAsStringAsync(cancellationToken); 119 | var json = JsonSerializer.Serialize(JsonSerializer.Deserialize(content), 120 | new JsonSerializerOptions { WriteIndented = true }); 121 | System.Console.WriteLine("***********************************************"); 122 | System.Console.WriteLine("Request:"); 123 | System.Console.WriteLine(json); 124 | } 125 | else 126 | { 127 | System.Console.WriteLine("***********************************************"); 128 | System.Console.WriteLine($"Request: {request.Method} {request.RequestUri}"); 129 | } 130 | 131 | var result = await base.SendAsync(request, cancellationToken); 132 | 133 | if (result.Content is not null) 134 | { 135 | var content = await result.Content.ReadAsStringAsync(cancellationToken); 136 | var json = JsonSerializer.Serialize(JsonSerializer.Deserialize(content), 137 | new JsonSerializerOptions { WriteIndented = true }); 138 | System.Console.WriteLine("***********************************************"); 139 | System.Console.WriteLine("Response:"); 140 | System.Console.WriteLine(json); 141 | } 142 | 143 | return result; 144 | } 145 | } 146 | public class RequestLoggingHttpClientHandler : HttpClientHandler 147 | { 148 | protected override async Task SendAsync( 149 | HttpRequestMessage request, CancellationToken cancellationToken) 150 | { 151 | if (request.Content is not null) 152 | { 153 | var content = await request.Content.ReadAsStringAsync(cancellationToken); 154 | var json = JsonSerializer.Serialize(JsonSerializer.Deserialize(content), 155 | new JsonSerializerOptions { WriteIndented = true }); 156 | System.Console.WriteLine("***********************************************"); 157 | System.Console.WriteLine("Request:"); 158 | System.Console.WriteLine(json); 159 | } 160 | else 161 | { 162 | System.Console.WriteLine("***********************************************"); 163 | System.Console.WriteLine($"Request: {request.Method} {request.RequestUri}"); 164 | } 165 | return await base.SendAsync(request, cancellationToken); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/HelloWorld.WebSearchEnginePlugin.Console/HelloWorld.WebSearchEnginePlugin.Console.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | enable 8 | SKEXP0050 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Always 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/HelloWorld.WebSearchEnginePlugin.Console/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.SemanticKernel; 3 | using Microsoft.Extensions.Logging; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using OpenTelemetry.Metrics; 6 | using OpenTelemetry.Logs; 7 | using HelloWorld.Configuration; 8 | using Microsoft.SemanticKernel.Connectors.OpenAI; 9 | using Microsoft.SemanticKernel.Plugins.Web; 10 | 11 | internal class Program 12 | { 13 | static void Main(string[] args) 14 | { 15 | MainAsync(args).Wait(); 16 | } 17 | 18 | static async Task MainAsync(string[] args) 19 | { 20 | var config = Configuration.ConfigureAppSettings(); 21 | 22 | // Get Settings (all this is just so I don't have hard coded config settings here) 23 | var openAiSettings = new OpenAIOptions(); 24 | config.GetSection(OpenAIOptions.OpenAI).Bind(openAiSettings); 25 | 26 | var pluginSettings = new PluginOptions(); 27 | config.GetSection(PluginOptions.PluginConfig).Bind(pluginSettings); 28 | 29 | using var loggerFactory = LoggerFactory.Create(builder => 30 | { 31 | builder.SetMinimumLevel(LogLevel.Information); 32 | 33 | builder.AddConfiguration(config); 34 | builder.AddConsole(); 35 | }); 36 | 37 | // Configure Semantic Kernel 38 | var builder = Kernel.CreateBuilder(); 39 | 40 | builder.Services.AddSingleton(loggerFactory); 41 | builder.AddChatCompletionService(openAiSettings); 42 | //builder.AddChatCompletionService(openAiSettings, ApiLoggingLevel.ResponseAndRequest); // use this line to see the JSON between SK and OpenAI 43 | 44 | //builder.AddBraveConnector(pluginSettings); 45 | builder.AddBraveConnector(pluginSettings, ApiLoggingLevel.ResponseAndRequest); // use this line to see the JSON between SK and OpenAI 46 | 47 | builder.Plugins.AddFromType(); 48 | 49 | Kernel kernel = builder.Build(); 50 | 51 | var prompt = "Who are the organizers for the Boston Azure meetup?"; 52 | 53 | WriteLine($"\nQUESTION: \n\n{prompt}"); 54 | 55 | OpenAIPromptExecutionSettings settings = new() 56 | { 57 | ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions, 58 | Temperature = 0.7f, 59 | MaxTokens = 2500 60 | }; 61 | 62 | var funcresult = await kernel.InvokePromptAsync(prompt, new KernelArguments(settings)); 63 | 64 | WriteLine($"\nANSWER: \n\n{funcresult}"); 65 | } 66 | 67 | static void WriteLine(string message) 68 | { 69 | Console.WriteLine("----------------------------------------------"); 70 | 71 | Console.WriteLine(message); 72 | 73 | Console.WriteLine("----------------------------------------------"); 74 | } 75 | } -------------------------------------------------------------------------------- /src/HelloWorld.WebSearchEnginePlugin.Console/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.SemanticKernel": "Information" 6 | }, 7 | "Console": { 8 | "LogLevel": { 9 | "Default": "Information" 10 | } 11 | } 12 | }, 13 | "OpenAI": { 14 | "Source": "AzureOpenAI", // or "OpenAI" 15 | "ChatModelId": "gpt-4o", 16 | "ApiKey": "Your-OpenAI-API-Key" 17 | 18 | // Add these settings if you are using AzureOpenAI 19 | // "ChatDeploymentName": "your-chat-deployment-name", 20 | // "Endpoint": "your-endpoint" 21 | }, 22 | "PluginConfig": { 23 | "BraveApiKey": "" // Add your Brave API key here 24 | } 25 | } 26 | --------------------------------------------------------------------------------