├── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── data └── ms10k.txt └── src ├── chapter1 ├── .gitignore ├── Chapter1Function.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── chapter1.csproj └── host.json ├── chapter2 ├── .gitignore ├── Chapter2Function.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── chapter2.csproj ├── chapter2.sln └── host.json ├── chapter3 ├── .gitignore ├── Chapter3Function.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── chapter3.csproj └── host.json ├── chatconsole ├── Program.cs └── chatconsole.csproj └── importmemories ├── Program.cs ├── importmemories.csproj └── importmemories.sln /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | 352 | data/qdrant -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-azuretools.vscode-azurefunctions", 4 | "ms-dotnettools.csharp" 5 | ] 6 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Attach to .NET Functions", 6 | "type": "coreclr", 7 | "request": "attach", 8 | "processId": "${command:azureFunctions.pickProcess}" 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "azureFunctions.deploySubpath": "src\\myfunc/bin/Release/net7.0/publish", 3 | "azureFunctions.projectLanguage": "C#", 4 | "azureFunctions.projectRuntime": "~4", 5 | "debug.internalConsoleOptions": "neverOpen", 6 | "azureFunctions.preDeployTask": "publish (functions)", 7 | "azureFunctions.projectSubpath": "src\\myfunc" 8 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "clean (functions)", 6 | "command": "dotnet", 7 | "args": [ 8 | "clean", 9 | "/property:GenerateFullPaths=true", 10 | "/consoleloggerparameters:NoSummary" 11 | ], 12 | "type": "process", 13 | "problemMatcher": "$msCompile", 14 | "options": { 15 | "cwd": "${workspaceFolder}/src\\myfunc" 16 | } 17 | }, 18 | { 19 | "label": "build (functions)", 20 | "command": "dotnet", 21 | "args": [ 22 | "build", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "type": "process", 27 | "dependsOn": "clean (functions)", 28 | "group": { 29 | "kind": "build", 30 | "isDefault": true 31 | }, 32 | "problemMatcher": "$msCompile", 33 | "options": { 34 | "cwd": "${workspaceFolder}/src\\myfunc" 35 | } 36 | }, 37 | { 38 | "label": "clean release (functions)", 39 | "command": "dotnet", 40 | "args": [ 41 | "clean", 42 | "--configuration", 43 | "Release", 44 | "/property:GenerateFullPaths=true", 45 | "/consoleloggerparameters:NoSummary" 46 | ], 47 | "type": "process", 48 | "problemMatcher": "$msCompile", 49 | "options": { 50 | "cwd": "${workspaceFolder}/src\\myfunc" 51 | } 52 | }, 53 | { 54 | "label": "publish (functions)", 55 | "command": "dotnet", 56 | "args": [ 57 | "publish", 58 | "--configuration", 59 | "Release", 60 | "/property:GenerateFullPaths=true", 61 | "/consoleloggerparameters:NoSummary" 62 | ], 63 | "type": "process", 64 | "dependsOn": "clean release (functions)", 65 | "problemMatcher": "$msCompile", 66 | "options": { 67 | "cwd": "${workspaceFolder}/src\\myfunc" 68 | } 69 | }, 70 | { 71 | "type": "func", 72 | "dependsOn": "build (functions)", 73 | "options": { 74 | "cwd": "${workspaceFolder}/src\\myfunc/bin/Debug/net7.0" 75 | }, 76 | "command": "host start", 77 | "isBackground": true, 78 | "problemMatcher": "$func-dotnet-watch" 79 | } 80 | ] 81 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to this Tutorial 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 4 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 5 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 6 | 7 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 8 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 9 | provided by the bot. You will only need to do this once across all repos using our CLA. 10 | 11 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 12 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 13 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 14 | 15 | - [Code of Conduct](#coc) 16 | - [Issues and Bugs](#issue) 17 | - [Feature Requests](#feature) 18 | - [Submission Guidelines](#submit) 19 | 20 | ## Code of Conduct 21 | Help us keep this project open and inclusive. Please read and follow our [Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 22 | 23 | ## Found an Issue? 24 | If you find a bug in the source code or a mistake in the documentation, you can help us by 25 | [submitting an issue](#submit-issue) to the GitHub Repository. Even better, you can 26 | [submit a Pull Request](#submit-pr) with a fix. 27 | 28 | ## Want a Feature? 29 | You can *request* a new feature by [submitting an issue](#submit-issue) to the GitHub 30 | Repository. If you would like to *implement* a new feature, please submit an issue with 31 | a proposal for your work first, to be sure that we can use it. 32 | 33 | * **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). 34 | 35 | ## Submission Guidelines 36 | 37 | ### Submitting an Issue 38 | Before you submit an issue, search the archive, maybe your question was already answered. 39 | 40 | If your issue appears to be a bug, and hasn't been reported, open a new issue. 41 | Help us to maximize the effort we can spend fixing issues and adding new 42 | features, by not reporting duplicate issues. Providing the following information will increase the 43 | chances of your issue being dealt with quickly: 44 | 45 | * **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps 46 | * **Version** - what version is affected (e.g. 0.1.2) 47 | * **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you 48 | * **Browsers and Operating System** - is this a problem with all browsers? 49 | * **Reproduce the Error** - provide a live example or a unambiguous set of steps 50 | * **Related Issues** - has a similar issue been reported before? 51 | * **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be 52 | causing the problem (line of code or commit) 53 | 54 | You can file new issues by providing the above information at the corresponding repository's issues link: https://github.com/Azure-Samples/semantic-kernel-rag-chat/issues/new. 55 | 56 | ### Submitting a Pull Request (PR) 57 | Before you submit your Pull Request (PR) consider the following guidelines: 58 | 59 | * Search the repository (https://github.com/Azure-Samples/semantic-kernel-rag-chat/pulls) for an open or closed PR 60 | that relates to your submission. You don't want to duplicate effort. 61 | 62 | * Make your changes in a new git fork: 63 | 64 | * Commit your changes using a descriptive commit message 65 | * Push your fork to GitHub: 66 | * In GitHub, create a pull request 67 | * If we suggest changes then: 68 | * Make the required updates. 69 | * Rebase your fork and force push to your GitHub repository (this will update your Pull Request): 70 | 71 | ```shell 72 | git rebase master -i 73 | git push -f 74 | ``` 75 | 76 | That's it! Thank you for your contribution! 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Microsoft 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 | # Tutorial: ChatGPT + Enterprise data with Semantic Kernel, OpenAI and Azure Cognitive Search 2 | This progressive tutorial is for building your own AI chat application informed with your enterprise data. In Chapter 1, we start with building a simple [ChatGPT](https://platform.openai.com/docs/models/gpt-3-5)-like application using [Semantic Kernel](https://aka.ms/skrepo) (SK). Chapter 2 imports files into a “Memories Store” for reference by the SK orchestrator when chatting. Having the data from these files allows SK to build better prompts so the AI can offer better answers to questions – this is a key part of the Retrieval Augmented Generation (RAG) pattern. Chapter 3 extends the context of the chat application by using [Azure Cognitive Search](https://learn.microsoft.com/en-us/azure/search/) for data indexing and retrieval. 3 | 4 | In all, this tutorial creates a minimal implementation for using Semantic Kernel as a foundation for enabling enterprise data ingestion, long-term memory, plug-ins, and more. 5 | 6 | Special thanks to Adam Hurwitz's for his [SemanticQuestion10K](https://github.com/adhurwit/SemanticQuestion10K) sample, which was used in Chapter 2. 7 | 8 | - [Chapter 1: ChatGPT](#chapter-1-chatgpt) 9 | - [Chapter 2: Memories of Enterprise Data](#chapter-2-memories-of-enterprise-data) 10 | - [Chapter 3: Azure Cognitive Search and Retrieval Augmented Generation](#chapter-3-azure-cognitive-search-and-retrieval-augmented-generation) 11 | - [Appendix](#appendix) 12 | 13 | # Chapter 1: ChatGPT 14 | In this section, we will create a minimal chat implementation for a chat application that uses Semantic Kernel as a foundation for enterprise data ingestion, long-term memory, plug-ins, and more. We will write a C# Azure Function in detail from scratch that wraps all AI calls using SK and we will use a prebuilt C# console app as our UI for the chat app. 15 | 16 | ## Configure your environment 17 | Before you get started, make sure you have the following requirements in place: 18 | - [Visual Studio Code](http://aka.ms/vscode) with extensions: 19 | - [C# Extension](https://aka.ms/csharp/vscode) 20 | - [Azure Functions Extension](https://aka.ms/azfn/vscode) 21 | - [.NET 7.0 SDK](https://aka.ms/net70) for building and deploying .NET 7 projects. 22 | - [Azure Function Core Tools 4.x](https://aka.ms/azfn/coretools) for managing Azure Functions 23 | - [OpenAI API key](https://platform.openai.com/account/api-keys) for using the OpenAI API (or click [here](https://platform.openai.com/signup) to signup). 24 | 25 | 26 | Then, open a terminal and clone this repo with the following command: 27 | 28 | ```bash 29 | git clone https://github.com/Azure-Samples/semantic-kernel-rag-chat 30 | ``` 31 | 32 | ## Create an Azure Function project 33 | 1. Open a new Visual Studio Code window and click on the Azure extension (or press `SHIFT+ALT+A`). 34 | 1. Mouse-over `WORKSPACE` (in the lower left pane) and select `Create Function` (i.e., +⚡) to create a new local Azure function project. 35 | 1. Select `Browse` and create a folder called `myfunc` inside the cloned repo's `src` directory to house your Azure Function code (e.g., `semantic-kernel-rag-chat/src/myfunc`). Then use the selections below when creating the project: 36 | 37 | | Selection | Value | 38 | | --------- | ----- | 39 | | Language | `C#` | 40 | | Runtime | `.NET 7 Isolated` | 41 | | Template | `Http trigger` | 42 | | Function name | `MyChatFunction` | 43 | | Namespace | `My.MyChatFunction` | 44 | | Access rights | `Function` | 45 | 46 | Now close and reopen Visual Studio Code, this time opening the `semantic-kernel-rag-chat` folder so you can view and interact with the entire repository. 47 | 48 | ## Add Semantic Kernel to your Azure Function 49 | 1. Open a terminal window, change to the directory with your Azure Function project file (e.g., `semantic-kernel-rag-chat/src/myfunc`), 50 | and run the `dotnet` command below to add the Semantic Kernel NuGet package to your project. 51 | ```bash 52 | dotnet add package Microsoft.SemanticKernel --prerelease -v 0.14.547.1-preview 53 | ``` 54 | 55 | In addition, use the commands below to configure .NET User Secrets and then securely store your OpenAI API key. 56 | ```bash 57 | dotnet add package Microsoft.Extensions.Configuration.UserSecrets 58 | dotnet user-secrets init --id semantic-kernel-rag-chat 59 | dotnet user-secrets set "OPENAI_APIKEY" "" 60 | ``` 61 | 62 | > Make sure to specify `semantic-kernel-rag-chat` as the `--id` parameter. This will enable you to access your secrets from any of the projects in this repository. 63 | 64 | 1. Back in your Azure Function project in Visual Studio Code, open the `Program.cs` file and replace everything in the file with the content below. This updates the `HostBuilder` to read configuration variables from user secrets and sets up a reference to the SK runtime. 65 | > We will walk through the code step-by-step, but you can find the complete code files in step 7 of this section. 66 | 67 | ```csharp 68 | using Microsoft.Extensions.Configuration; 69 | using Microsoft.Extensions.DependencyInjection; 70 | using Microsoft.Extensions.Hosting; 71 | using Microsoft.Extensions.Logging; 72 | using Microsoft.SemanticKernel; 73 | using Microsoft.SemanticKernel.AI.ChatCompletion; 74 | 75 | var hostBuilder = new HostBuilder() 76 | .ConfigureFunctionsWorkerDefaults(); 77 | 78 | hostBuilder.ConfigureAppConfiguration((context, config) => 79 | { 80 | config.AddUserSecrets(); 81 | }); 82 | 83 | hostBuilder.Build().Run(); 84 | ``` 85 | 86 | 1. Add the Semantic Kernel by adding a `ConfigureServices` call below the existing `ConfigureAppConfiguration` and populating it with an instance of the kernel. 87 | > The kernel below is configured to use an OpenAI ChatGPT model (e.g., gpt-3.5-turbo) for chat completions. 88 | 89 | ```csharp 90 | hostBuilder.ConfigureServices(services => 91 | { 92 | services.AddSingleton(sp => 93 | { 94 | IConfiguration configuration = sp.GetRequiredService(); 95 | string openAiApiKey = configuration["OPENAI_APIKEY"]; 96 | 97 | IKernel kernel = new KernelBuilder() 98 | .WithLogger(sp.GetRequiredService>()) 99 | .Configure(config => config.AddOpenAIChatCompletionService( 100 | modelId: "gpt-3.5-turbo", 101 | apiKey: openAiApiKey)) 102 | .Build(); 103 | 104 | return kernel; 105 | }); 106 | }); 107 | ``` 108 | 109 | 1. Enable a chat service and in-memory storage for the chat history by adding two more singletons inside the `ConfigureServices` call. 110 | ```csharp 111 | services.AddSingleton(sp => 112 | sp.GetRequiredService().GetService()); 113 | 114 | const string instructions = "You are a helpful friendly assistant."; 115 | services.AddSingleton(sp => 116 | sp.GetRequiredService().CreateNewChat(instructions)); 117 | ``` 118 | 119 | 1. Open your function code file (e.g., `MyChatFunction.cs`) and add the chat completion using statement to the top. 120 | ```csharp 121 | using Microsoft.SemanticKernel.AI.ChatCompletion; 122 | ``` 123 | Replace the private members and constructor to include the chat history and chat completion services – these will be used to give the AI a history of the conversation (since the AI is stateless) and to make calls to the AI. 124 | ```csharp 125 | private readonly ILogger _logger; 126 | private readonly IChatCompletion _chat; 127 | private readonly ChatHistory _chatHistory; 128 | 129 | public MyChatFunction(ILoggerFactory loggerFactory, ChatHistory chatHistory, IChatCompletion chat) 130 | { 131 | _logger = loggerFactory.CreateLogger(); 132 | _chat = chat; 133 | _chatHistory = chatHistory; 134 | } 135 | ``` 136 | 137 | 1. Update the `Run` method to read the user's chat message, add it to the chat history, use our chat service to call ChatGPT and generate a reply message, add the AI's reply to our chat history, and finally, send the reply back to the caller. 138 | ```csharp 139 | [Function("MyChatFunction")] 140 | public async Task Run([HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req) 141 | { 142 | _chatHistory!.AddMessage(ChatHistory.AuthorRoles.User, await req.ReadAsStringAsync() ?? string.Empty); 143 | string reply = await _chat.GenerateMessageAsync(_chatHistory, new ChatRequestSettings()); 144 | _chatHistory.AddMessage(ChatHistory.AuthorRoles.Assistant, reply); 145 | 146 | HttpResponseData response = req.CreateResponse(HttpStatusCode.OK); 147 | response.WriteString(reply); 148 | return response; 149 | } 150 | ``` 151 | 152 | 1. The complete code files (with additional comments). 153 |
154 | Program.cs 155 | 156 | ```csharp 157 | using Microsoft.Extensions.Configuration; 158 | using Microsoft.Extensions.DependencyInjection; 159 | using Microsoft.Extensions.Hosting; 160 | using Microsoft.Extensions.Logging; 161 | using Microsoft.SemanticKernel; 162 | using Microsoft.SemanticKernel.AI.ChatCompletion; 163 | 164 | var hostBuilder = new HostBuilder() 165 | .ConfigureFunctionsWorkerDefaults(); 166 | 167 | hostBuilder.ConfigureAppConfiguration((context, config) => 168 | { 169 | config.AddUserSecrets(); 170 | }); 171 | 172 | hostBuilder.ConfigureServices(services => 173 | { 174 | services.AddSingleton(sp => 175 | { 176 | // Retrieve the OpenAI API key from the configuration. 177 | IConfiguration configuration = sp.GetRequiredService(); 178 | string openAiApiKey = configuration["OPENAI_APIKEY"]; 179 | 180 | // Construct a semantic kernel and connect the OpenAI chat completion APIs. 181 | IKernel kernel = new KernelBuilder() 182 | .WithLogger(sp.GetRequiredService>()) 183 | .Configure(config => config.AddOpenAIChatCompletionService( 184 | modelId: "gpt-3.5-turbo", 185 | apiKey: openAiApiKey)) 186 | .Build(); 187 | 188 | return kernel; 189 | }); 190 | 191 | // Provide a chat completion service client to our function. 192 | services.AddSingleton(sp => 193 | sp.GetRequiredService().GetService()); 194 | 195 | // Provide a persistant in-memory chat history store with the 196 | // initial ChatGPT system message. 197 | const string instructions = "You are a helpful friendly assistant."; 198 | services.AddSingleton(sp => 199 | sp.GetRequiredService().CreateNewChat(instructions)); 200 | }); 201 | 202 | hostBuilder.Build().Run(); 203 | ``` 204 |
205 | 206 |
207 | MyChatFunction.cs 208 | 209 | ```csharp 210 | using System.Net; 211 | using Microsoft.Azure.Functions.Worker; 212 | using Microsoft.Azure.Functions.Worker.Http; 213 | using Microsoft.Extensions.Logging; 214 | using Microsoft.SemanticKernel.AI.ChatCompletion; 215 | 216 | namespace My.MyChatFunction 217 | { 218 | public class MyChatFunction 219 | { 220 | private readonly ILogger _logger; 221 | private readonly IChatCompletion _chat; 222 | private readonly ChatHistory _chatHistory; 223 | 224 | public MyChatFunction(ILoggerFactory loggerFactory, ChatHistory chatHistory, IChatCompletion chat) 225 | { 226 | _logger = loggerFactory.CreateLogger(); 227 | _chat = chat; 228 | _chatHistory = chatHistory; 229 | } 230 | 231 | [Function("MyChatFunction")] 232 | public async Task Run([HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req) 233 | { 234 | // Add the user's chat message to the history. 235 | _chatHistory!.AddMessage(ChatHistory.AuthorRoles.User, await req.ReadAsStringAsync() ?? string.Empty); 236 | 237 | // Send the chat history to the AI and receive a reply. 238 | string reply = await _chat.GenerateMessageAsync(_chatHistory, new ChatRequestSettings()); 239 | 240 | // Add the AI's reply to the chat history for next time. 241 | _chatHistory.AddMessage(ChatHistory.AuthorRoles.Assistant, reply); 242 | 243 | // Send the AI's response back to the caller. 244 | HttpResponseData response = req.CreateResponse(HttpStatusCode.OK); 245 | response.WriteString(reply); 246 | return response; 247 | } 248 | } 249 | } 250 | ``` 251 |
252 | 253 | ## Run the function locally 254 | 1. Run your Azure Function locally by opening a terminal, changing directory to your Azure Function project (e.g., `semantic-kernel-rag-chat/src/myfunc`), and starting the function by running 255 | ```bash 256 | func start 257 | ``` 258 | > Make note of the URL displayed (e.g., `http://localhost:7071/api/MyChatFunction`). 259 | 260 | 1. Start the test console application 261 | Open a second terminal and change directory to the `chatconsole` project folder (e.g., `semantic-kernel-rag-chat/src/chatconsole`) and run the application using the Azure Function URL. 262 | ```bash 263 | dotnet run http://localhost:7071/api/MyChatFunction 264 | ``` 265 | 1. Type a message and press enter to verify that we are able to chat with the AI! 266 | ``` 267 | Input: Hello, how are you? 268 | AI: Hello! As an AI language model, I don't have feelings, but I'm functioning properly and ready to 269 | assist you. How can I help you today? 270 | ``` 271 | 272 | 1. Now let's try to ask about something that is not in the current AI model, such as "What was Microsoft's total revenue for 2022?" 273 | ``` 274 | Input: What was Microsoft's cloud revenue for 2022? 275 | AI: I'm sorry, but I cannot provide information about Microsoft's cloud revenue for 2022 as it is not yet 276 | available. Microsoft's fiscal year 2022 ends on June 30, 2022, and the company typically releases its 277 | financial results a few weeks after the end of the fiscal year. However, Microsoft's cloud revenue for 278 | fiscal year 2021 was $59.5 billion, an increase of 34% from the previous year. 279 | ``` 280 | As you can see the AI is a bit out of date with its answers. 281 | 282 | In Chapter 1 we created an Azure function hosting the Semantic Kernel that makes it easy to send API calls we want to make to the AI. This gives us a shared, production ready endpoint that we could use from any given solution we want to build. 283 | 284 | Next we'll add a 'knowledge base' to the chat to help answer questions such as those above more accurately. 285 | 286 | # Chapter 2: Memories of Enterprise Data 287 | Semantic Kernel's memory stores are used to integrate data from your knowledge base into AI interactions. 288 | Any data can be added to a knowledge base and you have full control of that data and who it is shared with. 289 | SK uses [embeddings](https://platform.openai.com/docs/guides/embeddings) to encode data and store it in a 290 | vector database. Using a vector database also allows us to use vector search engines to quickly find the most 291 | relevant data for a given query that we then share with the AI. In this chapter, we'll add a memory store to 292 | our chat function, import the Microsoft revenue data, and use it to answer the question from Chapter 1. 293 | 294 | ## Configure your environment 295 | Before you get started, make sure you have the following additional requirements in place: 296 | - [Docker Desktop](https://www.docker.com/products/docker-desktop) for hosting the [Qdrant](https://github.com/qdrant/qdrant) vector search engine. 297 | > Note that a different vector store, such as Pinecone or Weviate, could be leveraged. 298 | 299 | ## Add a memory store in our Azure Function 300 | 1. Open a terminal window, change to the directory with your project file (e.g., `semantic-kernel-rag-chat/src/myfunc`), 301 | and run the `dotnet` command below to add the Semantic Kernel Qdrant Memory Store to your project. 302 | ```bash 303 | dotnet add package Microsoft.SemanticKernel.Connectors.Memory.Qdrant --prerelease -v 0.14.547.1-preview 304 | ``` 305 | 306 | 1. Open your Program code file (e.g., `Program.cs`) and add the Qdrant memory store using statement to the top. 307 | 308 | > We will walk through the code step-by-step, but you can find the complete code files in step 5 of this section. 309 | 310 | ```csharp 311 | using Microsoft.SemanticKernel.Connectors.Memory.Qdrant; 312 | ``` 313 | 314 | Replace the builder code we wrote in Chapter 1, where we instantiate the kernel, to include a Qdrant memory store and an OpenAI embedding generation service. 315 | ```csharp 316 | QdrantMemoryStore memoryStore = new QdrantMemoryStore( 317 | host: "http://localhost", 318 | port: 6333, 319 | vectorSize: 1536, 320 | logger: sp.GetRequiredService>()); 321 | 322 | IKernel kernel = new KernelBuilder() 323 | .WithLogger(sp.GetRequiredService>()) 324 | .Configure(config => config.AddOpenAIChatCompletionService( 325 | modelId: "gpt-3.5-turbo", 326 | apiKey: openAiApiKey)) 327 | .Configure(c => c.AddOpenAITextEmbeddingGenerationService( 328 | modelId: "text-embedding-ada-002", 329 | apiKey: openAiApiKey)) 330 | .WithMemoryStorage(memoryStore) 331 | .Build(); 332 | ``` 333 | 334 | 1. Open `MyChatFunction.cs` and add the following using statements to the top. 335 | ```csharp 336 | using System.Text; 337 | using Microsoft.SemanticKernel; 338 | using Microsoft.SemanticKernel.Memory; 339 | ``` 340 | 341 | Update the constructor to take an instance of the kernel and store it as a member variable. 342 | 343 | ```csharp 344 | private readonly ILogger _logger; 345 | private readonly IKernel _kernel; 346 | private readonly IChatCompletion _chat; 347 | private readonly ChatHistory _chatHistory; 348 | 349 | public MyChatFunction(ILoggerFactory loggerFactory, IKernel kernel, ChatHistory chatHistory, IChatCompletion chat) 350 | { 351 | _logger = loggerFactory.CreateLogger(); 352 | _kernel = kernel; 353 | _chat = chat; 354 | _chatHistory = chatHistory; 355 | } 356 | ``` 357 | 358 | Replace where we add the user's message to the chat history 359 | (`_chatHistory!.AddMessage(ChatHistory.AuthorRoles.User,...`) with a call that will search for related memories and include them 360 | in the user's message to the AI. 361 | ```csharp 362 | // _chatHistory!.AddMessage(ChatHistory.AuthorRoles.User, await req.ReadAsStringAsync() ?? string.Empty); 363 | string message = await SearchMemoriesAsync(_kernel, await req.ReadAsStringAsync() ?? string.Empty); 364 | _chatHistory!.AddMessage(ChatHistory.AuthorRoles.User, message); 365 | ``` 366 | 367 | 1. And finally we'll add the `SearchMemoriesAsync` method to this class. 368 | > The strategy of this memory search is to find memories that are similar to the user's input and then 369 | > include those memories in the user's message to the AI. This is done by first searching for memories 370 | > that are similar to the user's input and including the previous and subsequent memories. 371 | > These memories provide the AI with context for the user's input. 372 | ```csharp 373 | private async Task SearchMemoriesAsync(IKernel kernel, string query) 374 | { 375 | StringBuilder result = new StringBuilder(); 376 | result.Append("The below is relevant information.\n[START INFO]"); 377 | 378 | // Search for memories that are similar to the user's input. 379 | const string memoryCollectionName = "ms10k"; 380 | IAsyncEnumerable queryResults = 381 | kernel.Memory.SearchAsync(memoryCollectionName, query, limit: 3, minRelevanceScore: 0.77); 382 | 383 | // For each memory found, try to get previous and next memories. 384 | await foreach (MemoryQueryResult r in queryResults) 385 | { 386 | int id = int.Parse(r.Metadata.Id); 387 | MemoryQueryResult? rb2 = await kernel.Memory.GetAsync(memoryCollectionName, (id - 2).ToString()); 388 | MemoryQueryResult? rb = await kernel.Memory.GetAsync(memoryCollectionName, (id - 1).ToString()); 389 | MemoryQueryResult? ra = await kernel.Memory.GetAsync(memoryCollectionName, (id + 1).ToString()); 390 | MemoryQueryResult? ra2 = await kernel.Memory.GetAsync(memoryCollectionName, (id + 2).ToString()); 391 | 392 | if (rb2 != null) result.Append("\n " + rb2.Metadata.Id + ": " + rb2.Metadata.Description + "\n"); 393 | if (rb != null) result.Append("\n " + rb.Metadata.Description + "\n"); 394 | if (r != null) result.Append("\n " + r.Metadata.Description + "\n"); 395 | if (ra != null) result.Append("\n " + ra.Metadata.Description + "\n"); 396 | if (ra2 != null) result.Append("\n " + ra2.Metadata.Id + ": " + ra2.Metadata.Description + "\n"); 397 | } 398 | 399 | result.Append("\n[END INFO]"); 400 | result.Append($"\n{query}"); 401 | 402 | return result.ToString(); 403 | } 404 | ``` 405 | 406 | 1. The complete code files (with additional comments). 407 |
408 | Program.cs 409 | 410 | ```csharp 411 | using Microsoft.Extensions.Configuration; 412 | using Microsoft.Extensions.DependencyInjection; 413 | using Microsoft.Extensions.Hosting; 414 | using Microsoft.Extensions.Logging; 415 | using Microsoft.SemanticKernel; 416 | using Microsoft.SemanticKernel.AI.ChatCompletion; 417 | using Microsoft.SemanticKernel.Connectors.Memory.Qdrant; 418 | 419 | var hostBuilder = new HostBuilder() 420 | .ConfigureFunctionsWorkerDefaults(); 421 | 422 | hostBuilder.ConfigureAppConfiguration((context, config) => 423 | { 424 | config.AddUserSecrets(); 425 | }); 426 | 427 | hostBuilder.ConfigureServices(services => 428 | { 429 | services.AddSingleton(sp => 430 | { 431 | // Retrieve the OpenAI API key from the configuration. 432 | IConfiguration configuration = sp.GetRequiredService(); 433 | string openAiApiKey = configuration["OPENAI_APIKEY"]; 434 | 435 | // Create a memory store that will be used to store memories. 436 | QdrantMemoryStore memoryStore = new QdrantMemoryStore( 437 | host: "http://localhost", 438 | port: 6333, 439 | vectorSize: 1536, 440 | logger: sp.GetRequiredService>()); 441 | 442 | // Create the kerne with chat completion, embedding generation, and memory storage. 443 | IKernel kernel = new KernelBuilder() 444 | .WithLogger(sp.GetRequiredService>()) 445 | .Configure(config => config.AddOpenAIChatCompletionService( 446 | modelId: "gpt-3.5-turbo", 447 | apiKey: openAiApiKey)) 448 | .Configure(c => c.AddOpenAITextEmbeddingGenerationService( 449 | modelId: "text-embedding-ada-002", 450 | apiKey: openAiApiKey)) 451 | .WithMemoryStorage(memoryStore) 452 | .Build(); 453 | 454 | return kernel; 455 | }); 456 | 457 | // Register the chat completion service. 458 | services.AddSingleton(sp => 459 | sp.GetRequiredService().GetService()); 460 | 461 | // Create a new chat history. 462 | const string instructions = "You are a helpful friendly assistant."; 463 | services.AddSingleton(sp => 464 | sp.GetRequiredService().CreateNewChat(instructions)); 465 | }); 466 | 467 | hostBuilder.Build().Run(); 468 | ``` 469 |
470 | 471 |
472 | MyChatFunction.cs 473 | 474 | ```csharp 475 | using System.Net; 476 | using System.Text; 477 | using Microsoft.Azure.Functions.Worker; 478 | using Microsoft.Azure.Functions.Worker.Http; 479 | using Microsoft.Extensions.Logging; 480 | using Microsoft.SemanticKernel; 481 | using Microsoft.SemanticKernel.AI.ChatCompletion; 482 | using Microsoft.SemanticKernel.Memory; 483 | 484 | namespace My.MyChatFunction 485 | { 486 | public class MyChatFunction 487 | { 488 | private readonly ILogger _logger; 489 | private readonly IKernel _kernel; 490 | private readonly IChatCompletion _chat; 491 | private readonly ChatHistory _chatHistory; 492 | 493 | public MyChatFunction(ILoggerFactory loggerFactory, IKernel kernel, ChatHistory chatHistory, IChatCompletion chat) 494 | { 495 | _logger = loggerFactory.CreateLogger(); 496 | _kernel = kernel; 497 | _chat = chat; 498 | _chatHistory = chatHistory; 499 | } 500 | 501 | [Function("MyChatFunction")] 502 | public async Task Run([HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req) 503 | { 504 | _logger.LogInformation("C# HTTP trigger function processed a request."); 505 | 506 | //_chatHistory!.AddMessage(ChatHistory.AuthorRoles.User, await req.ReadAsStringAsync() ?? string.Empty); 507 | string message = await SearchMemoriesAsync(_kernel, await req.ReadAsStringAsync() ?? string.Empty); 508 | _chatHistory!.AddMessage(ChatHistory.AuthorRoles.User, message); 509 | 510 | string reply = await _chat.GenerateMessageAsync(_chatHistory, new ChatRequestSettings()); 511 | 512 | _chatHistory.AddMessage(ChatHistory.AuthorRoles.Assistant, reply); 513 | 514 | HttpResponseData response = req.CreateResponse(HttpStatusCode.OK); 515 | response.WriteString(reply); 516 | return response; 517 | } 518 | 519 | private async Task SearchMemoriesAsync(IKernel kernel, string query) 520 | { 521 | StringBuilder result = new StringBuilder(); 522 | result.Append("The below is relevant information.\n[START INFO]"); 523 | 524 | const string memoryCollectionName = "ms10k"; 525 | 526 | IAsyncEnumerable queryResults = 527 | kernel.Memory.SearchAsync(memoryCollectionName, query, limit: 3, minRelevanceScore: 0.77); 528 | 529 | // For each memory found, get previous and next memories. 530 | await foreach (MemoryQueryResult r in queryResults) 531 | { 532 | int id = int.Parse(r.Metadata.Id); 533 | MemoryQueryResult? rb2 = await kernel.Memory.GetAsync(memoryCollectionName, (id - 2).ToString()); 534 | MemoryQueryResult? rb = await kernel.Memory.GetAsync(memoryCollectionName, (id - 1).ToString()); 535 | MemoryQueryResult? ra = await kernel.Memory.GetAsync(memoryCollectionName, (id + 1).ToString()); 536 | MemoryQueryResult? ra2 = await kernel.Memory.GetAsync(memoryCollectionName, (id + 2).ToString()); 537 | 538 | if (rb2 != null) result.Append("\n " + rb2.Metadata.Id + ": " + rb2.Metadata.Description + "\n"); 539 | if (rb != null) result.Append("\n " + rb.Metadata.Description + "\n"); 540 | if (r != null) result.Append("\n " + r.Metadata.Description + "\n"); 541 | if (ra != null) result.Append("\n " + ra.Metadata.Description + "\n"); 542 | if (ra2 != null) result.Append("\n " + ra2.Metadata.Id + ": " + ra2.Metadata.Description + "\n"); 543 | } 544 | 545 | result.Append("\n[END INFO]"); 546 | result.Append($"\n{query}"); 547 | 548 | return result.ToString(); 549 | } 550 | } 551 | } 552 | 553 | ``` 554 |
555 | 556 | Before running our new code, we'll need to launch and populate the vector database. 557 | 558 | ## Deploy Qdrant VectorDB and Populate Data 559 | In this section we deploy the Qdrant vector database locally and populate it with example data (i.e., Microsoft's 2022 10-K financial report). This will take approximately 15 minutes to import and will use OpenAI’s embedding generation service to create embeddings for the 10-K. 560 | 561 | 1. Start Docker Desktop and wait until it is running. 562 | 563 | 1. Open a terminal and use Docker to pull down the container image for Qdrant. 564 | ```bash 565 | docker pull qdrant/qdrant 566 | ``` 567 | 568 | 1. Change directory to the root of this repo (e.g., `semantic-kernel-rag-chat`) and create a `./data/qdrant` directory for Qdrant to use as persistent storage. 569 | Then start the Qdrant container on port `6333` using the `./data/qdrant` folder as the persistent storage location. 570 | ```bash 571 | mkdir ./data/qdrant 572 | docker run --name mychat -p 6333:6333 -v "$(pwd)/data/qdrant:/qdrant/storage" qdrant/qdrant 573 | ``` 574 | > To stop the container, in another terminal window run `docker container stop mychat; docker container rm mychat;`. 575 | 576 | 1. Open a second terminal and change directory to the `importmemories` project folder in this repo (e.g., `semantic-kernel-rag-chat/src/importmemories`). Run the `importmemories` tool with the command below to populate the vector database with your data. 577 | > Make sure the `--collection` argument matches the `collectionName` variable in the `SearchMemoriesAsync` method above. 578 | 579 | > **Note:** This may take several minutes to several hours depending on the size of your data. This repo contains 580 | Microsoft's 2022 10-K financial report data as an example which should normally take about 15 minutes to import. 581 | 582 | ```bash 583 | dotnet run -- --memory-type qdrant --memory-url http://localhost:6333 --collection ms10k --text-file ../../data/ms10k.txt 584 | ``` 585 | > When importing your own data, try to import all files at the same time using multiple `--text-file` arguments. 586 | > This example leverages incremental indexes which are best constructed when all data is present. 587 | 588 | > If you want to reset the memory store, delete and recreate the directory in step 2, or create a new directory to use. 589 | 590 | ## Run the function locally 591 | 1. With Qdrant running and populated, run your Azure Function locally by opening a terminal, changing directory to your Azure Function project (e.g., `semantic-kernel-rag-chat/src/myfunc`), and starting the function by running 592 | ```bash 593 | func start 594 | ``` 595 | > Make a note of the URL displayed (e.g., `http://localhost:7071/api/MyChatFunction`). 596 | 597 | 1. Start the test console application 598 | Open a second terminal and change directory to the `chatconsole` project folder (e.g., `semantic-kernel-rag-chat/src/chatconsole`) and run the application using the Azure Function URL. 599 | ```bash 600 | dotnet run http://localhost:7071/api/MyChatFunction 601 | ``` 602 | 1. Type a message and press enter to verify that we are able to chat with the AI! 603 | ``` 604 | Input: Hello, how are you? 605 | AI: Hello! As an AI language model, I don't have feelings, but I'm functioning properly and ready to 606 | assist you. How can I help you today? 607 | ``` 608 | 609 | 1. Now let's try ask the same question from before about Microsoft's 2022 revenue 610 | ``` 611 | Input: What was Microsoft's cloud revenue for 2022? 612 | AI: Microsoft's cloud revenue for 2022 was $91.2 billion. 613 | ``` 614 | > The AI now has the ability to search through the Microsoft 10-K financial report and find the answer to our question. 615 | > Let's try another... 616 | ``` 617 | Input: Did linkedin's revenue grow in 2022? 618 | AI: Yes, LinkedIn's revenue grew in 2022. It increased by $3.5 billion or 34% driven by a strong job 619 | market in the Talent Solutions business and advertising demand in the Marketing Solutions business. 620 | ``` 621 | 622 | 623 | # Chapter 3: Azure Cognitive Search and Retrieval Augmented Generation 624 | [Azure Cognitive Search](https://learn.microsoft.com/en-us/azure/search/search-what-is-azure-search) is a powerful cloud search service that enables developers to build rich search experiences across their own private and heterogenous data sources. With [semantic search](https://learn.microsoft.com/en-us/azure/search/semantic-search-overview#what-is-semantic-search), Azure Cognitive Search can produce more semantically relevant results for text-based queries. 625 | 626 | This is an alternative to the vector-based approach we took in Chapter 2. With semantic search, we no longer need to generate embeddings like we did in the previous chapter. Instead, a [semantic re-ranking process](https://learn.microsoft.com/en-us/azure/search/semantic-ranking) is applied to the initial set of search results, using the context and meaning of words to elevate the results that are most relevant. 627 | 628 | In this chapter, we will modify our chat function to use Azure Cognitive Search with semantic search. We will once again demonstrate how we can use this memory to generate more meaningful results in our chat application. 629 | 630 | ## Configure your environment 631 | Before you get started, make sure you have the following additional requirements: 632 | - An instance of the Azure Cognitive Search service, with semantic search enabled. 633 | - For instructions on setting up an Azure Cognitive Search service instance, click [here](https://learn.microsoft.com/en-us/azure/search/search-create-service-portal). 634 | - For instructions on enabling semantic search, click [here](https://learn.microsoft.com/en-us/azure/search/semantic-search-overview#enable-semantic-search). 635 | - To connect to the service, you will need the following two pieces of data: 636 | - `AZURE_COGNITIVE_SEARCH_APIKEY`: an [admin key](https://learn.microsoft.com/en-us/azure/search/search-security-api-keys?tabs=portal-use%2Cportal-find%2Cportal-query#find-existing-keys) to your Azure Cognitive Search service 637 | - `AZURE_COGNITIVE_SEARCH_URL`: the URL to your Azure Cognitive Search endpoint 638 | 639 | ## Update the memory store in our Azure Function 640 | 1. Open a terminal window, change to the directory with your project file (e.g., `semantic-kernel-rag-chat/src/myfunc`), 641 | and run the `dotnet` command below to add the Semantic Kernel Azure Cognitive Search connector to your project. 642 | ```bash 643 | dotnet add package Microsoft.SemanticKernel.Connectors.Memory.AzureCognitiveSearch --prerelease -v 0.14.547.1-preview 644 | ``` 645 | 646 | In addition, use the `dotnet user-secrets` commands below to securely store your Azure Cognitive Search API key and endpoint URL. 647 | ```bash 648 | dotnet user-secrets set "AZURE_COGNITIVE_SEARCH_APIKEY" "" 649 | dotnet user-secrets set "AZURE_COGNITIVE_SEARCH_URL" "" 650 | ``` 651 | 652 | 1. Open your Program code file (e.g., `Program.cs`) and add the Azure Cognitive Search connector using statement to the top. 653 | ```csharp 654 | using Microsoft.SemanticKernel.Connectors.Memory.AzureCognitiveSearch; 655 | ``` 656 | 657 | Replace the Qdrant memory store that we added in Chapter 2 with the Azure Cognitive Search memory connector. 658 | 659 | ```csharp 660 | AzureCognitiveSearchMemory memory = new AzureCognitiveSearchMemory( 661 | configuration["AZURE_COGNITIVE_SEARCH_URL"], 662 | configuration["AZURE_COGNITIVE_SEARCH_APIKEY"] 663 | ); 664 | ``` 665 | 666 | Then, update the builder code where we instantiate the kernel. We can remove the OpenAI embedding generation service and the Qdrant memory store from the builder, and replace them with the Azure Cognitive Search memory that we just created. 667 | 668 | ```csharp 669 | IKernel kernel = new KernelBuilder() 670 | .WithLogger(sp.GetRequiredService>()) 671 | .Configure(config => config.AddOpenAIChatCompletionService( 672 | modelId: "gpt-3.5-turbo", 673 | apiKey: openAiApiKey)) 674 | .WithMemory(memory) 675 | .Build(); 676 | ``` 677 | 678 | > No changes need to be made to `SearchMemoriesAsync`, since it uses the kernel's semantic memory abstraction to generate context for the query. While the underlying memory source has changed, this abstraction has not. 679 | 680 | 1. The complete code files (with additional comments). 681 |
682 | Program.cs 683 | 684 | ```csharp 685 | using Microsoft.Extensions.Configuration; 686 | using Microsoft.Extensions.DependencyInjection; 687 | using Microsoft.Extensions.Hosting; 688 | using Microsoft.Extensions.Logging; 689 | using Microsoft.SemanticKernel; 690 | using Microsoft.SemanticKernel.AI.ChatCompletion; 691 | using Microsoft.SemanticKernel.Connectors.Memory.AzureCognitiveSearch; 692 | 693 | var hostBuilder = new HostBuilder() 694 | .ConfigureFunctionsWorkerDefaults(); 695 | 696 | hostBuilder.ConfigureAppConfiguration((context, config) => 697 | { 698 | config.AddUserSecrets(); 699 | }); 700 | 701 | hostBuilder.ConfigureServices(services => 702 | { 703 | services.AddSingleton(sp => 704 | { 705 | // Retrieve the OpenAI API key from the configuration. 706 | IConfiguration configuration = sp.GetRequiredService(); 707 | string openAiApiKey = configuration["OPENAI_APIKEY"]; 708 | 709 | // Create a memory connector to Azure Cognitive Search that will be used to store memories. 710 | AzureCognitiveSearchMemory memory = new AzureCognitiveSearchMemory( 711 | configuration["AZURE_COGNITIVE_SEARCH_URL"], 712 | configuration["AZURE_COGNITIVE_SEARCH_APIKEY"] 713 | ); 714 | 715 | // Create the kernel with chat completion and memory. 716 | IKernel kernel = new KernelBuilder() 717 | .WithLogger(sp.GetRequiredService>()) 718 | .Configure(config => config.AddOpenAIChatCompletionService( 719 | modelId: "gpt-3.5-turbo", 720 | apiKey: openAiApiKey)) 721 | .WithMemory(memory) 722 | .Build(); 723 | 724 | return kernel; 725 | }); 726 | 727 | // Register the chat completion service. 728 | services.AddSingleton(sp => 729 | sp.GetRequiredService().GetService()); 730 | 731 | // Create a new chat history. 732 | const string instructions = "You are a helpful friendly assistant."; 733 | services.AddSingleton(sp => 734 | sp.GetRequiredService().CreateNewChat(instructions)); 735 | }); 736 | 737 | hostBuilder.Build().Run(); 738 | ``` 739 |
740 | 741 |
742 | MyChatFunction.cs (unchanged) 743 | 744 | ```csharp 745 | using System.Net; 746 | using System.Text; 747 | using Microsoft.Azure.Functions.Worker; 748 | using Microsoft.Azure.Functions.Worker.Http; 749 | using Microsoft.Extensions.Logging; 750 | using Microsoft.SemanticKernel; 751 | using Microsoft.SemanticKernel.AI.ChatCompletion; 752 | using Microsoft.SemanticKernel.Memory; 753 | 754 | namespace My.MyChatFunction 755 | { 756 | public class MyChatFunction 757 | { 758 | private readonly ILogger _logger; 759 | private readonly IKernel _kernel; 760 | private readonly IChatCompletion _chat; 761 | private readonly ChatHistory _chatHistory; 762 | 763 | public MyChatFunction(ILoggerFactory loggerFactory, IKernel kernel, ChatHistory chatHistory, IChatCompletion chat) 764 | { 765 | _logger = loggerFactory.CreateLogger(); 766 | _kernel = kernel; 767 | _chat = chat; 768 | _chatHistory = chatHistory; 769 | } 770 | 771 | [Function("MyChatFunction")] 772 | public async Task Run([HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req) 773 | { 774 | _logger.LogInformation("C# HTTP trigger function processed a request."); 775 | 776 | //_chatHistory!.AddMessage(ChatHistory.AuthorRoles.User, await req.ReadAsStringAsync() ?? string.Empty); 777 | string message = await SearchMemoriesAsync(_kernel, await req.ReadAsStringAsync() ?? string.Empty); 778 | _chatHistory!.AddMessage(ChatHistory.AuthorRoles.User, message); 779 | 780 | string reply = await _chat.GenerateMessageAsync(_chatHistory, new ChatRequestSettings()); 781 | 782 | _chatHistory.AddMessage(ChatHistory.AuthorRoles.Assistant, reply); 783 | 784 | HttpResponseData response = req.CreateResponse(HttpStatusCode.OK); 785 | response.WriteString(reply); 786 | return response; 787 | } 788 | 789 | private async Task SearchMemoriesAsync(IKernel kernel, string query) 790 | { 791 | StringBuilder result = new StringBuilder(); 792 | result.Append("The below is relevant information.\n[START INFO]"); 793 | 794 | const string memoryCollectionName = "ms10k"; 795 | 796 | IAsyncEnumerable queryResults = 797 | kernel.Memory.SearchAsync(memoryCollectionName, query, limit: 3, minRelevanceScore: 0.77); 798 | 799 | // For each memory found, get previous and next memories. 800 | await foreach (MemoryQueryResult r in queryResults) 801 | { 802 | int id = int.Parse(r.Metadata.Id); 803 | MemoryQueryResult? rb2 = await kernel.Memory.GetAsync(memoryCollectionName, (id - 2).ToString()); 804 | MemoryQueryResult? rb = await kernel.Memory.GetAsync(memoryCollectionName, (id - 1).ToString()); 805 | MemoryQueryResult? ra = await kernel.Memory.GetAsync(memoryCollectionName, (id + 1).ToString()); 806 | MemoryQueryResult? ra2 = await kernel.Memory.GetAsync(memoryCollectionName, (id + 2).ToString()); 807 | 808 | if (rb2 != null) result.Append("\n " + rb2.Metadata.Id + ": " + rb2.Metadata.Description + "\n"); 809 | if (rb != null) result.Append("\n " + rb.Metadata.Description + "\n"); 810 | if (r != null) result.Append("\n " + r.Metadata.Description + "\n"); 811 | if (ra != null) result.Append("\n " + ra.Metadata.Description + "\n"); 812 | if (ra2 != null) result.Append("\n " + ra2.Metadata.Id + ": " + ra2.Metadata.Description + "\n"); 813 | } 814 | 815 | result.Append("\n[END INFO]"); 816 | result.Append($"\n{query}"); 817 | 818 | return result.ToString(); 819 | } 820 | } 821 | } 822 | 823 | ``` 824 |
825 | 826 | Before running our updated code, we'll need to populate an Azure Cognitive Search index. 827 | 828 | ## Populate the search index 829 | In this section we create and populate an Azure Cognitive Search index with example data (i.e., Microsoft's 2022 10-K financial report). This will take approximately 5 minutes to import. 830 | 831 | 1. Open a terminal and change directory to the `importmemories` project folder in this repo. Run the `importmemories` tool with the command below to populate the search index with your data. 832 | > Make sure the `--collection` argument matches the `collectionName` variable in the `SearchMemoriesAsync` method above. 833 | 834 | > **Note:** This may take several minutes to several hours depending on the size of your data. This repo contains Microsoft's 2022 10-K financial report data as an example which should normally take about 5 minutes to import. 835 | 836 | ```bash 837 | dotnet run -- --memory-type azurecognitivesearch --memory-url $AZURE_COGNITIVE_SEARCH_URL --collection ms10k --text-file ../../data/ms10k.txt 838 | ``` 839 | 840 | > If you want to reset the memory store, you can delete the index from your service via the Azure portal or [the Azure Cognitive Search REST API](https://learn.microsoft.com/en-us/rest/api/searchservice/delete-index). The index name is the same as the `--collection` argument (e.g. `ms10k`). 841 | 842 | ## Run the function locally 843 | 1. With the Azure Cognitive Search service running and populated, run your Azure Function locally by opening a terminal, changing directory to your Azure Function project (e.g., `semantic-kernel-rag-chat/src/myfunc`), and starting the function by running 844 | ```bash 845 | func start 846 | ``` 847 | > Make a note of the URL displayed (e.g., `http://localhost:7071/api/MyChatFunction`). 848 | 849 | 1. Start the test console application 850 | Open a second terminal and change directory to the `chatconsole` project folder (e.g., `semantic-kernel-rag-chat/src/chatconsole`) and run the application using the Azure Function URL. 851 | ```bash 852 | dotnet run http://localhost:7071/api/MyChatFunction 853 | ``` 854 | 1. Type a message and press enter to verify that we are still able to chat with the AI. 855 | ``` 856 | Input: Hi, how are you? 857 | AI: Hello! I'm an AI language model, so I don't have feelings, but I'm here to 858 | assist you. How can I help you today? 859 | ``` 860 | 861 | 1. Now let's try asking the same questions from before about Microsoft's 2022 revenue. 862 | ``` 863 | Input: What was Microsoft's cloud revenue for 2022? 864 | AI: Microsoft's cloud revenue in fiscal year 2022 was $91.2 billion. 865 | ``` 866 | ``` 867 | Input: Did linkedin's revenue grow in 2022? 868 | AI: Yes, LinkedIn's revenue increased by 34% in fiscal year 2022 compared to the previous year. 869 | ``` 870 | 871 | 872 | We've now seen how we can improve the experience of our Semantic Kernel chat application to leverage the Retrieval Augmented Generation pattern using Azure Cognitive Search with semantic search. Try adding more data to your search index and explore what else you can create with Semantic Kernel. 873 | 874 | For more guidance and ideas, checkout out the [SK documentation](https://learn.microsoft.com/en-us/semantic-kernel/), [blog](https://devblogs.microsoft.com/semantic-kernel/), and [Discord community](https://discord.com/invite/VpnfAZkv2a). Happy building! 875 | 876 | # Appendix 877 | ## Deploy Azure Function to Azure 878 | 1. If you don't already have an Azure account go to https://azure.microsoft.com, click on `Try Azure for free`, and select `Start Free` to start creating a free Azure account with your Microsoft or GitHub account. After signing in, you will be prompted to enter some information. 879 | 880 | > This tutorial uses **Azure Functions** ([pricing](https://azure.microsoft.com/en-us/pricing/details/functions/)) and **Azure Cognitive Search** ([pricing](https://azure.microsoft.com/pricing/details/search/)) that may incur a monthly cost. Visit [here](https://azure.microsoft.com/free/cognitive-search/) to get some free Azure credits to get you started. 881 | 882 | 1. In Visual Studio Code, click on the Azure extension (or press `SHIFT+ALT+A`) 883 | 1. Mouse-over `RESOURCES` and select `Create Resource` (i.e., +), select `Create Function App in Azure...`, select your Azure Subscription. 884 | 1. Enter a name for your deployed function, for example `fn-mychatfunction`. 885 | 1. Set the runtime stack to `.NET 7 Isolated` and choose a location in which to deploy your Azure Function. 886 | > If you don't have a preference, choose the recommended region. 887 | 1. Wait until the `Create Function App` completes, which should only take a minute or so. 888 | 889 | 1. Mouse-over `WORKSPACE` and select `Deploy` (i.e., ☁️) then `Deploy to Function App`. 890 | 1. Select the same Azure Subscription in which you created the Azure Function in Azure, then select the Azure Function you created above (e.g., `fn-mychatfunction`). 891 | > It may take a minute or two to complete the deployment. 892 | 893 | 894 | 895 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /data/ms10k.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/semantic-kernel-rag-chat/cc51e164ac1e559e80437918c671ab6257e7c873/data/ms10k.txt -------------------------------------------------------------------------------- /src/chapter1/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Azure Functions localsettings file 5 | local.settings.json 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # DNX 47 | project.lock.json 48 | project.fragment.lock.json 49 | artifacts/ 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # NCrunch 117 | _NCrunch_* 118 | .*crunch*.local.xml 119 | nCrunchTemp_* 120 | 121 | # MightyMoose 122 | *.mm.* 123 | AutoTest.Net/ 124 | 125 | # Web workbench (sass) 126 | .sass-cache/ 127 | 128 | # Installshield output folder 129 | [Ee]xpress/ 130 | 131 | # DocProject is a documentation generator add-in 132 | DocProject/buildhelp/ 133 | DocProject/Help/*.HxT 134 | DocProject/Help/*.HxC 135 | DocProject/Help/*.hhc 136 | DocProject/Help/*.hhk 137 | DocProject/Help/*.hhp 138 | DocProject/Help/Html2 139 | DocProject/Help/html 140 | 141 | # Click-Once directory 142 | publish/ 143 | 144 | # Publish Web Output 145 | *.[Pp]ublish.xml 146 | *.azurePubxml 147 | # TODO: Comment the next line if you want to checkin your web deploy settings 148 | # but database connection strings (with potential passwords) will be unencrypted 149 | #*.pubxml 150 | *.publishproj 151 | 152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 153 | # checkin your Azure Web App publish settings, but sensitive information contained 154 | # in these scripts will be unencrypted 155 | PublishScripts/ 156 | 157 | # NuGet Packages 158 | *.nupkg 159 | # The packages folder can be ignored because of Package Restore 160 | **/packages/* 161 | # except build/, which is used as an MSBuild target. 162 | !**/packages/build/ 163 | # Uncomment if necessary however generally it will be regenerated when needed 164 | #!**/packages/repositories.config 165 | # NuGet v3's project.json files produces more ignoreable files 166 | *.nuget.props 167 | *.nuget.targets 168 | 169 | # Microsoft Azure Build Output 170 | csx/ 171 | *.build.csdef 172 | 173 | # Microsoft Azure Emulator 174 | ecf/ 175 | rcf/ 176 | 177 | # Windows Store app package directories and files 178 | AppPackages/ 179 | BundleArtifacts/ 180 | Package.StoreAssociation.xml 181 | _pkginfo.txt 182 | 183 | # Visual Studio cache files 184 | # files ending in .cache can be ignored 185 | *.[Cc]ache 186 | # but keep track of directories ending in .cache 187 | !*.[Cc]ache/ 188 | 189 | # Others 190 | ClientBin/ 191 | ~$* 192 | *~ 193 | *.dbmdl 194 | *.dbproj.schemaview 195 | *.jfm 196 | *.pfx 197 | *.publishsettings 198 | node_modules/ 199 | orleans.codegen.cs 200 | 201 | # Since there are multiple workflows, uncomment next line to ignore bower_components 202 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 203 | #bower_components/ 204 | 205 | # RIA/Silverlight projects 206 | Generated_Code/ 207 | 208 | # Backup & report files from converting an old project file 209 | # to a newer Visual Studio version. Backup files are not needed, 210 | # because we have git ;-) 211 | _UpgradeReport_Files/ 212 | Backup*/ 213 | UpgradeLog*.XML 214 | UpgradeLog*.htm 215 | 216 | # SQL Server files 217 | *.mdf 218 | *.ldf 219 | 220 | # Business Intelligence projects 221 | *.rdl.data 222 | *.bim.layout 223 | *.bim_*.settings 224 | 225 | # Microsoft Fakes 226 | FakesAssemblies/ 227 | 228 | # GhostDoc plugin setting file 229 | *.GhostDoc.xml 230 | 231 | # Node.js Tools for Visual Studio 232 | .ntvs_analysis.dat 233 | 234 | # Visual Studio 6 build log 235 | *.plg 236 | 237 | # Visual Studio 6 workspace options file 238 | *.opt 239 | 240 | # Visual Studio LightSwitch build output 241 | **/*.HTMLClient/GeneratedArtifacts 242 | **/*.DesktopClient/GeneratedArtifacts 243 | **/*.DesktopClient/ModelManifest.xml 244 | **/*.Server/GeneratedArtifacts 245 | **/*.Server/ModelManifest.xml 246 | _Pvt_Extensions 247 | 248 | # Paket dependency manager 249 | .paket/paket.exe 250 | paket-files/ 251 | 252 | # FAKE - F# Make 253 | .fake/ 254 | 255 | # JetBrains Rider 256 | .idea/ 257 | *.sln.iml 258 | 259 | # CodeRush 260 | .cr/ 261 | 262 | # Python Tools for Visual Studio (PTVS) 263 | __pycache__/ 264 | *.pyc -------------------------------------------------------------------------------- /src/chapter1/Chapter1Function.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Microsoft.Azure.Functions.Worker; 3 | using Microsoft.Azure.Functions.Worker.Http; 4 | using Microsoft.Extensions.Logging; 5 | using Microsoft.SemanticKernel.AI.ChatCompletion; 6 | 7 | namespace My.Chapter1 8 | { 9 | public class Chapter1Function 10 | { 11 | private readonly ILogger _logger; 12 | private readonly IChatCompletion _chat; 13 | private readonly ChatHistory _chatHistory; 14 | 15 | public Chapter1Function(ILoggerFactory loggerFactory, ChatHistory chatHistory, IChatCompletion chat) 16 | { 17 | _logger = loggerFactory.CreateLogger(); 18 | _chat = chat; 19 | _chatHistory = chatHistory; 20 | } 21 | 22 | [Function("Chapter1")] 23 | public async Task Run([HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req) 24 | { 25 | _logger.LogInformation("C# HTTP trigger function processed a request."); 26 | 27 | _chatHistory!.AddMessage(ChatHistory.AuthorRoles.User, await req.ReadAsStringAsync() ?? string.Empty); 28 | 29 | string reply = await _chat.GenerateMessageAsync(_chatHistory, new ChatRequestSettings()); 30 | 31 | _chatHistory.AddMessage(ChatHistory.AuthorRoles.Assistant, reply); 32 | 33 | HttpResponseData response = req.CreateResponse(HttpStatusCode.OK); 34 | response.WriteString(reply); 35 | return response; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/chapter1/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | using Microsoft.Extensions.Logging; 5 | using Microsoft.SemanticKernel; 6 | using Microsoft.SemanticKernel.AI.ChatCompletion; 7 | 8 | var hostBuilder = new HostBuilder() 9 | .ConfigureFunctionsWorkerDefaults(); 10 | 11 | hostBuilder.ConfigureAppConfiguration((context, config) => 12 | { 13 | config.AddUserSecrets(); 14 | }); 15 | 16 | hostBuilder.ConfigureServices(services => 17 | { 18 | services.AddSingleton(sp => 19 | { 20 | IConfiguration configuration = sp.GetRequiredService(); 21 | string openAiApiKey = configuration["OPENAI_APIKEY"]; 22 | 23 | IKernel kernel = new KernelBuilder() 24 | .WithLogger(sp.GetRequiredService>()) 25 | .Configure(config => config.AddOpenAIChatCompletionService( 26 | modelId: "gpt-3.5-turbo", 27 | apiKey: openAiApiKey)) 28 | .Build(); 29 | 30 | return kernel; 31 | }); 32 | 33 | services.AddSingleton(sp => 34 | sp.GetRequiredService().GetService()); 35 | 36 | const string instructions = "You are a helpful friendly assistant."; 37 | services.AddSingleton(sp => 38 | sp.GetRequiredService().CreateNewChat(instructions)); 39 | }); 40 | 41 | IHost host = hostBuilder.Build(); 42 | host.Run(); -------------------------------------------------------------------------------- /src/chapter1/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "func": { 4 | "commandName": "Project", 5 | "commandLineArgs": "--port 7010", 6 | "launchBrowser": false 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /src/chapter1/chapter1.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net7.0 4 | v4 5 | Exe 6 | enable 7 | enable 8 | semantic-kernel-rag-chat 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | PreserveNewest 20 | 21 | 22 | PreserveNewest 23 | Never 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/chapter1/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "logging": { 4 | "applicationInsights": { 5 | "samplingSettings": { 6 | "isEnabled": true, 7 | "excludedTypes": "Request" 8 | } 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/chapter2/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Azure Functions localsettings file 5 | local.settings.json 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # DNX 47 | project.lock.json 48 | project.fragment.lock.json 49 | artifacts/ 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # NCrunch 117 | _NCrunch_* 118 | .*crunch*.local.xml 119 | nCrunchTemp_* 120 | 121 | # MightyMoose 122 | *.mm.* 123 | AutoTest.Net/ 124 | 125 | # Web workbench (sass) 126 | .sass-cache/ 127 | 128 | # Installshield output folder 129 | [Ee]xpress/ 130 | 131 | # DocProject is a documentation generator add-in 132 | DocProject/buildhelp/ 133 | DocProject/Help/*.HxT 134 | DocProject/Help/*.HxC 135 | DocProject/Help/*.hhc 136 | DocProject/Help/*.hhk 137 | DocProject/Help/*.hhp 138 | DocProject/Help/Html2 139 | DocProject/Help/html 140 | 141 | # Click-Once directory 142 | publish/ 143 | 144 | # Publish Web Output 145 | *.[Pp]ublish.xml 146 | *.azurePubxml 147 | # TODO: Comment the next line if you want to checkin your web deploy settings 148 | # but database connection strings (with potential passwords) will be unencrypted 149 | #*.pubxml 150 | *.publishproj 151 | 152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 153 | # checkin your Azure Web App publish settings, but sensitive information contained 154 | # in these scripts will be unencrypted 155 | PublishScripts/ 156 | 157 | # NuGet Packages 158 | *.nupkg 159 | # The packages folder can be ignored because of Package Restore 160 | **/packages/* 161 | # except build/, which is used as an MSBuild target. 162 | !**/packages/build/ 163 | # Uncomment if necessary however generally it will be regenerated when needed 164 | #!**/packages/repositories.config 165 | # NuGet v3's project.json files produces more ignoreable files 166 | *.nuget.props 167 | *.nuget.targets 168 | 169 | # Microsoft Azure Build Output 170 | csx/ 171 | *.build.csdef 172 | 173 | # Microsoft Azure Emulator 174 | ecf/ 175 | rcf/ 176 | 177 | # Windows Store app package directories and files 178 | AppPackages/ 179 | BundleArtifacts/ 180 | Package.StoreAssociation.xml 181 | _pkginfo.txt 182 | 183 | # Visual Studio cache files 184 | # files ending in .cache can be ignored 185 | *.[Cc]ache 186 | # but keep track of directories ending in .cache 187 | !*.[Cc]ache/ 188 | 189 | # Others 190 | ClientBin/ 191 | ~$* 192 | *~ 193 | *.dbmdl 194 | *.dbproj.schemaview 195 | *.jfm 196 | *.pfx 197 | *.publishsettings 198 | node_modules/ 199 | orleans.codegen.cs 200 | 201 | # Since there are multiple workflows, uncomment next line to ignore bower_components 202 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 203 | #bower_components/ 204 | 205 | # RIA/Silverlight projects 206 | Generated_Code/ 207 | 208 | # Backup & report files from converting an old project file 209 | # to a newer Visual Studio version. Backup files are not needed, 210 | # because we have git ;-) 211 | _UpgradeReport_Files/ 212 | Backup*/ 213 | UpgradeLog*.XML 214 | UpgradeLog*.htm 215 | 216 | # SQL Server files 217 | *.mdf 218 | *.ldf 219 | 220 | # Business Intelligence projects 221 | *.rdl.data 222 | *.bim.layout 223 | *.bim_*.settings 224 | 225 | # Microsoft Fakes 226 | FakesAssemblies/ 227 | 228 | # GhostDoc plugin setting file 229 | *.GhostDoc.xml 230 | 231 | # Node.js Tools for Visual Studio 232 | .ntvs_analysis.dat 233 | 234 | # Visual Studio 6 build log 235 | *.plg 236 | 237 | # Visual Studio 6 workspace options file 238 | *.opt 239 | 240 | # Visual Studio LightSwitch build output 241 | **/*.HTMLClient/GeneratedArtifacts 242 | **/*.DesktopClient/GeneratedArtifacts 243 | **/*.DesktopClient/ModelManifest.xml 244 | **/*.Server/GeneratedArtifacts 245 | **/*.Server/ModelManifest.xml 246 | _Pvt_Extensions 247 | 248 | # Paket dependency manager 249 | .paket/paket.exe 250 | paket-files/ 251 | 252 | # FAKE - F# Make 253 | .fake/ 254 | 255 | # JetBrains Rider 256 | .idea/ 257 | *.sln.iml 258 | 259 | # CodeRush 260 | .cr/ 261 | 262 | # Python Tools for Visual Studio (PTVS) 263 | __pycache__/ 264 | *.pyc -------------------------------------------------------------------------------- /src/chapter2/Chapter2Function.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Text; 3 | using Microsoft.Azure.Functions.Worker; 4 | using Microsoft.Azure.Functions.Worker.Http; 5 | using Microsoft.Extensions.Logging; 6 | using Microsoft.SemanticKernel; 7 | using Microsoft.SemanticKernel.AI.ChatCompletion; 8 | using Microsoft.SemanticKernel.Memory; 9 | 10 | namespace My.MyChatFunction 11 | { 12 | public class Chapter2Function 13 | { 14 | private readonly ILogger _logger; 15 | private readonly IKernel _kernel; 16 | private readonly IChatCompletion _chat; 17 | private readonly ChatHistory _chatHistory; 18 | 19 | public Chapter2Function(ILoggerFactory loggerFactory, IKernel kernel, ChatHistory chatHistory, IChatCompletion chat) 20 | { 21 | _logger = loggerFactory.CreateLogger(); 22 | _kernel = kernel; 23 | _chat = chat; 24 | _chatHistory = chatHistory; 25 | } 26 | 27 | [Function("MyChatFunction")] 28 | public async Task Run([HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req) 29 | { 30 | _logger.LogInformation("C# HTTP trigger function processed a request."); 31 | 32 | _chatHistory!.AddMessage(ChatHistory.AuthorRoles.User, await req.ReadAsStringAsync() ?? string.Empty); 33 | //string message = await SearchMemoriesAsync(_kernel, await req.ReadAsStringAsync() ?? string.Empty); 34 | //_chatHistory!.AddMessage(ChatHistory.AuthorRoles.User, message); 35 | 36 | string reply = await _chat.GenerateMessageAsync(_chatHistory, new ChatRequestSettings()); 37 | 38 | _chatHistory.AddMessage(ChatHistory.AuthorRoles.Assistant, reply); 39 | 40 | HttpResponseData response = req.CreateResponse(HttpStatusCode.OK); 41 | response.WriteString(reply); 42 | return response; 43 | } 44 | 45 | private async Task SearchMemoriesAsync(IKernel kernel, string query) 46 | { 47 | StringBuilder result = new StringBuilder(); 48 | result.Append("The below is relevant information.\n[START INFO]"); 49 | 50 | const string memoryCollectionName = "ms10k"; 51 | 52 | IAsyncEnumerable queryResults = 53 | kernel.Memory.SearchAsync(memoryCollectionName, query, limit: 3, minRelevanceScore: 0.77); 54 | 55 | // For each memory found, get previous and next memories. 56 | await foreach (MemoryQueryResult r in queryResults) 57 | { 58 | int id = int.Parse(r.Metadata.Id); 59 | MemoryQueryResult? rb2 = await kernel.Memory.GetAsync(memoryCollectionName, (id - 2).ToString()); 60 | MemoryQueryResult? rb = await kernel.Memory.GetAsync(memoryCollectionName, (id - 1).ToString()); 61 | MemoryQueryResult? ra = await kernel.Memory.GetAsync(memoryCollectionName, (id + 1).ToString()); 62 | MemoryQueryResult? ra2 = await kernel.Memory.GetAsync(memoryCollectionName, (id + 2).ToString()); 63 | 64 | if (rb2 != null) result.Append("\n " + rb2.Metadata.Id + ": " + rb2.Metadata.Description + "\n"); 65 | if (rb != null) result.Append("\n " + rb.Metadata.Description + "\n"); 66 | if (r != null) result.Append("\n " + r.Metadata.Description + "\n"); 67 | if (ra != null) result.Append("\n " + ra.Metadata.Description + "\n"); 68 | if (ra2 != null) result.Append("\n " + ra2.Metadata.Id + ": " + ra2.Metadata.Description + "\n"); 69 | } 70 | 71 | result.Append("\n[END INFO]"); 72 | result.Append($"\n{query}"); 73 | 74 | Console.WriteLine(result); 75 | return result.ToString(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/chapter2/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | using Microsoft.Extensions.Logging; 5 | using Microsoft.SemanticKernel; 6 | using Microsoft.SemanticKernel.AI.ChatCompletion; 7 | using Microsoft.SemanticKernel.Connectors.Memory.Qdrant; 8 | 9 | var hostBuilder = new HostBuilder() 10 | .ConfigureFunctionsWorkerDefaults(); 11 | 12 | hostBuilder.ConfigureAppConfiguration((context, config) => 13 | { 14 | config.AddUserSecrets(); 15 | }); 16 | 17 | hostBuilder.ConfigureServices(services => 18 | { 19 | services.AddSingleton(sp => 20 | { 21 | IConfiguration configuration = sp.GetRequiredService(); 22 | string openAiApiKey = configuration["OPENAI_APIKEY"]; 23 | 24 | QdrantMemoryStore memoryStore = new QdrantMemoryStore( 25 | host: "http://localhost", 26 | port: 6333, 27 | vectorSize: 1536, 28 | logger: sp.GetRequiredService>()); 29 | 30 | IKernel kernel = new KernelBuilder() 31 | .WithLogger(sp.GetRequiredService>()) 32 | .Configure(config => config.AddOpenAIChatCompletionService( 33 | modelId: "gpt-3.5-turbo", 34 | apiKey: openAiApiKey)) 35 | .Configure(c => c.AddOpenAITextEmbeddingGenerationService( 36 | modelId: "text-embedding-ada-002", 37 | apiKey: openAiApiKey)) 38 | .WithMemoryStorage(memoryStore) 39 | .Build(); 40 | 41 | return kernel; 42 | }); 43 | 44 | services.AddSingleton(sp => 45 | sp.GetRequiredService().GetService()); 46 | 47 | const string instructions = "You are a helpful friendly assistant."; 48 | services.AddSingleton(sp => 49 | sp.GetRequiredService().CreateNewChat(instructions)); 50 | }); 51 | 52 | hostBuilder.Build().Run(); -------------------------------------------------------------------------------- /src/chapter2/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "func": { 4 | "commandName": "Project", 5 | "commandLineArgs": "--port 7010", 6 | "launchBrowser": false 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /src/chapter2/chapter2.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net7.0 4 | v4 5 | Exe 6 | enable 7 | enable 8 | semantic-kernel-rag-chat 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | PreserveNewest 21 | 22 | 23 | PreserveNewest 24 | Never 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/chapter2/chapter2.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.6.33513.286 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "chapter2", "chapter2.csproj", "{5237AEA3-E3C0-4B71-A112-41C8F8B8C56F}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {5237AEA3-E3C0-4B71-A112-41C8F8B8C56F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {5237AEA3-E3C0-4B71-A112-41C8F8B8C56F}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {5237AEA3-E3C0-4B71-A112-41C8F8B8C56F}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {5237AEA3-E3C0-4B71-A112-41C8F8B8C56F}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {23CBCD5B-65EE-4E23-AF20-F5DFFAE54C7E} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /src/chapter2/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "logging": { 4 | "applicationInsights": { 5 | "samplingSettings": { 6 | "isEnabled": true, 7 | "excludedTypes": "Request" 8 | } 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/chapter3/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Azure Functions localsettings file 5 | local.settings.json 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # DNX 47 | project.lock.json 48 | project.fragment.lock.json 49 | artifacts/ 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # NCrunch 117 | _NCrunch_* 118 | .*crunch*.local.xml 119 | nCrunchTemp_* 120 | 121 | # MightyMoose 122 | *.mm.* 123 | AutoTest.Net/ 124 | 125 | # Web workbench (sass) 126 | .sass-cache/ 127 | 128 | # Installshield output folder 129 | [Ee]xpress/ 130 | 131 | # DocProject is a documentation generator add-in 132 | DocProject/buildhelp/ 133 | DocProject/Help/*.HxT 134 | DocProject/Help/*.HxC 135 | DocProject/Help/*.hhc 136 | DocProject/Help/*.hhk 137 | DocProject/Help/*.hhp 138 | DocProject/Help/Html2 139 | DocProject/Help/html 140 | 141 | # Click-Once directory 142 | publish/ 143 | 144 | # Publish Web Output 145 | *.[Pp]ublish.xml 146 | *.azurePubxml 147 | # TODO: Comment the next line if you want to checkin your web deploy settings 148 | # but database connection strings (with potential passwords) will be unencrypted 149 | #*.pubxml 150 | *.publishproj 151 | 152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 153 | # checkin your Azure Web App publish settings, but sensitive information contained 154 | # in these scripts will be unencrypted 155 | PublishScripts/ 156 | 157 | # NuGet Packages 158 | *.nupkg 159 | # The packages folder can be ignored because of Package Restore 160 | **/packages/* 161 | # except build/, which is used as an MSBuild target. 162 | !**/packages/build/ 163 | # Uncomment if necessary however generally it will be regenerated when needed 164 | #!**/packages/repositories.config 165 | # NuGet v3's project.json files produces more ignoreable files 166 | *.nuget.props 167 | *.nuget.targets 168 | 169 | # Microsoft Azure Build Output 170 | csx/ 171 | *.build.csdef 172 | 173 | # Microsoft Azure Emulator 174 | ecf/ 175 | rcf/ 176 | 177 | # Windows Store app package directories and files 178 | AppPackages/ 179 | BundleArtifacts/ 180 | Package.StoreAssociation.xml 181 | _pkginfo.txt 182 | 183 | # Visual Studio cache files 184 | # files ending in .cache can be ignored 185 | *.[Cc]ache 186 | # but keep track of directories ending in .cache 187 | !*.[Cc]ache/ 188 | 189 | # Others 190 | ClientBin/ 191 | ~$* 192 | *~ 193 | *.dbmdl 194 | *.dbproj.schemaview 195 | *.jfm 196 | *.pfx 197 | *.publishsettings 198 | node_modules/ 199 | orleans.codegen.cs 200 | 201 | # Since there are multiple workflows, uncomment next line to ignore bower_components 202 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 203 | #bower_components/ 204 | 205 | # RIA/Silverlight projects 206 | Generated_Code/ 207 | 208 | # Backup & report files from converting an old project file 209 | # to a newer Visual Studio version. Backup files are not needed, 210 | # because we have git ;-) 211 | _UpgradeReport_Files/ 212 | Backup*/ 213 | UpgradeLog*.XML 214 | UpgradeLog*.htm 215 | 216 | # SQL Server files 217 | *.mdf 218 | *.ldf 219 | 220 | # Business Intelligence projects 221 | *.rdl.data 222 | *.bim.layout 223 | *.bim_*.settings 224 | 225 | # Microsoft Fakes 226 | FakesAssemblies/ 227 | 228 | # GhostDoc plugin setting file 229 | *.GhostDoc.xml 230 | 231 | # Node.js Tools for Visual Studio 232 | .ntvs_analysis.dat 233 | 234 | # Visual Studio 6 build log 235 | *.plg 236 | 237 | # Visual Studio 6 workspace options file 238 | *.opt 239 | 240 | # Visual Studio LightSwitch build output 241 | **/*.HTMLClient/GeneratedArtifacts 242 | **/*.DesktopClient/GeneratedArtifacts 243 | **/*.DesktopClient/ModelManifest.xml 244 | **/*.Server/GeneratedArtifacts 245 | **/*.Server/ModelManifest.xml 246 | _Pvt_Extensions 247 | 248 | # Paket dependency manager 249 | .paket/paket.exe 250 | paket-files/ 251 | 252 | # FAKE - F# Make 253 | .fake/ 254 | 255 | # JetBrains Rider 256 | .idea/ 257 | *.sln.iml 258 | 259 | # CodeRush 260 | .cr/ 261 | 262 | # Python Tools for Visual Studio (PTVS) 263 | __pycache__/ 264 | *.pyc -------------------------------------------------------------------------------- /src/chapter3/Chapter3Function.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Text; 3 | using Microsoft.Azure.Functions.Worker; 4 | using Microsoft.Azure.Functions.Worker.Http; 5 | using Microsoft.Extensions.Logging; 6 | using Microsoft.SemanticKernel; 7 | using Microsoft.SemanticKernel.AI.ChatCompletion; 8 | using Microsoft.SemanticKernel.Memory; 9 | 10 | namespace My.MyChatFunction 11 | { 12 | public class Chapter3Function 13 | { 14 | private readonly ILogger _logger; 15 | private readonly IKernel _kernel; 16 | private readonly IChatCompletion _chat; 17 | private readonly ChatHistory _chatHistory; 18 | 19 | public Chapter3Function(ILoggerFactory loggerFactory, IKernel kernel, ChatHistory chatHistory, IChatCompletion chat) 20 | { 21 | _logger = loggerFactory.CreateLogger(); 22 | _kernel = kernel; 23 | _chat = chat; 24 | _chatHistory = chatHistory; 25 | } 26 | 27 | [Function("MyChatFunction")] 28 | public async Task Run([HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req) 29 | { 30 | _logger.LogInformation("C# HTTP trigger function processed a request."); 31 | 32 | //_chatHistory!.AddMessage(ChatHistory.AuthorRoles.User, await req.ReadAsStringAsync() ?? string.Empty); 33 | string message = await SearchMemoriesAsync(_kernel, await req.ReadAsStringAsync() ?? string.Empty); 34 | _chatHistory!.AddMessage(ChatHistory.AuthorRoles.User, message); 35 | 36 | string reply = await _chat.GenerateMessageAsync(_chatHistory, new ChatRequestSettings()); 37 | 38 | _chatHistory.AddMessage(ChatHistory.AuthorRoles.Assistant, reply); 39 | 40 | HttpResponseData response = req.CreateResponse(HttpStatusCode.OK); 41 | response.WriteString(reply); 42 | return response; 43 | } 44 | 45 | private async Task SearchMemoriesAsync(IKernel kernel, string query) 46 | { 47 | StringBuilder result = new StringBuilder(); 48 | result.Append("The below is relevant information.\n[START INFO]"); 49 | 50 | const string memoryCollectionName = "ms10k"; 51 | 52 | IAsyncEnumerable queryResults = 53 | kernel.Memory.SearchAsync(memoryCollectionName, query, limit: 3, minRelevanceScore: 0.77); 54 | 55 | // For each memory found, get previous and next memories. 56 | await foreach (MemoryQueryResult r in queryResults) 57 | { 58 | int id = int.Parse(r.Metadata.Id); 59 | MemoryQueryResult? rb2 = await kernel.Memory.GetAsync(memoryCollectionName, (id - 2).ToString()); 60 | MemoryQueryResult? rb = await kernel.Memory.GetAsync(memoryCollectionName, (id - 1).ToString()); 61 | MemoryQueryResult? ra = await kernel.Memory.GetAsync(memoryCollectionName, (id + 1).ToString()); 62 | MemoryQueryResult? ra2 = await kernel.Memory.GetAsync(memoryCollectionName, (id + 2).ToString()); 63 | 64 | if (rb2 != null) result.Append("\n " + rb2.Metadata.Id + ": " + rb2.Metadata.Description + "\n"); 65 | if (rb != null) result.Append("\n " + rb.Metadata.Description + "\n"); 66 | if (r != null) result.Append("\n " + r.Metadata.Description + "\n"); 67 | if (ra != null) result.Append("\n " + ra.Metadata.Description + "\n"); 68 | if (ra2 != null) result.Append("\n " + ra2.Metadata.Id + ": " + ra2.Metadata.Description + "\n"); 69 | } 70 | 71 | result.Append("\n[END INFO]"); 72 | result.Append($"\n{query}"); 73 | 74 | Console.WriteLine(result); 75 | return result.ToString(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/chapter3/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | using Microsoft.Extensions.Logging; 5 | using Microsoft.SemanticKernel; 6 | using Microsoft.SemanticKernel.AI.ChatCompletion; 7 | using Microsoft.SemanticKernel.Connectors.Memory.AzureCognitiveSearch; 8 | 9 | var hostBuilder = new HostBuilder() 10 | .ConfigureFunctionsWorkerDefaults(); 11 | 12 | hostBuilder.ConfigureAppConfiguration((context, config) => 13 | { 14 | config.AddUserSecrets(); 15 | }); 16 | 17 | hostBuilder.ConfigureServices(services => 18 | { 19 | services.AddSingleton(sp => 20 | { 21 | IConfiguration configuration = sp.GetRequiredService(); 22 | string openAiApiKey = configuration["OPENAI_APIKEY"]; 23 | 24 | AzureCognitiveSearchMemory memory = new AzureCognitiveSearchMemory( 25 | configuration["AZURE_COGNITIVE_SEARCH_URL"], 26 | configuration["AZURE_COGNITIVE_SEARCH_APIKEY"] 27 | ); 28 | 29 | IKernel kernel = new KernelBuilder() 30 | .WithLogger(sp.GetRequiredService>()) 31 | .Configure(config => config.AddOpenAIChatCompletionService( 32 | modelId: "gpt-3.5-turbo", 33 | apiKey: openAiApiKey)) 34 | .WithMemory(memory) 35 | .Build(); 36 | 37 | return kernel; 38 | }); 39 | 40 | services.AddSingleton(sp => 41 | sp.GetRequiredService().GetService()); 42 | 43 | const string instructions = "You are a helpful friendly assistant."; 44 | services.AddSingleton(sp => 45 | sp.GetRequiredService().CreateNewChat(instructions)); 46 | }); 47 | 48 | hostBuilder.Build().Run(); -------------------------------------------------------------------------------- /src/chapter3/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "func": { 4 | "commandName": "Project", 5 | "commandLineArgs": "--port 7010", 6 | "launchBrowser": false 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /src/chapter3/chapter3.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net7.0 4 | v4 5 | Exe 6 | enable 7 | enable 8 | semantic-kernel-rag-chat 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | PreserveNewest 21 | 22 | 23 | PreserveNewest 24 | Never 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/chapter3/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "logging": { 4 | "applicationInsights": { 5 | "samplingSettings": { 6 | "isEnabled": true, 7 | "excludedTypes": "Request" 8 | } 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/chatconsole/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | internal class Program 4 | { 5 | private static async Task Main(string[] args) 6 | { 7 | if (args.Length < 1 && string.IsNullOrWhiteSpace(args[0])) 8 | { 9 | Console.Error.WriteLine("Usage: chatconsole.exe {url}"); 10 | Console.Error.WriteLine("Example: chatconsole.exe http://localhost:7077/api/MySemanticKernelFunction"); 11 | return; 12 | } 13 | 14 | string url = args[0]; 15 | 16 | Uri? tmp; 17 | if (!Uri.TryCreate(url, UriKind.Absolute, out tmp)) 18 | { 19 | Console.Error.WriteLine("Please provide a valid URL to your running Semantic Kernel Azure Function"); 20 | Console.Error.WriteLine("Example: chatconsole.exe http://localhost:7077/api/MySemanticKernelFunction"); 21 | return; 22 | } 23 | 24 | Console.WriteLine("Hello! This is a chat console."); 25 | Console.WriteLine(url); 26 | 27 | while (true) 28 | { 29 | Console.Write("Input: "); 30 | string? input = Console.ReadLine() ?? string.Empty; 31 | 32 | using HttpClient client = new(); 33 | 34 | Task response = client.PostAsync( 35 | requestUri: url, 36 | content: new StringContent(input, Encoding.UTF8, "text/plain")); 37 | 38 | Console.WriteLine($"AI: {await response.Result.Content.ReadAsStringAsync()}"); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/chatconsole/chatconsole.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net7.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/importmemories/Program.cs: -------------------------------------------------------------------------------- 1 | using BlingFire; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.SemanticKernel; 4 | using Microsoft.SemanticKernel.Connectors.Memory.Qdrant; 5 | using Microsoft.SemanticKernel.Connectors.Memory.AzureCognitiveSearch; 6 | 7 | internal class Program 8 | { 9 | /// 10 | /// This program imports text files into a Qdrant VectorDB using Semantic Kernel. 11 | /// 12 | /// Either "qdrant" or "azurecognitivesearch" 13 | /// The URL to a running Qdrant VectorDB (e.g., http://localhost:6333) or to your Azure Cognitive Search endpoint. 14 | /// Name of the database collection in which to import (e.g., "mycollection"). 15 | /// Text files to import. 16 | static async Task Main(string memoryType, string memoryUrl, string collection, params FileInfo[] textFile) 17 | { 18 | // Validate arguments. 19 | if (textFile.Length == 0) 20 | { 21 | Console.Error.WriteLine("No text files provided. Use '--help' for usage."); 22 | return; 23 | } 24 | 25 | IKernel kernel; 26 | IConfiguration config = new ConfigurationBuilder() 27 | .AddUserSecrets() 28 | .Build(); 29 | 30 | if (memoryType.Equals("qdrant", StringComparison.InvariantCultureIgnoreCase)) 31 | { 32 | // Get the OpenAI API key from the configuration. 33 | string? openAiApiKey = config["OPENAI_APIKEY"]; 34 | 35 | if (string.IsNullOrWhiteSpace(openAiApiKey)) 36 | { 37 | Console.Error.WriteLine("Please set the 'OPENAI_APIKEY' user secret with your OpenAI API key."); 38 | return; 39 | } 40 | 41 | // Create a new memory store that will store the embeddings in Qdrant. 42 | Uri qdrantUri = new Uri(memoryUrl); 43 | QdrantMemoryStore memoryStore = new QdrantMemoryStore( 44 | host: $"{qdrantUri.Scheme}://{qdrantUri.Host}", 45 | port: qdrantUri.Port, 46 | vectorSize: 1536); 47 | 48 | // Create a new kernel with an OpenAI Embedding Generation service. 49 | kernel = new KernelBuilder() 50 | .Configure(c => c.AddOpenAITextEmbeddingGenerationService( 51 | modelId: "text-embedding-ada-002", 52 | apiKey: openAiApiKey)) 53 | .WithMemoryStorage(memoryStore) 54 | .Build(); 55 | 56 | } 57 | else if (memoryType.Equals("azurecognitivesearch", StringComparison.InvariantCultureIgnoreCase)) 58 | { 59 | // Get the Azure Cognitive Search API key from the environment. 60 | string? azureCognitiveSearchApiKey = config["AZURE_COGNITIVE_SEARCH_APIKEY"]; 61 | 62 | if (string.IsNullOrWhiteSpace(azureCognitiveSearchApiKey)) 63 | { 64 | Console.Error.WriteLine("Please set the 'AZURE_COGNITIVE_SEARCH_APIKEY' user secret with your Azure Cognitive Search API key."); 65 | return; 66 | } 67 | 68 | AzureCognitiveSearchMemory memory = new AzureCognitiveSearchMemory( 69 | memoryUrl, 70 | azureCognitiveSearchApiKey 71 | ); 72 | 73 | // Create a new kernel with an OpenAI Embedding Generation service. 74 | kernel = new KernelBuilder() 75 | .WithMemory(memory) 76 | .Build(); 77 | } 78 | else 79 | { 80 | Console.Error.WriteLine("Not a supported memory type. Use '--help' for usage."); 81 | return; 82 | } 83 | 84 | await ImportMemoriesAsync(kernel, collection, textFile); 85 | } 86 | 87 | static async Task ImportMemoriesAsync(IKernel kernel, string collection, params FileInfo[] textFile) 88 | { 89 | 90 | // Use sequential memory IDs; this makes it easier to retrieve sentences near a given sentence. 91 | int memoryId = 0; 92 | 93 | // Import the text files. 94 | int fileCount = 0; 95 | foreach (FileInfo fileInfo in textFile) 96 | { 97 | Console.WriteLine($"Importing [{++fileCount}/{fileInfo.Length}] {fileInfo.FullName}"); 98 | 99 | // Read the text file. 100 | string text = File.ReadAllText(fileInfo.FullName); 101 | 102 | // Split the text into sentences. 103 | string[] sentences = BlingFireUtils.GetSentences(text).ToArray(); 104 | 105 | // Save each sentence to the memory store. 106 | int sentenceCount = 0; 107 | foreach (string sentence in sentences) 108 | { 109 | ++sentenceCount; 110 | if (sentenceCount % 10 == 0) 111 | { 112 | // Log progress every 10 sentences. 113 | Console.WriteLine($"[{fileCount}/{fileInfo.Length}] {fileInfo.FullName}: {sentenceCount}/{sentences.Length}"); 114 | } 115 | 116 | await kernel.Memory.SaveInformationAsync( 117 | collection: collection, 118 | text: sentence, 119 | id: memoryId++.ToString(), 120 | description: sentence); 121 | } 122 | } 123 | 124 | Console.WriteLine("Done!"); 125 | } 126 | } -------------------------------------------------------------------------------- /src/importmemories/importmemories.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net7.0 6 | enable 7 | enable 8 | semantic-kernel-rag-chat 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/importmemories/importmemories.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.6.33513.286 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "importmemories", "importmemories.csproj", "{DE68E507-F976-442C-9718-A4F3948C34C0}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {DE68E507-F976-442C-9718-A4F3948C34C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {DE68E507-F976-442C-9718-A4F3948C34C0}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {DE68E507-F976-442C-9718-A4F3948C34C0}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {DE68E507-F976-442C-9718-A4F3948C34C0}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {F95A4A4E-CD30-4205-8B5A-0095F90C6FF8} 24 | EndGlobalSection 25 | EndGlobal 26 | --------------------------------------------------------------------------------