diff --git a/samples/apps/copilot-chat-app/scripts/Configure.ps1 b/samples/apps/copilot-chat-app/scripts/Configure.ps1 new file mode 100644 index 000000000000..1b10d883814a --- /dev/null +++ b/samples/apps/copilot-chat-app/scripts/Configure.ps1 @@ -0,0 +1,133 @@ +<# +.SYNOPSIS +Configure user secrets, appsettings.Development.json, and .env for Copilot Chat. + +.PARAMETER OpenAI +Switch to configure for OpenAI. + +.PARAMETER AzureOpenAI +Switch to configure for Azure OpenAI. + +.PARAMETER Endpoint +Set when using Azure OpenAI. + +.PARAMETER ApiKey +The API key for the AI service. + +.PARAMETER CompletionModel +The chat completion model to use (e.g., gpt-3.5-turbo or gpt-4). + +.PARAMETER EmbeddingModel +The embedding model to use (e.g., text-embedding-ada-002). + +.PARAMETER PlannerModel +The chat completion model to use for planning (e.g., gpt-3.5-turbo or gpt-4). + +.PARAMETER ClientID +The client (application) ID associated with your AAD app registration. + +.PARAMETER Tenant +The tenant (directory) associated with your AAD app registration. +Defaults to 'common'. +See https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-client-application-configuration#authority. +#> + +param( + [Parameter(ParameterSetName='OpenAI',Mandatory=$false)] + [switch]$OpenAI, + + [Parameter(ParameterSetName='AzureOpenAI',Mandatory=$false)] + [switch]$AzureOpenAI, + + [Parameter(ParameterSetName='AzureOpenAI',Mandatory=$true)] + [string]$Endpoint, + + [Parameter(Mandatory=$true)] + [string]$ApiKey, + + [Parameter(Mandatory=$false)] + [string]$CompletionModel = "gpt-3.5-turbo", + + [Parameter(Mandatory=$false)] + [string]$EmbeddingModel = "text-embedding-ada-002", + + [Parameter(Mandatory=$false)] + [string]$PlannerModel = "gpt-3.5-turbo", + + [Parameter(Mandatory = $true)] + [string] $ClientId, + + [Parameter(Mandatory = $false)] + [string] $Tenant = 'common' +) + +Write-Host "#########################" +Write-Host "# Backend configuration #" +Write-Host "#########################" + +# Install dev certificate +if ($IsLinux) +{ + dotnet dev-certs https + if ($LASTEXITCODE -ne 0) { exit(1) } +} +else # Windows/MacOS +{ + dotnet dev-certs https --trust + if ($LASTEXITCODE -ne 0) { exit(1) } +} + +if ($OpenAI) +{ + $aiServiceType = "OpenAI" + $Endpoint = "" +} +elseif ($AzureOpenAI) +{ + $aiServiceType = "AzureOpenAI" + + # Azure OpenAI has a different model name for gpt-3.5-turbo (no decimal). + $CompletionModel = $CompletionModel.Replace("3.5", "35") + $EmbeddingModel = $EmbeddingModel.Replace("3.5", "35") + $PlannerModel = $PlannerModel.Replace("3.5", "35") +} +else { + Write-Error "Please specify either -OpenAI or -AzureOpenAI" + exit(1) +} + +$appsettingsOverrides = @{ AIService = @{ Type = $aiServiceType; Endpoint = $Endpoint; Models = @{ Completion = $CompletionModel; Embedding = $EmbeddingModel; Planner = $PlannerModel } } } + +$webapiProjectPath = Join-Path "$PSScriptRoot" '../webapi' +$appsettingsOverridesFilePath = Join-Path $webapiProjectPath 'appsettings.Development.json' + +Write-Host "Setting 'AIService:Key' user secret for $aiServiceType..." +dotnet user-secrets set --project $webapiProjectPath AIService:Key $ApiKey +if ($LASTEXITCODE -ne 0) { exit(1) } + +Write-Host "Setting up 'appsettings.Development.json' for $aiServiceType..." +ConvertTo-Json $appsettingsOverrides | Out-File -Encoding utf8 $appsettingsOverridesFilePath + +Write-Host "($appsettingsOverridesFilePath)" +Write-Host "========" +Get-Content $appsettingsOverridesFilePath | Write-Host +Write-Host "========" + +Write-Host "" +Write-Host "##########################" +Write-Host "# Frontend configuration #" +Write-Host "##########################" + +$envFilePath = Join-Path "$PSScriptRoot" '../webapp/.env' + +Write-Host "Setting up '.env'..." +Set-Content -Path $envFilePath -Value "REACT_APP_BACKEND_URI=https://localhost:40443/" +Add-Content -Path $envFilePath -Value "REACT_APP_AAD_AUTHORITY=https://login.microsoftonline.com/$Tenant" +Add-Content -Path $envFilePath -Value "REACT_APP_AAD_CLIENT_ID=$ClientId" + +Write-Host "($envFilePath)" +Write-Host "========" +Get-Content $envFilePath | Write-Host +Write-Host "========" + +Write-Host "Done!" diff --git a/samples/apps/copilot-chat-app/scripts/Configure.sh b/samples/apps/copilot-chat-app/scripts/Configure.sh new file mode 100644 index 000000000000..1e3706bebd00 --- /dev/null +++ b/samples/apps/copilot-chat-app/scripts/Configure.sh @@ -0,0 +1,152 @@ +#!/bin/bash +# Configure user secrets, appsettings.Development.json, and .env for Copilot Chat. + +set -e + +# Defaults +COMPLETION_MODEL="gpt-3.5-turbo" +EMBEDDING_MODEL="text-embedding-ada-002" +PLANNER_MODEL="gpt-3.5-turbo" +TENANT_ID="common" + +# Argument parsing +POSITIONAL_ARGS=() + +while [[ $# -gt 0 ]]; do + case $1 in + --openai) + OPENAI=YES + shift # past argument + ;; + --azureopenai) + AZURE_OPENAI=YES + shift + ;; + -e|--endpoint) + ENDPOINT="$2" + shift # past argument + shift # past value + ;; + -a|--apikey) + API_KEY="$2" + shift + shift + ;; + --completion) + COMPLETION_MODEL="$2" + shift + shift + ;; + --embedding) + EMBEDDING_MODEL="$2" + shift + shift + ;; + --planner) + PLANNER_MODEL="$2" + shift + shift + ;; + -c|--clientid) + CLIENT_ID="$2" + shift + shift + ;; + -t|--tenantid) + TENANT_ID="$2" + shift + shift + ;; + -*|--*) + echo "Unknown option $1" + exit 1 + ;; + *) + POSITIONAL_ARGS+=("$1") # save positional arg + shift # past argument + ;; + esac +done + +set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters + +SCRIPT_DIRECTORY="$(dirname $0)" + +# Validate arguments +if [ -z "$API_KEY" ]; then + echo "Please specify an API key with -a or --apikey."; exit 1; +fi +if [ -z "$CLIENT_ID" ]; then + echo "Please specify a client (application) ID with -c or --clientid."; exit 1; +fi +if [ "$AZURE_OPENAI" = "YES" ] && [ -z "$ENDPOINT" ]; then + echo "When using --azureopenti, please specify an endpoint with -e or --endpoint."; exit 1; +fi + +echo "#########################" +echo "# Backend configuration #" +echo "#########################" + +# Install dev certificate +case "$OSTYPE" in + darwin*) + dotnet dev-certs https --trust + if [ $? -ne 0 ]; then `exit` 1; fi ;; + msys*) + dotnet dev-certs https --trust + if [ $? -ne 0 ]; then exit 1; fi ;; + cygwin*) + dotnet dev-certs https --trust + if [ $? -ne 0 ]; then exit 1; fi ;; + linux*) + dotnet dev-certs https + if [ $? -ne 0 ]; then exit 1; fi ;; +esac + +if [ "$OPENAI" = "YES" ]; then + AI_SERVICE_TYPE="OpenAI" +elif [ "$AZURE_OPENAI" = "YES" ]; then + # Azure OpenAI has a different model name for gpt-3.5-turbo (no decimal). + AI_SERVICE_TYPE="AzureOpenAI" + COMPLETION_MODEL="${COMPLETION_MODEL/3.5/"35"}" + EMBEDDING_MODEL="${EMBEDDING_MODEL/3.5/"35"}" + PLANNER_MODEL="${PLANNER_MODEL/3.5/"35"}" +else + echo "Please specify either --openai or --azureopenai." + exit 1 +fi + +APPSETTINGS_JSON="{ \"AIService\": { \"Type\": \"${AI_SERVICE_TYPE}\", \"Endpoint\": \"${ENDPOINT}\", \"Models\": { \"Completion\": \"${COMPLETION_MODEL}\", \"Embedding\": \"${EMBEDDING_MODEL}\", \"Planner\": \"${PLANNER_MODEL}\" } } }" +WEBAPI_PROJECT_PATH="${SCRIPT_DIRECTORY}/../webapi" +APPSETTINGS_OVERRIDES_FILEPATH="${WEBAPI_PROJECT_PATH}/appsettings.Development.json" + +echo "Setting 'AIService:Key' user secret for $AI_SERVICE_TYPE..." +dotnet user-secrets set --project $WEBAPI_PROJECT_PATH AIService:Key $API_KEY +if [ $? -ne 0 ]; then exit 1; fi + +echo "Setting up 'appsettings.Development.json' for $AI_SERVICE_TYPE..." +echo $APPSETTINGS_JSON > $APPSETTINGS_OVERRIDES_FILEPATH + +echo "($APPSETTINGS_OVERRIDES_FILEPATH)" +echo "========" +cat $APPSETTINGS_OVERRIDES_FILEPATH +echo "========" + +echo "" +echo "##########################" +echo "# Frontend configuration #" +echo "##########################" + +ENV_FILEPATH="${SCRIPT_DIRECTORY}/../webapp/.env" + +echo "Setting up '.env'..." +echo "REACT_APP_BACKEND_URI=https://localhost:40443/" > $ENV_FILEPATH +echo "REACT_APP_AAD_AUTHORITY=https://login.microsoftonline.com/$TENANT_ID" >> $ENV_FILEPATH +echo "REACT_APP_AAD_CLIENT_ID=$CLIENT_ID" >> $ENV_FILEPATH + +echo "($ENV_FILEPATH)" +echo "========" +cat $ENV_FILEPATH +echo "========" + +echo "Done!" \ No newline at end of file diff --git a/samples/apps/copilot-chat-app/scripts/Install-Requirements-UbuntuDebian.sh b/samples/apps/copilot-chat-app/scripts/Install-Requirements-UbuntuDebian.sh new file mode 100644 index 000000000000..5a012d0292b6 --- /dev/null +++ b/samples/apps/copilot-chat-app/scripts/Install-Requirements-UbuntuDebian.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# Installs the requirements for running Copilot Chat. + +set -e + +# Add Yarn's package repository to the system +curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - +echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list + +# Add .NET's package repository to the system +wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb +sudo dpkg -i packages-microsoft-prod.deb +rm packages-microsoft-prod.deb + +# Add NodeJS's package repository to the system +curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - + +# Install the requirements +sudo apt update; +sudo apt install yarn -y; +sudo apt install dotnet-sdk-6.0 -y; +sudo apt install nodejs -y; + +echo "" +echo "YARN $(yarn --version) installed." +echo "NODEJS $(node --version) installed." +echo "DOTNET $(dotnet --version) installed." \ No newline at end of file diff --git a/samples/apps/copilot-chat-app/scripts/README.md b/samples/apps/copilot-chat-app/scripts/README.md index 3d98d3756621..de51f5bf0e97 100644 --- a/samples/apps/copilot-chat-app/scripts/README.md +++ b/samples/apps/copilot-chat-app/scripts/README.md @@ -1,45 +1,79 @@ -# Copilot Chat Setup Scripts - -> The PowerShell scripts in this directory require [PowerShell Core 6 or higher](https://github.com/PowerShell/PowerShell#get-powershell). +# Copilot Chat Setup Scripts (local deployment) ## Before You Begin -To run these scripts, you will need the following: -- *REACT_APP_CLIENT_ID* - - This is the client ID (also known as application ID) associated with your Azure Active Directory (AAD) application registration, which you can find in the Azure portal. -- *MY_AZUREOPENAI_OR_OPENAI_KEY* +To run Copilot Chat, you will need the following: +- *Application ID* + - This is the Client ID (i.e., Application ID) associated with your Azure Active Directory (AAD) application registration, which you can find in the Azure portal. +- *Azure OpenAI or OpenAI API Key* - This is your API key for Azure OpenAI or OpenAI -- An updated [`appsettings.json`](../webapi/appsettings.json) file. At a minimum, you must fill out the `Completion` and `Embedding` sections per the instructions in the comments. - -For more information on how to prepare this data, [see the full instructions for Copilot Chat](../README.md). -## Install Requirements +## 1. Configure your environment ### Windows -Open a PowerShell window as an administrator, navigate to this directory, and run the following command: +Open a PowerShell terminal as an administrator, navigate to this directory, and run the following command: ```powershell -.\Install-Requirements.ps1 +./Install-Requirements.ps1 +``` +> This script uses the Chocolatey package manager install .NET 6.0 SDK, latest Node.js, and Yarn package manager. + +### Ubuntu/Debian Linux +Open a bash terminal as an administrator, navigate to this directory, and run the following command: +```bash +./Install-Requirements-UbuntuDebian.ps1 ``` -This will install .NET 6.0 SDK, Node.js, and Yarn using the Chocolatey package manager. -For all other operating systems, you need to ensure that these requirements are already installed before proceeding. +### Other Linux/MacOS +For all other operating systems, ensure NET 6.0 SDK (or newer), Node.js 14 (or newer), and Yarn classic ([v1.22.19](https://classic.yarnpkg.com/)) package manager are installed before proceeding. -## Run Copilot Chat locally -The `Start` script initializes and runs both the backend and frontend for Copilot Chat on your local machine. +## 2. Configure CopilotChat +Configure the projects with your AI service and application registration information from above. + +**Powershell** +```powershell +./Configure.ps1 -AzureOpenAI -Endpoint {AZURE_OPENAI_ENDPOINT} -ApiKey {AZURE_OPENAI_API_KEY} -ClientId {CLIENT_ID} +``` +> For OpenAI, replace `-AzureOpenAI` with `-OpenAI` and omit `-Endpoint`. + +**Bash** +```bash +./Configure.sh --azureopenai --endpoint {AZURE_OPENAI_ENDPOINT} --apikey {AZURE_OPENAI_API_KEY} --clientid {CLIENT_ID} +``` +> For OpenAI, replace `--azureopenai` with `--openai` and omit `--endpoint`. + +> **Note:** `Configure.ps1`/`Configure.sh` scripts also have parameters for setting additional options, such as AI models and Azure Active Directory tenant IDs. + +## 3. Run Copilot Chat +The `Start` script initializes and runs the WebApp (frontend) and WebApi (backend) for Copilot Chat on your local machine. ### PowerShell Open a PowerShell window, navigate to this directory, and run the following command: - ```powershell -.\Start.ps1 -ClientId REACT_APP_CLIENT_ID -AzureOpenAIOrOpenAIKey MY_AZUREOPENAI_OR_OPENAI_KEY +./Start.ps1 ``` ### Bash -Open a Bash window and navigate to this directory. First, ensure the `Start.sh` script is executable: +Open a Bash window, navigate to this directory, and run the following commands: ```bash +# Ensure ./Start.sh is executable chmod +x Start.sh +# Start CopilotChat +./Start.sh ``` +> **Note:** The first time you run this may take a few minutes for Yarn packages to install. +> **Note:** This script starts `CopilotChatWebApi.exe` as a background process. Be sure to terminate it when you are finished. + +# Troubleshooting +## 1. "A fatal error occurred. The folder [/usr/share/dotnet/host/fxr] does not exist" when running dotnet commands on Linux. +> From https://stackoverflow.com/questions/73753672/a-fatal-error-occurred-the-folder-usr-share-dotnet-host-fxr-does-not-exist -Then run the following command: +When .NET (Core) was first released for Linux, it was not yet available in the official Ubuntu repo. So instead, many of us added the Microsoft APT repo in order to install it. Now, the packages are part of the Ubuntu repo, and they are conflicting with the Microsoft packages. This error is a result of mixed packages. ```bash -./Start.sh REACT_APP_CLIENT_ID MY_AZUREOPENAI_OR_OPENAI_KEY -``` -Note that this script starts `CopilotChatApi.exe` as a background process. Be sure to terminate it when you are finished. +# Remove all existing packages to get to a clean state: +sudo apt remove --assume-yes dotnet*; +sudo apt remove --assume-yes aspnetcore*; +sudo apt remove --assume-yes netstandard*; +# Set the Microsoft package provider priority +echo -e "Package: *\nPin: origin \"packages.microsoft.com\"\nPin-Priority: 1001" | sudo tee /etc/apt/preferences.d/99microsoft-dotnet.pref; +# Update and install dotnet +sudo apt update; +sudo apt install --assume-yes dotnet-sdk-6.0; +``` \ No newline at end of file diff --git a/samples/apps/copilot-chat-app/scripts/Start-Backend.ps1 b/samples/apps/copilot-chat-app/scripts/Start-Backend.ps1 index 4d7e7ba37026..2a459d6085df 100644 --- a/samples/apps/copilot-chat-app/scripts/Start-Backend.ps1 +++ b/samples/apps/copilot-chat-app/scripts/Start-Backend.ps1 @@ -1,36 +1,8 @@ <# .SYNOPSIS -Initializes and runs the Copilot Chat backend. - -.PARAMETER AzureOpenAIOrOpenAIKey -Your Azure OpenAI or OpenAI API key. +Builds and runs the Copilot Chat backend. #> -#Requires -Version 6 - -param ( - [string] $AzureOpenAIOrOpenAIKey -) - -Join-Path "$PSScriptRoot" '..' 'WebApi' | Set-Location - -# Install dev certificate -if ($IsWindows -or $IsMacOS) -{ - dotnet dev-certs https --trust -} -elseif ($IsLinux) -{ - dotnet dev-certs https -} - -# If key provided, store it in user secrets -if (-not $AzureOpenAIOrOpenAIKey -eq '') { - dotnet user-secrets set "Completion:Key" "$AzureOpenAIOrOpenAIKey" - dotnet user-secrets set "Embedding:Key" "$AzureOpenAIOrOpenAIKey" - dotnet user-secrets set "Planner:AIService:Key" "$AzureOpenAIOrOpenAIKey" -} - -# Build and run the backend API server +Join-Path "$PSScriptRoot" '../webapi' | Set-Location dotnet build dotnet run diff --git a/samples/apps/copilot-chat-app/scripts/Start-Backend.sh b/samples/apps/copilot-chat-app/scripts/Start-Backend.sh index 0345e3454374..dce319620d29 100644 --- a/samples/apps/copilot-chat-app/scripts/Start-Backend.sh +++ b/samples/apps/copilot-chat-app/scripts/Start-Backend.sh @@ -1,32 +1,11 @@ #!/bin/bash -# Initializes and runs the Copilot Chat backend. +# Builds and runs the Copilot Chat backend. set -e ScriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$ScriptDir/../WebApi" -# Parameters -AzureOpenAIOrOpenAIKey="$1" - -case "$OSTYPE" in - darwin*) - dotnet dev-certs https --trust ;; - msys*) - dotnet dev-certs https --trust ;; - cygwin*) - dotnet dev-certs https --trust ;; - linux*) - dotnet dev-certs https ;; -esac - -# If key provided, store it in user secrets -if [ "$AzureOpenAIOrOpenAIKey" != "" ]; then - dotnet user-secrets set "Completion:Key" "$AzureOpenAIOrOpenAIKey" - dotnet user-secrets set "Embedding:Key" "$AzureOpenAIOrOpenAIKey" - dotnet user-secrets set "Planner:AIService:Key" "$AzureOpenAIOrOpenAIKey" -fi - # Build and run the backend API server dotnet build && dotnet run diff --git a/samples/apps/copilot-chat-app/scripts/Start-Frontend.ps1 b/samples/apps/copilot-chat-app/scripts/Start-Frontend.ps1 index faa4dba3b4cd..13e870c91f2c 100644 --- a/samples/apps/copilot-chat-app/scripts/Start-Frontend.ps1 +++ b/samples/apps/copilot-chat-app/scripts/Start-Frontend.ps1 @@ -1,33 +1,8 @@ <# .SYNOPSIS -Initializes and runs the Copilot Chat frontend. - -.PARAMETER ClientId -The client (application) ID associated with your AAD app registration. - -.PARAMETER Tenant -The tenant associated with your AAD app registration. -Defaults to 'common'. +Builds and runs the Copilot Chat frontend. #> -#Requires -Version 6 - -param ( - [Parameter(Mandatory)] - [string] $ClientId, - - [string] $Tenant = 'common' -) - -Join-Path "$PSScriptRoot" '..' 'WebApp' | Set-Location -$EnvFilePath = '.env' - -# Overwrite existing .env file -Set-Content -Path $EnvFilePath -Value "REACT_APP_BACKEND_URI=https://localhost:40443/" - -Add-Content -Path $EnvFilePath -Value "REACT_APP_AAD_AUTHORITY=https://login.microsoftonline.com/$Tenant" -Add-Content -Path $EnvFilePath -Value "REACT_APP_AAD_CLIENT_ID=$ClientId" - -# Build and run the frontend application +Join-Path "$PSScriptRoot" '../webapp' | Set-Location yarn install yarn start diff --git a/samples/apps/copilot-chat-app/scripts/Start-Frontend.sh b/samples/apps/copilot-chat-app/scripts/Start-Frontend.sh index 99ca557a1db0..152b544bfafd 100644 --- a/samples/apps/copilot-chat-app/scripts/Start-Frontend.sh +++ b/samples/apps/copilot-chat-app/scripts/Start-Frontend.sh @@ -5,18 +5,7 @@ set -e ScriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$ScriptDir/../WebApp" -EnvFilePath='.env' - -# Parameters -ClientId="$1" -Tenant="${2:-common}" - -# Overwrite existing .env file -echo "REACT_APP_BACKEND_URI=https://localhost:40443/" > $EnvFilePath - -echo "REACT_APP_AAD_AUTHORITY=https://login.microsoftonline.com/$Tenant" >> $EnvFilePath -echo "REACT_APP_AAD_CLIENT_ID=$ClientId" >> $EnvFilePath +cd "$ScriptDir/../webapp" # Build and run the frontend application yarn install && yarn start diff --git a/samples/apps/copilot-chat-app/scripts/Start.ps1 b/samples/apps/copilot-chat-app/scripts/Start.ps1 index b01a7a075d78..e5f046f29d39 100644 --- a/samples/apps/copilot-chat-app/scripts/Start.ps1 +++ b/samples/apps/copilot-chat-app/scripts/Start.ps1 @@ -1,44 +1,13 @@ <# .SYNOPSIS -Initializes and runs both the backend and frontend for Copilot Chat. - -.PARAMETER ClientId -The client (application) ID associated with your AAD app registration. - -.PARAMETER Tenant -The tenant associated with your AAD app registration. -Defaults to 'common'. - -.PARAMETER AzureOpenAIOrOpenAIKey -Your Azure OpenAI or OpenAI API key. +Builds and runs both the backend and frontend for Copilot Chat. #> -#Requires -Version 6 - -param ( - [Parameter(Mandatory)] - [string] $ClientId, - - [string] $Tenant = 'common', - - [string] $AzureOpenAIOrOpenAIKey -) - -Set-Location "$PSScriptRoot" -$BackendScript = Join-Path '.' 'Start-Backend.ps1' -$FrontendScript = Join-Path '.' 'Start-Frontend.ps1' +$BackendScript = Join-Path "$PSScriptRoot" 'Start-Backend.ps1' +$FrontendScript = Join-Path "$PSScriptRoot" 'Start-Frontend.ps1' # Start backend (in new PS process) -if ($AzureOpenAIOrOpenAIKey -eq '') -{ - # no key - Start-Process pwsh -ArgumentList "-noexit", "-command $BackendScript" -} -else -{ - # with key - Start-Process pwsh -ArgumentList "-noexit", "-command $BackendScript -AzureOpenAIOrOpenAIKey $AzureOpenAIOrOpenAIKey" -} +Start-Process pwsh -ArgumentList "-noexit", "-command $BackendScript" # Start frontend (in current PS process) -& $FrontendScript -ClientId $ClientId -Tenant $Tenant +& $FrontendScript diff --git a/samples/apps/copilot-chat-app/scripts/Start.sh b/samples/apps/copilot-chat-app/scripts/Start.sh index d3a9a3333eb0..edf88aff4330 100644 --- a/samples/apps/copilot-chat-app/scripts/Start.sh +++ b/samples/apps/copilot-chat-app/scripts/Start.sh @@ -4,16 +4,11 @@ set -e -# Parameters -ClientId="$1" -Tenant="common" -AzureOpenAIOrOpenAIKey="${2:-}" - ScriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$ScriptDir" # Start backend (in background) -./Start-Backend.sh $AzureOpenAIOrOpenAIKey & +./Start-Backend.sh & # Start frontend -./Start-Frontend.sh $ClientId $Tenant +./Start-Frontend.sh diff --git a/samples/apps/copilot-chat-app/webapi/CopilotChat/Controllers/BotController.cs b/samples/apps/copilot-chat-app/webapi/CopilotChat/Controllers/BotController.cs index f7258928e1ad..9d3b75d4c60e 100644 --- a/samples/apps/copilot-chat-app/webapi/CopilotChat/Controllers/BotController.cs +++ b/samples/apps/copilot-chat-app/webapi/CopilotChat/Controllers/BotController.cs @@ -44,7 +44,7 @@ public class BotController : ControllerBase IMemoryStore memoryStore, ChatSessionRepository chatRepository, ChatMessageRepository chatMessageRepository, - IOptionsSnapshot aiServiceOptions, + IOptions aiServiceOptions, IOptions botSchemaOptions, IOptions documentMemoryOptions, ILogger logger) @@ -54,7 +54,7 @@ public class BotController : ControllerBase this._chatRepository = chatRepository; this._chatMessageRepository = chatMessageRepository; this._botSchemaOptions = botSchemaOptions.Value; - this._embeddingOptions = aiServiceOptions.Get(AIServiceOptions.EmbeddingPropertyName); + this._embeddingOptions = aiServiceOptions.Value; this._documentMemoryOptions = documentMemoryOptions.Value; } @@ -87,7 +87,7 @@ public class BotController : ControllerBase { return this.BadRequest("Incompatible schema. " + $"The supported bot schema is {this._botSchemaOptions.Name}/{this._botSchemaOptions.Version} " + - $"for the {this._embeddingOptions.DeploymentOrModelId} model from {this._embeddingOptions.AIService}. " + + $"for the {this._embeddingOptions.Models.Embedding} model from {this._embeddingOptions.Type}. " + $"But the uploaded file is with schema {bot.Schema.Name}/{bot.Schema.Version} " + $"for the {bot.EmbeddingConfigurations.DeploymentOrModelId} model from {bot.EmbeddingConfigurations.AIService}."); } @@ -170,8 +170,8 @@ public class BotController : ControllerBase // The app can define what schema/version it supports before the community comes out with an open schema. return externalBotSchema.Name.Equals(botSchemaOptions.Name, StringComparison.OrdinalIgnoreCase) && externalBotSchema.Version == botSchemaOptions.Version - && externalBotEmbeddingConfig.AIService == embeddingOptions.AIService - && externalBotEmbeddingConfig.DeploymentOrModelId.Equals(embeddingOptions.DeploymentOrModelId, StringComparison.OrdinalIgnoreCase); + && externalBotEmbeddingConfig.AIService == embeddingOptions.Type + && externalBotEmbeddingConfig.DeploymentOrModelId.Equals(embeddingOptions.Models.Embedding, StringComparison.OrdinalIgnoreCase); } /// @@ -221,8 +221,8 @@ private async Task CreateBotAsync(IKernel kernel, Guid chatId) // get the embedding configuration EmbeddingConfigurations = new BotEmbeddingConfig { - AIService = this._embeddingOptions.AIService, - DeploymentOrModelId = this._embeddingOptions.DeploymentOrModelId + AIService = this._embeddingOptions.Type, + DeploymentOrModelId = this._embeddingOptions.Models.Embedding } }; diff --git a/samples/apps/copilot-chat-app/webapi/CopilotChat/Extensions/SemanticKernelExtensions.cs b/samples/apps/copilot-chat-app/webapi/CopilotChat/Extensions/SemanticKernelExtensions.cs index d8ddef9d393b..731f35e53093 100644 --- a/samples/apps/copilot-chat-app/webapi/CopilotChat/Extensions/SemanticKernelExtensions.cs +++ b/samples/apps/copilot-chat-app/webapi/CopilotChat/Extensions/SemanticKernelExtensions.cs @@ -29,7 +29,7 @@ public static IServiceCollection AddCopilotChatPlannerServices(this IServiceColl .WithLogger(sp.GetRequiredService>()) .WithConfiguration( new KernelConfig().AddPlannerBackend( - sp.GetRequiredService>().Value.AIService!) + sp.GetRequiredService>().Value) ) // TODO verify planner has AI service configured .Build())); @@ -70,25 +70,11 @@ public static IKernel RegisterCopilotChatSkills(this IKernel kernel, IServicePro /// private static KernelConfig AddPlannerBackend(this KernelConfig kernelConfig, AIServiceOptions options) { - switch (options.AIService) + return options.Type switch { - case AIServiceOptions.AIServiceType.AzureOpenAI: - kernelConfig.AddAzureChatCompletionService( - deploymentName: options.DeploymentOrModelId, - endpoint: options.Endpoint, - apiKey: options.Key); - break; - - case AIServiceOptions.AIServiceType.OpenAI: - kernelConfig.AddOpenAIChatCompletionService( - modelId: options.DeploymentOrModelId, - apiKey: options.Key); - break; - - default: - throw new ArgumentException($"Invalid {nameof(options.AIService)} value in '{AIServiceOptions.CompletionPropertyName}' settings."); - } - - return kernelConfig; + AIServiceOptions.AIServiceType.AzureOpenAI => kernelConfig.AddAzureChatCompletionService(options.Models.Planner, options.Endpoint, options.Key), + AIServiceOptions.AIServiceType.OpenAI => kernelConfig.AddOpenAIChatCompletionService(options.Models.Planner, options.Key), + _ => throw new ArgumentException($"Invalid {nameof(options.Type)} value in '{AIServiceOptions.PropertyName}' settings."), + }; } } diff --git a/samples/apps/copilot-chat-app/webapi/CopilotChat/Extensions/ServiceExtensions.cs b/samples/apps/copilot-chat-app/webapi/CopilotChat/Extensions/ServiceExtensions.cs index 049512193426..324631bf34d6 100644 --- a/samples/apps/copilot-chat-app/webapi/CopilotChat/Extensions/ServiceExtensions.cs +++ b/samples/apps/copilot-chat-app/webapi/CopilotChat/Extensions/ServiceExtensions.cs @@ -27,13 +27,8 @@ public static IServiceCollection AddCopilotChatOptions(this IServiceCollection s { // AI service configurations for Copilot Chat. // They are using the same configuration section as Semantic Kernel. - services.AddOptions(AIServiceOptions.CompletionPropertyName) - .Bind(configuration.GetSection(AIServiceOptions.CompletionPropertyName)) - .ValidateOnStart() - .PostConfigure(TrimStringProperties); - - services.AddOptions(AIServiceOptions.EmbeddingPropertyName) - .Bind(configuration.GetSection(AIServiceOptions.EmbeddingPropertyName)) + services.AddOptions(AIServiceOptions.PropertyName) + .Bind(configuration.GetSection(AIServiceOptions.PropertyName)) .ValidateOnStart() .PostConfigure(TrimStringProperties); diff --git a/samples/apps/copilot-chat-app/webapi/CopilotChat/Options/PlannerOptions.cs b/samples/apps/copilot-chat-app/webapi/CopilotChat/Options/PlannerOptions.cs index b75dda880e04..ea5cb8411699 100644 --- a/samples/apps/copilot-chat-app/webapi/CopilotChat/Options/PlannerOptions.cs +++ b/samples/apps/copilot-chat-app/webapi/CopilotChat/Options/PlannerOptions.cs @@ -1,8 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -using System.ComponentModel.DataAnnotations; -using SemanticKernel.Service.Options; - namespace SemanticKernel.Service.CopilotChat.Options; /// @@ -12,12 +9,6 @@ public class PlannerOptions { public const string PropertyName = "Planner"; - /// - /// The AI service to use for planning. - /// - [Required] - public AIServiceOptions? AIService { get; set; } - /// /// Whether to enable the planner. /// diff --git a/samples/apps/copilot-chat-app/webapi/DeploymentTemplates/main.bicep b/samples/apps/copilot-chat-app/webapi/DeploymentTemplates/main.bicep index 750a31f1f01e..60dd7d480c69 100644 --- a/samples/apps/copilot-chat-app/webapi/DeploymentTemplates/main.bicep +++ b/samples/apps/copilot-chat-app/webapi/DeploymentTemplates/main.bicep @@ -143,53 +143,29 @@ resource appServiceWeb 'Microsoft.Web/sites@2022-03-01' = { use32BitWorkerProcess: false appSettings: [ { - name: 'Completion:AIService' + name: 'AIService:Type' value: aiService } { - name: 'Completion:DeploymentOrModelId' - value: completionModel - } - { - name: 'Completion:Endpoint' + name: 'AIService:Endpoint' value: deployNewAzureOpenAI ? openAI.properties.endpoint : endpoint } { - name: 'Completion:Key' + name: 'AIService:Key' value: deployNewAzureOpenAI ? openAI.listKeys().key1 : apiKey } { - name: 'Embedding:AIService' - value: aiService + name: 'AIService:Models:Completion' + value: completionModel } { - name: 'Embedding:DeploymentOrModelId' + name: 'AIService:Models:Embedding' value: embeddingModel } { - name: 'Embedding:Endpoint' - value: deployNewAzureOpenAI ? openAI.properties.endpoint : endpoint - } - { - name: 'Embedding:Key' - value: deployNewAzureOpenAI ? openAI.listKeys().key1 : apiKey - } - { - name: 'Planner:AIService:AIService' - value: aiService - } - { - name: 'Planner:AIService:DeploymentOrModelId' + name: 'AIService:Models:Planner' value: plannerModel } - { - name: 'Planner:AIService:Endpoint' - value: deployNewAzureOpenAI ? openAI.properties.endpoint : endpoint - } - { - name: 'Planner:AIService:Key' - value: deployNewAzureOpenAI ? openAI.listKeys().key1 : apiKey - } { name: 'Authorization:Type' value: empty(skServerApiKey) ? 'None' : 'ApiKey' diff --git a/samples/apps/copilot-chat-app/webapi/Options/AIServiceOptions.cs b/samples/apps/copilot-chat-app/webapi/Options/AIServiceOptions.cs index 6f385e320b12..853c5ad0966e 100644 --- a/samples/apps/copilot-chat-app/webapi/Options/AIServiceOptions.cs +++ b/samples/apps/copilot-chat-app/webapi/Options/AIServiceOptions.cs @@ -9,37 +9,64 @@ namespace SemanticKernel.Service.Options; /// public sealed class AIServiceOptions { - public const string CompletionPropertyName = "Completion"; - public const string EmbeddingPropertyName = "Embedding"; + public const string PropertyName = "AIService"; + /// + /// Supported types of AI services. + /// public enum AIServiceType { + /// + /// Azure OpenAI https://learn.microsoft.com/en-us/azure/cognitive-services/openai/ + /// AzureOpenAI, + + /// + /// OpenAI https://openai.com/ + /// OpenAI } /// - /// Label used for referencing the AI service in Semantic Kernel. + /// AI models to use. /// - [Required, NotEmptyOrWhitespace] - public string Label { get; set; } = string.Empty; + public class ModelTypes + { + /// + /// Azure OpenAI deployment name or OpenAI model name to use for completions. + /// + [Required, NotEmptyOrWhitespace] + public string Completion { get; set; } = string.Empty; + + /// + /// Azure OpenAI deployment name or OpenAI model name to use for embeddings. + /// + [Required, NotEmptyOrWhitespace] + public string Embedding { get; set; } = string.Empty; + + /// + /// Azure OpenAI deployment name or OpenAI model name to use for planner. + /// + [Required, NotEmptyOrWhitespace] + public string Planner { get; set; } = string.Empty; + } /// /// Type of AI service. /// [Required] - public AIServiceType AIService { get; set; } = AIServiceType.AzureOpenAI; + public AIServiceType Type { get; set; } = AIServiceType.AzureOpenAI; /// - /// Azure OpenAI deployment name or OpenAI model name. + /// Models/deployment names to use. /// - [Required, NotEmptyOrWhitespace] - public string DeploymentOrModelId { get; set; } = string.Empty; + [Required] + public ModelTypes Models { get; set; } = new ModelTypes(); /// /// (Azure OpenAI only) Azure OpenAI endpoint. /// - [RequiredOnPropertyValue(nameof(AIService), AIServiceType.AzureOpenAI, notEmptyOrWhitespace: true)] + [RequiredOnPropertyValue(nameof(Type), AIServiceType.AzureOpenAI, notEmptyOrWhitespace: true)] public string Endpoint { get; set; } = string.Empty; /// @@ -47,6 +74,4 @@ public enum AIServiceType /// [Required, NotEmptyOrWhitespace] public string Key { get; set; } = string.Empty; - - // TODO support OpenAI's orgID } diff --git a/samples/apps/copilot-chat-app/webapi/Options/AuthorizationOptions.cs b/samples/apps/copilot-chat-app/webapi/Options/AuthorizationOptions.cs index 3bdbf7e3ff59..d8ee6f70271e 100644 --- a/samples/apps/copilot-chat-app/webapi/Options/AuthorizationOptions.cs +++ b/samples/apps/copilot-chat-app/webapi/Options/AuthorizationOptions.cs @@ -21,12 +21,13 @@ public enum AuthorizationType /// /// Type of authorization. /// + [Required] public AuthorizationType Type { get; set; } = AuthorizationType.None; /// /// When is , this is the API key to use. /// - [Required, NotEmptyOrWhitespace] + [RequiredOnPropertyValue(nameof(Type), AuthorizationType.ApiKey, notEmptyOrWhitespace: true)] public string ApiKey { get; set; } = string.Empty; /// diff --git a/samples/apps/copilot-chat-app/webapi/Options/ServiceOptions.cs b/samples/apps/copilot-chat-app/webapi/Options/ServiceOptions.cs index 1a817639ca0f..58c05547297b 100644 --- a/samples/apps/copilot-chat-app/webapi/Options/ServiceOptions.cs +++ b/samples/apps/copilot-chat-app/webapi/Options/ServiceOptions.cs @@ -21,5 +21,5 @@ public class ServiceOptions /// /// Local directory in which to load semantic skills. /// - public string SemanticSkillsDirectory { get; set; } = string.Empty; + public string? SemanticSkillsDirectory { get; set; } } diff --git a/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs b/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs index b2c8984ca3ad..1d4b6b94171c 100644 --- a/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs +++ b/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs @@ -53,10 +53,8 @@ internal static IServiceCollection AddSemanticKernelServices(this IServiceCollec // AI backends services.AddScoped(serviceProvider => new KernelConfig() - .AddCompletionBackend(serviceProvider.GetRequiredService>() - .Get(AIServiceOptions.CompletionPropertyName)) - .AddEmbeddingBackend(serviceProvider.GetRequiredService>() - .Get(AIServiceOptions.EmbeddingPropertyName))); + .AddCompletionBackend(serviceProvider.GetRequiredService>().Value) + .AddEmbeddingBackend(serviceProvider.GetRequiredService>().Value)); // Register skills services.AddScoped(sp => RegisterSkills); @@ -107,7 +105,7 @@ private static void AddSemanticTextMemory(this IServiceCollection services) services.AddSingleton(); services.AddScoped(sp => new SemanticTextMemory( sp.GetRequiredService(), - sp.GetRequiredService>().Get(AIServiceOptions.EmbeddingPropertyName) + sp.GetRequiredService>().Value .ToTextEmbeddingsService(logger: sp.GetRequiredService>()))); break; @@ -121,7 +119,7 @@ private static void AddSemanticTextMemory(this IServiceCollection services) config.Qdrant.Host, config.Qdrant.Port, config.Qdrant.VectorSize, sp.GetRequiredService>())); services.AddScoped(sp => new SemanticTextMemory( sp.GetRequiredService(), - sp.GetRequiredService>().Get(AIServiceOptions.EmbeddingPropertyName) + sp.GetRequiredService>().Value .ToTextEmbeddingsService(logger: sp.GetRequiredService>()))); break; @@ -142,85 +140,53 @@ private static void AddSemanticTextMemory(this IServiceCollection services) /// /// Add the completion backend to the kernel config /// - private static KernelConfig AddCompletionBackend(this KernelConfig kernelConfig, AIServiceOptions aiServiceOptions) + private static KernelConfig AddCompletionBackend(this KernelConfig kernelConfig, AIServiceOptions options) { - switch (aiServiceOptions.AIService) + return options.Type switch { - case AIServiceOptions.AIServiceType.AzureOpenAI: - kernelConfig.AddAzureChatCompletionService( - deploymentName: aiServiceOptions.DeploymentOrModelId, - endpoint: aiServiceOptions.Endpoint, - apiKey: aiServiceOptions.Key); - break; - - case AIServiceOptions.AIServiceType.OpenAI: - kernelConfig.AddOpenAIChatCompletionService( - modelId: aiServiceOptions.DeploymentOrModelId, - apiKey: aiServiceOptions.Key); - break; - - default: - throw new ArgumentException($"Invalid {nameof(aiServiceOptions.AIService)} value in '{AIServiceOptions.CompletionPropertyName}' settings."); - } - - return kernelConfig; + AIServiceOptions.AIServiceType.AzureOpenAI + => kernelConfig.AddAzureChatCompletionService(options.Models.Completion, options.Endpoint, options.Key), + AIServiceOptions.AIServiceType.OpenAI + => kernelConfig.AddOpenAIChatCompletionService(options.Models.Completion, options.Key), + _ + => throw new ArgumentException($"Invalid {nameof(options.Type)} value in '{AIServiceOptions.PropertyName}' settings."), + }; } /// /// Add the embedding backend to the kernel config /// - private static KernelConfig AddEmbeddingBackend(this KernelConfig kernelConfig, AIServiceOptions aiServiceOptions) + private static KernelConfig AddEmbeddingBackend(this KernelConfig kernelConfig, AIServiceOptions options) { - switch (aiServiceOptions.AIService) + return options.Type switch { - case AIServiceOptions.AIServiceType.AzureOpenAI: - kernelConfig.AddAzureTextEmbeddingGenerationService( - deploymentName: aiServiceOptions.DeploymentOrModelId, - endpoint: aiServiceOptions.Endpoint, - apiKey: aiServiceOptions.Key, - serviceId: aiServiceOptions.Label); - break; - - case AIServiceOptions.AIServiceType.OpenAI: - kernelConfig.AddOpenAITextEmbeddingGenerationService( - modelId: aiServiceOptions.DeploymentOrModelId, - apiKey: aiServiceOptions.Key, - serviceId: aiServiceOptions.Label); - break; - - default: - throw new ArgumentException($"Invalid {nameof(aiServiceOptions.AIService)} value in '{AIServiceOptions.EmbeddingPropertyName}' settings."); - } - - return kernelConfig; + AIServiceOptions.AIServiceType.AzureOpenAI + => kernelConfig.AddAzureTextEmbeddingGenerationService(options.Models.Embedding, options.Endpoint, options.Key), + AIServiceOptions.AIServiceType.OpenAI + => kernelConfig.AddOpenAITextEmbeddingGenerationService(options.Models.Embedding, options.Key), + _ + => throw new ArgumentException($"Invalid {nameof(options.Type)} value in '{AIServiceOptions.PropertyName}' settings."), + }; } /// /// Construct IEmbeddingGeneration from /// - /// The service configuration + /// The service configuration /// Custom for HTTP requests. /// Application logger - private static IEmbeddingGeneration ToTextEmbeddingsService(this AIServiceOptions serviceConfig, + private static IEmbeddingGeneration ToTextEmbeddingsService(this AIServiceOptions options, HttpClient? httpClient = null, ILogger? logger = null) { - return serviceConfig.AIService switch + return options.Type switch { - AIServiceOptions.AIServiceType.AzureOpenAI => new AzureTextEmbeddingGeneration( - serviceConfig.DeploymentOrModelId, - serviceConfig.Endpoint, - serviceConfig.Key, - httpClient: httpClient, - logger: logger), - - AIServiceOptions.AIServiceType.OpenAI => new OpenAITextEmbeddingGeneration( - serviceConfig.DeploymentOrModelId, - serviceConfig.Key, - httpClient: httpClient, - logger: logger), - - _ => throw new ArgumentException("Invalid AIService value in embeddings backend settings"), + AIServiceOptions.AIServiceType.AzureOpenAI + => new AzureTextEmbeddingGeneration(options.Models.Embedding, options.Endpoint, options.Key, httpClient: httpClient, logger: logger), + AIServiceOptions.AIServiceType.OpenAI + => new OpenAITextEmbeddingGeneration(options.Models.Embedding, options.Key, httpClient: httpClient, logger: logger), + _ + => throw new ArgumentException("Invalid AIService value in embeddings backend settings"), }; } } diff --git a/samples/apps/copilot-chat-app/webapi/ServiceExtensions.cs b/samples/apps/copilot-chat-app/webapi/ServiceExtensions.cs index bd3792c78093..6bfbb1b53717 100644 --- a/samples/apps/copilot-chat-app/webapi/ServiceExtensions.cs +++ b/samples/apps/copilot-chat-app/webapi/ServiceExtensions.cs @@ -24,29 +24,30 @@ internal static IServiceCollection AddOptions(this IServiceCollection services, // General configuration services.AddOptions() .Bind(configuration.GetSection(ServiceOptions.PropertyName)) + .ValidateDataAnnotations() .ValidateOnStart() .PostConfigure(TrimStringProperties); - // AI service configurations for Semantic Kernel - services.AddOptions(AIServiceOptions.CompletionPropertyName) - .Bind(configuration.GetSection(AIServiceOptions.CompletionPropertyName)) + // Default AI service configurations for Semantic Kernel + services.AddOptions() + .Bind(configuration.GetSection(AIServiceOptions.PropertyName)) + .ValidateDataAnnotations() .ValidateOnStart() .PostConfigure(TrimStringProperties); - services.AddOptions(AIServiceOptions.EmbeddingPropertyName) - .Bind(configuration.GetSection(AIServiceOptions.EmbeddingPropertyName)) - .ValidateOnStart() - .PostConfigure(TrimStringProperties); + var foo = services.BuildServiceProvider().GetService>(); // Authorization configuration services.AddOptions() .Bind(configuration.GetSection(AuthorizationOptions.PropertyName)) .ValidateOnStart() + .ValidateDataAnnotations() .PostConfigure(TrimStringProperties); // Memory store configuration services.AddOptions() .Bind(configuration.GetSection(MemoriesStoreOptions.PropertyName)) + .ValidateDataAnnotations() .ValidateOnStart() .PostConfigure(TrimStringProperties); diff --git a/samples/apps/copilot-chat-app/webapi/appsettings.json b/samples/apps/copilot-chat-app/webapi/appsettings.json index 8642defc3538..76efa9a7151c 100644 --- a/samples/apps/copilot-chat-app/webapi/appsettings.json +++ b/samples/apps/copilot-chat-app/webapi/appsettings.json @@ -5,7 +5,7 @@ // - Update the "Completion" and "Embedding" sections below to use your AI services. // // # Secrets -// Consider populating secrets, such as "Key" and "ConnectionString" properties, from dotnet's user-secrets when running locally. +// Consider populating secrets, such as "Key" and "ConnectionString" properties, using dotnet's user-secrets command when running locally. // https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-7.0&tabs=windows#secret-manager // Values in user secrets and (optionally) Key Vault take precedence over those in this file. // @@ -16,59 +16,52 @@ // - Optionally set KeyVaultUri to the URI of the Key Vault for secrets (e.g., "https://contoso.vault.azure.net/"). // "Service": { - "SemanticSkillsDirectory": "", - "KeyVaultUri": "" + // "SemanticSkillsDirectory": "", + // "KeyVaultUri": "" }, // - // Completions are used for generating AI responses from the user's input. + // Default AI service configuration for generating AI responses and embeddings from the user's input. // https://platform.openai.com/docs/guides/chat // To use Azure OpenAI as the AI completion service: - // - Set "AIService" to "AzureOpenAI" - // - Set "DeploymentOrModelId" to the name of the deployment to use (e.g., "gpt-35-turbo", "gpt-4", etc.) - // - Set "Endpoint" to the endpoint of your Azure OpenAI instance (e.g., "https://contoso.openai.azure.com") - // - Set "Key" using dotnet's user secrets (see above) (i.e. dotnet user-secrets set "Completion:Key" "MY_AZURE_OPENAI_KEY") + // - Set "Type" to "AzureOpenAI" + // - Set "Endpoint" to the endpoint of your Azure OpenAI instance (e.g., "https://contoso.openai.azure.com") + // - Set "Key" using dotnet's user secrets (see above) + // (i.e. dotnet user-secrets set "AIService:Key" "MY_AZURE_OPENAI_KEY") // // To use OpenAI as the AI completion service: - // - Set "AIService" to "OpenAI" - // - Set "DeploymentOrModelId" to the name of the deployment to use (e.g., "gpt-3.5-turbo", "gpt-4", etc.) - // - Set "Key" using dotnet's user secrets (see above) (i.e. dotnet user-secrets set "Completion:Key" "MY_OPENAI_KEY") - // - "Completion": { - "Label": "Completion", - "AIService": "AzureOpenAI", - "DeploymentOrModelId": "gpt-35-turbo", - "Endpoint": "" // ignored when AIService is "OpenAI" + // - Set "Type" to "OpenAI" + // - Set "Key" using dotnet's user secrets (see above) + // (i.e. dotnet user-secrets set "AIService:Key" "MY_OPENAI_KEY") + // + // - Set Completion and Planner models to a chat completion model (e.g., gpt-35-turbo, gpt-4). + // - Set the Embedding model to an embedding model (e.g., "text-embedding-ada-002"). + + "AIService": { + "Type": "AzureOpenAI", + "Endpoint": "", // ignored when AIService is "OpenAI" // "Key": "" + "Models": { + "Completion": "gpt-35-turbo", // For OpenAI, change to 'gpt-3.5-turbo' (with a period). + "Embedding": "text-embedding-ada-002", + "Planner": "gpt-35-turbo" // For OpenAI, change to 'gpt-3.5-turbo' (with a period). + } }, // - // Embeddings are used for semantically encoding memories. - // This section is ignored when MemoriesStore:Type is set to Azure Cognitive Search. - // https://platform.openai.com/docs/guides/embeddings - // To use Azure OpenAI as the AI embedding service: - // - Set "AIService" to "AzureOpenAI" - // - Set "DeploymentOrModelId" to the name of the deployment to use (e.g., "text-embedding-ada-002") - // - Set "Endpoint" to the endpoint of your Azure OpenAI instance (e.g., "https://contoso.openai.azure.com") - // - Set "Key" using dotnet's user secrets (see above) (i.e. dotnet user-secrets set "Embedding:Key" "MY_AZURE_OPENAI_KEY") - // - // To use OpenAI as the AI embedding service: - // - Set "AIService" to "OpenAI" - // - Set "DeploymentOrModelId" to the name of the deployment to use (e.g., "text-embedding-ada-002" ) - // - Set "Key" using dotnet's user secrets (see above) (i.e. dotnet user-secrets set "Embedding:Key" "MY_OPENAI_KEY") - // - "Embedding": { - "Label": "Embeddings", - "AIService": "AzureOpenAI", - "DeploymentOrModelId": "text-embedding-ada-002", - "Endpoint": "" // ignored when AIService is "OpenAI" - // "Key": "" + // Planner can determine which skill functions, if any, need to be used to fulfill a user's request. + // https://learn.microsoft.com/en-us/semantic-kernel/concepts-sk/planner + // - Set Enabled to false to disable the planner which will save a round-trip to the AI service but disables plug-in support. + // + "Planner": { + "Enabled": true }, // // Optional Azure Speech service configuration for providing Azure Speech access tokens. // - Set the Region to the region of your Azure Speech resource (e.g., "westus"). - // - Set the Key using dotnet's user secrets (see above) (i.e. dotnet user-secrets set "AzureSpeech:Key" "MY_AZURE_SPEECH_KEY") + // - Set the Key using dotnet's user secrets (see above) + // (i.e. dotnet user-secrets set "AzureSpeech:Key" "MY_AZURE_SPEECH_KEY") // "AzureSpeech": { "Region": "" @@ -77,8 +70,9 @@ // // Authorization configuration to gate access to the service. - // - Supported Types are "None", "ApiKey", or "AzureAd". - // - Set ApiKey using dotnet's user secrets (see above) (i.e. dotnet user-secret set "Authorization:ApiKey" "MY_API_KEY") + // - Supported Types are "None", "ApiKey", or "AzureAd". + // - Set ApiKey using dotnet's user secrets (see above) + // (i.e. dotnet user-secret set "Authorization:ApiKey" "MY_API_KEY") // "Authorization": { "Type": "None", @@ -94,6 +88,8 @@ // // Chat stores are used for storing chat sessions and messages. // - Supported Types are "volatile", "filesystem", or "cosmos". + // - Set "ChatStore:Cosmos:ConnectionString" using dotnet's user secrets (see above) + // (i.e. dotnet user-secrets set "ChatStore:Cosmos:ConnectionString" "MY_COSMOS_CONNSTRING") // "ChatStore": { "Type": "volatile", @@ -114,6 +110,8 @@ // - When using Qdrant or Azure Cognitive Search, see ./README.md for deployment instructions. // - The "Semantic Search" feature must be enabled on Azure Cognitive Search. // - The Embedding configuration above will not be used when Azure Cognitive Search is selected. + // - Set "MemoriesStore:AzureCognitiveSearch:Key" using dotnet's user secrets (see above) + // (i.e. dotnet user-secrets set "MemoriesStore:AzureCognitiveSearch:Key" "MY_AZCOGSRCH_KEY") // "MemoriesStore": { "Type": "volatile", @@ -124,7 +122,7 @@ }, "AzureCognitiveSearch": { "Endpoint": "" - // "Key": "" // dotnet user-secrets set "MemoriesStore:AzureCognitiveSearch:Key" "MY_ACS_KEY" + // "Key": "" } }, @@ -145,32 +143,6 @@ "FileSizeLimit": 4000000 }, - // - // Planner can determine which skill functions, if any, need to be used to fulfill a user's request. - // https://learn.microsoft.com/en-us/semantic-kernel/concepts-sk/planner - // - Set Enabled to false to disable the planner which can save a round-trip to the AI service but will disable plug-in support. - // - // To use Azure OpenAI with the planner: - // - Set "AIService" to "AzureOpenAI" - // - Set "DeploymentOrModelId" to the name of the deployment to use (e.g., "gpt-35-turbo") - // - Set "Endpoint" to the endpoint of your Azure OpenAI instance (e.g., "https://contoso.openai.azure.com") - // - Set "Key" using dotnet's user secrets (see above) (i.e. dotnet user-secrets set "Planner:AIService:Key" "MY_AZURE_OPENAI_KEY") - // - // To use OpenAI with the planner: - // - Set "AIService" to "OpenAI" - // - Set "DeploymentOrModelId" to the name of the deployment to use (e.g., "gpt-3.5-turbo" ) - // - Set "Key" using dotnet's user secrets (see above) (i.e. dotnet user-secrets set "Planner:AIService:Key" "MY_OPENAI_KEY") - // - "Planner": { - "Enabled": true, - "AIService": { - "AIService": "AzureOpenAI", - "DeploymentOrModelId": "gpt-35-turbo", - "Endpoint": "" // ignored when AIService is "OpenAI" - // "Key": "" - } - }, - // // ChatSkill prompts are used to generate responses to user messages. // - CompletionTokenLimit is the token limit of the chat model, see https://platform.openai.com/docs/models/overview @@ -201,30 +173,21 @@ "LongTermMemoryExtraction": "Extract information that is encoded and consolidated from other memory types, such as working memory or sensory memory. It should be useful for maintaining and recalling one's personal identity, history, and knowledge over time." }, - // // Filter for hostnames app can bind to - // "AllowedHosts": "*", - // // CORS - // "AllowedOrigins": [ "http://localhost:3000" ], - - // // The schema information for a serialized bot that is supported by this applocation. - // "BotSchema": { "Name": "CopilotChat", "Version": 1 }, - // // Server endpoints - // "Kestrel": { "Endpoints": { "Https": { @@ -233,9 +196,7 @@ } }, - // // Logging configuration - // "Logging": { "LogLevel": { "Default": "Warning", @@ -248,7 +209,9 @@ // // Application Insights configuration - // + // - Set "ApplicationInsights:ConnectionString" using dotnet's user secrets (see above) + // (i.e. dotnet user-secrets set "ApplicationInsights:ConnectionString" "MY_APPINS_CONNSTRING") + // "ApplicationInsights": { "ConnectionString": "" } diff --git a/samples/apps/copilot-chat-app/webapi/config.ps1 b/samples/apps/copilot-chat-app/webapi/config.ps1 new file mode 100644 index 000000000000..9af2284812ec --- /dev/null +++ b/samples/apps/copilot-chat-app/webapi/config.ps1 @@ -0,0 +1,76 @@ +<# +.SYNOPSIS +Configure user secrets and appsettings.Development.json for the Copilot Chat AI service. + +.PARAMETER OpenAI +Switch to configure for OpenAI. + +.PARAMETER AzureOpenAI +Switch to configure for Azure OpenAI. + +.PARAMETER Endpoint +Set when using Azure OpenAI. + +.PARAMETER ApiKey +The API key for the AI service. + +.PARAMETER CompletionModel +The chat completion model to use (e.g., gpt-3.5-turbo or gpt-4). + +.PARAMETER EmbeddingModel +The embedding model to use (e.g., text-embedding-ada-002). + +.PARAMETER PlannerModel +The chat completion model to use for planning (e.g., gpt-3.5-turbo or gpt-4). +#> + +param( + [Parameter(ParameterSetName='OpenAI',Mandatory=$false)] + [switch]$OpenAI, + + [Parameter(ParameterSetName='AzureOpenAI',Mandatory=$false)] + [switch]$AzureOpenAI, + + [Parameter(ParameterSetName='AzureOpenAI',Mandatory=$true)] + [string]$Endpoint, + + [Parameter(Mandatory=$true)] + [string]$ApiKey, + + [Parameter(Mandatory=$false)] + [string]$CompletionModel = "gpt-3.5-turbo", + + [Parameter(Mandatory=$false)] + [string]$EmbeddingModel = "text-embedding-ada-002", + + [Parameter(Mandatory=$false)] + [string]$PlannerModel = "gpt-3.5-turbo" + +) + +if ($OpenAI) +{ + $appsettingsOverrides = @{ AIService = @{ Type = "OpenAI"; Models = @{ Completion = $CompletionModel; Embedding = $EmbeddingModel; Planner = $PlannerModel } } } +} +elseif ($AzureOpenAI) +{ + # Azure OpenAI has a different model name for gpt-3.5-turbo (no decimal). + $CompletionModel = $CompletionModel.Replace("gpt-3.5-turbo", "gpt-35-turbo") + $PlannerModel = $PlannerModel.Replace("gpt-3.5-turbo", "gpt-35-turbo") + $appsettingsOverrides = @{ AIService = @{ Type = "AzureOpenAI"; Endpoint = $Endpoint; Models = @{ Completion = $CompletionModel; Embedding = $EmbeddingModel; Planner = $PlannerModel } } } +} +else { + Write-Error "Please specify either -OpenAI or -AzureOpenAI" + exit(1) +} + +$appsettingsOverridesFilePath = Join-Path "$PSScriptRoot" 'appsettings.Development.json' + +Write-Host "Setting 'AIService:Key' user secret for $($appsettingsOverrides.AIService.Type)..." +dotnet user-secrets set AIService:Key $ApiKey + +Write-Host "Setting up appsettings.Development.json for $($appsettingsOverrides.AIService.Type)..." +Write-Host "($appsettingsOverridesFilePath)" +ConvertTo-Json $appsettingsOverrides | Out-File -Encoding utf8 $appsettingsOverridesFilePath +Get-Content $appsettingsOverridesFilePath +Write-Host "Done! Please rebuild (i.e., dotnet build) and run (i.e., dotnet run) the application."