├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── azure.yaml ├── infra ├── abbreviations.json ├── main.bicep ├── main.parameters.json └── modules │ ├── appservice.bicep │ ├── bing.bicep │ ├── botservice.bicep │ ├── cosmos.bicep │ ├── documentIntelligence.bicep │ ├── msi.bicep │ ├── openai.bicep │ ├── searchService.bicep │ ├── sql.bicep │ └── storage.bicep ├── readme_assets ├── 1-resources.png ├── 1-test.png ├── 2-add-doc.png ├── 2-import-vectorize.png ├── 2-stg-container.png ├── architecture.png ├── architecture.vsdx ├── banner.png ├── cognitive-search-home.png ├── cognitive-search-index-sample.png ├── webchat-dalle.png ├── webchat-debug.png ├── webchat-general.png ├── webchat-search.png ├── webchat-sql.png ├── webchat-test.png └── webchat-upload.png ├── src ├── .deployment ├── .editorconfig ├── AdapterWithErrorHandler.cs ├── Bots │ ├── DocumentUploadBot.cs │ ├── SemanticKernelBot.cs │ └── StateManagementBot.cs ├── Controllers │ ├── BotController.cs │ └── DirectLineController.cs ├── ConversationData.cs ├── Dialogs │ └── LoginDialog.cs ├── Factory │ └── SqlConnectionFactory.cs ├── Models │ ├── DirectLineTokenDetails.cs │ ├── RetrievedPassage.cs │ └── SearchResult.cs ├── Plugins │ ├── BingPlugin.cs │ ├── DALLEPlugin.cs │ ├── HRHandbookPlugin.cs │ ├── HumanInterfacePlugin.cs │ ├── SQLPlugin.cs │ └── UploadPlugin.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── SemanticKernelBot.csproj ├── Services │ ├── BingClient.cs │ └── DirectLineService.cs ├── Startup.cs ├── Teams │ ├── color.png │ ├── manifest.json │ └── outline.png ├── UserProfile.cs ├── appsettings.Development.json ├── appsettings.example.json └── wwwroot │ ├── default.htm │ └── images │ ├── BotServices-Translucent.svg │ └── BotServices.png └── use_cases ├── 1-sql-assistant.md ├── 2-ai-search-rag.md ├── 3-custom-data-api-rag.md ├── 4-bing-chat.md ├── 5-dalle-integration.md ├── 6-document-analyzer.md ├── 7-bot-authentication.md └── 8-network-security.md /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | appsettings.json 4 | Archive.zip 5 | .DS_Store 6 | git-exclude 7 | .azure 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | // Use IntelliSense to find out which attributes exist for C# debugging 6 | // Use hover for the description of the existing attributes 7 | // For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md. 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/net8.0/SemanticKernelBot.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | "stopAtEntry": false, 17 | // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser 18 | "serverReadyAction": { 19 | "action": "openExternally", 20 | "pattern": "\\bNow listening on:\\s+(https?://\\S+)" 21 | }, 22 | "env": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | }, 25 | "sourceFileMap": { 26 | "/Views": "${workspaceFolder}/Views" 27 | } 28 | }, 29 | { 30 | "name": ".NET Core Attach", 31 | "type": "coreclr", 32 | "request": "attach" 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/SemanticKernelBot.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary;ForceNoAlign" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/SemanticKernelBot.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary;ForceNoAlign" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "--project", 36 | "${workspaceFolder}/SemanticKernelBot.csproj" 37 | ], 38 | "problemMatcher": "$msCompile" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 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 | We've moved! Please find the latest version of this solution in the AI-in-a-Box repository: [https://aka.ms/ai-in-a-box](https://aka.ms/ai-in-a-box) 2 | This repository will be archived on February 2024. 3 | 4 | # Semantic Kernel Bot in-a-box 5 | ![Banner](./readme_assets/banner.png) 6 | 7 | This project deploys an extensible Semantic Kernel bot template to Azure. 8 | 9 | ## Solution Architecture 10 | 11 | The solution architecture is described in the diagram below. 12 | 13 | ![Solution Architecture](./readme_assets/architecture.png) 14 | 15 | The flow of messages is as follows: 16 | 17 | - End-users connect to a messaging channel your bot is publised to, such as Web, a PowerBI dashboard or Teams; 18 | - Messages get processed through Azure Bot Services, which communicates with a .NET application running on App Services. 19 | - The .NET application runs a Semantic Kernel Stepwise Planner at its core. The planner elaborates a series of steps to process the user's request, and then executes it. 20 | - Each step of the plan is formulated through Azure OpenAI, and the executed against Cognitive Search (traditional RAG pattern) or Azure SQL (structured data RAG). 21 | - Cognitive search contains an index of hotels, while Azure SQL contains customer data from the AdventureWorksLT sample. Azure OpenAI is responsible for deciding which data source each question gets routed to. Questions may also span multiple data sources. Check out the Sample Scenarios section for more details. 22 | 23 | 24 | ## Pre-requisites 25 | 26 | - For running locally: 27 | - [Install .NET](https://dotnet.microsoft.com/en-us/download); 28 | - [Install Bot Framework Emulator](https://github.com/Microsoft/BotFramework-Emulator); 29 | 30 | - For deploying to Azure: 31 | - Install Azure CLI 32 | - Install Azure Developer CLI 33 | - Log into your Azure subscription 34 | 35 | ``` 36 | azd auth login 37 | ``` 38 | 39 | ## Deploy to Azure 40 | 41 | 1. Clone this repository locally: 42 | 43 | ``` 44 | git clone https://github.com/Azure/AI-in-a-Box 45 | cd semantic-kernel-bot-in-a-box 46 | ``` 47 | 2. Deploy resources: 48 | ``` 49 | azd up 50 | ``` 51 | You will be prompted for a subcription, region and model information. Keep regional model availability when proceeding. 52 | 53 | 3. Test on Web Chat - go to your Azure Bot resource on the Azure portal and look for the Web Chat feature on the left side menu. 54 | 55 | ![Test Web Chat](./readme_assets/webchat-test.png) 56 | 57 | 58 | ## Running Locally (must deploy resources to Azure first) 59 | 60 | After running the deployment template, you may also run the application locally for development and debugging. 61 | 62 | - Make sure you have the appropriate permissions and are logged in the Azure CLI. The `AI Developer` role at the resource group level is recommended. 63 | - Go to the `src` directory and look for the `appsettings.example.json` file. Rename it to `appsettings.json` and fill out the required service endpoints and configurations 64 | - Execute the project: 65 | ``` 66 | dotnet run 67 | ``` 68 | - Open Bot Framework Emulator and connect to http://localhost:3987/api/messages 69 | - Don't forget to enable firewall access to any services where it may be restricted. By default, SQL Server will disable public connections. 70 | 71 | ## Sample scenarios 72 | 73 | The application has the ability to consume information from GPT-4 itself, Cognitive Search, SQL and documents uploaded by the end user directly. Each of these data sources will be preloaded with some sample data, but you may use the connections as templates to connect your own data sources. 74 | 75 | You may ask about the following topics to test each functionality 76 | 77 | 1. General knowledge questions 78 | - Ask about any publicly available knowledge; 79 | ![General question scenario](./readme_assets/webchat-general.png) 80 | 81 | 2. Retrieval-augmented generation (SearchPlugin) 82 | - Ask to look for hotels matching a description; 83 | ![Retrieval-augmented scenario](./readme_assets/webchat-search.png) 84 | 85 | 3. Structured data retrieval (SQLPlugin) 86 | - Ask about your customers and sales; 87 | ![SQL connection scenario](./readme_assets/webchat-sql.png) 88 | 89 | 4. Upload documents as context (UploadPlugin) 90 | - Upload a file and ask questions about it; 91 | ![Upload scenario](./readme_assets/webchat-upload.png) 92 | 93 | 5. Generate images (DALLEPlugin) 94 | - Ask to generate images; 95 | ![Image Generation scenario](./readme_assets/webchat-dalle.png) 96 | 97 | 98 | ## Keywords 99 | 100 | - Send "clear" to reset the conversation context; 101 | - Send "logout" to sign out when SSO is enabled; 102 | 103 | ## Developing your own plugins 104 | 105 | This project comes with a few plugins, which may be found in the Plugins/ directory. You may use these as examples when developing your own plugins. 106 | 107 | To create a custom plugin: 108 | 109 | - Add a new file to the Plugins directory. Use one of the examples as a template. 110 | - Add your code to the plugin. Each Semantic Function should contain a top-level description, and a description of each argument, so that Semantic Kernel may understand how to leverage that functionality. 111 | - Load your plugin in the Bots/SemanticKernelBot.cs file 112 | 113 | And you're done! Redeploy your app and Semantic Kernel will now use your plugin whenever the user's questions call for it. 114 | 115 | ## Enabling Web Chat 116 | 117 | To deploy a Web Chat version of your app: 118 | 119 | - Go to your Azure Bot Resource; 120 | - Go to Channels; 121 | - Click on Direct Line; 122 | - Obtain a Direct Line Secret; 123 | - Add the secret to your App Service's environment variables, under the key DIRECT_LINE_SECRET; 124 | - Your bot will be available at https://APP_NAME.azurewebsites.net. 125 | 126 | Please note that doing so will make your bot public, unless you implement authentication / SSO. 127 | 128 | ## Contributing 129 | 130 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 131 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 132 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 133 | 134 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 135 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 136 | provided by the bot. You will only need to do this once across all repos using our CLA. 137 | 138 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 139 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 140 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 141 | 142 | ## Trademarks 143 | 144 | This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft 145 | trademarks or logos is subject to and must follow 146 | [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). 147 | Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. 148 | Any use of third-party trademarks or logos are subject to those third-party's policies. 149 | -------------------------------------------------------------------------------- /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) and [Xamarin](https://github.com/xamarin). 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/security.md/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/security.md/msrc/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/security.md/msrc/pgp). 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://www.microsoft.com/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/security.md/msrc/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/security.md/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## How to file issues and get help 4 | 5 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 6 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 7 | feature request as a new Issue. 8 | 9 | ## Microsoft Support Policy 10 | 11 | Support for this Project is limited to the resources listed above. 12 | -------------------------------------------------------------------------------- /azure.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json 2 | 3 | name: semantic-kernel-bot-in-a-box 4 | metadata: 5 | template: azd-init@1.4.4 6 | services: 7 | semantic-kernel-bot-app: 8 | project: src 9 | host: appservice 10 | language: dotnet -------------------------------------------------------------------------------- /infra/abbreviations.json: -------------------------------------------------------------------------------- 1 | { 2 | "analysisServicesServers": "as", 3 | "apiManagementService": "apim-", 4 | "appConfigurationConfigurationStores": "appcs-", 5 | "appManagedEnvironments": "cae-", 6 | "appContainerApps": "ca-", 7 | "authorizationPolicyDefinitions": "policy-", 8 | "automationAutomationAccounts": "aa-", 9 | "blueprintBlueprints": "bp-", 10 | "blueprintBlueprintsArtifacts": "bpa-", 11 | "cacheRedis": "redis-", 12 | "cdnProfiles": "cdnp-", 13 | "cdnProfilesEndpoints": "cdne-", 14 | "cognitiveServicesAccounts": "cog-", 15 | "cognitiveServicesBing": "cog-bg-", 16 | "cognitiveServicesOpenAI": "cog-oa-", 17 | "cognitiveServicesFormRecognizer": "cog-fr-", 18 | "cognitiveServicesTextAnalytics": "cog-ta-", 19 | "cognitiveServicesBot": "cog-bot-", 20 | "computeAvailabilitySets": "avail-", 21 | "computeCloudServices": "cld-", 22 | "computeDiskEncryptionSets": "des", 23 | "computeDisks": "disk", 24 | "computeDisksOs": "osdisk", 25 | "computeGalleries": "gal", 26 | "computeSnapshots": "snap-", 27 | "computeVirtualMachines": "vm", 28 | "computeVirtualMachineScaleSets": "vmss-", 29 | "containerInstanceContainerGroups": "ci", 30 | "containerRegistryRegistries": "cr", 31 | "containerServiceManagedClusters": "aks-", 32 | "databricksWorkspaces": "dbw-", 33 | "dataFactoryFactories": "adf-", 34 | "dataLakeAnalyticsAccounts": "dla", 35 | "dataLakeStoreAccounts": "dls", 36 | "dataMigrationServices": "dms-", 37 | "dBforMySQLServers": "mysql-", 38 | "dBforPostgreSQLServers": "psql-", 39 | "devicesIotHubs": "iot-", 40 | "devicesProvisioningServices": "provs-", 41 | "devicesProvisioningServicesCertificates": "pcert-", 42 | "documentDBDatabaseAccounts": "cosmos-", 43 | "eventGridDomains": "evgd-", 44 | "eventGridDomainsTopics": "evgt-", 45 | "eventGridEventSubscriptions": "evgs-", 46 | "eventHubNamespaces": "evhns-", 47 | "eventHubNamespacesEventHubs": "evh-", 48 | "hdInsightClustersHadoop": "hadoop-", 49 | "hdInsightClustersHbase": "hbase-", 50 | "hdInsightClustersKafka": "kafka-", 51 | "hdInsightClustersMl": "mls-", 52 | "hdInsightClustersSpark": "spark-", 53 | "hdInsightClustersStorm": "storm-", 54 | "hybridComputeMachines": "arcs-", 55 | "insightsActionGroups": "ag-", 56 | "insightsComponents": "appi-", 57 | "keyVaultVaults": "kv-", 58 | "kubernetesConnectedClusters": "arck", 59 | "kustoClusters": "dec", 60 | "kustoClustersDatabases": "dedb", 61 | "logicIntegrationAccounts": "ia-", 62 | "logicWorkflows": "logic-", 63 | "machineLearningServicesWorkspaces": "mlw-", 64 | "managedIdentityUserAssignedIdentities": "id-", 65 | "managementManagementGroups": "mg-", 66 | "migrateAssessmentProjects": "migr-", 67 | "networkApplicationGateways": "agw-", 68 | "networkApplicationSecurityGroups": "asg-", 69 | "networkAzureFirewalls": "afw-", 70 | "networkBastionHosts": "bas-", 71 | "networkConnections": "con-", 72 | "networkDnsZones": "dnsz-", 73 | "networkExpressRouteCircuits": "erc-", 74 | "networkFirewallPolicies": "afwp-", 75 | "networkFirewallPoliciesWebApplication": "waf", 76 | "networkFirewallPoliciesRuleGroups": "wafrg", 77 | "networkFrontDoors": "fd-", 78 | "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", 79 | "networkLoadBalancersExternal": "lbe-", 80 | "networkLoadBalancersInternal": "lbi-", 81 | "networkLoadBalancersInboundNatRules": "rule-", 82 | "networkLocalNetworkGateways": "lgw-", 83 | "networkNatGateways": "ng-", 84 | "networkNetworkInterfaces": "nic-", 85 | "networkNetworkSecurityGroups": "nsg-", 86 | "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", 87 | "networkNetworkWatchers": "nw-", 88 | "networkPrivateDnsZones": "pdnsz-", 89 | "networkPrivateLinkServices": "pl-", 90 | "networkPublicIPAddresses": "pip-", 91 | "networkPublicIPPrefixes": "ippre-", 92 | "networkRouteFilters": "rf-", 93 | "networkRouteTables": "rt-", 94 | "networkRouteTablesRoutes": "udr-", 95 | "networkTrafficManagerProfiles": "traf-", 96 | "networkVirtualNetworkGateways": "vgw-", 97 | "networkVirtualNetworks": "vnet-", 98 | "networkVirtualNetworksSubnets": "snet-", 99 | "networkVirtualNetworksVirtualNetworkPeerings": "peer-", 100 | "networkVirtualWans": "vwan-", 101 | "networkVpnGateways": "vpng-", 102 | "networkVpnGatewaysVpnConnections": "vcn-", 103 | "networkVpnGatewaysVpnSites": "vst-", 104 | "notificationHubsNamespaces": "ntfns-", 105 | "notificationHubsNamespacesNotificationHubs": "ntf-", 106 | "operationalInsightsWorkspaces": "log-", 107 | "portalDashboards": "dash-", 108 | "powerBIDedicatedCapacities": "pbi-", 109 | "purviewAccounts": "pview-", 110 | "recoveryServicesVaults": "rsv-", 111 | "resourcesResourceGroups": "rg-", 112 | "searchSearchServices": "srch-", 113 | "serviceBusNamespaces": "sb-", 114 | "serviceBusNamespacesQueues": "sbq-", 115 | "serviceBusNamespacesTopics": "sbt-", 116 | "serviceEndPointPolicies": "se-", 117 | "serviceFabricClusters": "sf-", 118 | "signalRServiceSignalR": "sigr", 119 | "sqlManagedInstances": "sqlmi-", 120 | "sqlServers": "sql-", 121 | "sqlServersDataWarehouse": "sqldw-", 122 | "sqlServersDatabases": "sqldb-", 123 | "sqlServersDatabasesStretch": "sqlstrdb-", 124 | "storageStorageAccounts": "st", 125 | "storageStorageAccountsVm": "stvm", 126 | "storSimpleManagers": "ssimp", 127 | "streamAnalyticsCluster": "asa-", 128 | "synapseWorkspaces": "syn", 129 | "synapseWorkspacesAnalyticsWorkspaces": "synw", 130 | "synapseWorkspacesSqlPoolsDedicated": "syndp", 131 | "synapseWorkspacesSqlPoolsSpark": "synsp", 132 | "timeSeriesInsightsEnvironments": "tsi-", 133 | "webServerFarms": "plan-", 134 | "webSitesAppService": "app-", 135 | "webSitesAppServiceEnvironment": "ase-", 136 | "webSitesFunctions": "func-", 137 | "webStaticSites": "stapp-" 138 | } -------------------------------------------------------------------------------- /infra/main.bicep: -------------------------------------------------------------------------------- 1 | targetScope = 'subscription' 2 | 3 | param location string 4 | param environmentName string 5 | param resourceGroupName string = '' 6 | 7 | param tags object 8 | 9 | param openaiName string = '' 10 | 11 | @allowed(['gpt-4', 'gpt-4-32k']) 12 | param gptModel string 13 | @allowed(['0613', '1106-Preview']) 14 | param gptVersion string 15 | 16 | param msiName string = '' 17 | param appServicePlanName string = '' 18 | param appServiceName string = '' 19 | param botServiceName string = '' 20 | param cosmosName string = '' 21 | 22 | param sqlServerName string = '' 23 | param sqlDBName string = '' 24 | param searchName string = '' 25 | param storageName string = '' 26 | param documentIntelligenceName string = '' 27 | param bingName string = '' 28 | @description('Deploy SQL Database? (required for SQL Plugin demo)') 29 | param deploySQL bool 30 | @description('Deploy Search service? (required for Search Plugin demo)') 31 | param deploySearch bool 32 | @description('Deploy Document Intelligence service? (required for Upload Plugin demo)') 33 | param deployDocIntel bool 34 | param deployDalle3 bool = false 35 | param deployBing bool 36 | 37 | @allowed(['Enabled', 'Disabled']) 38 | param publicNetworkAccess string 39 | 40 | var abbrs = loadJsonContent('abbreviations.json') 41 | 42 | var uniqueSuffix = substring(uniqueString(subscription().id, resourceGroup.id), 1, 3) 43 | 44 | // Organize resources in a resource group 45 | resource resourceGroup 'Microsoft.Resources/resourceGroups@2023-07-01' = { 46 | name: !empty(resourceGroupName) ? resourceGroupName : '${abbrs.resourcesResourceGroups}${environmentName}' 47 | location: location 48 | tags: tags 49 | } 50 | 51 | module m_msi 'modules/msi.bicep' = { 52 | name: 'deploy_msi' 53 | scope: resourceGroup 54 | params: { 55 | location: location 56 | msiName: !empty(msiName) ? msiName : '${abbrs.managedIdentityUserAssignedIdentities}${environmentName}-${uniqueSuffix}' 57 | tags: tags 58 | } 59 | } 60 | 61 | module m_openai 'modules/openai.bicep' = { 62 | name: 'deploy_openai' 63 | scope: resourceGroup 64 | params: { 65 | location: location 66 | openaiName: !empty(openaiName) ? openaiName : '${abbrs.cognitiveServicesOpenAI}${environmentName}-${uniqueSuffix}' 67 | gptModel: gptModel 68 | gptVersion: gptVersion 69 | msiPrincipalID: m_msi.outputs.msiPrincipalID 70 | publicNetworkAccess: publicNetworkAccess 71 | deployDalle3: deployDalle3 72 | tags: tags 73 | } 74 | } 75 | 76 | module m_docs 'modules/documentIntelligence.bicep' = if (deployDocIntel) { 77 | name: 'deploy_docs' 78 | scope: resourceGroup 79 | params: { 80 | location: location 81 | documentIntelligenceName: !empty(documentIntelligenceName) ? documentIntelligenceName : '${abbrs.cognitiveServicesFormRecognizer}${environmentName}-${uniqueSuffix}' 82 | msiPrincipalID: m_msi.outputs.msiPrincipalID 83 | publicNetworkAccess: publicNetworkAccess 84 | tags: tags 85 | } 86 | } 87 | 88 | module m_search 'modules/searchService.bicep' = if (deploySearch) { 89 | name: 'deploy_search' 90 | scope: resourceGroup 91 | params: { 92 | location: location 93 | searchName: !empty(searchName) ? searchName : '${abbrs.searchSearchServices}${environmentName}-${uniqueSuffix}' 94 | msiPrincipalID: m_msi.outputs.msiPrincipalID 95 | publicNetworkAccess: publicNetworkAccess 96 | tags: tags 97 | } 98 | } 99 | 100 | module m_storage 'modules/storage.bicep' = if (deploySearch) { 101 | name: 'deploy_storage' 102 | scope: resourceGroup 103 | params: { 104 | location: location 105 | storageName: !empty(storageName) ? storageName : '${abbrs.storageStorageAccounts}${replace(replace(environmentName,'-',''),'_','')}${uniqueSuffix}' 106 | msiPrincipalID: m_msi.outputs.msiPrincipalID 107 | publicNetworkAccess: publicNetworkAccess 108 | tags: tags 109 | } 110 | } 111 | 112 | module m_sql 'modules/sql.bicep' = if (deploySQL) { 113 | name: 'deploy_sql' 114 | scope: resourceGroup 115 | params: { 116 | location: location 117 | sqlServerName: !empty(sqlServerName) ? sqlServerName : '${abbrs.sqlServers}${environmentName}-${uniqueSuffix}' 118 | sqlDBName: !empty(sqlDBName) ? sqlDBName : '${abbrs.sqlServersDatabases}${environmentName}-${uniqueSuffix}' 119 | msiPrincipalID: m_msi.outputs.msiPrincipalID 120 | msiClientID: m_msi.outputs.msiClientID 121 | publicNetworkAccess: publicNetworkAccess 122 | tags: tags 123 | } 124 | } 125 | 126 | module m_cosmos 'modules/cosmos.bicep' = { 127 | name: 'deploy_cosmos' 128 | scope: resourceGroup 129 | params: { 130 | location: location 131 | cosmosName: !empty(cosmosName) ? cosmosName : '${abbrs.documentDBDatabaseAccounts}${environmentName}-${uniqueSuffix}' 132 | msiPrincipalID: m_msi.outputs.msiPrincipalID 133 | publicNetworkAccess: publicNetworkAccess 134 | tags: tags 135 | } 136 | } 137 | 138 | module m_bing 'modules/bing.bicep' = if (deployBing) { 139 | name: 'deploy_bing' 140 | scope: resourceGroup 141 | params: { 142 | location: 'global' 143 | bingName: !empty(bingName) ? bingName : '${abbrs.cognitiveServicesBing}${environmentName}-${uniqueSuffix}' 144 | msiPrincipalID: m_msi.outputs.msiPrincipalID 145 | tags: tags 146 | } 147 | } 148 | 149 | module m_app 'modules/appservice.bicep' = { 150 | name: 'deploy_app' 151 | scope: resourceGroup 152 | params: { 153 | location: location 154 | appServicePlanName: !empty(appServicePlanName) ? appServicePlanName : '${abbrs.webServerFarms}${environmentName}-${uniqueSuffix}' 155 | appServiceName: !empty(appServiceName) ? appServiceName : '${abbrs.webSitesAppService}${environmentName}-${uniqueSuffix}' 156 | tags: tags 157 | msiID: m_msi.outputs.msiID 158 | msiClientID: m_msi.outputs.msiClientID 159 | openaiName: m_openai.outputs.openaiName 160 | openaiEndpoint: m_openai.outputs.openaiEndpoint 161 | openaiGPTModel: m_openai.outputs.openaiGPTModel 162 | openaiEmbeddingsModel: m_openai.outputs.openaiEmbeddingsModel 163 | bingName: deployBing ? m_bing.outputs.bingName : '' 164 | documentIntelligenceName: deployDocIntel ? m_docs.outputs.documentIntelligenceName : '' 165 | documentIntelligenceEndpoint: deployDocIntel ? m_docs.outputs.documentIntelligenceEndpoint : '' 166 | searchEndpoint: deploySearch ? m_search.outputs.searchEndpoint : '' 167 | cosmosEndpoint: m_cosmos.outputs.cosmosEndpoint 168 | sqlConnectionString: deploySQL ? m_sql.outputs.sqlConnectionString : '' 169 | } 170 | } 171 | 172 | module m_bot 'modules/botservice.bicep' = { 173 | name: 'deploy_bot' 174 | scope: resourceGroup 175 | params: { 176 | location: 'global' 177 | botServiceName: !empty(botServiceName) ? botServiceName : '${abbrs.cognitiveServicesBot}${environmentName}-${uniqueSuffix}' 178 | tags: tags 179 | endpoint: 'https://${m_app.outputs.hostName}/api/messages' 180 | msiClientID: m_msi.outputs.msiClientID 181 | msiID: m_msi.outputs.msiID 182 | publicNetworkAccess: publicNetworkAccess 183 | } 184 | } 185 | 186 | output AZURE_SEARCH_ENDPOINT string = deploySearch ? m_search.outputs.searchEndpoint : '' 187 | output AZURE_SEARCH_NAME string = deploySearch ? m_search.outputs.searchName : '' 188 | output AZURE_RESOURCE_GROUP_ID string = resourceGroup.id 189 | output AZURE_RESOURCE_GROUP_NAME string = resourceGroup.name 190 | -------------------------------------------------------------------------------- /infra/main.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "resourceGroupName": { 6 | "value": "${AZURE_APP_RG}" 7 | }, 8 | "environmentName": { 9 | "value": "${AZURE_ENV_NAME}" 10 | }, 11 | "location": { 12 | "value": "${AZURE_LOCATION}" 13 | }, 14 | "gptModel": { 15 | "value": "${AZURE_GPT_MODEL}" 16 | }, 17 | "gptVersion": { 18 | "value": "${AZURE_GPT_VERSION}" 19 | }, 20 | "openaiName": { 21 | "value": "${AZURE_OPENAI_NAME}" 22 | }, 23 | "msiName": { 24 | "value": "${AZURE_MSI_NAME}" 25 | }, 26 | "appServicePlanName": { 27 | "value": "${AZURE_APP_SERVICE_PLAN_NAME}" 28 | }, 29 | "appServiceName": { 30 | "value": "${AZURE_APP_SERVICE_NAME}" 31 | }, 32 | "botServiceName": { 33 | "value": "${AZURE_BOT_SERVICE_NAME}" 34 | }, 35 | "cosmosName": { 36 | "value": "${AZURE_COSMOS_NAME}" 37 | }, 38 | "sqlServerName": { 39 | "value": "${AZURE_SQL_SERVER_NAME}" 40 | }, 41 | "sqlDBName": { 42 | "value": "${AZURE_SQL_DB_NAME}" 43 | }, 44 | "searchName": { 45 | "value": "${AZURE_SEARCH_NAME}" 46 | }, 47 | "documentIntelligenceName": { 48 | "value": "${AZURE_DOCUMENT_INTELLIGENCE_NAME}" 49 | }, 50 | "tags": { 51 | "value": { 52 | "Owner": "AI Team", 53 | "Project": "GPTBot", 54 | "Environment": "Dev", 55 | "Toolkit": "Bicep" 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /infra/modules/appservice.bicep: -------------------------------------------------------------------------------- 1 | param location string 2 | param appServicePlanName string 3 | param appServiceName string 4 | param msiID string 5 | param msiClientID string 6 | param sku string = 'S1' 7 | param tags object = {} 8 | param openaiGPTModel string 9 | param openaiEmbeddingsModel string 10 | 11 | param documentIntelligenceName string 12 | var documentIntelligenceNames = !empty(documentIntelligenceName) ? [documentIntelligenceName] : [] 13 | param bingName string 14 | var bingNames = !empty(bingName) ? [bingName] : [] 15 | param openaiName string 16 | 17 | param openaiEndpoint string 18 | param searchEndpoint string 19 | param documentIntelligenceEndpoint string 20 | param sqlConnectionString string 21 | param cosmosEndpoint string 22 | 23 | 24 | resource openai 'Microsoft.CognitiveServices/accounts@2023-05-01' existing = { 25 | name: openaiName 26 | } 27 | 28 | resource bingAccounts 'Microsoft.Bing/accounts@2020-06-10' existing = [for name in bingNames: { 29 | name: name 30 | }] 31 | resource bingAccount 'Microsoft.Bing/accounts@2020-06-10' existing = { 32 | name: bingName 33 | } 34 | 35 | 36 | resource documentIntelligences 'Microsoft.CognitiveServices/accounts@2023-05-01' existing = [for name in documentIntelligenceNames: { 37 | name: name 38 | }] 39 | 40 | resource appServicePlan 'Microsoft.Web/serverfarms@2020-06-01' = { 41 | name: appServicePlanName 42 | location: location 43 | tags: tags 44 | sku: { 45 | name: sku 46 | } 47 | } 48 | 49 | resource appService 'Microsoft.Web/sites@2022-09-01' = { 50 | name: appServiceName 51 | location: location 52 | tags: union(tags, { 'azd-service-name': 'semantic-kernel-bot-app' }) 53 | identity: { 54 | type: 'UserAssigned' 55 | userAssignedIdentities: { 56 | '${msiID}': {} 57 | } 58 | } 59 | properties: { 60 | serverFarmId: appServicePlan.id 61 | httpsOnly: true 62 | siteConfig: { 63 | http20Enabled: true 64 | appSettings: [ 65 | { 66 | name: 'MicrosoftAppType' 67 | value: 'UserAssignedMSI' 68 | } 69 | { 70 | name: 'MicrosoftAppId' 71 | value: msiClientID 72 | } 73 | { 74 | name: 'MicrosoftAppTenantId' 75 | value: tenant().tenantId 76 | } 77 | { 78 | name: 'AOAI_API_ENDPOINT' 79 | value: openaiEndpoint 80 | } 81 | { 82 | name: 'AOAI_API_KEY' 83 | value: openai.listKeys().key1 84 | } 85 | { 86 | name: 'AOAI_GPT_MODEL' 87 | value: openaiGPTModel 88 | } 89 | { 90 | name: 'AOAI_EMBEDDINGS_MODEL' 91 | value: openaiEmbeddingsModel 92 | } 93 | { 94 | name: 'SEARCH_API_ENDPOINT' 95 | value: searchEndpoint 96 | } 97 | { 98 | name: 'SEARCH_INDEX' 99 | value: 'hotels-sample-index' 100 | } 101 | { 102 | name: 'DOCINTEL_API_ENDPOINT' 103 | value: documentIntelligenceEndpoint 104 | } 105 | { 106 | name: 'DOCINTEL_API_KEY' 107 | value: !empty(documentIntelligenceName) ? documentIntelligences[0].listKeys().key1 : '' 108 | } 109 | { 110 | name: 'SQL_CONNECTION_STRING' 111 | value: sqlConnectionString 112 | } 113 | { 114 | name: 'COSMOS_API_ENDPOINT' 115 | value: cosmosEndpoint 116 | } 117 | { 118 | name: 'DIRECT_LINE_SECRET' 119 | value: '' 120 | } 121 | { 122 | name: 'BING_API_ENDPOINT' 123 | value: !empty(bingName) ? 'https://api.bing.microsoft.com/' : '' 124 | } 125 | { 126 | name: 'BING_API_KEY' 127 | value: !empty(bingName) ? bingAccounts[0].listKeys().key1 : '' 128 | } 129 | { 130 | name: 'PROMPT_WELCOME_MESSAGE' 131 | value: 'Welcome to Semantic Kernel Bot in-a-box! Ask me anything to get started.' 132 | } 133 | { 134 | name: 'PROMPT_SYSTEM_MESSAGE' 135 | value: 'Answer the questions as accurately as possible using the provided functions.' 136 | } 137 | { 138 | name: 'PROMPT_SUGGESTED_QUESTIONS' 139 | value: '[]' 140 | } 141 | { 142 | name: 'SSO_ENABLED' 143 | value: 'false' 144 | } 145 | { 146 | name: 'SSO_CONFIG_NAME' 147 | value: '' 148 | } 149 | { 150 | name: 'SSO_MESSAGE_TITLE' 151 | value: 'Please sign in to continue.' 152 | } 153 | { 154 | name: 'SSO_MESSAGE_PROMPT' 155 | value: 'Sign in' 156 | } 157 | { 158 | name: 'SSO_MESSAGE_SUCCESS' 159 | value: 'User logged in successfully! Please repeat your question.' 160 | } 161 | { 162 | name: 'SSO_MESSAGE_FAILED' 163 | value: 'Log in failed. Type anything to retry.' 164 | } 165 | { 166 | name: 'USE_STEPWISE_PLANNER' 167 | value: 'true' 168 | } 169 | ] 170 | } 171 | } 172 | } 173 | 174 | output hostName string = appService.properties.defaultHostName 175 | -------------------------------------------------------------------------------- /infra/modules/bing.bicep: -------------------------------------------------------------------------------- 1 | param location string 2 | param bingName string 3 | param tags object = {} 4 | param msiPrincipalID string 5 | 6 | resource bing 'Microsoft.Bing/accounts@2020-06-10' = { 7 | name: bingName 8 | location: location 9 | tags: tags 10 | sku: { 11 | name: 'F1' 12 | } 13 | kind: 'Bing.Search.v7' 14 | } 15 | 16 | output bingID string = bing.id 17 | output bingName string = bing.name 18 | output bingApiEndpoint string = bing.properties.endpoint 19 | -------------------------------------------------------------------------------- /infra/modules/botservice.bicep: -------------------------------------------------------------------------------- 1 | param location string 2 | param botServiceName string 3 | param endpoint string 4 | param msiID string 5 | param msiClientID string 6 | param sku string = 'F0' 7 | param kind string = 'azurebot' 8 | param tags object = {} 9 | param publicNetworkAccess string 10 | 11 | resource botservice 'Microsoft.BotService/botServices@2022-09-15' = { 12 | name: botServiceName 13 | location: location 14 | tags: tags 15 | sku: { 16 | name: sku 17 | } 18 | kind: kind 19 | properties: { 20 | displayName: botServiceName 21 | endpoint: endpoint 22 | msaAppMSIResourceId: msiID 23 | msaAppId: msiClientID 24 | msaAppType: 'UserAssignedMSI' 25 | msaAppTenantId: tenant().tenantId 26 | publicNetworkAccess: publicNetworkAccess 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /infra/modules/cosmos.bicep: -------------------------------------------------------------------------------- 1 | param location string 2 | param cosmosName string 3 | param tags object = {} 4 | param msiPrincipalID string 5 | param publicNetworkAccess string 6 | 7 | resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2022-05-15' = { 8 | name: cosmosName 9 | location: location 10 | tags: tags 11 | kind: 'GlobalDocumentDB' 12 | properties: { 13 | locations: [ 14 | { 15 | locationName: location 16 | failoverPriority: 0 17 | isZoneRedundant: false 18 | } 19 | ] 20 | databaseAccountOfferType: 'Standard' 21 | publicNetworkAccess: publicNetworkAccess 22 | } 23 | 24 | resource db 'sqlDatabases' = { 25 | name: 'SemanticKernelBot' 26 | properties: { 27 | resource: { 28 | id: 'SemanticKernelBot' 29 | } 30 | } 31 | 32 | 33 | 34 | resource col 'containers' = { 35 | name: 'Conversations' 36 | properties: { 37 | resource: { 38 | id: 'Conversations' 39 | partitionKey: { 40 | paths: ['/id'] 41 | kind: 'Hash' 42 | } 43 | } 44 | } 45 | } 46 | } 47 | } 48 | 49 | resource cosmosDataReader 'Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions@2021-10-15' existing = { 50 | name: '00000000-0000-0000-0000-000000000001' 51 | parent: cosmos 52 | } 53 | 54 | resource cosmosDataContributor 'Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions@2021-10-15' existing = { 55 | name: '00000000-0000-0000-0000-000000000002' 56 | parent: cosmos 57 | } 58 | 59 | resource appReadAccess 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2023-04-15' = { 60 | name: guid(cosmos.id, msiPrincipalID, cosmosDataReader.id) 61 | parent: cosmos 62 | properties: { 63 | roleDefinitionId: cosmosDataReader.id 64 | principalId: msiPrincipalID 65 | scope: cosmos.id 66 | } 67 | } 68 | 69 | resource appWriteAccess 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2023-04-15' = { 70 | name: guid(cosmos.id, msiPrincipalID, cosmosDataContributor.id) 71 | parent: cosmos 72 | properties: { 73 | roleDefinitionId: cosmosDataContributor.id 74 | principalId: msiPrincipalID 75 | scope: cosmos.id 76 | } 77 | } 78 | 79 | 80 | 81 | output cosmosID string = cosmos.id 82 | output cosmosEndpoint string = cosmos.properties.documentEndpoint 83 | -------------------------------------------------------------------------------- /infra/modules/documentIntelligence.bicep: -------------------------------------------------------------------------------- 1 | param location string 2 | param documentIntelligenceName string 3 | param tags object = {} 4 | param msiPrincipalID string 5 | param publicNetworkAccess string 6 | 7 | resource documentIntelligence 'Microsoft.CognitiveServices/accounts@2023-05-01' = { 8 | name: documentIntelligenceName 9 | location: location 10 | tags: tags 11 | sku: { 12 | name: 'S0' 13 | } 14 | kind: 'FormRecognizer' 15 | properties: { 16 | customSubDomainName: documentIntelligenceName 17 | apiProperties: { 18 | statisticsEnabled: false 19 | } 20 | networkAcls: { 21 | defaultAction: 'Allow' 22 | } 23 | publicNetworkAccess: publicNetworkAccess 24 | } 25 | } 26 | 27 | output documentIntelligenceID string = documentIntelligence.id 28 | output documentIntelligenceName string = documentIntelligence.name 29 | output documentIntelligenceEndpoint string = documentIntelligence.properties.endpoint 30 | -------------------------------------------------------------------------------- /infra/modules/msi.bicep: -------------------------------------------------------------------------------- 1 | param location string 2 | param msiName string 3 | param tags object = {} 4 | 5 | resource msi 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { 6 | name: msiName 7 | location: location 8 | tags: tags 9 | } 10 | 11 | output msiID string = msi.id 12 | output msiClientID string = msi.properties.clientId 13 | output msiPrincipalID string = msi.properties.principalId 14 | -------------------------------------------------------------------------------- /infra/modules/openai.bicep: -------------------------------------------------------------------------------- 1 | param location string 2 | param openaiName string 3 | param tags object = {} 4 | param gptModel string 5 | param gptVersion string 6 | param msiPrincipalID string 7 | param deployDalle3 bool 8 | param publicNetworkAccess string 9 | 10 | resource openai 'Microsoft.CognitiveServices/accounts@2023-05-01' = { 11 | name: openaiName 12 | location: location 13 | tags: tags 14 | sku: { 15 | name: 'S0' 16 | } 17 | kind: 'OpenAI' 18 | properties: { 19 | customSubDomainName: openaiName 20 | apiProperties: { 21 | statisticsEnabled: false 22 | } 23 | networkAcls: { 24 | defaultAction: 'Allow' 25 | } 26 | publicNetworkAccess: publicNetworkAccess 27 | } 28 | } 29 | 30 | resource gpt4deployment 'Microsoft.CognitiveServices/accounts/deployments@2023-05-01' = { 31 | parent: openai 32 | name: 'gpt-4' 33 | properties: { 34 | model: { 35 | format: 'OpenAI' 36 | name: gptModel 37 | version: gptVersion 38 | } 39 | } 40 | sku: { 41 | capacity: 10 42 | name: 'Standard' 43 | } 44 | } 45 | 46 | 47 | resource adaEmbeddingsdeployment 'Microsoft.CognitiveServices/accounts/deployments@2023-05-01' = { 48 | parent: openai 49 | name: 'text-embedding-ada-002' 50 | properties: { 51 | model: { 52 | format: 'OpenAI' 53 | name: 'text-embedding-ada-002' 54 | version: '2' 55 | } 56 | } 57 | sku: { 58 | capacity: 10 59 | name: 'Standard' 60 | } 61 | dependsOn: [gpt4deployment] 62 | } 63 | 64 | 65 | resource dalle3deployment 'Microsoft.CognitiveServices/accounts/deployments@2023-05-01' = if (deployDalle3) { 66 | parent: openai 67 | name: 'dall-e-3' 68 | properties: { 69 | model: { 70 | format: 'OpenAI' 71 | name: 'dall-e-3' 72 | version: '3.0' 73 | } 74 | } 75 | sku: { 76 | capacity: 1 77 | name: 'Standard' 78 | } 79 | dependsOn: [adaEmbeddingsdeployment] 80 | } 81 | 82 | resource openaiUser 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { 83 | name: '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' 84 | } 85 | 86 | resource appAccess 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 87 | name: guid(openai.id, msiPrincipalID, openaiUser.id) 88 | scope: openai 89 | properties: { 90 | roleDefinitionId: openaiUser.id 91 | principalId: msiPrincipalID 92 | principalType: 'ServicePrincipal' 93 | } 94 | } 95 | 96 | output openaiID string = openai.id 97 | output openaiName string = openai.name 98 | output openaiEndpoint string = openai.properties.endpoint 99 | output openaiGPTModel string = gpt4deployment.name 100 | output openaiEmbeddingsModel string = adaEmbeddingsdeployment.name 101 | -------------------------------------------------------------------------------- /infra/modules/searchService.bicep: -------------------------------------------------------------------------------- 1 | param location string 2 | param searchName string 3 | param tags object = {} 4 | param msiPrincipalID string 5 | param publicNetworkAccess string 6 | 7 | resource search 'Microsoft.Search/searchServices@2023-11-01' = { 8 | name: searchName 9 | location: location 10 | tags: tags 11 | sku: { 12 | name: 'standard' 13 | } 14 | properties: { 15 | authOptions: { 16 | aadOrApiKey: {} 17 | } 18 | replicaCount: 1 19 | partitionCount: 1 20 | hostingMode: 'default' 21 | publicNetworkAccess: publicNetworkAccess 22 | } 23 | } 24 | 25 | resource searchContributor 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { 26 | name: '7ca78c08-252a-4471-8644-bb5ff32d4ba0' 27 | } 28 | 29 | resource searchIndexContributor 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { 30 | name: '8ebe5a00-799e-43f5-93ac-243d3dce84a7' 31 | } 32 | 33 | resource searchUser 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = { 34 | name: '1407120a-92aa-4202-b7e9-c0e197c71c8f' 35 | } 36 | 37 | resource appAccess1 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 38 | name: guid(search.id, msiPrincipalID, searchContributor.id) 39 | scope: search 40 | properties: { 41 | roleDefinitionId: searchContributor.id 42 | principalId: msiPrincipalID 43 | principalType: 'ServicePrincipal' 44 | } 45 | } 46 | 47 | resource appAccess12'Microsoft.Authorization/roleAssignments@2022-04-01' = { 48 | name: guid(search.id, msiPrincipalID, searchIndexContributor.id) 49 | scope: search 50 | properties: { 51 | roleDefinitionId: searchIndexContributor.id 52 | principalId: msiPrincipalID 53 | principalType: 'ServicePrincipal' 54 | } 55 | } 56 | 57 | resource appAccess3 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 58 | name: guid(search.id, msiPrincipalID, searchUser.id) 59 | scope: search 60 | properties: { 61 | roleDefinitionId: searchUser.id 62 | principalId: msiPrincipalID 63 | principalType: 'ServicePrincipal' 64 | } 65 | } 66 | 67 | output searchID string = search.id 68 | output searchName string = search.name 69 | output searchEndpoint string = 'https://${search.name}.search.windows.net' 70 | -------------------------------------------------------------------------------- /infra/modules/sql.bicep: -------------------------------------------------------------------------------- 1 | param location string 2 | param sqlServerName string 3 | param sqlDBName string 4 | param tags object = {} 5 | param msiPrincipalID string 6 | param msiClientID string 7 | param publicNetworkAccess string 8 | 9 | resource sqlServer 'Microsoft.Sql/servers@2022-05-01-preview' = { 10 | name: sqlServerName 11 | location: location 12 | tags: tags 13 | properties: { 14 | administrators: { 15 | azureADOnlyAuthentication: true 16 | principalType: 'Application' 17 | administratorType: 'ActiveDirectory' 18 | login: msiPrincipalID 19 | sid: msiPrincipalID 20 | tenantId: tenant().tenantId 21 | } 22 | publicNetworkAccess: publicNetworkAccess 23 | } 24 | 25 | resource fw 'firewallRules' = if (publicNetworkAccess == 'Enabled') { 26 | name: 'default-fw' 27 | properties: { 28 | startIpAddress: '0.0.0.0' 29 | endIpAddress: '0.0.0.0' 30 | } 31 | } 32 | } 33 | 34 | resource sqlDB 'Microsoft.Sql/servers/databases@2022-05-01-preview' = { 35 | parent: sqlServer 36 | name: sqlDBName 37 | location: location 38 | properties: { 39 | sampleName: 'AdventureWorksLT' 40 | } 41 | sku: { 42 | name: 'Basic' 43 | tier: 'Basic' 44 | } 45 | } 46 | 47 | output sqlServer string = sqlServer.id 48 | output sqlDB string = sqlDB.id 49 | output sqlConnectionString string = 'Server=tcp:${sqlServer.properties.fullyQualifiedDomainName},1433;Initial Catalog=${sqlDB.name};Persist Security Info=False;Authentication=Active Directory MSI; User Id=${msiClientID};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;' 50 | -------------------------------------------------------------------------------- /infra/modules/storage.bicep: -------------------------------------------------------------------------------- 1 | param location string 2 | param storageName string 3 | param tags object = {} 4 | param msiPrincipalID string 5 | param publicNetworkAccess string 6 | 7 | resource storage 'Microsoft.Storage/storageAccounts@2021-09-01' = { 8 | name: storageName 9 | location: location 10 | tags: tags 11 | sku: { 12 | name: 'Standard_LRS' 13 | } 14 | kind: 'StorageV2' 15 | properties: { 16 | accessTier: 'Hot' 17 | encryption: { 18 | keySource: 'Microsoft.Storage' 19 | services: { 20 | blob: { 21 | enabled: true 22 | keyType: 'Account' 23 | } 24 | } 25 | } 26 | minimumTlsVersion: 'TLS1_2' 27 | networkAcls: { 28 | bypass: 'AzureServices' 29 | defaultAction: publicNetworkAccess == 'Enabled' ? 'Allow' : 'Deny' 30 | } 31 | supportsHttpsTrafficOnly: true 32 | } 33 | } 34 | 35 | output storageID string = storage.id 36 | output storageName string = storage.name 37 | -------------------------------------------------------------------------------- /readme_assets/1-resources.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/semantic-kernel-bot-in-a-box/70014145f9af276f8b64f2a701dc0d859a28bc29/readme_assets/1-resources.png -------------------------------------------------------------------------------- /readme_assets/1-test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/semantic-kernel-bot-in-a-box/70014145f9af276f8b64f2a701dc0d859a28bc29/readme_assets/1-test.png -------------------------------------------------------------------------------- /readme_assets/2-add-doc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/semantic-kernel-bot-in-a-box/70014145f9af276f8b64f2a701dc0d859a28bc29/readme_assets/2-add-doc.png -------------------------------------------------------------------------------- /readme_assets/2-import-vectorize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/semantic-kernel-bot-in-a-box/70014145f9af276f8b64f2a701dc0d859a28bc29/readme_assets/2-import-vectorize.png -------------------------------------------------------------------------------- /readme_assets/2-stg-container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/semantic-kernel-bot-in-a-box/70014145f9af276f8b64f2a701dc0d859a28bc29/readme_assets/2-stg-container.png -------------------------------------------------------------------------------- /readme_assets/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/semantic-kernel-bot-in-a-box/70014145f9af276f8b64f2a701dc0d859a28bc29/readme_assets/architecture.png -------------------------------------------------------------------------------- /readme_assets/architecture.vsdx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/semantic-kernel-bot-in-a-box/70014145f9af276f8b64f2a701dc0d859a28bc29/readme_assets/architecture.vsdx -------------------------------------------------------------------------------- /readme_assets/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/semantic-kernel-bot-in-a-box/70014145f9af276f8b64f2a701dc0d859a28bc29/readme_assets/banner.png -------------------------------------------------------------------------------- /readme_assets/cognitive-search-home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/semantic-kernel-bot-in-a-box/70014145f9af276f8b64f2a701dc0d859a28bc29/readme_assets/cognitive-search-home.png -------------------------------------------------------------------------------- /readme_assets/cognitive-search-index-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/semantic-kernel-bot-in-a-box/70014145f9af276f8b64f2a701dc0d859a28bc29/readme_assets/cognitive-search-index-sample.png -------------------------------------------------------------------------------- /readme_assets/webchat-dalle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/semantic-kernel-bot-in-a-box/70014145f9af276f8b64f2a701dc0d859a28bc29/readme_assets/webchat-dalle.png -------------------------------------------------------------------------------- /readme_assets/webchat-debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/semantic-kernel-bot-in-a-box/70014145f9af276f8b64f2a701dc0d859a28bc29/readme_assets/webchat-debug.png -------------------------------------------------------------------------------- /readme_assets/webchat-general.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/semantic-kernel-bot-in-a-box/70014145f9af276f8b64f2a701dc0d859a28bc29/readme_assets/webchat-general.png -------------------------------------------------------------------------------- /readme_assets/webchat-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/semantic-kernel-bot-in-a-box/70014145f9af276f8b64f2a701dc0d859a28bc29/readme_assets/webchat-search.png -------------------------------------------------------------------------------- /readme_assets/webchat-sql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/semantic-kernel-bot-in-a-box/70014145f9af276f8b64f2a701dc0d859a28bc29/readme_assets/webchat-sql.png -------------------------------------------------------------------------------- /readme_assets/webchat-test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/semantic-kernel-bot-in-a-box/70014145f9af276f8b64f2a701dc0d859a28bc29/readme_assets/webchat-test.png -------------------------------------------------------------------------------- /readme_assets/webchat-upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/semantic-kernel-bot-in-a-box/70014145f9af276f8b64f2a701dc0d859a28bc29/readme_assets/webchat-upload.png -------------------------------------------------------------------------------- /src/.deployment: -------------------------------------------------------------------------------- 1 | [config] 2 | SCM_SCRIPT_GENERATOR_ARGS=--aspNetCore "SemanticKernelBot.csproj" 3 | SCM_DO_BUILD_DURING_DEPLOYMENT=true -------------------------------------------------------------------------------- /src/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | dotnet_diagnostic.SKEXP0011.severity = none 3 | dotnet_diagnostic.SKEXP0060.severity = none 4 | dotnet_diagnostic.SKEXP0061.severity = none 5 | -------------------------------------------------------------------------------- /src/AdapterWithErrorHandler.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using Microsoft.Bot.Builder; 6 | using Microsoft.Bot.Builder.Integration.AspNet.Core; 7 | using Microsoft.Bot.Builder.TraceExtensions; 8 | using Microsoft.Bot.Connector.Authentication; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace Microsoft.BotBuilderSamples 12 | { 13 | public class AdapterWithErrorHandler : CloudAdapter 14 | { 15 | public AdapterWithErrorHandler(BotFrameworkAuthentication auth, ILogger logger, ConversationState conversationState = default) 16 | : base(auth, logger) 17 | { 18 | OnTurnError = async (turnContext, exception) => 19 | { 20 | // Log any leaked exception from the application. 21 | // NOTE: In production environment, you should consider logging this to 22 | // Azure Application Insights. Visit https://aka.ms/bottelemetry to see how 23 | // to add telemetry capture to your bot. 24 | logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}"); 25 | 26 | // Send a message to the user 27 | await turnContext.SendActivityAsync("The bot encountered an error or bug."); 28 | await turnContext.SendActivityAsync("To continue to run this bot, please fix the bot source code."); 29 | 30 | if (conversationState != null) 31 | { 32 | try 33 | { 34 | // Delete the conversationState for the current conversation to prevent the 35 | // bot from getting stuck in a error-loop caused by being in a bad state. 36 | // ConversationState should be thought of as similar to "cookie-state" in a Web pages. 37 | await conversationState.DeleteAsync(turnContext); 38 | } 39 | catch (Exception e) 40 | { 41 | logger.LogError(e, $"Exception caught on attempting to Delete ConversationState : {e.Message}"); 42 | } 43 | } 44 | 45 | // Send a trace activity, which will be displayed in the Bot Framework Emulator 46 | await turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError"); 47 | }; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Bots/DocumentUploadBot.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Net.Http; 9 | using System.Threading.Tasks; 10 | using Azure; 11 | using Azure.AI.FormRecognizer.DocumentAnalysis; 12 | using Microsoft.Bot.Builder; 13 | using Microsoft.Bot.Schema; 14 | using Microsoft.Bot.Builder.Dialogs; 15 | using Microsoft.Extensions.Configuration; 16 | using Microsoft.IdentityModel.Tokens; 17 | using Microsoft.SemanticKernel.Connectors.OpenAI; 18 | 19 | 20 | namespace Microsoft.BotBuilderSamples 21 | { 22 | public class DocumentUploadBot : StateManagementBot where T : Dialog 23 | { 24 | private readonly AzureOpenAITextEmbeddingGenerationService _embeddingsClient; 25 | private readonly DocumentAnalysisClient _documentAnalysisClient; 26 | 27 | public DocumentUploadBot(IConfiguration config, ConversationState conversationState, UserState userState, AzureOpenAITextEmbeddingGenerationService embeddingsClient, DocumentAnalysisClient documentAnalysisClient, T dialog) : base(config, conversationState, userState, dialog) 28 | { 29 | _embeddingsClient = embeddingsClient; 30 | _documentAnalysisClient = documentAnalysisClient; 31 | } 32 | 33 | public async Task HandleFileUploads(ConversationData conversationData, ITurnContext turnContext) 34 | { 35 | if (turnContext.Activity.Attachments.IsNullOrEmpty()) 36 | return; 37 | var pdfAttachments = turnContext.Activity.Attachments.Where(x => x.ContentType == "application/pdf"); 38 | if (pdfAttachments.IsNullOrEmpty()) 39 | return; 40 | if (_documentAnalysisClient == null) { 41 | await turnContext.SendActivityAsync("Document upload not supported as no Document Intelligence endpoint was provided"); 42 | return; 43 | } 44 | foreach (Bot.Schema.Attachment pdfAttachment in pdfAttachments) { 45 | await IngestPdfAttachment(conversationData, turnContext, pdfAttachment); 46 | } 47 | } 48 | 49 | private async Task IngestPdfAttachment(ConversationData conversationData, ITurnContext turnContext, Bot.Schema.Attachment pdfAttachment) 50 | { 51 | Uri fileUri = new Uri(pdfAttachment.ContentUrl); 52 | 53 | var httpClient = new HttpClient(); 54 | var stream = await httpClient.GetStreamAsync(fileUri); 55 | 56 | var ms = new MemoryStream(); 57 | stream.CopyTo(ms); 58 | ms.Position = 0; 59 | 60 | var operation = await _documentAnalysisClient.AnalyzeDocumentAsync(WaitUntil.Completed, "prebuilt-layout", ms); 61 | 62 | ms.Dispose(); 63 | 64 | AnalyzeResult result = operation.Value; 65 | 66 | var attachment = new Attachment(); 67 | attachment.Name = pdfAttachment.Name; 68 | foreach (DocumentPage page in result.Pages) 69 | { 70 | var attachmentPage = new AttachmentPage(); 71 | attachmentPage.Content = ""; 72 | for (int i = 0; i < page.Lines.Count; i++) 73 | { 74 | DocumentLine line = page.Lines[i]; 75 | attachmentPage.Content += $"{line.Content}\n"; 76 | } 77 | // Embed content 78 | var embedding = await _embeddingsClient.GenerateEmbeddingsAsync(new List { attachmentPage.Content }); 79 | attachmentPage.Vector = embedding.First().ToArray(); 80 | attachment.Pages.Add(attachmentPage); 81 | } 82 | conversationData.Attachments.Add(attachment); 83 | 84 | var replyText = $"File {pdfAttachment.Name} uploaded successfully! {result.Pages.Count()} pages ingested."; 85 | conversationData.History.Add(new ConversationTurn { Role = "assistant", Message = replyText }); 86 | await turnContext.SendActivityAsync(replyText); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Bots/SemanticKernelBot.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Azure.AI.FormRecognizer.DocumentAnalysis; 9 | using Azure.AI.OpenAI; 10 | using Azure.Search.Documents; 11 | using Azure.Storage.Blobs; 12 | using Microsoft.Bot.Builder; 13 | using Microsoft.Bot.Builder.Dialogs; 14 | using Microsoft.Bot.Schema; 15 | using Microsoft.Extensions.Configuration; 16 | using Microsoft.IdentityModel.Tokens; 17 | using Microsoft.SemanticKernel; 18 | using Microsoft.SemanticKernel.Connectors.OpenAI; 19 | using Microsoft.SemanticKernel.Planning; 20 | using Microsoft.SemanticKernel.Planning.Handlebars; 21 | using Plugins; 22 | using Services; 23 | 24 | namespace Microsoft.BotBuilderSamples 25 | { 26 | public class SemanticKernelBot : DocumentUploadBot where T : Dialog 27 | { 28 | private Kernel kernel; 29 | private string _aoaiModel; 30 | private readonly OpenAIClient _aoaiClient; 31 | private readonly BingClient _bingClient; 32 | private readonly SearchClient _searchClient; 33 | private readonly BlobServiceClient _blobServiceClient; 34 | private readonly AzureOpenAITextEmbeddingGenerationService _embeddingsClient; 35 | private readonly DocumentAnalysisClient _documentAnalysisClient; 36 | private readonly SqlConnectionFactory _sqlConnectionFactory; 37 | private readonly string _welcomeMessage; 38 | private readonly List _suggestedQuestions; 39 | private readonly bool _useStepwisePlanner; 40 | private readonly string _searchSemanticConfig; 41 | 42 | public SemanticKernelBot( 43 | IConfiguration config, 44 | ConversationState conversationState, 45 | UserState userState, 46 | OpenAIClient aoaiClient, 47 | AzureOpenAITextEmbeddingGenerationService embeddingsClient, 48 | T dialog, 49 | DocumentAnalysisClient documentAnalysisClient = null, 50 | SearchClient searchClient = null, 51 | BlobServiceClient blobServiceClient = null, 52 | BingClient bingClient = null, 53 | SqlConnectionFactory sqlConnectionFactory = null) : 54 | base(config, conversationState, userState, embeddingsClient, documentAnalysisClient, dialog) 55 | { 56 | _aoaiModel = config.GetValue("AOAI_GPT_MODEL"); 57 | _welcomeMessage = config.GetValue("PROMPT_WELCOME_MESSAGE"); 58 | _systemMessage = config.GetValue("PROMPT_SYSTEM_MESSAGE"); 59 | _suggestedQuestions = System.Text.Json.JsonSerializer.Deserialize>(config.GetValue("PROMPT_SUGGESTED_QUESTIONS")); 60 | _useStepwisePlanner = config.GetValue("USE_STEPWISE_PLANNER"); 61 | _searchSemanticConfig = config.GetValue("SEARCH_SEMANTIC_CONFIG"); 62 | _aoaiClient = aoaiClient; 63 | _searchClient = searchClient; 64 | _blobServiceClient = blobServiceClient; 65 | _bingClient = bingClient; 66 | _embeddingsClient = embeddingsClient; 67 | _documentAnalysisClient = documentAnalysisClient; 68 | _sqlConnectionFactory = sqlConnectionFactory; 69 | } 70 | 71 | protected override async Task OnMembersAddedAsync(IList membersAdded, ITurnContext turnContext, CancellationToken cancellationToken) 72 | { 73 | await turnContext.SendActivityAsync(new Activity() 74 | { 75 | Type = "message", 76 | Text = _welcomeMessage, 77 | SuggestedActions = new SuggestedActions() 78 | { 79 | Actions = _suggestedQuestions 80 | .Select(value => new CardAction(type: "postBack", value: value)) 81 | .ToList() 82 | } 83 | }); 84 | } 85 | 86 | public override async Task ProcessMessage(ConversationData conversationData, ITurnContext turnContext) 87 | { 88 | 89 | await turnContext.SendActivityAsync(new Activity(type: "typing")); 90 | 91 | await HandleFileUploads(conversationData, turnContext); 92 | if (turnContext.Activity.Text.IsNullOrEmpty()) 93 | return ""; 94 | 95 | kernel = Kernel.CreateBuilder() 96 | .AddAzureOpenAIChatCompletion( 97 | deploymentName: _aoaiModel, 98 | _aoaiClient 99 | ) 100 | .Build(); 101 | 102 | if (_sqlConnectionFactory != null) kernel.ImportPluginFromObject(new SQLPlugin(conversationData, turnContext, _sqlConnectionFactory), "SQLPlugin"); 103 | if (_documentAnalysisClient != null) kernel.ImportPluginFromObject(new UploadPlugin(conversationData, turnContext, _embeddingsClient), "UploadPlugin"); 104 | if (_searchClient != null) kernel.ImportPluginFromObject(new HRHandbookPlugin(conversationData, turnContext, _embeddingsClient, _searchClient, _blobServiceClient, _searchSemanticConfig), "HRHandbookPlugin"); 105 | kernel.ImportPluginFromObject(new DALLEPlugin(conversationData, turnContext, _aoaiClient), "DALLEPlugin"); 106 | if (_bingClient != null) kernel.ImportPluginFromObject(new BingPlugin(conversationData, turnContext, _bingClient), "BingPlugin"); 107 | if (!_useStepwisePlanner) kernel.ImportPluginFromObject(new HumanInterfacePlugin(conversationData, turnContext, _aoaiClient), "HumanInterfacePlugin"); 108 | 109 | if (_useStepwisePlanner) 110 | { 111 | var plannerOptions = new FunctionCallingStepwisePlannerConfig 112 | { 113 | MaxTokens = 128000, 114 | }; 115 | 116 | var planner = new FunctionCallingStepwisePlanner(plannerOptions); 117 | string prompt = FormatConversationHistory(conversationData); 118 | var result = await planner.ExecuteAsync(kernel, prompt); 119 | 120 | return result.FinalAnswer; 121 | } 122 | else 123 | { 124 | var plannerOptions = new HandlebarsPlannerOptions 125 | { 126 | MaxTokens = 128000, 127 | }; 128 | 129 | var planner = new HandlebarsPlanner(plannerOptions); 130 | string prompt = FormatConversationHistory(conversationData); 131 | var plan = await planner.CreatePlanAsync(kernel, prompt); 132 | var result = await plan.InvokeAsync(kernel, default); 133 | return result; 134 | } 135 | } 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /src/Bots/StateManagementBot.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IdentityModel.Tokens.Jwt; 6 | using System.Text.Json; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Microsoft.Bot.Builder; 10 | using Microsoft.Bot.Builder.Dialogs; 11 | using Microsoft.Bot.Builder.Teams; 12 | using Microsoft.Bot.Connector.Authentication; 13 | using Microsoft.Bot.Schema; 14 | using Microsoft.Extensions.Configuration; 15 | 16 | namespace Microsoft.BotBuilderSamples 17 | { 18 | public class StateManagementBot : TeamsActivityHandler where T : Dialog 19 | { 20 | public readonly BotState _conversationState; 21 | public readonly BotState _userState; 22 | protected readonly Dialog _dialog; 23 | private int _max_messages; 24 | private int _max_attachments; 25 | public string _systemMessage; 26 | public bool _sso_enabled; 27 | public string _sso_config_name; 28 | 29 | public StateManagementBot(IConfiguration config, ConversationState conversationState, UserState userState, T dialog) 30 | { 31 | _conversationState = conversationState; 32 | _userState = userState; 33 | _dialog = dialog; 34 | _max_messages = config.GetValue("CONVERSATION_HISTORY_MAX_MESSAGES") ?? 10; 35 | _max_attachments = config.GetValue("MAX_ATTACHMENTS") ?? 5; 36 | _sso_enabled = config.GetValue("SSO_ENABLED") ?? false; 37 | _sso_config_name = config.GetValue("SSO_CONFIG_NAME"); 38 | } 39 | 40 | public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken)) 41 | { 42 | await base.OnTurnAsync(turnContext, cancellationToken); 43 | // Save any state changes that might have occurred during the turn. 44 | await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken); 45 | await _userState.SaveChangesAsync(turnContext, false, cancellationToken); 46 | } 47 | 48 | protected override async Task OnMembersAddedAsync(IList membersAdded, ITurnContext turnContext, CancellationToken cancellationToken) 49 | { 50 | await turnContext.SendActivityAsync("Welcome to State Bot Sample. Type anything to get started."); 51 | } 52 | 53 | protected override async Task OnMessageActivityAsync(ITurnContext turnContext, CancellationToken cancellationToken) 54 | { 55 | var conversationStateAccessors = _conversationState.CreateProperty(nameof(ConversationData)); 56 | var conversationData = await conversationStateAccessors.GetAsync(turnContext, () => new ConversationData()); 57 | 58 | var userStateAccessors = _userState.CreateProperty(nameof(UserProfile)); 59 | var userProfile = await userStateAccessors.GetAsync(turnContext, () => new UserProfile()); 60 | 61 | var userTokenClient = turnContext.TurnState.Get(); 62 | 63 | // -- Special keywords 64 | // Clear conversation 65 | if (turnContext.Activity.Text != null) 66 | { 67 | if (turnContext.Activity.Text.ToLower() == "clear") 68 | { 69 | conversationData.History.Clear(); 70 | conversationData.Attachments.Clear(); 71 | await turnContext.SendActivityAsync("Conversation context cleared"); 72 | return; 73 | } 74 | if (turnContext.Activity.Text.ToLower() == "logout") 75 | { 76 | await userTokenClient.SignOutUserAsync(turnContext.Activity.From.Id, _sso_config_name, turnContext.Activity.ChannelId, cancellationToken).ConfigureAwait(false); 77 | await turnContext.SendActivityAsync("Signed out"); 78 | return; 79 | } 80 | } 81 | 82 | // Log in if not already done - You can also do this within Plugins and check their claims/groups 83 | if (_sso_enabled) 84 | { 85 | TokenResponse userToken; 86 | try 87 | { 88 | userToken = await userTokenClient.GetUserTokenAsync(turnContext.Activity.From.Id, _sso_config_name, turnContext.Activity.ChannelId, null, cancellationToken); 89 | var tokenHandler = new JwtSecurityTokenHandler(); 90 | var securityToken = tokenHandler.ReadToken(userToken.Token) as JwtSecurityToken; 91 | securityToken.Payload.TryGetValue("name", out var userName); 92 | userProfile.Name = userName as string; 93 | } 94 | catch 95 | { 96 | await _dialog.RunAsync(turnContext, _conversationState.CreateProperty(nameof(DialogState)), cancellationToken); 97 | return; 98 | } 99 | } 100 | 101 | conversationData.History.Add(new ConversationTurn { Role = "user", Message = turnContext.Activity.Text }); 102 | 103 | var replyText = await ProcessMessage(conversationData, turnContext); 104 | 105 | 106 | conversationData.History.Add(new ConversationTurn { Role = "assistant", Message = replyText }); 107 | 108 | if (turnContext.Activity.Text == null || turnContext.Activity.Text.ToLower() == "") 109 | { 110 | return; 111 | } 112 | 113 | await turnContext.SendActivityAsync(replyText); 114 | 115 | conversationData.History = conversationData.History.GetRange( 116 | Math.Max(conversationData.History.Count - _max_messages, 0), 117 | Math.Min(conversationData.History.Count, _max_messages) 118 | ); 119 | conversationData.Attachments = conversationData.Attachments.GetRange( 120 | Math.Max(conversationData.Attachments.Count - _max_attachments, 0), 121 | Math.Min(conversationData.Attachments.Count, _max_attachments) 122 | ); 123 | 124 | } 125 | 126 | public virtual async Task ProcessMessage(ConversationData conversationData, ITurnContext turnContext) 127 | { 128 | await turnContext.SendActivityAsync(JsonSerializer.Serialize(conversationData.History)); 129 | return $"This chat now contains {conversationData.History.Count} messages"; 130 | } 131 | 132 | public string FormatConversationHistory(ConversationData conversationData) 133 | { 134 | string history = $"{_systemMessage} Continue the conversation acting as the ASSISTANT. Respond to the USER by using available information and functions.\n\n [CONVERSATION_HISTORY]\n"; 135 | List latestMessages = conversationData.History.GetRange( 136 | Math.Max(conversationData.History.Count - _max_messages, 0), 137 | Math.Min(conversationData.History.Count, _max_messages) 138 | ); 139 | foreach (ConversationTurn conversationTurn in latestMessages) 140 | { 141 | history += $"{conversationTurn.Role.ToUpper()}:\n{conversationTurn.Message}\n"; 142 | } 143 | history += "ASSISTANT: {{Plan response goes here}}"; 144 | return history; 145 | } 146 | 147 | 148 | protected override async Task OnTeamsSigninVerifyStateAsync(ITurnContext turnContext, CancellationToken cancellationToken) 149 | { 150 | // The OAuth Prompt needs to see the Invoke Activity in order to complete the login process. 151 | // Run the Dialog with the new Invoke Activity. 152 | await _dialog.RunAsync(turnContext, _conversationState.CreateProperty(nameof(DialogState)), cancellationToken); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/Controllers/BotController.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Bot.Builder; 7 | using Microsoft.Bot.Builder.Integration.AspNet.Core; 8 | 9 | namespace Microsoft.BotBuilderSamples 10 | { 11 | // This ASP Controller is created to handle a request. Dependency Injection will provide the Adapter and IBot 12 | // implementation at runtime. Multiple different IBot implementations running at different endpoints can be 13 | // achieved by specifying a more specific type for the bot constructor argument. 14 | [Route("api/messages")] 15 | [ApiController] 16 | public class BotController : ControllerBase 17 | { 18 | private readonly IBotFrameworkHttpAdapter _adapter; 19 | private readonly IBot _bot; 20 | 21 | public BotController(IBotFrameworkHttpAdapter adapter, IBot bot) 22 | { 23 | _adapter = adapter; 24 | _bot = bot; 25 | } 26 | 27 | [HttpPost] 28 | public async Task PostAsync() 29 | { 30 | // Delegate the processing of the HTTP POST to the adapter. 31 | // The adapter will invoke the bot. 32 | await _adapter.ProcessAsync(Request, Response, _bot); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Controllers/DirectLineController.cs: -------------------------------------------------------------------------------- 1 | // Sample code from: https://github.com/microsoft/BotFramework-WebChat 2 | 3 | using System; 4 | using System.Security.Cryptography; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.Extensions.Configuration; 8 | using Models; 9 | using Services; 10 | 11 | namespace TokenSampleApi.Controllers 12 | { 13 | [ApiController] 14 | public class DirectLineController : ControllerBase 15 | { 16 | private readonly DirectLineService _directLineService; 17 | 18 | private readonly string _directLineSecret; 19 | 20 | public DirectLineController(DirectLineService directLineService, IConfiguration configuration) 21 | { 22 | _directLineService = directLineService; 23 | _directLineSecret = configuration["DIRECT_LINE_SECRET"]; 24 | } 25 | 26 | // Endpoint for generating a Direct Line token bound to a random user ID 27 | [HttpGet] 28 | [Route("/api/directline/token")] 29 | public async Task Get() 30 | { 31 | // Generate a random user ID to use for DirectLine token 32 | var randomUserId = GenerateRandomUserId(); 33 | 34 | DirectLineTokenDetails directLineTokenDetails; 35 | try 36 | { 37 | directLineTokenDetails = await _directLineService.GetTokenAsync(_directLineSecret, randomUserId); 38 | } 39 | catch (InvalidOperationException invalidOpException) 40 | { 41 | return BadRequest(new { message = invalidOpException.Message }); 42 | } 43 | 44 | return this.Ok(new { token = directLineTokenDetails.Token }); 45 | } 46 | 47 | // Generates a random user ID 48 | // Prefixed with "dl_", as required by the Direct Line API 49 | private static string GenerateRandomUserId() 50 | { 51 | byte[] tokenData = new byte[16]; 52 | using var rng = RandomNumberGenerator.Create(); 53 | rng.GetBytes(tokenData); 54 | 55 | return $"dl_{BitConverter.ToString(tokenData).Replace("-", "").ToLower()}"; 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/ConversationData.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace Microsoft.BotBuilderSamples 7 | { 8 | public class ConversationTurn 9 | { 10 | public string Role { get; set; } = null; 11 | public string Message { get; set; } = null; 12 | } 13 | public class Attachment 14 | { 15 | public string Name { get; set; } 16 | public List Pages { get; set; } = new List(); 17 | } 18 | public class AttachmentPage 19 | { 20 | public string Content { get; set; } = null; 21 | public float[] Vector { get; set; } = null; 22 | } 23 | // Defines a state property used to track conversation data. 24 | public class ConversationData 25 | { 26 | // The time-stamp of the most recent incoming message. 27 | public string Timestamp { get; set; } 28 | 29 | // The ID of the user's channel. 30 | public string ChannelId { get; set; } 31 | 32 | // Track whether we have already asked the user's name 33 | public bool PromptedUserForName { get; set; } = false; 34 | 35 | // Track conversation history 36 | public List History = new List(); 37 | 38 | // Track attached documents 39 | public List Attachments = new List(); 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Dialogs/LoginDialog.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Microsoft. All rights reserved. 3 | // 4 | 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.Bot.Builder; 8 | using Microsoft.Bot.Builder.Dialogs; 9 | using Microsoft.Bot.Schema; 10 | using Microsoft.Extensions.Configuration; 11 | 12 | namespace Microsoft.BotBuilderSamples 13 | { 14 | public class LoginDialog : ComponentDialog 15 | { 16 | public readonly BotState _conversationState; 17 | public readonly string _login_successful_message; 18 | public readonly string _login_failed_message; 19 | public LoginDialog(IConfiguration configuration) 20 | : base(nameof(LoginDialog)) 21 | { 22 | _login_successful_message = configuration.GetValue("SSO_MESSAGE_SUCCESS"); 23 | _login_failed_message = configuration.GetValue("SSO_MESSAGE_FAILED"); 24 | AddDialog(new OAuthPrompt( 25 | nameof(OAuthPrompt), 26 | new OAuthPromptSettings 27 | { 28 | ConnectionName = configuration.GetValue("SSO_CONFIG_NAME"), 29 | Text = configuration.GetValue("SSO_MESSAGE_TITLE"), 30 | Title = configuration.GetValue("SSO_MESSAGE_PROMPT"), 31 | Timeout = 300000, // User has 5 minutes to login (1000 * 60 * 5) 32 | EndOnInvalidMessage = true 33 | })); 34 | 35 | 36 | AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] 37 | { 38 | PromptStepAsync, 39 | LoginStepAsync 40 | })); 41 | 42 | // The initial child Dialog to run. 43 | InitialDialogId = nameof(WaterfallDialog); 44 | } 45 | 46 | private async Task PromptStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) 47 | { 48 | return await stepContext.BeginDialogAsync(nameof(OAuthPrompt), null, cancellationToken); 49 | } 50 | 51 | private async Task LoginStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) 52 | { 53 | // Get the token from the previous step. 54 | var tokenResponse = (TokenResponse)stepContext.Result; 55 | if (tokenResponse?.Token != null) 56 | { 57 | try 58 | { 59 | await stepContext.Context.SendActivityAsync(_login_successful_message); 60 | return await stepContext.EndDialogAsync(cancellationToken: cancellationToken); 61 | } 62 | catch { } 63 | 64 | } 65 | 66 | await stepContext.Context.SendActivityAsync(_login_failed_message); 67 | return await stepContext.EndDialogAsync(cancellationToken: cancellationToken); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/Factory/SqlConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Data.SqlClient; 2 | 3 | namespace Microsoft.BotBuilderSamples 4 | { 5 | public class SqlConnectionFactory 6 | { 7 | private string _connectionString; 8 | public SqlConnectionFactory(string connectionString) 9 | { 10 | _connectionString = connectionString; 11 | } 12 | 13 | public SqlConnection createConnection() 14 | { 15 | return new SqlConnection(_connectionString); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/Models/DirectLineTokenDetails.cs: -------------------------------------------------------------------------------- 1 | // Sample code from: https://github.com/microsoft/BotFramework-WebChat 2 | 3 | namespace Models 4 | { 5 | public class DirectLineTokenDetails 6 | { 7 | public string Token { get; set; } 8 | 9 | public int ExpiresIn { get; set; } 10 | 11 | public string ConversationId { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /src/Models/RetrievedPassage.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using Azure.Search.Documents.Indexes; 3 | 4 | namespace Models; 5 | 6 | public class RetrievedPassage 7 | { 8 | [JsonPropertyName("title")] 9 | [SimpleField(IsFilterable = true, IsSortable = true)] 10 | public string Title { get; set; } 11 | 12 | [JsonPropertyName("chunk_id")] 13 | [SimpleField(IsFilterable = true, IsSortable = true)] 14 | public string ChunkId { get; set; } 15 | 16 | [JsonPropertyName("path")] 17 | [SimpleField(IsFilterable = true, IsSortable = true)] 18 | public string Path { get; set; } 19 | 20 | [JsonPropertyName("chunk")] 21 | [SimpleField(IsFilterable = true, IsSortable = true)] 22 | public string Chunk { get; set; } 23 | } -------------------------------------------------------------------------------- /src/Models/SearchResult.cs: -------------------------------------------------------------------------------- 1 | 2 | using System.Collections.Generic; 3 | 4 | namespace Models; 5 | public struct SearchResult 6 | { 7 | public Value webPages { get; set; } 8 | public Value news { get; set; } 9 | public Value images { get; set; } 10 | public Value videos { get; set; } 11 | } 12 | 13 | public struct Value 14 | { 15 | public List value { get; set; } 16 | 17 | } 18 | public struct WebpageResult 19 | { 20 | public string name { get; set; } 21 | public string description { get; set; } 22 | public string url { get; set; } 23 | } 24 | public struct NewsResult 25 | { 26 | public string name { get; set; } 27 | public string description { get; set; } 28 | public string url { get; set; } 29 | } 30 | public struct ImageResult 31 | { 32 | public string name { get; set; } 33 | public string contentUrl { get; set; } 34 | } 35 | public struct VideoResult 36 | { 37 | public string name { get; set; } 38 | public string description { get; set; } 39 | public string contentUrl { get; set; } 40 | } -------------------------------------------------------------------------------- /src/Plugins/BingPlugin.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Threading.Tasks; 3 | using Microsoft.SemanticKernel; 4 | using Microsoft.BotBuilderSamples; 5 | using Microsoft.Bot.Builder; 6 | using Microsoft.Bot.Schema; 7 | using System.Text.Json; 8 | using Models; 9 | using Services; 10 | using HtmlAgilityPack; 11 | using System; 12 | 13 | namespace Plugins; 14 | public class BingPlugin 15 | { 16 | private ITurnContext _turnContext; 17 | private BingClient _bingClient; 18 | 19 | public BingPlugin(ConversationData conversationData, ITurnContext turnContext, BingClient bingClient) 20 | { 21 | _turnContext = turnContext; 22 | _bingClient = bingClient; 23 | } 24 | // Returns search results with headers. 25 | 26 | [KernelFunction, Description("Search the internet by text using Bing. Terms and conditions require you to explicitly say results were pulled from the web, and list links the links associated with any information you provide. Before using this function, the assistant should always ask whether the user would like it to search the web.")] 27 | public async Task BingSearch( 28 | [Description("The query to pass into Bing")] string query, 29 | [Description("The result type you are looking for. One of \"webpages\",\"images\",\"videos\",\"news\". If no news are returned, you may try webpages as a fallback.")] string resultType 30 | ) 31 | { 32 | await _turnContext.SendActivityAsync($"Searching the internet for {resultType} with the description \"{query}\"..."); 33 | 34 | SearchResult result = await _bingClient.WebSearch(query, resultType); 35 | 36 | return JsonSerializer.Serialize(result); 37 | } 38 | 39 | [KernelFunction, Description("Browse to a URL to get information. This can only be used with URLs returned from Bing search. It will not work for any others. Always provide the URL source and human-readable name of the source if available when using this function.")] 40 | public async Task BingBrowse( 41 | [Description("The URL to browse")] string url 42 | ) 43 | { 44 | await _turnContext.SendActivityAsync($"Browsing to {url}..."); 45 | 46 | var web = new HtmlWeb(); 47 | var doc = web.Load(url); 48 | 49 | Console.WriteLine(doc.DocumentNode.InnerText); 50 | return doc.DocumentNode.InnerText; 51 | } 52 | } -------------------------------------------------------------------------------- /src/Plugins/DALLEPlugin.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Threading.Tasks; 3 | using Azure; 4 | using Microsoft.SemanticKernel; 5 | using Microsoft.BotBuilderSamples; 6 | using Microsoft.Bot.Builder; 7 | using Microsoft.Bot.Schema; 8 | using Azure.AI.OpenAI; 9 | using System.Collections.Generic; 10 | 11 | namespace Plugins; 12 | public class DALLEPlugin 13 | { 14 | private readonly OpenAIClient _aoaiClient; 15 | private ITurnContext _turnContext; 16 | 17 | public DALLEPlugin(ConversationData conversationData, ITurnContext turnContext, OpenAIClient aoaiClient) 18 | { 19 | _aoaiClient = aoaiClient; 20 | _turnContext = turnContext; 21 | } 22 | 23 | 24 | 25 | [KernelFunction, Description("Generate images from descriptions.")] 26 | public async Task GenerateImages( 27 | [Description("The description of the images to be generated")] string prompt, 28 | [Description("The number of images to generate. If not specified, I should use 1")] int n 29 | ) 30 | { 31 | await _turnContext.SendActivityAsync($"Generating {n} images with the description \"{prompt}\"..."); 32 | Response imageGenerations = await _aoaiClient.GetImageGenerationsAsync( 33 | new ImageGenerationOptions() 34 | { 35 | Prompt = prompt, 36 | Size = ImageSize.Size512x512, 37 | ImageCount = n 38 | }); 39 | 40 | List images = new(); 41 | images.Add( 42 | new { 43 | type="TextBlock", 44 | text="Here are the generated images.", 45 | size="large" 46 | } 47 | ); 48 | foreach (ImageGenerationData img in imageGenerations.Value.Data) 49 | images.Add(new { type = "Image", url = img.Url.AbsoluteUri }); 50 | object adaptiveCardJson = new 51 | { 52 | type = "AdaptiveCard", 53 | version = "1.0", 54 | body = images 55 | }; 56 | 57 | var adaptiveCardAttachment = new Microsoft.Bot.Schema.Attachment() 58 | { 59 | ContentType = "application/vnd.microsoft.card.adaptive", 60 | Content = adaptiveCardJson, 61 | }; 62 | await _turnContext.SendActivityAsync(MessageFactory.Attachment(adaptiveCardAttachment)); 63 | return "Images were generated successfully and already sent to user."; 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /src/Plugins/HRHandbookPlugin.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Threading.Tasks; 3 | using Models; 4 | using Microsoft.SemanticKernel; 5 | using System.Linq; 6 | using Microsoft.BotBuilderSamples; 7 | using Microsoft.Bot.Builder; 8 | using Microsoft.Bot.Schema; 9 | using Azure.Search.Documents; 10 | using System; 11 | using Azure.Search.Documents.Models; 12 | using Microsoft.SemanticKernel.Connectors.OpenAI; 13 | using System.Collections.Generic; 14 | using Microsoft.IdentityModel.Tokens; 15 | using Azure.Storage.Blobs; 16 | using Azure.Storage.Sas; 17 | using System.Web; 18 | 19 | namespace Plugins; 20 | 21 | public class HRHandbookPlugin 22 | { 23 | private readonly SearchClient _searchClient; 24 | private readonly BlobServiceClient _blobServiceClient; 25 | private ITurnContext _turnContext; 26 | private readonly AzureOpenAITextEmbeddingGenerationService _embeddingClient; 27 | private readonly string _searchSemanticConfig; 28 | 29 | public HRHandbookPlugin(ConversationData conversationData, ITurnContext turnContext, AzureOpenAITextEmbeddingGenerationService embeddingClient, SearchClient searchClient, BlobServiceClient blobServiceClient, string searchSemanticConfig) 30 | { 31 | _searchClient = searchClient; 32 | _blobServiceClient = blobServiceClient; 33 | _searchSemanticConfig = searchSemanticConfig; 34 | _embeddingClient = embeddingClient; 35 | _turnContext = turnContext; 36 | } 37 | 38 | [KernelFunction, Description("Search for HR information in the handbook. Whenever you use information from this source, you must provide itemized sources at the end of your response, including the document name, and link where available. When adding links, always include query paramenters. If the result is a PDF file and the page number is available, append #page={page number} to the end of the link. Do not provide links if they were not retrieved.")] 39 | public async Task FindHR( 40 | [Description("The query to be used in the search")] string query 41 | ) 42 | { 43 | await _turnContext.SendActivityAsync($"Searching the HR Handbook with the query \"{query}\"..."); 44 | var embedding = await _embeddingClient.GenerateEmbeddingsAsync(new List { query }); 45 | var vector = embedding.First().ToArray(); 46 | 47 | var searchOptions = new SearchOptions 48 | { 49 | VectorSearch = new() 50 | { 51 | Queries = { new VectorizedQuery(vector) { KNearestNeighborsCount = 3, Fields = { "vector" } } } 52 | }, 53 | SemanticSearch = new() 54 | { 55 | SemanticConfigurationName = _searchSemanticConfig, 56 | QueryCaption = new(QueryCaptionType.Extractive), 57 | QueryAnswer = new(QueryAnswerType.Extractive), 58 | }, 59 | QueryType = SearchQueryType.Semantic, 60 | Size = 3, 61 | }; 62 | var response = await _searchClient.SearchAsync(query, searchOptions); 63 | var textResults = "[HR HANDBOOK RESULTS]\n\n"; 64 | var searchResults = response.Value.GetResults(); 65 | if (searchResults.Count() == 0) 66 | return "No results found"; 67 | foreach (SearchResult result in searchResults) 68 | { 69 | textResults += $"Title: {result.Document.Title} \n\n"; 70 | textResults += $"Section: {string.Join(" ", result.Document.ChunkId.Split("_").Skip(2))} \n\n"; 71 | if (!result.Document.Path.IsNullOrEmpty()) 72 | { 73 | textResults += $"Link: \"{createSasUri(result.Document.Path)} \n\n\""; 74 | } 75 | textResults += $"Content: {result.Document.Chunk}\n*****\n\n"; 76 | } 77 | Console.WriteLine(textResults); 78 | return textResults; 79 | } 80 | 81 | private string createSasUri(string resourceUri) 82 | { 83 | var resourceUriParts = resourceUri.Split('/'); 84 | string containerName = resourceUriParts[3]; 85 | string blobName = HttpUtility.UrlDecode(string.Join("/", resourceUriParts.Skip(4))); 86 | // Create a Uri object with a service SAS appended 87 | BlobClient blobClient = _blobServiceClient 88 | .GetBlobContainerClient(containerName) 89 | .GetBlobClient(blobName); 90 | // Create a SAS token that's valid for one day 91 | BlobSasBuilder sasBuilder = new BlobSasBuilder 92 | { 93 | BlobContainerName = containerName, 94 | BlobName = blobName, 95 | Resource = "b", 96 | ExpiresOn = DateTimeOffset.UtcNow.AddDays(1) 97 | }; 98 | sasBuilder.SetPermissions(BlobContainerSasPermissions.Read); 99 | Uri sasURI = blobClient.GenerateSasUri(sasBuilder); 100 | 101 | return sasURI.ToString(); 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/Plugins/HumanInterfacePlugin.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Threading.Tasks; 3 | using Microsoft.SemanticKernel; 4 | using Microsoft.BotBuilderSamples; 5 | using Microsoft.Bot.Builder; 6 | using Microsoft.Bot.Schema; 7 | using Azure.AI.OpenAI; 8 | using System.Collections.Generic; 9 | 10 | namespace Plugins; 11 | public class HumanInterfacePlugin 12 | { 13 | private readonly OpenAIClient _aoaiClient; 14 | private ITurnContext _turnContext; 15 | 16 | public HumanInterfacePlugin(ConversationData conversationData, ITurnContext turnContext, OpenAIClient aoaiClient) 17 | { 18 | _aoaiClient = aoaiClient; 19 | _turnContext = turnContext; 20 | } 21 | 22 | 23 | 24 | [KernelFunction, Description("Generate a human-readable final answer based on the results of a plan. Always run this as a final step of any plan to respond to the user.")] 25 | public async Task GenerateFinalResponse( 26 | [Description("Plan results")] string planResults, 27 | [Description("User's goal")] string goal 28 | ) 29 | { 30 | await _turnContext.SendActivityAsync($"Generating final answer..."); 31 | var completionsOptions = new ChatCompletionsOptions("gpt-4", new List{ 32 | new ChatRequestSystemMessage(@$"The information below was obtained by connecting to external systems. Please use it to formulate a response to the user. 33 | [PLAN RESULTS]: 34 | {planResults}"), 35 | new ChatRequestUserMessage(goal) 36 | }); 37 | var completions = await _aoaiClient.GetChatCompletionsAsync(completionsOptions); 38 | return completions.Value.Choices[0].Message.Content; 39 | } 40 | 41 | 42 | } -------------------------------------------------------------------------------- /src/Plugins/SQLPlugin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using Microsoft.Data.SqlClient; 4 | using Microsoft.SemanticKernel; 5 | using Microsoft.BotBuilderSamples; 6 | using Microsoft.Bot.Builder; 7 | using Microsoft.Bot.Schema; 8 | using System.Threading.Tasks; 9 | 10 | namespace Plugins; 11 | 12 | public class SQLPlugin 13 | { 14 | private readonly SqlConnectionFactory _sqlConnectionFactory; 15 | private ITurnContext _turnContext; 16 | public SQLPlugin(ConversationData conversationData, ITurnContext turnContext, SqlConnectionFactory sqlConnectionFactory) 17 | { 18 | _turnContext = turnContext; 19 | _sqlConnectionFactory = sqlConnectionFactory; 20 | } 21 | 22 | 23 | 24 | 25 | [KernelFunction, Description("Obtain the table names in AdventureWorksLT, which contains customer and sales data. Always run this before running other queries instead of assuming the user mentioned the correct name. Remember the salesperson information is contained in the Customer table.")] 26 | public async Task GetTables() { 27 | await _turnContext.SendActivityAsync($"Getting tables..."); 28 | return QueryAsCSV($"SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES;"); 29 | } 30 | 31 | 32 | 33 | [KernelFunction, Description("Obtain the database schema for a table in AdventureWorksLT.")] 34 | public async Task GetSchema( 35 | [Description("The table to get the schema for. Do not include the schema name.")] string tableName 36 | ) 37 | { 38 | await _turnContext.SendActivityAsync($"Getting schema for table \"{tableName}\"..."); 39 | return QueryAsCSV($"SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '{tableName}';"); 40 | } 41 | 42 | 43 | 44 | [KernelFunction, Description("Run SQL against the AdventureWorksLT database")] 45 | public async Task RunQuery( 46 | [Description("The query to run on SQL Server. When referencing tables, make sure to add the schema names.")] string query 47 | ) 48 | { 49 | await _turnContext.SendActivityAsync($"Running query..."); 50 | return QueryAsCSV(query); 51 | } 52 | 53 | 54 | 55 | 56 | private string QueryAsCSV(string query) 57 | { 58 | var output = "[DATABASE RESULTS] \n"; 59 | using (SqlConnection connection = _sqlConnectionFactory.createConnection()) 60 | { 61 | SqlCommand command = new SqlCommand(query, connection); 62 | connection.Open(); 63 | SqlDataReader reader = command.ExecuteReader(); 64 | try 65 | { 66 | for (int i = 0; i < reader.FieldCount; i++) { 67 | output += reader.GetName(i); 68 | if (i < reader.FieldCount - 1) 69 | output += ","; 70 | } 71 | output += "\n"; 72 | while (reader.Read()) 73 | { 74 | for (int i = 0; i < reader.FieldCount; i++) { 75 | var columnName = reader.GetName(i); 76 | output += reader[columnName].ToString(); 77 | if (i < reader.FieldCount - 1) 78 | output += ","; 79 | } 80 | output += "\n"; 81 | } 82 | } catch (Exception e) { 83 | Console.WriteLine(e); 84 | } 85 | finally 86 | { 87 | reader.Close(); 88 | } 89 | } 90 | return output; 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /src/Plugins/UploadPlugin.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Threading.Tasks; 3 | using System.Linq; 4 | using System.Collections.Generic; 5 | using Microsoft.BotBuilderSamples; 6 | using Microsoft.Bot.Builder; 7 | using Microsoft.Bot.Schema; 8 | using Microsoft.SemanticKernel.Connectors.OpenAI; 9 | using Microsoft.SemanticKernel; 10 | 11 | namespace Plugins; 12 | 13 | public class UploadPlugin 14 | { 15 | private readonly AzureOpenAITextEmbeddingGenerationService _embeddingClient; 16 | private ConversationData _conversationData; 17 | private ITurnContext _turnContext; 18 | 19 | public UploadPlugin(ConversationData conversationData, ITurnContext turnContext, AzureOpenAITextEmbeddingGenerationService embeddingClient) 20 | { 21 | _embeddingClient = embeddingClient; 22 | _conversationData = conversationData; 23 | _turnContext = turnContext; 24 | } 25 | 26 | 27 | [KernelFunction, Description("Search for relevant information in the uploaded documents. Only use this when the user refers to documents they uploaded. Do not use or ask follow up questions about this function if the user did not specifically mention a document")] 28 | public async Task SearchUploads( 29 | [Description("The exact name of the document to be searched.")] string docName, 30 | [Description("The text to search by similarity.")] string query 31 | ) 32 | { 33 | await _turnContext.SendActivityAsync($"Searching document {docName} for \"{query}\"..."); 34 | var embedding = await _embeddingClient.GenerateEmbeddingsAsync(new List { query }); 35 | var vector = embedding.First().ToArray(); 36 | var similarities = new List(); 37 | var attachment = _conversationData.Attachments.Find(x => x.Name == docName); 38 | foreach (AttachmentPage page in attachment.Pages) 39 | { 40 | float similarity = 0; 41 | for (int i = 0; i < page.Vector.Count(); i++) 42 | { 43 | similarity += page.Vector[i] * vector[i]; 44 | } 45 | similarities.Add(similarity); 46 | } 47 | var maxIndex = similarities.IndexOf(similarities.Max()); 48 | return _conversationData.Attachments.First().Pages[maxIndex].Content; 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /src/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.Hosting; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace Microsoft.BotBuilderSamples 10 | { 11 | public class Program 12 | { 13 | public static void Main(string[] args) 14 | { 15 | CreateHostBuilder(args).Build().Run(); 16 | } 17 | 18 | public static IHostBuilder CreateHostBuilder(string[] args) => 19 | Host.CreateDefaultBuilder(args) 20 | .ConfigureWebHostDefaults(webBuilder => 21 | { 22 | webBuilder.ConfigureAppConfiguration((hostingContext, config) => 23 | { 24 | config.AddJsonFile("appsettings.json", 25 | optional: true, 26 | reloadOnChange: true).AddEnvironmentVariables(); 27 | }); 28 | webBuilder.ConfigureLogging((logging) => 29 | { 30 | logging.AddDebug(); 31 | logging.AddConsole(); 32 | }); 33 | webBuilder.UseStartup(); 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:3978/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | ".NET Core": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "http://localhost:3978/" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/SemanticKernelBot.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | latest 5 | 73c2a03b-aae7-4833-b3a8-c01744ad2b7d 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 41 | $(NoWarn);NU1701 42 | 43 | 44 | 45 | 46 | Always 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/Services/BingClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Text.Json; 4 | using System.Threading.Tasks; 5 | using Models; 6 | 7 | namespace Services 8 | { 9 | public class BingClient 10 | { 11 | private readonly HttpClient _httpClient; 12 | private readonly string _accessKey; 13 | public BingClient(HttpClient httpClient, Uri uriBase, string apiKey) { 14 | httpClient.BaseAddress = uriBase; 15 | _httpClient = httpClient; 16 | _accessKey = apiKey; 17 | } 18 | public async Task WebSearch(string searchQuery, string resultType) 19 | { 20 | // Construct the search request URI. 21 | var path = "/v7.0/search?count=3&q=" + Uri.EscapeDataString(searchQuery) + "&responseFilter=" + Uri.EscapeDataString(resultType); 22 | 23 | var tokenRequest = new HttpRequestMessage(HttpMethod.Get, path) 24 | { 25 | Headers = 26 | { 27 | { "Ocp-Apim-Subscription-Key", _accessKey }, 28 | }, 29 | }; 30 | var response = await _httpClient.SendAsync(tokenRequest, default); 31 | 32 | var responseContent = await response.Content.ReadAsStringAsync(); 33 | var searchResult = JsonSerializer.Deserialize(responseContent); 34 | 35 | return searchResult; 36 | } 37 | 38 | } 39 | } -------------------------------------------------------------------------------- /src/Services/DirectLineService.cs: -------------------------------------------------------------------------------- 1 | // Sample code from: https://github.com/microsoft/BotFramework-WebChat 2 | using System; 3 | using System.Net.Http; 4 | using System.Net.Mime; 5 | using System.Text; 6 | using System.Text.Json; 7 | using System.Text.Json.Serialization; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using Models; 11 | 12 | namespace Services 13 | { 14 | public class DirectLineService 15 | { 16 | private readonly HttpClient _httpClient; 17 | 18 | public DirectLineService(HttpClient httpClient) 19 | { 20 | httpClient.BaseAddress = new Uri("https://directline.botframework.com/"); 21 | 22 | _httpClient = httpClient; 23 | } 24 | 25 | // Generates a new Direct Line token given the secret. 26 | // Provides user ID in the request body to bind the user ID to the token. 27 | public async Task GetTokenAsync(string directLineSecret, string userId, CancellationToken cancellationToken = default) 28 | { 29 | var tokenRequestBody = new { user = new { id = userId } }; 30 | var tokenRequest = new HttpRequestMessage(HttpMethod.Post, "v3/directline/tokens/generate") 31 | { 32 | Headers = 33 | { 34 | { "Authorization", $"Bearer {directLineSecret}" }, 35 | }, 36 | Content = new StringContent(JsonSerializer.Serialize(tokenRequestBody), Encoding.UTF8, MediaTypeNames.Application.Json), 37 | }; 38 | 39 | var tokenResponseMessage = await _httpClient.SendAsync(tokenRequest, cancellationToken); 40 | 41 | if (!tokenResponseMessage.IsSuccessStatusCode) 42 | { 43 | throw new InvalidOperationException($"Direct Line token API call failed with status code {tokenResponseMessage.StatusCode}"); 44 | } 45 | 46 | using var responseContentStream = await tokenResponseMessage.Content.ReadAsStreamAsync(); 47 | var tokenResponse = await JsonSerializer.DeserializeAsync(responseContentStream); 48 | 49 | return new DirectLineTokenDetails 50 | { 51 | Token = tokenResponse.Token, 52 | ConversationId = tokenResponse.ConversationId, 53 | ExpiresIn = tokenResponse.ExpiresIn, 54 | }; 55 | } 56 | 57 | private class DirectLineTokenApiResponse 58 | { 59 | [JsonPropertyName("token")] 60 | public string Token { get; set; } 61 | 62 | [JsonPropertyName("expires_in")] 63 | public int ExpiresIn { get; set; } 64 | 65 | [JsonPropertyName("conversationId")] 66 | public string ConversationId { get; set; } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/Startup.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | using System; 5 | using Azure; 6 | using Azure.AI.FormRecognizer.DocumentAnalysis; 7 | using Azure.AI.OpenAI; 8 | using Azure.Identity; 9 | using Azure.Search.Documents; 10 | using Azure.Storage; 11 | using Azure.Storage.Blobs; 12 | using Microsoft.AspNetCore.Builder; 13 | using Microsoft.AspNetCore.Hosting; 14 | using Microsoft.Bot.Builder; 15 | using Microsoft.Bot.Builder.Azure; 16 | using Microsoft.Bot.Builder.Integration.AspNet.Core; 17 | using Microsoft.Bot.Connector.Authentication; 18 | using Microsoft.Extensions.Configuration; 19 | using Microsoft.Extensions.DependencyInjection; 20 | using Microsoft.Extensions.Hosting; 21 | using Microsoft.IdentityModel.Tokens; 22 | using Microsoft.SemanticKernel.Connectors.OpenAI; 23 | using Microsoft.WindowsAzure.Storage.Auth; 24 | using Services; 25 | 26 | namespace Microsoft.BotBuilderSamples 27 | { 28 | public class Startup 29 | { 30 | // This method gets called by the runtime. Use this method to add services to the container. 31 | public void ConfigureServices(IServiceCollection services) 32 | { 33 | var configuration = new ConfigurationBuilder() 34 | .AddJsonFile("appsettings.json", optional: true) 35 | .AddEnvironmentVariables() 36 | .Build(); 37 | services.AddSingleton(configuration); 38 | 39 | services.AddHttpClient(); 40 | 41 | DefaultAzureCredential azureCredentials; 42 | if (configuration.GetValue("MicrosoftAppType") == "UserAssignedMSI") 43 | azureCredentials = new DefaultAzureCredential(new DefaultAzureCredentialOptions() { ManagedIdentityClientId = configuration.GetValue("MicrosoftAppId") }); 44 | else 45 | azureCredentials = new DefaultAzureCredential(); 46 | services.AddHttpClient().AddControllers().AddNewtonsoftJson(options => 47 | { 48 | options.SerializerSettings.MaxDepth = HttpHelper.BotMessageSerializerSettings.MaxDepth; 49 | }); 50 | 51 | // Create the Bot Framework Authentication to be used with the Bot Adapter. 52 | services.AddSingleton(); 53 | 54 | // Create the Bot Adapter with error handling enabled. 55 | services.AddSingleton(); 56 | 57 | IStorage storage; 58 | if (configuration.GetValue("COSMOS_API_ENDPOINT") != null) 59 | { 60 | var cosmosDbStorageOptions = new CosmosDbPartitionedStorageOptions() 61 | { 62 | CosmosDbEndpoint = configuration.GetValue("COSMOS_API_ENDPOINT"), 63 | TokenCredential = azureCredentials, 64 | DatabaseId = "SemanticKernelBot", 65 | ContainerId = "Conversations" 66 | }; 67 | storage = new CosmosDbPartitionedStorage(cosmosDbStorageOptions); 68 | } 69 | else 70 | { 71 | storage = new MemoryStorage(); 72 | } 73 | 74 | 75 | // Create the User state passing in the storage layer. 76 | var userState = new UserState(storage); 77 | services.AddSingleton(userState); 78 | 79 | // Create the Conversation state passing in the storage layer. 80 | var conversationState = new ConversationState(storage); 81 | services.AddSingleton(conversationState); 82 | 83 | if (!configuration.GetValue("AOAI_API_KEY").IsNullOrEmpty()) 84 | { 85 | services.AddSingleton(new OpenAIClient(new Uri(configuration.GetValue("AOAI_API_ENDPOINT")), new AzureKeyCredential(configuration.GetValue("AOAI_API_KEY")))); 86 | services.AddSingleton(new AzureOpenAITextEmbeddingGenerationService(configuration.GetValue("AOAI_EMBEDDINGS_MODEL"), configuration.GetValue("AOAI_API_ENDPOINT"), configuration.GetValue("AOAI_API_KEY"))); 87 | } 88 | else 89 | { 90 | services.AddSingleton(new OpenAIClient(new Uri(configuration.GetValue("AOAI_API_ENDPOINT")), azureCredentials)); 91 | services.AddSingleton(new AzureOpenAITextEmbeddingGenerationService(configuration.GetValue("AOAI_EMBEDDINGS_MODEL"), configuration.GetValue("AOAI_API_ENDPOINT"), azureCredentials)); 92 | } 93 | if (!configuration.GetValue("DOCINTEL_API_ENDPOINT").IsNullOrEmpty()) 94 | services.AddSingleton(new DocumentAnalysisClient(new Uri(configuration.GetValue("DOCINTEL_API_ENDPOINT")), new AzureKeyCredential(configuration.GetValue("DOCINTEL_API_KEY")))); 95 | if (!configuration.GetValue("SEARCH_API_ENDPOINT").IsNullOrEmpty()) 96 | if (!configuration.GetValue("SEARCH_API_KEY").IsNullOrEmpty()) 97 | services.AddSingleton(new SearchClient(new Uri(configuration.GetValue("SEARCH_API_ENDPOINT")), configuration.GetValue("SEARCH_INDEX"), new AzureKeyCredential(configuration.GetValue("SEARCH_API_KEY")))); 98 | else 99 | services.AddSingleton(new SearchClient(new Uri(configuration.GetValue("SEARCH_API_ENDPOINT")), configuration.GetValue("SEARCH_INDEX"), azureCredentials)); 100 | if (!configuration.GetValue("SQL_CONNECTION_STRING").IsNullOrEmpty()) 101 | services.AddSingleton(new SqlConnectionFactory(configuration.GetValue("SQL_CONNECTION_STRING"))); 102 | if (!configuration.GetValue("BING_API_ENDPOINT").IsNullOrEmpty()) 103 | services.AddSingleton(new BingClient(new System.Net.Http.HttpClient(), new Uri(configuration.GetValue("BING_API_ENDPOINT")), configuration.GetValue("BING_API_KEY"))); 104 | if (!configuration.GetValue("BLOB_API_ENDPOINT").IsNullOrEmpty()) 105 | if (!configuration.GetValue("BLOB_API_KEY").IsNullOrEmpty()) 106 | services.AddSingleton(new BlobServiceClient(new Uri(configuration.GetValue("BLOB_API_ENDPOINT")), new StorageSharedKeyCredential(configuration.GetValue("BLOB_API_ENDPOINT").Split('/')[2].Split('.')[0], configuration.GetValue("BLOB_API_KEY")))); 107 | else 108 | services.AddSingleton(new BlobServiceClient(new Uri(configuration.GetValue("BLOB_API_ENDPOINT")), azureCredentials)); 109 | 110 | services.AddHttpClient(); 111 | // Create the bot as a transient. In this case the ASP Controller is expecting an IBot. 112 | // services.AddSingleton(); 113 | services.AddSingleton(); 114 | services.AddTransient>(); 115 | } 116 | 117 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 118 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 119 | { 120 | if (env.IsDevelopment()) 121 | { 122 | app.UseDeveloperExceptionPage(); 123 | } 124 | 125 | app.UseDefaultFiles() 126 | .UseStaticFiles() 127 | .UseRouting() 128 | .UseAuthorization() 129 | .UseEndpoints(endpoints => 130 | { 131 | endpoints.MapControllers(); 132 | }); 133 | 134 | // app.UseHttpsRedirection(); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/Teams/color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/semantic-kernel-bot-in-a-box/70014145f9af276f8b64f2a701dc0d859a28bc29/src/Teams/color.png -------------------------------------------------------------------------------- /src/Teams/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.16/MicrosoftTeams.schema.json", 3 | "manifestVersion": "1.16", 4 | "version": "1.0.0", 5 | "id": "{MSI_CLIENT_ID}", 6 | "packageName": "com.microsoft.teams.semantickernelbot", 7 | "developer": { 8 | "name": "Teams App, Inc.", 9 | "websiteUrl": "https://www.microsoft.com", 10 | "privacyUrl": "https://www.teams.com/privacy", 11 | "termsOfUseUrl": "https://www.teams.com/termsofuse" 12 | }, 13 | "icons": { 14 | "color": "color.png", 15 | "outline": "outline.png" 16 | }, 17 | "name": { 18 | "short": "Semantic Kernel Bot", 19 | "full": "Semantic Kernel Bot" 20 | }, 21 | "description": { 22 | "short": "Implement a Semantic Kernel based bot for Microsoft Teams", 23 | "full": "Implement a Semantic Kernel based bot for Microsoft Teams" 24 | }, 25 | "accentColor": "#FFFFFF", 26 | "bots": [ 27 | { 28 | "botId": "{MSI_CLIENT_ID}", 29 | "scopes": [ 30 | "personal" 31 | ], 32 | "supportsFiles": false, 33 | "isNotificationOnly": false 34 | } 35 | ], 36 | "validDomains": [ 37 | "token.botframework.com", 38 | "{APP_SERVICES_NAME}.azurewebsites.net" 39 | ], 40 | "webApplicationInfo": { 41 | "id": "{REGISTRATION_APP_ID}", 42 | "resource": "api://botid-{REGISTRATION_APP_ID}" 43 | } 44 | } -------------------------------------------------------------------------------- /src/Teams/outline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/semantic-kernel-bot-in-a-box/70014145f9af276f8b64f2a701dc0d859a28bc29/src/Teams/outline.png -------------------------------------------------------------------------------- /src/UserProfile.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | namespace Microsoft.BotBuilderSamples 5 | { 6 | // Defines a state property used to track information about the user. 7 | public class UserProfile 8 | { 9 | public string Name { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/appsettings.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "DEBUG": false, 3 | "USE_STEPWISE_PLANNER": true, 4 | "CONVERSATION_HISTORY_MAX_MESSAGES": 10, 5 | "MAX_ATTACHMENTS": 5, 6 | "AOAI_API_ENDPOINT": "https://YOUR_AOAI_SERVICE_NAME.openai.azure.com/", 7 | "AOAI_GPT_MODEL": "YOUR_AOAI_DEPLOYMENT_NAME", 8 | "AOAI_API_KEY": "YOUR_AOAI_API_KEY", 9 | "AOAI_EMBEDDINGS_MODEL": "YOUR_AOAI_EMBEDDINGS_DEPLOYMENT_NAME", 10 | "SEARCH_API_ENDPOINT": "https://YOUR_COGNITIVE_SEARCH_NAME.search.windows.net", 11 | "SEARCH_API_KEY": "YOUR_SEARCH_QUERY_API_KEY", 12 | "SEARCH_INDEX": "YOUR_INDEX_NAME", 13 | "SEARCH_SEMANTIC_CONFIG": "YOUR_INDEX_NAME-semantic-configuration", 14 | "DOCINTEL_API_ENDPOINT": "https://YOUR_DOC_INTEL_NAME.cognitiveservices.azure.com/", 15 | "DOCINTEL_API_KEY": "YOUR_DOCINTEL_APIKEY", 16 | "SQL_CONNECTION_STRING": "YOUR_SQL_CONNECTION_STRING", 17 | "DIRECT_LINE_SECRET": "YOUR_DIRECT_LINE_SECRET_IF_USING_WEB", 18 | 19 | "BING_API_ENDPOINT": "https://api.bing.microsoft.com/", 20 | "BING_API_KEY": "YOUR_BING_APIKEY", 21 | 22 | "PROMPT_WELCOME_MESSAGE": "Welcome to Semantic Kernel Bot in-a-box! Ask me anything to get started.", 23 | "PROMPT_SYSTEM_MESSAGE": "Answer the questions as accurately as possible using the provided functions.", 24 | "PROMPT_SUGGESTED_QUESTIONS": "[\"Find some hotels at the beach\",\"Draw an image of a golden retriever in a halloween costume\",\"How many customers are in the AdventureWorksLT database\"]", 25 | 26 | "SSO_ENABLED": false, 27 | "SSO_CONFIG_NAME": "bot_auth_connection_name", 28 | "SSO_MESSAGE_PROMPT": "Please sign in to continue.", 29 | "SSO_MESSAGE_TITLE": "Sign in", 30 | "SSO_MESSAGE_SUCCESS": "User logged in successfully! Please repeat your question.", 31 | "SSO_MESSAGE_FAILED": "Log in failed. Type anything to retry.", 32 | 33 | "BLOB_API_ENDPOINT": "https://YOUR_ACCOUNT_NAME.blob.core.windows.net/", 34 | "BLOB_API_KEY": "YOUR_BLOB_APIKEY" 35 | } -------------------------------------------------------------------------------- /src/wwwroot/default.htm: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Web Chat Direct Line Token Demo 10 | 11 | 12 | 13 | 39 | 40 | 41 | 42 |
43 | 78 |
79 | 80 | -------------------------------------------------------------------------------- /src/wwwroot/images/BotServices-Translucent.svg: -------------------------------------------------------------------------------- 1 | Asset 5 2 | -------------------------------------------------------------------------------- /src/wwwroot/images/BotServices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/semantic-kernel-bot-in-a-box/70014145f9af276f8b64f2a701dc0d859a28bc29/src/wwwroot/images/BotServices.png -------------------------------------------------------------------------------- /use_cases/1-sql-assistant.md: -------------------------------------------------------------------------------- 1 | # SQL-powered GenAI Assistant 2 | 3 | Deploy an assistant capable of running SQL queries on your database to respond to questions. 4 | This sample uses AdventureWorksLT out of the box, and can be customized to your own database. 5 | 6 | ## Sample questions 7 | 8 | - How much in total sales do we have? 9 | - What are my biggest customers? 10 | - What are my top selling products? 11 | 12 | ## Recommended deployment parameters 13 | 14 | To deploy this solution, use the [Semantic Kernel Bot in-a-box](../README.md) accelerator with the following parameters: 15 | 16 | Azure location = East US 2 17 | gptModel = gpt-4 18 | gptVersion = 1106-preview 19 | deploySql = true 20 | publicNetworkAccess = true 21 | 22 | ## Deployment validation 23 | 24 | After deployment, you should see the following resources on your Azure subscription. 25 | 26 | ![SQL bot resources](../readme_assets/1-resources.png) 27 | 28 | Go to the Azure Bot resource and ask the sample questions to verify that the deployment was successful: 29 | 30 | ![SQL connection scenario](../readme_assets/1-test.png) 31 | 32 | ## Customization steps 33 | 34 | Once the sample is deployed, you will likely want to connect the assistant to your own database. 35 | To do this, follow the steps below: 36 | 37 | - Update the environment variable SQL_CONNECTION_STRING to point to your own SQL database. You may use the existing MSI to connect, or choose a different authentication method; 38 | - Update the file [src/Plugins/SQLPlugin.cs](../src/Plugins/SQLPlugin.cs) with information about your data. This includes: 39 | 1. The method descriptions should describe the type of information found on the database; 40 | 2. Any additional information about the data that might help the model consume it; 41 | 3. Optionally, add other pre-canned methods that might be more targeted to your database. This will reduce the possibility of errors 42 | 43 | ## Notes 44 | 45 | - Always use the bare minimum access you can grant to SQL via large language model. Assume end-users will have full control of that user, regardless of guidelines provided in the prompt. This pattern is not suitable for giving write access to real data. 46 | - This plugin will heavily rely on column names and prompt instructions to draw information from the right tables. Consider building a schema of views for the AI to read from, rather than directly. 47 | - Limit result sets as much as possible to reduce hallucinations. 48 | - Do not expect the AI to perform calculations outside of those performed by SQL commands. -------------------------------------------------------------------------------- /use_cases/2-ai-search-rag.md: -------------------------------------------------------------------------------- 1 | # AI Search Retrieval-augmented generation 2 | 3 | Deploy an assistant that can search an Azure AI Search index to respond to questions. 4 | This sample will consider a sample HR Handbook. 5 | 6 | ## Sample questions 7 | 8 | - What's my company's PTO policy? 9 | - Where do I report an IT security incident? 10 | - How do I request a new workstation? 11 | 12 | ## Recommended deployment parameters 13 | 14 | To deploy this solution, use the [Semantic Kernel Bot in-a-box](../README.md) accelerator with the following parameters: 15 | 16 | Azure location = East US 2 17 | gptModel = gpt-4 18 | gptVersion = 1106-preview 19 | deploySearch = true 20 | publicNetworkAccess = true 21 | 22 | ## Additional steps 23 | 24 | In addition to deploying the sample, You will need to configure the documents and search index to be used by the bot. Follow the steps below to do this: 25 | 26 | - Go to the Storage Account created as part of the deployment and add a container. 27 | - Drop any files you would like to be searched by the bot into the container. Supported file formats include. It is recommended to start with PDF, DOCX or PPTX. A great example to quickly validate the solution is an HR Handbook template, which can be easily found online. 28 | - Go to your Azure AI Search resource and use the Import and Vectorize data feature. It will take care of the entire process of chunking, vectorizing and adding each document in the blob storage container to your search index. 29 | - Back to App Services, add the relevant environment variables: 30 | - **SEARCH_INDEX**: The name of the search index created. 31 | - **SEARCH_SEMANTIC_CONFIG**: The name of the semantic configuration created, if you chose to do so. If not, leave this field empty. You can locate this value by going to Indexes -> Selec your index -> Semantic Configurations 32 | - (Optional) To enable linked citations: 33 | - Go to the created Index and add a "path" field, with the Retrievable option enabled. 34 | - Go to the created Indexer's JSON definition and add a field mapping to the path field 35 | 36 | "fieldMappings": [ 37 | { 38 | "sourceFieldName": "metadata_storage_name", 39 | "targetFieldName": "title", 40 | "mappingFunction": null 41 | }, 42 | { 43 | "sourceFieldName": "metadata_storage_path", 44 | "targetFieldName": "path", 45 | "mappingFunction": null 46 | } 47 | ], 48 | 49 | - Go to the created Skillset and add a mapping to the "path" field 50 | 51 | 52 | "indexProjections": { 53 | "selectors": [ 54 | { 55 | "targetIndexName": "vector-1705514319324", 56 | "parentKeyFieldName": "parent_id", 57 | "sourceContext": "/document/pages/*", 58 | "mappings": [ 59 | { 60 | "name": "chunk", 61 | "source": "/document/pages/*", 62 | "sourceContext": null, 63 | "inputs": [] 64 | }, 65 | { 66 | "name": "vector", 67 | "source": "/document/pages/*/vector", 68 | "sourceContext": null, 69 | "inputs": [] 70 | }, 71 | { 72 | "name": "title", 73 | "source": "/document/metadata_storage_name", 74 | "sourceContext": null, 75 | "inputs": [] 76 | }, 77 | { 78 | "name": "path", 79 | "source": "/document/path", 80 | "sourceContext": null, 81 | "inputs": [] 82 | } 83 | ] 84 | } 85 | ], 86 | "parameters": { 87 | "projectionMode": "skipIndexingParentDocuments" 88 | } 89 | }, 90 | ... 91 | 92 | ## Deployment validation 93 | 94 | Ask the sample questions to verify that the deployment was successful: 95 | 96 | ![AI Search scenario](../readme_assets/webchat-rag.png) 97 | 98 | ## Customization steps 99 | 100 | Once the sample is deployed, you will likely want to connect the assistant to your own document collection. To do this, simply add any other documents you would like added to the bot and re-run the indexing process. -------------------------------------------------------------------------------- /use_cases/3-custom-data-api-rag.md: -------------------------------------------------------------------------------- 1 | # Custom data API Retrieval-Augmented Generation 2 | 3 | Deploy an assistant that consumes data from an external API to respond to questions. 4 | This sample will consider Wikipedia data as a starting point. 5 | 6 | ## Work in progress 7 | 8 | This scenario is a work in progress. Drop an issue if you're looking to implement it, so we can prioritize accordingly! -------------------------------------------------------------------------------- /use_cases/4-bing-chat.md: -------------------------------------------------------------------------------- 1 | # Bing-enabled assistant 2 | 3 | Deploy an assistant capable of searching the web using Bing Search to enhance responses. 4 | 5 | ## Work in progress 6 | 7 | This scenario is a work in progress. Drop an issue if you're looking to implement it, so we can prioritize accordingly! -------------------------------------------------------------------------------- /use_cases/5-dalle-integration.md: -------------------------------------------------------------------------------- 1 | # Dall-E Assistant 2 | 3 | Deploy an assistant that can respond to questions with images. 4 | 5 | ## Work in progress 6 | 7 | This scenario is a work in progress. Drop an issue if you're looking to implement it, so we can prioritize accordingly! -------------------------------------------------------------------------------- /use_cases/6-document-analyzer.md: -------------------------------------------------------------------------------- 1 | # Document Analyzer Assistant 2 | 3 | Deploy an assistant that can receive document uploads and respond to questions about them. 4 | 5 | ## Work in progress 6 | 7 | This scenario is a work in progress. Drop an issue if you're looking to implement it, so we can prioritize accordingly! -------------------------------------------------------------------------------- /use_cases/7-bot-authentication.md: -------------------------------------------------------------------------------- 1 | # Semantic Kernel Bot Authentication 2 | 3 | Deploy an assistant with authentication capabilities. 4 | 5 | ## Work in progress 6 | 7 | This scenario is a work in progress. Drop an issue if you're looking to implement it, so we can prioritize accordingly! -------------------------------------------------------------------------------- /use_cases/8-network-security.md: -------------------------------------------------------------------------------- 1 | # Semantic Kernel Bot Network Security 2 | 3 | Deploy an assistant with restricted network access. 4 | 5 | ## Work in progress 6 | 7 | This scenario is a work in progress. Drop an issue if you're looking to implement it, so we can prioritize accordingly! --------------------------------------------------------------------------------