├── .gitignore ├── LICENSE ├── README.md ├── csharp ├── .gitignore ├── .vscode │ ├── extensions.json │ └── launch.json ├── CosmosEmbeddingGenerator.cs ├── CosmosEmbeddingGenerator.csproj ├── CosmosEmbeddingGenerator.sln ├── Program.cs ├── Properties │ └── launchSettings.json ├── azure.yaml ├── host.json ├── infra │ ├── abbreviations.json │ ├── app │ │ ├── ai.bicep │ │ ├── database.bicep │ │ ├── functions.bicep │ │ ├── identity.bicep │ │ ├── security.bicep │ │ └── web.bicep │ ├── core │ │ ├── ai │ │ │ └── cognitive-services │ │ │ │ ├── account.bicep │ │ │ │ └── deployment.bicep │ │ ├── database │ │ │ └── cosmos-db │ │ │ │ ├── account.bicep │ │ │ │ └── nosql │ │ │ │ ├── account.bicep │ │ │ │ ├── container.bicep │ │ │ │ ├── database.bicep │ │ │ │ └── role │ │ │ │ ├── assignment.bicep │ │ │ │ └── definition.bicep │ │ ├── host │ │ │ ├── app-service │ │ │ │ ├── config.bicep │ │ │ │ ├── plan.bicep │ │ │ │ └── site.bicep │ │ │ └── functions │ │ │ │ ├── appserviceplan.bicep │ │ │ │ └── flexconsumption.bicep │ │ ├── monitor │ │ │ ├── appinsights-access.bicep │ │ │ ├── applicationinsights.bicep │ │ │ ├── loganalytics.bicep │ │ │ └── monitoring.bicep │ │ ├── security │ │ │ ├── identity │ │ │ │ └── user-assigned.bicep │ │ │ └── role │ │ │ │ ├── assignment.bicep │ │ │ │ ├── definition.bicep │ │ │ │ └── storage-Access.bicep │ │ └── storage │ │ │ └── storage-account.bicep │ ├── main.bicep │ ├── main.parameters.json │ ├── main.test.bicep │ └── scripts │ │ ├── createlocalsettings.ps1 │ │ └── createlocalsettings.sh └── sample.settings.json ├── media └── sample-embeddings.png └── python ├── .funcignore ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── azure.yaml ├── function_app.py ├── host.json ├── infra ├── abbreviations.json ├── app │ ├── ai.bicep │ ├── database.bicep │ ├── functions.bicep │ ├── identity.bicep │ ├── security.bicep │ └── web.bicep ├── core │ ├── ai │ │ └── cognitive-services │ │ │ ├── account.bicep │ │ │ └── deployment.bicep │ ├── database │ │ └── cosmos-db │ │ │ ├── account.bicep │ │ │ └── nosql │ │ │ ├── account.bicep │ │ │ ├── container.bicep │ │ │ ├── database.bicep │ │ │ └── role │ │ │ ├── assignment.bicep │ │ │ └── definition.bicep │ ├── host │ │ ├── app-service │ │ │ ├── config.bicep │ │ │ ├── plan.bicep │ │ │ └── site.bicep │ │ └── functions │ │ │ ├── appserviceplan.bicep │ │ │ └── flexconsumption.bicep │ ├── monitor │ │ ├── appinsights-access.bicep │ │ ├── applicationinsights.bicep │ │ ├── loganalytics.bicep │ │ └── monitoring.bicep │ ├── security │ │ ├── identity │ │ │ └── user-assigned.bicep │ │ └── role │ │ │ ├── assignment.bicep │ │ │ ├── definition.bicep │ │ │ └── storage-Access.bicep │ └── storage │ │ └── storage-account.bicep ├── main.bicep ├── main.parameters.json ├── main.test.bicep └── scripts │ ├── createlocalsettings.ps1 │ └── createlocalsettings.sh ├── requirements.txt └── sample.settings.json /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.tlog 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml 399 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Azure Cosmos DB 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 | # Azure Cosmos DB Embeddings Generator 2 | 3 | This sample shows how to use an Azure Cosmos DB Trigger and Output Binding in Azure Functions to automatically generate Azure OpenAI embeddings on new or updated data. 4 | 5 | ## This sample demonstrates the following concepts 6 | - Azure Cosmos DB Trigger and Output Bindings for Azure Functions in both C# and Python 7 | - Embedding generation using Azure OpenAI SDK with *text-embedding-3-small* embedding model 8 | - Preventing endless loops in Functions Triggers from in-place document updates by comparing hash values generated on the document. 9 | - Keyless deployment of Azure Functions, Azure Cosmos DB, Azure OpenAI with managed identities and RBAC 10 | 11 | ## Getting Started: 12 | 13 | ### Deployment 14 | 15 | 1. Open a terminal and navigate to where you would like to clone this solution. 16 | 17 | 1. Navigate to either the `csharp` or `python` directory in this solution. 18 | 19 | 1. Run the following command to ensure correct permissions to write a `local.settings.json` file locally. 20 | 21 | - If using Windows, open a second Terminal as Administrator and run the following PowerShell command 22 | ```Powershell 23 | set-executionpolicy remotesigned 24 | ``` 25 | 26 | - If using Mac or Linux, open Bash and run the following command. This likely requires sudo. 27 | ```bash 28 | chmod +x ./infra/scripts/*.sh 29 | ``` 30 | 31 | Note: This sample deploys using azd. To enable local debugging, a `local.settings.json` file is created in the project directory with the values for the deployed sample in Azure. 32 | 33 | 1. From within the `csharp` or `python` directory, deploy the sample to Azure. 34 | 35 | ```bash 36 | azd up 37 | ``` 38 | 39 | ### Post Deployment 40 | 41 | 1. Check for a `local.settings.json` file in the directory you deployed from. If it does not exist, create a new one using the `sample.settings.json` in the same directory. 42 | 1. Replace the placeholder text for the Cosmos DB and OpenAI Account names below. Use the correct value for `FUNCTIONS_WORKER_RUNTIME` 43 | 44 | ```json 45 | { 46 | "IsEncrypted": false, 47 | "Values": { 48 | "AzureWebJobsStorage": "UseDevelopmentStorage=true", 49 | "FUNCTIONS_WORKER_RUNTIME": "python", //"dotnet-isolated" 50 | "COSMOS_CONNECTION__accountEndpoint": "https://{my-cosmos-account}.documents.azure.com:443/", 51 | "OPENAI_ENDPOINT": "https://{my-open-ai-account}.openai.azure.com/", 52 | "COSMOS_DATABASE_NAME": "embeddings-db", 53 | "COSMOS_CONTAINER_NAME": "customer", 54 | "COSMOS_VECTOR_PROPERTY": "vectors", 55 | "COSMOS_HASH_PROPERTY": "hash", 56 | "COSMOS_PROPERTY_TO_EMBED": "customerNotes", 57 | "OPENAI_DEPLOYMENT_NAME": "text-3-small", 58 | "OPENAI_DIMENSIONS": "1536" 59 | } 60 | } 61 | ``` 62 | 63 | ## Quick-Start: 64 | 65 | 1. Open a browser to Azure Portal. Locate the resource group for the deployed sample. 66 | 1. Open the deployed Azure Cosmos DB account and navigate to the `customer` container in Cosmos Data Explorer 67 | 1. Create a new document with the same schema as the one below and save. 68 | 69 | Example document: 70 | ```json 71 | { 72 | "id": "00001", 73 | "customerId": "10001", 74 | "customerNotes": "lorum ipsum." 75 | } 76 | ``` 77 | 1. After clicking Save, the document should reappear with a number of system properties. 78 | 1. Press F5 or refresh the browser window and it should then reappear with a hash property and vectors array stored in the document as shown below. 79 | 80 | Note: The Functions start-up may miss the first trigger execution. If the embeddings do not appear as below. Make a small change to the same document and save to re-execute the trigger. 81 | 82 | ![Sample Embeddings Document](./media/sample-embeddings.png) 83 | 84 | ## Run locally: 85 | 86 | Run the sample locally in a debugger for either the Python or CSharp version that was deployed. 87 | 88 | ### Python 89 | 90 | #### Pre-reqs 91 | 1. [Python 3.11](https://www.python.org/) 92 | 1. [Azure Functions Core Tools 4.0.6610 or higher](https://learn.microsoft.com/azure/azure-functions/functions-run-local?tabs=v4%2Cmacos%2Ccsharp%2Cportal%2Cbash#install-the-azure-functions-core-tools) 93 | 1. [Azurite](https://github.com/Azure/Azurite) 94 | 95 | #### Setup 96 | ##### MacOS/Linux/WSL 97 | ```bash 98 | python -m venv venv 99 | source venv/Scripts/activate 100 | pip install -r requirements.txt 101 | ``` 102 | 103 | ##### Windows 104 | ```Powershell 105 | python -m venv venv 106 | source venv\Scripts\activate 107 | pip install -r requirements.txt 108 | ``` 109 | 110 | #### Run the Sample: 111 | 1. Ensure you have the Python extension installed. If not, install it from the Extensions view (`Ctrl+Shift+X`). 112 | 1. Open the Command Palette (`Ctrl+Shift+P`) and type `Python: Select Interpreter`. Choose the appropriate Python interpreter for your project. 113 | 1. Open the `function_app.py` Python file you want to debug. 114 | 1. Open the Run and Debug view by clicking the Run icon on the sidebar or pressing `Ctrl+Shift+D`. 115 | 1. Click on `create a launch.json file` link to create a new launch configuration. 116 | 1. Select `Python` from the list of environments. 117 | 1. Press `F5` to start debugging. The Azure Function will start, and execution will pause at any breakpoints you've set. 118 | 119 | 120 | ### CSharp 121 | 122 | #### Pre-reqs 123 | 1. [.NET 8](https://dot.net/) 124 | 1. [Azure Functions Core Tools 4.0.6610 or higher](https://learn.microsoft.com/azure/azure-functions/functions-run-local?tabs=v4%2Cmacos%2Ccsharp%2Cportal%2Cbash#install-the-azure-functions-core-tools) 125 | 1. [Azurite](https://github.com/Azure/Azurite) 126 | 127 | #### Run the Sample: 128 | 1. Open the csharp project folder in VS Code. 129 | 1. Ensure you have the C# extension installed. If not, install it from the Extensions view (`Ctrl+Shift+X`). 130 | 1. Open the `CosmosEmbeddingGenerator.cs` file to debug. 131 | 1. Set breakpoints by clicking in the gutter to the left of the line numbers. 132 | 1. Open the Run and Debug view by clicking the Run icon on the sidebar or pressing `Ctrl+Shift+D`. 133 | 1. Click on `create a launch.json file` link to create a new launch configuration. 134 | 1. Press `F5` to start debugging. The Azure Function will start, and execution will pause at any breakpoints you've set. -------------------------------------------------------------------------------- /csharp/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Azure Functions localsettings file 5 | local.settings.json 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # DNX 47 | project.lock.json 48 | project.fragment.lock.json 49 | artifacts/ 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # NCrunch 117 | _NCrunch_* 118 | .*crunch*.local.xml 119 | nCrunchTemp_* 120 | 121 | # MightyMoose 122 | *.mm.* 123 | AutoTest.Net/ 124 | 125 | # Web workbench (sass) 126 | .sass-cache/ 127 | 128 | # Installshield output folder 129 | [Ee]xpress/ 130 | 131 | # DocProject is a documentation generator add-in 132 | DocProject/buildhelp/ 133 | DocProject/Help/*.HxT 134 | DocProject/Help/*.HxC 135 | DocProject/Help/*.hhc 136 | DocProject/Help/*.hhk 137 | DocProject/Help/*.hhp 138 | DocProject/Help/Html2 139 | DocProject/Help/html 140 | 141 | # Click-Once directory 142 | publish/ 143 | 144 | # Publish Web Output 145 | *.[Pp]ublish.xml 146 | *.azurePubxml 147 | # TODO: Comment the next line if you want to checkin your web deploy settings 148 | # but database connection strings (with potential passwords) will be unencrypted 149 | #*.pubxml 150 | *.publishproj 151 | 152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 153 | # checkin your Azure Web App publish settings, but sensitive information contained 154 | # in these scripts will be unencrypted 155 | PublishScripts/ 156 | 157 | # NuGet Packages 158 | *.nupkg 159 | # The packages folder can be ignored because of Package Restore 160 | **/packages/* 161 | # except build/, which is used as an MSBuild target. 162 | !**/packages/build/ 163 | # Uncomment if necessary however generally it will be regenerated when needed 164 | #!**/packages/repositories.config 165 | # NuGet v3's project.json files produces more ignoreable files 166 | *.nuget.props 167 | *.nuget.targets 168 | 169 | # Microsoft Azure Build Output 170 | csx/ 171 | *.build.csdef 172 | 173 | # Microsoft Azure Emulator 174 | ecf/ 175 | rcf/ 176 | 177 | # Windows Store app package directories and files 178 | AppPackages/ 179 | BundleArtifacts/ 180 | Package.StoreAssociation.xml 181 | _pkginfo.txt 182 | 183 | # Visual Studio cache files 184 | # files ending in .cache can be ignored 185 | *.[Cc]ache 186 | # but keep track of directories ending in .cache 187 | !*.[Cc]ache/ 188 | 189 | # Others 190 | ClientBin/ 191 | ~$* 192 | *~ 193 | *.dbmdl 194 | *.dbproj.schemaview 195 | *.jfm 196 | *.pfx 197 | *.publishsettings 198 | node_modules/ 199 | orleans.codegen.cs 200 | 201 | # Since there are multiple workflows, uncomment next line to ignore bower_components 202 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 203 | #bower_components/ 204 | 205 | # RIA/Silverlight projects 206 | Generated_Code/ 207 | 208 | # Backup & report files from converting an old project file 209 | # to a newer Visual Studio version. Backup files are not needed, 210 | # because we have git ;-) 211 | _UpgradeReport_Files/ 212 | Backup*/ 213 | UpgradeLog*.XML 214 | UpgradeLog*.htm 215 | 216 | # SQL Server files 217 | *.mdf 218 | *.ldf 219 | 220 | # Business Intelligence projects 221 | *.rdl.data 222 | *.bim.layout 223 | *.bim_*.settings 224 | 225 | # Microsoft Fakes 226 | FakesAssemblies/ 227 | 228 | # GhostDoc plugin setting file 229 | *.GhostDoc.xml 230 | 231 | # Node.js Tools for Visual Studio 232 | .ntvs_analysis.dat 233 | 234 | # Visual Studio 6 build log 235 | *.plg 236 | 237 | # Visual Studio 6 workspace options file 238 | *.opt 239 | 240 | # Visual Studio LightSwitch build output 241 | **/*.HTMLClient/GeneratedArtifacts 242 | **/*.DesktopClient/GeneratedArtifacts 243 | **/*.DesktopClient/ModelManifest.xml 244 | **/*.Server/GeneratedArtifacts 245 | **/*.Server/ModelManifest.xml 246 | _Pvt_Extensions 247 | 248 | # Paket dependency manager 249 | .paket/paket.exe 250 | paket-files/ 251 | 252 | # FAKE - F# Make 253 | .fake/ 254 | 255 | # JetBrains Rider 256 | .idea/ 257 | *.sln.iml 258 | 259 | # CodeRush 260 | .cr/ 261 | 262 | # Python Tools for Visual Studio (PTVS) 263 | __pycache__/ 264 | *.pyc -------------------------------------------------------------------------------- /csharp/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-azuretools.vscode-azurefunctions" 4 | ] 5 | } -------------------------------------------------------------------------------- /csharp/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Attach to .NET Functions", 6 | "type": "coreclr", 7 | "request": "attach", 8 | "processId": "${command:pickProcess}", 9 | "preLaunchTask": "host start" 10 | }, 11 | { 12 | "name": "Run Functions Host", 13 | "type": "coreclr", 14 | "request": "launch", 15 | "program": "${workspaceFolder}/bin/Debug/net8.0/workers/FunctionApp.dll", 16 | "args": [], 17 | "cwd": "${workspaceFolder}", 18 | "stopAtEntry": false, 19 | "console": "internalConsole", 20 | "env": { 21 | "AzureWebJobsStorage": "UseDevelopmentStorage=true" 22 | }, 23 | "preLaunchTask": "host start" 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /csharp/CosmosEmbeddingGenerator.cs: -------------------------------------------------------------------------------- 1 | using Azure.AI.OpenAI; 2 | using Azure.Identity; 3 | using Microsoft.Azure.Functions.Worker; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.Logging; 6 | using OpenAI.Embeddings; 7 | using System.Security.Cryptography; 8 | using System.Text; 9 | using System.Text.Json; 10 | 11 | namespace CosmosEmbeddingGenerator 12 | { 13 | public class CosmosEmbeddingGeneratorFunction 14 | { 15 | private readonly ILogger _logger; 16 | private readonly AzureOpenAIClient _openAiClient; 17 | private readonly EmbeddingClient _embeddingClient; 18 | private readonly int _dimensions; 19 | private readonly string _vectorProperty; 20 | private readonly string _hashProperty; 21 | private readonly string _propertyToEmbed; 22 | 23 | public CosmosEmbeddingGeneratorFunction(ILoggerFactory loggerFactory, IConfiguration configuration) 24 | { 25 | _logger = loggerFactory.CreateLogger(); 26 | 27 | string openAiEndpoint = configuration["OPENAI_ENDPOINT"] ?? throw new InvalidOperationException("'OpenAiEndpoint' must be defined."); 28 | string deploymentName = configuration["OPENAI_DEPLOYMENT_NAME"] ?? throw new InvalidOperationException("'OpenAiDeploymentName' must be defined."); 29 | string openAiDimensions = configuration["OPENAI_DIMENSIONS"] ?? throw new InvalidOperationException("'OpenAiDimensions' must be defined."); 30 | string vectorProperty = configuration["COSMOS_VECTOR_PROPERTY"] ?? throw new InvalidOperationException("'VectorProperty' must be defined."); 31 | string hashProperty = configuration["COSMOS_HASH_PROPERTY"] ?? throw new InvalidOperationException("'HashProperty' must be defined."); 32 | string propertyToEmbed = configuration["COSMOS_PROPERTY_TO_EMBED"] ?? throw new InvalidOperationException("'PropertyToEmbed' must be defined."); 33 | 34 | 35 | _openAiClient = new AzureOpenAIClient(new Uri(openAiEndpoint), new DefaultAzureCredential()); 36 | _embeddingClient = _openAiClient.GetEmbeddingClient(deploymentName); 37 | _dimensions = int.Parse(openAiDimensions); 38 | _vectorProperty = vectorProperty; 39 | _hashProperty = hashProperty; 40 | _propertyToEmbed = propertyToEmbed; 41 | 42 | } 43 | 44 | /// 45 | /// This function listens for changes to new or existing CosmosDb documents/items, 46 | /// and updates them in place with vector embeddings. 47 | /// 48 | /// The expected document/item has at least these 3 properties, and note that 'customerNotes' 49 | /// is the property that gets embedded. 50 | /// 51 | /// Example document: 52 | /// { 53 | /// "id": "00001", 54 | /// "customerId": "10001", 55 | /// "customerNotes": "lorum ipsum." 56 | /// } 57 | /// 58 | /// The list of documents that were modified in the CosmosDB container. 59 | /// The execution context of the Azure Function. 60 | /// A task that represents the asynchronous operation. The task result contains the updated documents with vector embeddings. 61 | [Function(nameof(CosmosEmbeddingGeneratorFunction))] 62 | [CosmosDBOutput( 63 | databaseName: "%COSMOS_DATABASE_NAME%", 64 | containerName: "%COSMOS_CONTAINER_NAME%", 65 | Connection = "COSMOS_CONNECTION")] 66 | public async Task Run( 67 | [CosmosDBTrigger( 68 | databaseName: "%COSMOS_DATABASE_NAME%", 69 | containerName: "%COSMOS_CONTAINER_NAME%", 70 | Connection = "COSMOS_CONNECTION", 71 | LeaseContainerName = "leases", 72 | CreateLeaseContainerIfNotExists = true)] IReadOnlyList input, 73 | FunctionContext context) 74 | { 75 | // List of documents to be returned to output binding 76 | var toBeUpdated = new List<(JsonDocument doc, string hash, string toEmbed)>(); 77 | 78 | if (input?.Count > 0) 79 | { 80 | _logger.LogInformation("Documents modified: {count}", input.Count); 81 | for (int i = 0; i < input.Count; i++) 82 | { 83 | var document = input[i]; 84 | 85 | // Parse document into a json object 86 | JsonDocument jsonDocument = JsonDocument.Parse(document.ToString()); 87 | 88 | // Check hash value to see if document is new or modified 89 | if (IsDocumentNewOrModified(jsonDocument, out var newHash)) 90 | { 91 | jsonDocument.RootElement.TryGetProperty(_propertyToEmbed, out JsonElement propertyElement); 92 | toBeUpdated.Add((jsonDocument, newHash, propertyElement.GetString() ?? string.Empty)); 93 | } 94 | } 95 | } 96 | 97 | // Process documents that have been modified 98 | if (toBeUpdated.Count > 0) 99 | { 100 | _logger.LogInformation("Updating embeddings for: {count}", toBeUpdated.Count); 101 | 102 | // Generate embeddings on the specified document property or document 103 | var embeddings = await GetEmbeddingsAsync(toBeUpdated.Select(tbu => tbu.toEmbed)); 104 | 105 | StringBuilder output = new StringBuilder().AppendLine("["); 106 | for (int i = 0; i < toBeUpdated.Count; i++) 107 | { 108 | var (jsonDocument, hash, toEmbed) = toBeUpdated[i]; 109 | 110 | // Create a new JSON object with the updated properties 111 | using (var stream = new MemoryStream()) 112 | { 113 | using (var writer = new Utf8JsonWriter(stream)) 114 | { 115 | writer.WriteStartObject(); 116 | 117 | foreach (JsonProperty property in jsonDocument.RootElement.EnumerateObject()) 118 | { 119 | if (property.Name != _hashProperty && property.Name != _vectorProperty) 120 | { 121 | property.WriteTo(writer); 122 | } 123 | } 124 | 125 | writer.WriteString(_hashProperty, hash); 126 | writer.WriteStartArray(_vectorProperty); 127 | foreach (var value in embeddings[i]) 128 | { 129 | writer.WriteNumberValue(value); 130 | } 131 | writer.WriteEndArray(); 132 | 133 | writer.WriteEndObject(); 134 | } 135 | 136 | stream.Position = 0; 137 | var updatedJsonDocument = JsonDocument.Parse(stream); 138 | output.Append(updatedJsonDocument.RootElement.GetRawText()); 139 | output.AppendLine(","); 140 | } 141 | } 142 | output.Length -= 1 + Environment.NewLine.Length; 143 | output.AppendLine().AppendLine("]"); 144 | 145 | return output.ToString(); 146 | } 147 | 148 | return null; 149 | } 150 | 151 | private bool IsDocumentNewOrModified(JsonDocument jsonDocument, out string newHash) 152 | { 153 | if (jsonDocument.RootElement.TryGetProperty(_hashProperty, out JsonElement existingProperty)) 154 | { 155 | // Generate a hash of the document/property 156 | newHash = ComputeJsonHash(jsonDocument); 157 | 158 | // Document has changed, process it 159 | if (newHash != existingProperty.GetString()) 160 | return true; 161 | 162 | // Document has not changed, skip processing 163 | return false; 164 | } 165 | else 166 | { 167 | // No hash property, document is new 168 | newHash = ComputeJsonHash(jsonDocument); 169 | return true; 170 | } 171 | } 172 | 173 | private async Task> GetEmbeddingsAsync(IEnumerable inputs) 174 | { 175 | var options = new EmbeddingGenerationOptions 176 | { 177 | Dimensions = _dimensions 178 | }; 179 | 180 | var response = await _embeddingClient.GenerateEmbeddingsAsync(inputs, options); 181 | var results = new List(response.Value.Count); 182 | foreach (var e in response.Value) 183 | { 184 | results.Add(e.ToFloats().ToArray()); 185 | } 186 | return results; 187 | } 188 | 189 | private string ComputeJsonHash(JsonDocument jsonDocument) 190 | { 191 | 192 | // Cleanse the document of system, vector and hash properties 193 | jsonDocument = RemoveProperties(jsonDocument); 194 | 195 | // Compute a hash on entire document generating embeddings on entire document 196 | // Re-serialize the JSON to canonical form (sorted keys, no extra whitespace) 197 | 198 | // Generate a hash on the property to be embedded 199 | jsonDocument.RootElement.TryGetProperty(_propertyToEmbed, out JsonElement propertyElement); 200 | var property = propertyElement.GetString() ?? string.Empty; 201 | 202 | // Compute SHA256 hash 203 | byte[] hashBytes = SHA256.HashData(Encoding.UTF8.GetBytes(property)); 204 | return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant(); 205 | } 206 | 207 | private JsonDocument RemoveProperties(JsonDocument jsonDocument) 208 | { 209 | using (var stream = new MemoryStream()) 210 | { 211 | using (var writer = new Utf8JsonWriter(stream)) 212 | { 213 | writer.WriteStartObject(); 214 | 215 | foreach (JsonProperty property in jsonDocument.RootElement.EnumerateObject()) 216 | { 217 | if (property.Name != _vectorProperty && 218 | property.Name != _hashProperty && 219 | property.Name != "_rid" && 220 | property.Name != "_self" && 221 | property.Name != "_etag" && 222 | property.Name != "_attachments" && 223 | property.Name != "_lsn" && 224 | property.Name != "_ts") 225 | { 226 | property.WriteTo(writer); 227 | } 228 | } 229 | 230 | writer.WriteEndObject(); 231 | } 232 | 233 | stream.Position = 0; 234 | return JsonDocument.Parse(stream); 235 | } 236 | } 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /csharp/CosmosEmbeddingGenerator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | v4 5 | Exe 6 | enable 7 | enable 8 | 5b7b28e2-32b5-4814-8463-240a3c53b2db 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | PreserveNewest 24 | 25 | 26 | PreserveNewest 27 | Never 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /csharp/CosmosEmbeddingGenerator.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.12.35527.113 d17.12 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CosmosEmbeddingGenerator", "CosmosEmbeddingGenerator.csproj", "{46C376BC-CE44-4A53-9363-4364D4C30CC0}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {46C376BC-CE44-4A53-9363-4364D4C30CC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {46C376BC-CE44-4A53-9363-4364D4C30CC0}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {46C376BC-CE44-4A53-9363-4364D4C30CC0}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {46C376BC-CE44-4A53-9363-4364D4C30CC0}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /csharp/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.Functions.Worker.Builder; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | 5 | var builder = FunctionsApplication.CreateBuilder(args); 6 | 7 | // Configure Application Insights 8 | builder.Services.AddApplicationInsightsTelemetryWorkerService(); 9 | 10 | builder.ConfigureFunctionsWebApplication(); 11 | 12 | builder.Build().Run(); 13 | -------------------------------------------------------------------------------- /csharp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "csharp": { 4 | "commandName": "Project", 5 | "commandLineArgs": "--port 7272", 6 | "launchBrowser": false 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /csharp/azure.yaml: -------------------------------------------------------------------------------- 1 | name: cosmos-embeddings-generator 2 | metadata: 3 | template: cosmos-embeddings-generator 4 | services: 5 | embeddingGenerator: 6 | project: ./ 7 | language: csharp 8 | host: function 9 | hooks: 10 | postprovision: 11 | windows: 12 | shell: pwsh 13 | run: .\infra\scripts\createlocalsettings.ps1 14 | interactive: true 15 | continueOnError: false 16 | posix: 17 | shell: sh 18 | run: ./infra/scripts/createlocalsettings.sh 19 | interactive: true 20 | continueOnError: false -------------------------------------------------------------------------------- /csharp/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "logging": { 4 | "applicationInsights": { 5 | "samplingSettings": { 6 | "isEnabled": true, 7 | "excludedTypes": "Request" 8 | }, 9 | "enableLiveMetricsFilters": true 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /csharp/infra/abbreviations.json: -------------------------------------------------------------------------------- 1 | { 2 | "cosmosDbAccount": "cosmos", 3 | "openAiAccount": "openai", 4 | "userAssignedIdentity": "ua-id", 5 | "appServiceWebApp": "web", 6 | "appServicePlan": "plan", 7 | "functionApp": "func", 8 | "storageAccount": "strg", 9 | "analysisServicesServers": "as", 10 | "apiManagementService": "apim-", 11 | "appConfigurationConfigurationStores": "appcs-", 12 | "appManagedEnvironments": "cae-", 13 | "appContainerApps": "ca-", 14 | "authorizationPolicyDefinitions": "policy-", 15 | "automationAutomationAccounts": "aa-", 16 | "blueprintBlueprints": "bp-", 17 | "blueprintBlueprintsArtifacts": "bpa-", 18 | "cacheRedis": "redis-", 19 | "cdnProfiles": "cdnp-", 20 | "cdnProfilesEndpoints": "cdne-", 21 | "cognitiveServicesAccounts": "cog-", 22 | "cognitiveServicesFormRecognizer": "cog-fr-", 23 | "cognitiveServicesTextAnalytics": "cog-ta-", 24 | "computeAvailabilitySets": "avail-", 25 | "computeCloudServices": "cld-", 26 | "computeDiskEncryptionSets": "des", 27 | "computeDisks": "disk", 28 | "computeDisksOs": "osdisk", 29 | "computeGalleries": "gal", 30 | "computeSnapshots": "snap-", 31 | "computeVirtualMachines": "vm", 32 | "computeVirtualMachineScaleSets": "vmss-", 33 | "containerInstanceContainerGroups": "ci", 34 | "containerRegistryRegistries": "cr", 35 | "containerServiceManagedClusters": "aks-", 36 | "databricksWorkspaces": "dbw-", 37 | "dataFactoryFactories": "adf-", 38 | "dataLakeAnalyticsAccounts": "dla", 39 | "dataLakeStoreAccounts": "dls", 40 | "dataMigrationServices": "dms-", 41 | "dBforMySQLServers": "mysql-", 42 | "dBforPostgreSQLServers": "psql-", 43 | "devicesIotHubs": "iot-", 44 | "devicesProvisioningServices": "provs-", 45 | "devicesProvisioningServicesCertificates": "pcert-", 46 | "documentDBDatabaseAccounts": "cosmos-", 47 | "eventGridDomains": "evgd-", 48 | "eventGridDomainsTopics": "evgt-", 49 | "eventGridEventSubscriptions": "evgs-", 50 | "eventHubNamespaces": "evhns-", 51 | "eventHubNamespacesEventHubs": "evh-", 52 | "hdInsightClustersHadoop": "hadoop-", 53 | "hdInsightClustersHbase": "hbase-", 54 | "hdInsightClustersKafka": "kafka-", 55 | "hdInsightClustersMl": "mls-", 56 | "hdInsightClustersSpark": "spark-", 57 | "hdInsightClustersStorm": "storm-", 58 | "hybridComputeMachines": "arcs-", 59 | "insightsActionGroups": "ag-", 60 | "insightsComponents": "appi-", 61 | "keyVaultVaults": "kv-", 62 | "kubernetesConnectedClusters": "arck", 63 | "kustoClusters": "dec", 64 | "kustoClustersDatabases": "dedb", 65 | "logicIntegrationAccounts": "ia-", 66 | "logicWorkflows": "logic-", 67 | "machineLearningServicesWorkspaces": "mlw-", 68 | "managedIdentityUserAssignedIdentities": "id-", 69 | "managementManagementGroups": "mg-", 70 | "migrateAssessmentProjects": "migr-", 71 | "networkApplicationGateways": "agw-", 72 | "networkApplicationSecurityGroups": "asg-", 73 | "networkAzureFirewalls": "afw-", 74 | "networkBastionHosts": "bas-", 75 | "networkConnections": "con-", 76 | "networkDnsZones": "dnsz-", 77 | "networkExpressRouteCircuits": "erc-", 78 | "networkFirewallPolicies": "afwp-", 79 | "networkFirewallPoliciesWebApplication": "waf", 80 | "networkFirewallPoliciesRuleGroups": "wafrg", 81 | "networkFrontDoors": "fd-", 82 | "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", 83 | "networkLoadBalancersExternal": "lbe-", 84 | "networkLoadBalancersInternal": "lbi-", 85 | "networkLoadBalancersInboundNatRules": "rule-", 86 | "networkLocalNetworkGateways": "lgw-", 87 | "networkNatGateways": "ng-", 88 | "networkNetworkInterfaces": "nic-", 89 | "networkNetworkSecurityGroups": "nsg-", 90 | "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", 91 | "networkNetworkWatchers": "nw-", 92 | "networkPrivateDnsZones": "pdnsz-", 93 | "networkPrivateLinkServices": "pl-", 94 | "networkPublicIPAddresses": "pip-", 95 | "networkPublicIPPrefixes": "ippre-", 96 | "networkRouteFilters": "rf-", 97 | "networkRouteTables": "rt-", 98 | "networkRouteTablesRoutes": "udr-", 99 | "networkTrafficManagerProfiles": "traf-", 100 | "networkVirtualNetworkGateways": "vgw-", 101 | "networkVirtualNetworks": "vnet-", 102 | "networkVirtualNetworksSubnets": "snet-", 103 | "networkVirtualNetworksVirtualNetworkPeerings": "peer-", 104 | "networkVirtualWans": "vwan-", 105 | "networkVpnGateways": "vpng-", 106 | "networkVpnGatewaysVpnConnections": "vcn-", 107 | "networkVpnGatewaysVpnSites": "vst-", 108 | "notificationHubsNamespaces": "ntfns-", 109 | "notificationHubsNamespacesNotificationHubs": "ntf-", 110 | "operationalInsightsWorkspaces": "log-", 111 | "portalDashboards": "dash-", 112 | "powerBIDedicatedCapacities": "pbi-", 113 | "purviewAccounts": "pview-", 114 | "recoveryServicesVaults": "rsv-", 115 | "resourcesResourceGroups": "rg-", 116 | "searchSearchServices": "srch-", 117 | "serviceBusNamespaces": "sb-", 118 | "serviceBusNamespacesQueues": "sbq-", 119 | "serviceBusNamespacesTopics": "sbt-", 120 | "serviceEndPointPolicies": "se-", 121 | "serviceFabricClusters": "sf-", 122 | "signalRServiceSignalR": "sigr", 123 | "sqlManagedInstances": "sqlmi-", 124 | "sqlServers": "sql-", 125 | "sqlServersDataWarehouse": "sqldw-", 126 | "sqlServersDatabases": "sqldb-", 127 | "sqlServersDatabasesStretch": "sqlstrdb-", 128 | "storageStorageAccountsVm": "stvm", 129 | "storSimpleManagers": "ssimp", 130 | "streamAnalyticsCluster": "asa-", 131 | "synapseWorkspaces": "syn", 132 | "synapseWorkspacesAnalyticsWorkspaces": "synw", 133 | "synapseWorkspacesSqlPoolsDedicated": "syndp", 134 | "synapseWorkspacesSqlPoolsSpark": "synsp", 135 | "timeSeriesInsightsEnvironments": "tsi-", 136 | "webServerFarms": "plan-", 137 | "webSitesAppService": "app-", 138 | "webSitesAppServiceEnvironment": "ase-", 139 | "webSitesFunctions": "func-", 140 | "webStaticSites": "stapp-" 141 | } -------------------------------------------------------------------------------- /csharp/infra/app/ai.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Create AI accounts.' 2 | 3 | param accountName string 4 | param location string = resourceGroup().location 5 | param tags object = {} 6 | param embeddingsModelName string 7 | param embeddingsDeploymentName string 8 | 9 | var deployments = [ 10 | { 11 | name: embeddingsDeploymentName 12 | skuCapacity: 5 13 | modelName: embeddingsModelName 14 | modelVersion: '1' 15 | } 16 | ] 17 | 18 | module openAiAccount '../core/ai/cognitive-services/account.bicep' = { 19 | name: 'openai-account' 20 | params: { 21 | name: accountName 22 | location: location 23 | tags: tags 24 | kind: 'OpenAI' 25 | sku: 'S0' 26 | enablePublicNetworkAccess: true 27 | } 28 | } 29 | 30 | @batchSize(1) 31 | module openAiModelDeployments '../core/ai/cognitive-services/deployment.bicep' = [ 32 | for (deployment, _) in deployments: { 33 | name: 'openai-model-deployment-${deployment.name}' 34 | params: { 35 | name: deployment.name 36 | parentAccountName: openAiAccount.outputs.name 37 | skuName: 'Standard' 38 | skuCapacity: deployment.skuCapacity 39 | modelName: deployment.modelName 40 | modelVersion: deployment.modelVersion 41 | modelFormat: 'OpenAI' 42 | } 43 | } 44 | ] 45 | 46 | output name string = openAiAccount.outputs.name 47 | output endpoint string = openAiAccount.outputs.endpoint 48 | output key string = openAiAccount.outputs.key 49 | output deployments array = [ 50 | for (_, index) in deployments: { 51 | name: openAiModelDeployments[index].outputs.name 52 | } 53 | ] 54 | 55 | -------------------------------------------------------------------------------- /csharp/infra/app/database.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Create database accounts.' 2 | 3 | param location string = resourceGroup().location 4 | param tags object = {} 5 | 6 | param accountName string 7 | param databaseName string 8 | param containerNames array 9 | param partitionKeyName string 10 | param vectorPropertyName string 11 | 12 | var database = { 13 | name: databaseName // Database for application 14 | } 15 | 16 | // concatenate / with the partition key and vector property names 17 | var partitionKeyPath = '/${partitionKeyName}' 18 | var vectorPath = '/${vectorPropertyName}' 19 | var vectorExcludedIndexPath = '${vectorPath}/?' 20 | 21 | var containers = [for containerName in containerNames:{ 22 | name: containerName // Container for products 23 | partitionKeyPaths: [ 24 | partitionKeyPath // Partition for product data 25 | ] 26 | indexingPolicy: { 27 | automatic: true 28 | indexingMode: 'consistent' 29 | includedPaths: [ 30 | { 31 | path: '/*' 32 | } 33 | ] 34 | excludedPaths: [ 35 | { 36 | path: vectorExcludedIndexPath 37 | } 38 | ] 39 | vectorIndexes: [ 40 | { 41 | path: vectorPath 42 | type: 'quantizedFlat' //'diskANN' 43 | } 44 | ] 45 | } 46 | vectorEmbeddingPolicy: { 47 | vectorEmbeddings: [ 48 | { 49 | path: vectorPath 50 | dataType: 'float32' 51 | dimensions: 1536 52 | distanceFunction: 'cosine' 53 | } 54 | ] 55 | } 56 | } 57 | ] 58 | 59 | module cosmosDbAccount '../core/database/cosmos-db/nosql/account.bicep' = { 60 | name: 'cosmos-db-account' 61 | params: { 62 | name: accountName 63 | location: location 64 | tags: tags 65 | enableServerless: true 66 | enableVectorSearch: true 67 | enableNoSQLFullTextSearch: false 68 | disableKeyBasedAuth: true 69 | } 70 | } 71 | 72 | module cosmosDbDatabase '../core/database/cosmos-db/nosql/database.bicep' = { 73 | name: 'cosmos-db-database-${database.name}' 74 | params: { 75 | name: database.name 76 | parentAccountName: cosmosDbAccount.outputs.name 77 | tags: tags 78 | setThroughput: false 79 | } 80 | } 81 | 82 | module cosmosDbContainers '../core/database/cosmos-db/nosql/container.bicep' = [ 83 | for (container, _) in containers: { 84 | name: 'cosmos-db-container-${container.name}' 85 | params: { 86 | name: container.name 87 | parentAccountName: cosmosDbAccount.outputs.name 88 | parentDatabaseName: cosmosDbDatabase.outputs.name 89 | tags: tags 90 | setThroughput: false 91 | partitionKeyPaths: container.partitionKeyPaths 92 | indexingPolicy: container.indexingPolicy 93 | vectorEmbeddingPolicy: container.vectorEmbeddingPolicy 94 | } 95 | } 96 | ] 97 | 98 | module cosmosDbLeases '../core/database/cosmos-db/nosql/container.bicep' = { 99 | name: 'cosmos-db-container-leases' 100 | params: { 101 | name: 'leases' 102 | parentAccountName: cosmosDbAccount.outputs.name 103 | parentDatabaseName: cosmosDbDatabase.outputs.name 104 | tags: tags 105 | setThroughput: false 106 | partitionKeyPaths: ['/id'] 107 | } 108 | } 109 | 110 | output endpoint string = cosmosDbAccount.outputs.endpoint 111 | output accountName string = cosmosDbAccount.outputs.name 112 | output connectionString string = cosmosDbAccount.outputs.connectionString 113 | 114 | output database object = { 115 | name: cosmosDbDatabase.outputs.name 116 | } 117 | output containers array = [ 118 | for (_, index) in containers: { 119 | name: cosmosDbContainers[index].outputs.name 120 | } 121 | ] 122 | -------------------------------------------------------------------------------- /csharp/infra/app/functions.bicep: -------------------------------------------------------------------------------- 1 | param name string 2 | param location string = resourceGroup().location 3 | param tags object = {} 4 | param applicationInsightsName string = '' 5 | param planName string 6 | param appSettings object = {} 7 | param runtimeName string 8 | param runtimeVersion string 9 | param serviceName string 10 | param storageAccountName string 11 | param deploymentStorageContainerName string 12 | param virtualNetworkSubnetId string = '' 13 | param instanceMemoryMB int = 2048 14 | param maximumInstanceCount int = 100 15 | param identityId string = '' 16 | param identityClientId string = '' 17 | 18 | var applicationInsightsIdentity = 'ClientId=${identityClientId};Authorization=AAD' 19 | 20 | module appServicePlan '../core/host/functions/appserviceplan.bicep' = { 21 | name: 'appserviceplan' 22 | params: { 23 | name: planName 24 | location: location 25 | tags: tags 26 | sku: { 27 | name: 'FC1' 28 | tier: 'FlexConsumption' 29 | } 30 | } 31 | } 32 | 33 | module function '../core/host/functions/flexconsumption.bicep' = { 34 | name: '${serviceName}-functions-module' 35 | params: { 36 | name: name 37 | location: location 38 | tags: union(tags, { 'azd-service-name': serviceName }) 39 | identityType: 'UserAssigned' 40 | identityId: identityId 41 | identityClientId: identityClientId 42 | appSettings: union(appSettings, 43 | { 44 | AzureWebJobsStorage__clientId : identityClientId 45 | APPLICATIONINSIGHTS_AUTHENTICATION_STRING: applicationInsightsIdentity 46 | AZURE_CLIENT_ID: identityClientId 47 | }) 48 | applicationInsightsName: applicationInsightsName 49 | appServicePlanId: appServicePlan.outputs.id 50 | runtimeName: runtimeName 51 | runtimeVersion: runtimeVersion 52 | storageAccountName: storageAccountName 53 | deploymentStorageContainerName: deploymentStorageContainerName 54 | instanceMemoryMB: instanceMemoryMB 55 | maximumInstanceCount: maximumInstanceCount 56 | } 57 | } 58 | 59 | output SERVICE_API_NAME string = function.outputs.name 60 | output SERVICE_API_URI string = function.outputs.uri 61 | output SERVICE_API_IDENTITY_PRINCIPAL_ID string = function.outputs.identityPrincipalId 62 | -------------------------------------------------------------------------------- /csharp/infra/app/identity.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Create identity resources.' 2 | 3 | param identityName string 4 | param location string = resourceGroup().location 5 | param tags object = {} 6 | 7 | module userAssignedIdentity '../core/security/identity/user-assigned.bicep' = { 8 | name: 'user-assigned-identity' 9 | params: { 10 | name: identityName 11 | location: location 12 | tags: tags 13 | } 14 | } 15 | 16 | output name string = userAssignedIdentity.outputs.name 17 | output resourceId string = userAssignedIdentity.outputs.resourceId 18 | output principalId string = userAssignedIdentity.outputs.principalId 19 | output clientId string = userAssignedIdentity.outputs.clientId 20 | -------------------------------------------------------------------------------- /csharp/infra/app/security.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Create role assignment and definition resources.' 2 | 3 | 4 | @description('Id of the service principals to assign database and application roles.') 5 | param appPrincipalId string = '' 6 | 7 | @description('Id of the user principals to assign database and application roles.') 8 | param userPrincipalId string = '' 9 | 10 | 11 | param storageAccountName string 12 | resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' existing = { 13 | name: storageAccountName 14 | } 15 | 16 | param databaseAccountName string 17 | resource database 'Microsoft.DocumentDB/databaseAccounts@2023-04-15' existing = { 18 | name: databaseAccountName 19 | } 20 | 21 | module nosqlDefinition '../core/database/cosmos-db/nosql/role/definition.bicep' = { 22 | name: 'nosql-role-definition' 23 | params: { 24 | targetAccountName: database.name // Existing account 25 | definitionName: 'Write to Azure Cosmos DB for NoSQL data plane' // Custom role name 26 | permissionsDataActions: [ 27 | 'Microsoft.DocumentDB/databaseAccounts/readMetadata' // Read account metadata 28 | 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*' // Create items 29 | 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*' // Manage items 30 | ] 31 | } 32 | } 33 | 34 | module nosqlAppAssignment '../core/database/cosmos-db/nosql/role/assignment.bicep' = if (!empty(appPrincipalId)) { 35 | name: 'nosql-role-assignment-app' 36 | params: { 37 | targetAccountName: database.name // Existing account 38 | roleDefinitionId: nosqlDefinition.outputs.id // New role definition 39 | principalId: appPrincipalId // Principal to assign role 40 | principalType: 'ServicePrincipal' // Principal type for assigning role 41 | } 42 | } 43 | 44 | module nosqlUserAssignment '../core/database/cosmos-db/nosql/role/assignment.bicep' = if (!empty(userPrincipalId)) { 45 | name: 'nosql-role-assignment-user' 46 | params: { 47 | targetAccountName: database.name // Existing account 48 | roleDefinitionId: nosqlDefinition.outputs.id // New role definition 49 | principalId: userPrincipalId ?? '' // Principal to assign role 50 | principalType: 'User' // Principal type for assigning role 51 | } 52 | } 53 | 54 | module openaiAppAssignment '../core/security/role/assignment.bicep' = if (!empty(appPrincipalId)) { 55 | name: 'openai-role-assignment-read-app' 56 | params: { 57 | roleDefinitionId: subscriptionResourceId( 58 | 'Microsoft.Authorization/roleDefinitions', 59 | '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' 60 | ) // Cognitive Services OpenAI User built-in role 61 | principalId: appPrincipalId // Principal to assign role 62 | principalType: 'ServicePrincipal' // Specify the principal type // was 'None' but this appears to cause issues 63 | } 64 | } 65 | 66 | module openaiUserAssignment '../core/security/role/assignment.bicep' = if (!empty(userPrincipalId)) { 67 | name: 'openai-role-assignment-read-user' 68 | params: { 69 | roleDefinitionId: subscriptionResourceId( 70 | 'Microsoft.Authorization/roleDefinitions', 71 | '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' 72 | ) // Cognitive Services OpenAI User built-in role 73 | principalId: userPrincipalId // Principal to assign role 74 | principalType: 'User' // Principal type or current deployment user 75 | } 76 | } 77 | 78 | // Allow access from API to storage account to user identity 79 | resource storageUserAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 80 | name: guid(storageAccountName, userPrincipalId, 'storage-blob-owner') 81 | scope: storageAccount 82 | properties: { 83 | roleDefinitionId: subscriptionResourceId( 84 | 'Microsoft.Authorization/roleDefinitions', 85 | 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b') // Storage Blob Data Owner role 86 | principalId: userPrincipalId // Principal to assign role 87 | principalType: 'User' // Principal type or current deployment user 88 | } 89 | } 90 | 91 | // Allow access from API to storage account to user identity 92 | resource storageAppAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 93 | name: guid(storageAccountName, appPrincipalId, 'storage-blob-owner') 94 | scope: storageAccount 95 | properties: { 96 | roleDefinitionId: subscriptionResourceId( 97 | 'Microsoft.Authorization/roleDefinitions', 98 | 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b') // Storage Blob Data Owner role 99 | principalId: appPrincipalId // Principal to assign role 100 | principalType: 'ServicePrincipal' 101 | } 102 | } 103 | 104 | //output roleDefinitions object = { 105 | // nosql: nosqlDefinition.outputs.id 106 | //} 107 | 108 | //output roleAssignments array = union( 109 | // !empty(appPrincipalId) ? [nosqlAppAssignment.outputs.id, openaiAppAssignment.outputs.id] : [], 110 | // !empty(userPrincipalId) ? [nosqlUserAssignment.outputs.id, openaiUserAssignment.outputs.id] : [] 111 | //) 112 | -------------------------------------------------------------------------------- /csharp/infra/app/web.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Create web apps.' 2 | 3 | param planName string 4 | param appName string 5 | param serviceTag string 6 | param location string = resourceGroup().location 7 | param tags object = {} 8 | 9 | @description('SKU of the App Service Plan.') 10 | param sku string = 'B1' 11 | 12 | @description('Endpoint for Azure Cosmos DB for NoSQL account.') 13 | param databaseAccountEndpoint string 14 | 15 | @description('Endpoint for Azure OpenAI account.') 16 | param openAiAccountEndpoint string 17 | 18 | type openAiOptions = { 19 | completionDeploymentName: string 20 | embeddingDeploymentName: string 21 | maxRagTokens: string 22 | maxContextTokens: string 23 | } 24 | 25 | @description('Application configuration settings for OpenAI.') 26 | param openAiSettings openAiOptions 27 | 28 | type cosmosDbOptions = { 29 | database: string 30 | chatContainer: string 31 | cacheContainer: string 32 | productContainer: string 33 | productDataSourceUri: string 34 | } 35 | @description('Application configuration settings for Azure Cosmos DB.') 36 | param cosmosDbSettings cosmosDbOptions 37 | 38 | type chatOptions = { 39 | maxContextWindow: string 40 | cacheSimilarityScore: string 41 | productMaxResults: string 42 | } 43 | 44 | @description('Application configuration settings for Chat Service.') 45 | param chatSettings chatOptions 46 | 47 | type managedIdentity = { 48 | resourceId: string 49 | clientId: string 50 | } 51 | 52 | @description('Unique identifier for user-assigned managed identity.') 53 | param userAssignedManagedIdentity managedIdentity 54 | 55 | module appServicePlan '../core/host/app-service/plan.bicep' = { 56 | name: 'app-service-plan' 57 | params: { 58 | name: planName 59 | location: location 60 | tags: tags 61 | sku: sku 62 | kind: 'linux' 63 | } 64 | } 65 | 66 | module appServiceWebApp '../core/host/app-service/site.bicep' = { 67 | name: 'app-service-web-app' 68 | params: { 69 | name: appName 70 | location: location 71 | tags: union(tags, { 72 | 'azd-service-name': serviceTag 73 | }) 74 | parentPlanName: appServicePlan.outputs.name 75 | runtimeName: 'dotnetcore' 76 | runtimeVersion: '8.0' 77 | kind: 'app,linux' 78 | enableSystemAssignedManagedIdentity: false 79 | userAssignedManagedIdentityIds: [ 80 | userAssignedManagedIdentity.resourceId 81 | ] 82 | } 83 | } 84 | 85 | module appServiceWebAppConfig '../core/host/app-service/config.bicep' = { 86 | name: 'app-service-config' 87 | params: { 88 | parentSiteName: appServiceWebApp.outputs.name 89 | appSettings: { 90 | OPENAI__ENDPOINT: openAiAccountEndpoint 91 | OPENAI__COMPLETIONDEPLOYMENTNAME: openAiSettings.completionDeploymentName 92 | OPENAI__EMBEDDINGDEPLOYMENTNAME: openAiSettings.embeddingDeploymentName 93 | OPENAI__MAXRAGTOKENS: openAiSettings.maxRagTokens 94 | OPENAI__MAXCONTEXTTOKENS: openAiSettings.maxContextTokens 95 | COSMOSDB__ENDPOINT: databaseAccountEndpoint 96 | COSMOSDB__DATABASE: cosmosDbSettings.database 97 | COSMOSDB__CHATCONTAINER: cosmosDbSettings.chatContainer 98 | COSMOSDB__CACHECONTAINER: cosmosDbSettings.cacheContainer 99 | COSMOSDB__PRODUCTCONTAINER: cosmosDbSettings.productContainer 100 | COSMOSDB__PRODUCTDATASOURCEURI: cosmosDbSettings.productDataSourceUri 101 | CHAT__MAXCONTEXTWINDOW: chatSettings.maxContextWindow 102 | CHAT__CACHESIMILARITYSCORE: chatSettings.cacheSimilarityScore 103 | CHAT__PRODUCTMAXRESULTS: chatSettings.productMaxResults 104 | AZURE_CLIENT_ID: userAssignedManagedIdentity.clientId 105 | } 106 | } 107 | } 108 | 109 | output name string = appServiceWebApp.outputs.name 110 | output endpoint string = appServiceWebApp.outputs.endpoint 111 | -------------------------------------------------------------------------------- /csharp/infra/core/ai/cognitive-services/account.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates an Azure Cognitive Services account.' 2 | 3 | param name string 4 | param location string = resourceGroup().location 5 | param tags object = {} 6 | 7 | @allowed([ 'OpenAI', 'ComputerVision', 'TextTranslation', 'CognitiveServices' ]) 8 | @description('Sets the kind of account.') 9 | param kind string 10 | 11 | @allowed([ 12 | 'S0' 13 | ]) 14 | @description('SKU for the account. Defaults to "S0".') 15 | param sku string = 'S0' 16 | 17 | @description('Enables access from public networks. Defaults to true.') 18 | param enablePublicNetworkAccess bool = true 19 | 20 | resource account 'Microsoft.CognitiveServices/accounts@2023-05-01' = { 21 | name: name 22 | location: location 23 | tags: tags 24 | kind: kind 25 | sku: { 26 | name: sku 27 | } 28 | properties: { 29 | customSubDomainName: name 30 | publicNetworkAccess: enablePublicNetworkAccess ? 'Enabled' : 'Disabled' 31 | } 32 | } 33 | 34 | output endpoint string = account.properties.endpoint 35 | output key string = account.listKeys().key1 36 | output name string = account.name 37 | -------------------------------------------------------------------------------- /csharp/infra/core/ai/cognitive-services/deployment.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates an Azure Cognitive Services deployment.' 2 | 3 | param name string 4 | 5 | @description('Name of the parent Azure Cognitive Services account.') 6 | param parentAccountName string 7 | 8 | @description('Name of the SKU for the deployment. Defaults to "Standard".') 9 | param skuName string = 'Standard' 10 | 11 | @description('Capacity of the SKU for the deployment. Defaults to 100.') 12 | param skuCapacity int = 100 13 | 14 | @description('Name of the model to use in the deployment.') 15 | param modelName string 16 | 17 | @description('Format of the model to use in the deployment.') 18 | param modelFormat string 19 | 20 | @description('Version of the model to use in the deployment.') 21 | param modelVersion string 22 | 23 | resource account 'Microsoft.CognitiveServices/accounts@2023-05-01' existing = { 24 | name: parentAccountName 25 | } 26 | 27 | resource deployment 'Microsoft.CognitiveServices/accounts/deployments@2023-05-01' = { 28 | parent: account 29 | name: name 30 | sku: { 31 | name: skuName 32 | capacity: skuCapacity 33 | } 34 | properties: { 35 | model: { 36 | name: modelName 37 | format: modelFormat 38 | version: modelVersion 39 | } 40 | } 41 | } 42 | 43 | output name string = deployment.name 44 | -------------------------------------------------------------------------------- /csharp/infra/core/database/cosmos-db/account.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Create an Azure Cosmos DB account.' 2 | 3 | param name string 4 | param location string = resourceGroup().location 5 | param tags object = {} 6 | 7 | @allowed(['GlobalDocumentDB']) 8 | @description('Sets the kind of account.') 9 | param kind string = 'GlobalDocumentDB' 10 | 11 | @description('Enables serverless for this account. Defaults to false.') 12 | param enableServerless bool = true 13 | 14 | @description('Enables NoSQL vector search for this account. Defaults to false.') 15 | param enableNoSQLVectorSearch bool = false 16 | 17 | @description('Enables NoSQL full text search for this account. Defaults to false.') 18 | param enableNoSQLFullTextSearch bool = false 19 | 20 | @description('Disables key-based authentication. Defaults to false.') 21 | param disableKeyBasedAuth bool = false 22 | 23 | resource account 'Microsoft.DocumentDB/databaseAccounts@2024-05-15' = { 24 | name: name 25 | location: location 26 | tags: tags 27 | kind: kind 28 | properties: { 29 | consistencyPolicy: { 30 | defaultConsistencyLevel: 'Session' 31 | } 32 | databaseAccountOfferType: 'Standard' 33 | locations: [ 34 | { 35 | locationName: location 36 | failoverPriority: 0 37 | isZoneRedundant: false 38 | } 39 | ] 40 | enableAutomaticFailover: false 41 | enableMultipleWriteLocations: false 42 | disableLocalAuth: disableKeyBasedAuth 43 | capabilities: union( 44 | (enableServerless) 45 | ? [ 46 | { 47 | name: 'EnableServerless' 48 | } 49 | ] 50 | : [], 51 | (enableNoSQLVectorSearch) 52 | ? [ 53 | { 54 | name: 'EnableNoSQLVectorSearch' 55 | } 56 | ] 57 | : [], 58 | (enableNoSQLFullTextSearch) 59 | ? [ 60 | { 61 | name: 'EnableNoSQLFullTextSearch' 62 | } 63 | ] 64 | : [] 65 | ) 66 | } 67 | } 68 | 69 | output endpoint string = account.properties.documentEndpoint 70 | output key string = account.listKeys().primaryMasterKey 71 | output connectionString string = account.listConnectionStrings().connectionStrings[0].connectionString 72 | output name string = account.name 73 | -------------------------------------------------------------------------------- /csharp/infra/core/database/cosmos-db/nosql/account.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Create an Azure Cosmos DB for NoSQL account.' 2 | 3 | param name string 4 | param location string = resourceGroup().location 5 | param tags object = {} 6 | 7 | @description('Enables serverless for this account. Defaults to false.') 8 | param enableServerless bool = false 9 | 10 | @description('Disables key-based authentication. Defaults to false.') 11 | param disableKeyBasedAuth bool = false 12 | 13 | @description('Enables vector search for this account. Defaults to false.') 14 | param enableVectorSearch bool = false 15 | 16 | @description('Enables NoSQL full text search for this account. Defaults to false.') 17 | param enableNoSQLFullTextSearch bool = false 18 | 19 | module account '../account.bicep' = { 20 | name: 'cosmos-db-nosql-account' 21 | params: { 22 | name: name 23 | location: location 24 | tags: tags 25 | kind: 'GlobalDocumentDB' 26 | enableServerless: enableServerless 27 | enableNoSQLVectorSearch: enableVectorSearch 28 | enableNoSQLFullTextSearch: enableNoSQLFullTextSearch 29 | disableKeyBasedAuth: disableKeyBasedAuth 30 | } 31 | } 32 | 33 | output endpoint string = account.outputs.endpoint 34 | output key string = account.outputs.key 35 | output connectionString string = account.outputs.connectionString 36 | output name string = account.outputs.name 37 | -------------------------------------------------------------------------------- /csharp/infra/core/database/cosmos-db/nosql/container.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Create an Azure Cosmos DB for NoSQL container.' 2 | 3 | param name string 4 | param tags object = {} 5 | 6 | @description('Name of the parent Azure Cosmos DB account.') 7 | param parentAccountName string 8 | 9 | @description('Name of the parent Azure Cosmos DB database.') 10 | param parentDatabaseName string 11 | 12 | @description('Enables throughput setting at this resource level. Defaults to true.') 13 | param setThroughput bool = false 14 | 15 | @description('Enables autoscale. If setThroughput is enabled, defaults to false.') 16 | param autoscale bool = false 17 | 18 | @description('The amount of throughput set. If setThroughput is enabled, defaults to 400.') 19 | param throughput int = 400 20 | 21 | @description('List of hierarhical partition key paths. Defaults to an array that only contains /id.') 22 | param partitionKeyPaths string[] = [ 23 | '/id' 24 | ] 25 | 26 | @description('Optional custom indexing policy for the container.') 27 | param indexingPolicy object = {} 28 | 29 | @description('Optional vector embedding policy for the container.') 30 | param vectorEmbeddingPolicy object = {} 31 | 32 | @description('Optional full text policy for the container.') 33 | param fullTextPolicy object = {} 34 | 35 | var options = setThroughput 36 | ? autoscale 37 | ? { 38 | autoscaleSettings: { 39 | maxThroughput: throughput 40 | } 41 | } 42 | : { 43 | throughput: throughput 44 | } 45 | : {} 46 | 47 | resource account 'Microsoft.DocumentDB/databaseAccounts@2024-05-15' existing = { 48 | name: parentAccountName 49 | } 50 | 51 | resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2024-05-15' existing = { 52 | name: parentDatabaseName 53 | parent: account 54 | } 55 | 56 | resource container 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2024-05-15' = { 57 | name: name 58 | parent: database 59 | tags: tags 60 | properties: { 61 | options: options 62 | resource: union( 63 | { 64 | id: name 65 | partitionKey: { 66 | paths: partitionKeyPaths 67 | kind: 'MultiHash' 68 | version: 2 69 | } 70 | }, 71 | !empty(indexingPolicy) 72 | ? { 73 | indexingPolicy: indexingPolicy 74 | } 75 | : {}, 76 | !empty(vectorEmbeddingPolicy) 77 | ? { 78 | vectorEmbeddingPolicy: vectorEmbeddingPolicy 79 | } 80 | : {}, 81 | !empty(fullTextPolicy) 82 | ? { 83 | fullTextPolicy: fullTextPolicy 84 | } 85 | : {} 86 | ) 87 | } 88 | } 89 | 90 | output name string = container.name 91 | -------------------------------------------------------------------------------- /csharp/infra/core/database/cosmos-db/nosql/database.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Create an Azure Cosmos DB for NoSQL database.' 2 | 3 | param name string 4 | param tags object = {} 5 | 6 | @description('Name of the parent Azure Cosmos DB account.') 7 | param parentAccountName string 8 | 9 | @description('Enables throughput setting at this resource level. Defaults to false.') 10 | param setThroughput bool = false 11 | 12 | @description('Enables autoscale. If setThroughput is enabled, defaults to false.') 13 | param autoscale bool = false 14 | 15 | @description('The amount of throughput set. If setThroughput is enabled, defaults to 400.') 16 | param throughput int = 400 17 | 18 | var options = setThroughput 19 | ? autoscale 20 | ? { 21 | autoscaleSettings: { 22 | maxThroughput: throughput 23 | } 24 | } 25 | : { 26 | throughput: throughput 27 | } 28 | : {} 29 | 30 | resource account 'Microsoft.DocumentDB/databaseAccounts@2024-05-15' existing = { 31 | name: parentAccountName 32 | } 33 | 34 | resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2024-05-15' = { 35 | name: name 36 | parent: account 37 | tags: tags 38 | properties: { 39 | options: options 40 | resource: { 41 | id: name 42 | } 43 | } 44 | } 45 | 46 | output name string = database.name 47 | -------------------------------------------------------------------------------- /csharp/infra/core/database/cosmos-db/nosql/role/assignment.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Create an Azure Cosmos DB for NoSQL role assignment.' 2 | 3 | @description('Name of the target Azure Cosmos DB account.') 4 | param targetAccountName string 5 | 6 | @description('Id of the role definition to assign to the targeted principal and account.') 7 | param roleDefinitionId string 8 | 9 | @description('Id of the principal to assign the role definition for the account.') 10 | param principalId string 11 | 12 | @description('Principal type used for the role assignment.') 13 | param principalType string 14 | 15 | resource account 'Microsoft.DocumentDB/databaseAccounts@2023-04-15' existing = { 16 | name: targetAccountName 17 | } 18 | 19 | resource assignment 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2023-04-15' = { 20 | name: guid(roleDefinitionId, principalId, account.id) 21 | parent: account 22 | properties: { 23 | principalId: principalId 24 | roleDefinitionId: roleDefinitionId 25 | scope: account.id 26 | principalType: principalType 27 | } 28 | } 29 | 30 | output id string = assignment.id 31 | -------------------------------------------------------------------------------- /csharp/infra/core/database/cosmos-db/nosql/role/definition.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Create an Azure Cosmos DB for NoSQL role definition.' 2 | 3 | @description('Name of the target Azure Cosmos DB account.') 4 | param targetAccountName string 5 | 6 | @description('Name of the role definiton.') 7 | param definitionName string 8 | 9 | @description('An array of data actions that are allowed. Defaults to an empty array.') 10 | param permissionsDataActions string[] = [] 11 | 12 | @description('An array of data actions that are denied. Defaults to an empty array.') 13 | param permissionsNonDataActions string[] = [] 14 | 15 | resource account 'Microsoft.DocumentDB/databaseAccounts@2023-04-15' existing = { 16 | name: targetAccountName 17 | } 18 | 19 | resource definition 'Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions@2023-04-15' = { 20 | name: guid('nosql-role-definition', account.id) 21 | parent: account 22 | properties: { 23 | assignableScopes: [ 24 | account.id 25 | ] 26 | permissions: [ 27 | { 28 | dataActions: permissionsDataActions 29 | notDataActions: permissionsNonDataActions 30 | } 31 | ] 32 | roleName: definitionName 33 | type: 'CustomRole' 34 | } 35 | } 36 | 37 | output id string = definition.id 38 | -------------------------------------------------------------------------------- /csharp/infra/core/host/app-service/config.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates an Azure App Service configuration for a site.' 2 | 3 | @description('Name of the parent App Service site for the configuration.') 4 | param parentSiteName string 5 | 6 | @secure() 7 | param appSettings object = {} 8 | 9 | resource site 'Microsoft.Web/sites@2022-09-01' existing = { 10 | name: parentSiteName 11 | } 12 | 13 | resource config 'Microsoft.Web/sites/config@2022-09-01' = { 14 | name: 'appsettings' 15 | parent: site 16 | kind: 'string' 17 | properties: appSettings 18 | } 19 | -------------------------------------------------------------------------------- /csharp/infra/core/host/app-service/plan.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates an Azure App Service plan.' 2 | 3 | param name string 4 | param location string = resourceGroup().location 5 | param tags object = {} 6 | 7 | @allowed([ 8 | 'linux' 9 | ]) 10 | @description('OS type of the plan. Defaults to "linux".') 11 | param kind string = 'linux' 12 | 13 | 14 | @description('SKU for the plan. Defaults to "F1".') 15 | param sku string = 'F1' 16 | 17 | resource plan 'Microsoft.Web/serverfarms@2022-09-01' = { 18 | name: name 19 | location: location 20 | tags: tags 21 | sku: { 22 | name: sku 23 | } 24 | kind: kind 25 | properties: { 26 | reserved: kind == 'linux' ? true : null 27 | } 28 | } 29 | 30 | output name string = plan.name 31 | -------------------------------------------------------------------------------- /csharp/infra/core/host/app-service/site.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates an Azure App Service site.' 2 | 3 | param name string 4 | param location string = resourceGroup().location 5 | param tags object = {} 6 | 7 | @description('Name of the parent plan for the site.') 8 | param parentPlanName string 9 | 10 | @allowed([ 11 | 'dotnet' 12 | 'dotnetcore' 13 | 'dotnet-isolated' 14 | 'node' 15 | 'python' 16 | 'java' 17 | 'powershell' 18 | 'custom' 19 | ]) 20 | @description('Runtime to use for the site.') 21 | param runtimeName string 22 | 23 | @description('Version of the runtime to use for the site.') 24 | param runtimeVersion string 25 | 26 | @description('The OS kind of the site. Defaults to "app, linux"') 27 | param kind string = 'app,linux' 28 | 29 | @description('If the site should be always on. Defaults to true.') 30 | param alwaysOn bool = true 31 | 32 | @description('Allowed origins for client-side CORS request on the site.') 33 | param allowedCorsOrigins string[] = [] 34 | 35 | @description('Enable system-assigned managed identity. Defaults to false.') 36 | param enableSystemAssignedManagedIdentity bool = false 37 | 38 | @description('List of user-assigned managed identities. Defaults to an empty array.') 39 | param userAssignedManagedIdentityIds string[] = [] 40 | 41 | var linuxFxVersion = '${runtimeName}|${runtimeVersion}' 42 | 43 | resource plan 'Microsoft.Web/serverfarms@2022-09-01' existing = { 44 | name: parentPlanName 45 | } 46 | 47 | resource site 'Microsoft.Web/sites@2022-09-01' = { 48 | name: name 49 | location: location 50 | tags: tags 51 | kind: kind 52 | identity: { 53 | type: enableSystemAssignedManagedIdentity 54 | ? !empty(userAssignedManagedIdentityIds) ? 'SystemAssigned, UserAssigned' : 'SystemAssigned' 55 | : !empty(userAssignedManagedIdentityIds) ? 'UserAssigned' : 'None' 56 | userAssignedIdentities: !empty(userAssignedManagedIdentityIds) 57 | ? toObject(userAssignedManagedIdentityIds, uaid => uaid, uaid => {}) 58 | : null 59 | } 60 | properties: { 61 | serverFarmId: plan.id 62 | siteConfig: { 63 | linuxFxVersion: linuxFxVersion 64 | alwaysOn: alwaysOn 65 | http20Enabled: true 66 | minTlsVersion: '1.2' 67 | cors: { 68 | allowedOrigins: union(['https://portal.azure.com', 'https://ms.portal.azure.com'], allowedCorsOrigins) 69 | } 70 | } 71 | httpsOnly: true 72 | } 73 | } 74 | 75 | output endpoint string = 'https://${site.properties.defaultHostName}' 76 | output name string = site.name 77 | output managedIdentityPrincipalId string = enableSystemAssignedManagedIdentity ? site.identity.principalId : '' 78 | -------------------------------------------------------------------------------- /csharp/infra/core/host/functions/appserviceplan.bicep: -------------------------------------------------------------------------------- 1 | param name string 2 | param location string = resourceGroup().location 3 | param tags object = {} 4 | 5 | @allowed([ 6 | 'linux' 7 | ]) 8 | @description('OS type of the plan. Defaults to "linux".') 9 | param kind string = 'linux' 10 | 11 | param sku object 12 | 13 | param reserved bool = true 14 | 15 | 16 | resource appServicePlan 'Microsoft.Web/serverfarms@2023-12-01' = { 17 | name: name 18 | location: location 19 | tags: tags 20 | sku: sku 21 | kind: kind 22 | properties: { 23 | reserved: reserved 24 | } 25 | } 26 | 27 | output id string = appServicePlan.id 28 | -------------------------------------------------------------------------------- /csharp/infra/core/host/functions/flexconsumption.bicep: -------------------------------------------------------------------------------- 1 | param name string 2 | param location string = resourceGroup().location 3 | param tags object = {} 4 | 5 | // Reference Properties 6 | param applicationInsightsName string = '' 7 | param appServicePlanId string 8 | param storageAccountName string 9 | param virtualNetworkSubnetId string = '' 10 | @allowed(['SystemAssigned', 'UserAssigned']) 11 | param identityType string 12 | @description('User assigned identity name') 13 | param identityId string 14 | @description('User assigned identity client id') 15 | param identityClientId string 16 | 17 | // Runtime Properties 18 | @allowed([ 19 | 'dotnet-isolated', 'node', 'python', 'java', 'powershell', 'custom' 20 | ]) 21 | param runtimeName string 22 | @allowed(['3.10', '3.11', '7.4', '8.0', '10', '11', '17', '20']) 23 | param runtimeVersion string 24 | param kind string = 'functionapp,linux' 25 | 26 | // Microsoft.Web/sites/config 27 | param appSettings object = {} 28 | param instanceMemoryMB int = 2048 29 | param maximumInstanceCount int = 100 30 | param deploymentStorageContainerName string 31 | 32 | resource stg 'Microsoft.Storage/storageAccounts@2022-09-01' existing = { 33 | name: storageAccountName 34 | } 35 | 36 | resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightsName)) { 37 | name: applicationInsightsName 38 | } 39 | 40 | resource functions 'Microsoft.Web/sites@2023-12-01' = { 41 | name: name 42 | location: location 43 | tags: tags 44 | kind: kind 45 | identity: { 46 | type: identityType 47 | userAssignedIdentities: { 48 | '${identityId}': {} 49 | } 50 | } 51 | properties: { 52 | serverFarmId: appServicePlanId 53 | functionAppConfig: { 54 | deployment: { 55 | storage: { 56 | type: 'blobContainer' 57 | value: '${stg.properties.primaryEndpoints.blob}${deploymentStorageContainerName}' 58 | authentication: { 59 | type: identityType == 'SystemAssigned' ? 'SystemAssignedIdentity' : 'UserAssignedIdentity' 60 | userAssignedIdentityResourceId: identityType == 'UserAssigned' ? identityId : '' 61 | } 62 | } 63 | } 64 | scaleAndConcurrency: { 65 | instanceMemoryMB: instanceMemoryMB 66 | maximumInstanceCount: maximumInstanceCount 67 | } 68 | runtime: { 69 | name: runtimeName 70 | version: runtimeVersion 71 | } 72 | } 73 | virtualNetworkSubnetId: !empty(virtualNetworkSubnetId) ? virtualNetworkSubnetId : null 74 | } 75 | 76 | resource configAppSettings 'config' = { 77 | name: 'appsettings' 78 | properties: union(appSettings, 79 | { 80 | AzureWebJobsStorage__blobServiceUri: stg.properties.primaryEndpoints.blob 81 | AzureWebJobsStorage__credential : 'managedidentity' 82 | AzureWebJobsStorage__clientId : identityClientId 83 | APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights.properties.ConnectionString 84 | }) 85 | } 86 | } 87 | 88 | output name string = functions.name 89 | output uri string = 'https://${functions.properties.defaultHostName}' 90 | output identityPrincipalId string = identityType == 'SystemAssigned' ? functions.identity.principalId : '' 91 | -------------------------------------------------------------------------------- /csharp/infra/core/monitor/appinsights-access.bicep: -------------------------------------------------------------------------------- 1 | param principalID string 2 | param roleDefinitionID string 3 | param appInsightsName string 4 | 5 | resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { 6 | name: appInsightsName 7 | } 8 | 9 | // Allow access from API to app insights using a managed identity and least priv role 10 | resource appInsightsRoleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { 11 | name: guid(applicationInsights.id, principalID, roleDefinitionID) 12 | scope: applicationInsights 13 | properties: { 14 | roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionID) 15 | principalId: principalID 16 | principalType: 'ServicePrincipal' // Workaround for https://learn.microsoft.com/en-us/azure/role-based-access-control/role-assignments-template#new-service-principal 17 | } 18 | } 19 | 20 | output ROLE_ASSIGNMENT_NAME string = appInsightsRoleAssignment.name 21 | 22 | -------------------------------------------------------------------------------- /csharp/infra/core/monitor/applicationinsights.bicep: -------------------------------------------------------------------------------- 1 | param name string 2 | param location string = resourceGroup().location 3 | param tags object = {} 4 | 5 | param logAnalyticsWorkspaceId string 6 | param disableLocalAuth bool = false 7 | 8 | resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { 9 | name: name 10 | location: location 11 | tags: tags 12 | kind: 'web' 13 | properties: { 14 | Application_Type: 'web' 15 | WorkspaceResourceId: logAnalyticsWorkspaceId 16 | DisableLocalAuth: disableLocalAuth 17 | } 18 | } 19 | 20 | output connectionString string = applicationInsights.properties.ConnectionString 21 | output instrumentationKey string = applicationInsights.properties.InstrumentationKey 22 | output name string = applicationInsights.name 23 | -------------------------------------------------------------------------------- /csharp/infra/core/monitor/loganalytics.bicep: -------------------------------------------------------------------------------- 1 | param name string 2 | param location string = resourceGroup().location 3 | param tags object = {} 4 | 5 | resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = { 6 | name: name 7 | location: location 8 | tags: tags 9 | properties: any({ 10 | retentionInDays: 30 11 | features: { 12 | searchVersion: 1 13 | } 14 | sku: { 15 | name: 'PerGB2018' 16 | } 17 | }) 18 | } 19 | 20 | output id string = logAnalytics.id 21 | output name string = logAnalytics.name 22 | -------------------------------------------------------------------------------- /csharp/infra/core/monitor/monitoring.bicep: -------------------------------------------------------------------------------- 1 | param logAnalyticsName string 2 | param applicationInsightsName string 3 | param location string = resourceGroup().location 4 | param tags object = {} 5 | 6 | 7 | module logAnalytics 'loganalytics.bicep' = { 8 | name: 'loganalytics' 9 | params: { 10 | name: logAnalyticsName 11 | location: location 12 | tags: tags 13 | } 14 | } 15 | 16 | module applicationInsights 'applicationinsights.bicep' = { 17 | name: 'applicationinsights' 18 | params: { 19 | name: applicationInsightsName 20 | location: location 21 | tags: tags 22 | logAnalyticsWorkspaceId: logAnalytics.outputs.id 23 | disableLocalAuth: true 24 | } 25 | } 26 | 27 | output applicationInsightsConnectionString string = applicationInsights.outputs.connectionString 28 | output applicationInsightsInstrumentationKey string = applicationInsights.outputs.instrumentationKey 29 | output applicationInsightsName string = applicationInsights.outputs.name 30 | output logAnalyticsWorkspaceId string = logAnalytics.outputs.id 31 | output logAnalyticsWorkspaceName string = logAnalytics.outputs.name 32 | -------------------------------------------------------------------------------- /csharp/infra/core/security/identity/user-assigned.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates a Microsoft Entra user-assigned identity.' 2 | 3 | param name string 4 | param location string = resourceGroup().location 5 | param tags object = {} 6 | 7 | resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { 8 | name: name 9 | location: location 10 | tags: tags 11 | } 12 | 13 | output name string = identity.name 14 | output resourceId string = identity.id 15 | output principalId string = identity.properties.principalId 16 | output clientId string = identity.properties.clientId 17 | output tenantId string = identity.properties.tenantId 18 | -------------------------------------------------------------------------------- /csharp/infra/core/security/role/assignment.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates a role-based access control assignment.' 2 | 3 | @description('Id of the role definition to assign to the targeted principal and account.') 4 | param roleDefinitionId string 5 | 6 | @description('Id of the principal to assign the role definition for the account.') 7 | param principalId string 8 | 9 | @description('Type of principal associated with the principal Id.') 10 | param principalType string 11 | 12 | resource assignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 13 | name: guid(subscription().id, resourceGroup().id, principalId, roleDefinitionId) 14 | scope: resourceGroup() 15 | properties: { 16 | principalId: principalId 17 | roleDefinitionId: roleDefinitionId 18 | principalType: principalType != 'None' ? principalType : null 19 | } 20 | } 21 | 22 | output id string = assignment.id 23 | -------------------------------------------------------------------------------- /csharp/infra/core/security/role/definition.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates a role-based access control role definition.' 2 | 3 | @description('Name of the role definiton.') 4 | param definitionName string 5 | 6 | @description('Description for the role definition.') 7 | param definitionDescription string 8 | 9 | @description('Array of control-plane actions allowed for the role definition.') 10 | param actions string[] = [] 11 | 12 | @description('Array of control-plane actions disallowed for the role definition.') 13 | param notActions string[] = [] 14 | 15 | @description('Array of data-plane actions allowed for the role definition.') 16 | param dataActions string[] = [] 17 | 18 | @description('Array of data-plane actions disallowed for the role definition.') 19 | param notDataActions string[] = [] 20 | 21 | resource definition 'Microsoft.Authorization/roleDefinitions@2022-04-01' = { 22 | name: guid(subscription().id, resourceGroup().id) 23 | scope: resourceGroup() 24 | properties: { 25 | roleName: definitionName 26 | description: definitionDescription 27 | type: 'CustomRole' 28 | permissions: [ 29 | { 30 | actions: actions 31 | notActions: notActions 32 | dataActions: dataActions 33 | notDataActions: notDataActions 34 | } 35 | ] 36 | assignableScopes: [ 37 | resourceGroup().id 38 | ] 39 | } 40 | } 41 | 42 | output id string = definition.id 43 | -------------------------------------------------------------------------------- /csharp/infra/core/security/role/storage-Access.bicep: -------------------------------------------------------------------------------- 1 | param principalID string 2 | param principalType string = 'ServicePrincipal' // Workaround for https://learn.microsoft.com/en-us/azure/role-based-access-control/role-assignments-template#new-service-principal 3 | param roleDefinitionID string 4 | param storageAccountName string 5 | 6 | resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' existing = { 7 | name: storageAccountName 8 | } 9 | 10 | // Allow access from API to storage account using a managed identity and least priv Storage roles 11 | resource storageRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 12 | name: guid(storageAccount.id, principalID, roleDefinitionID) 13 | scope: storageAccount 14 | properties: { 15 | roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionID) 16 | principalId: principalID 17 | principalType: principalType 18 | } 19 | } 20 | 21 | output ROLE_ASSIGNMENT_NAME string = storageRoleAssignment.name 22 | -------------------------------------------------------------------------------- /csharp/infra/core/storage/storage-account.bicep: -------------------------------------------------------------------------------- 1 | param name string 2 | param location string = resourceGroup().location 3 | param tags object = {} 4 | 5 | param allowBlobPublicAccess bool = false 6 | param containers array = [] 7 | param kind string = 'StorageV2' 8 | param minimumTlsVersion string = 'TLS1_2' 9 | param sku object = { name: 'Standard_LRS' } 10 | param networkAcls object = { 11 | bypass: 'AzureServices' 12 | defaultAction: 'Allow' 13 | } 14 | 15 | resource storage 'Microsoft.Storage/storageAccounts@2023-05-01' = { 16 | name: name 17 | location: location 18 | tags: tags 19 | kind: kind 20 | sku: sku 21 | properties: { 22 | minimumTlsVersion: minimumTlsVersion 23 | allowBlobPublicAccess: allowBlobPublicAccess 24 | allowSharedKeyAccess: false 25 | networkAcls: networkAcls 26 | } 27 | 28 | resource blobServices 'blobServices' = if (!empty(containers)) { 29 | name: 'default' 30 | resource container 'containers' = [for container in containers: { 31 | name: container.name 32 | properties: { 33 | publicAccess: container.?publicAccess ?? 'None' 34 | } 35 | }] 36 | } 37 | } 38 | 39 | output name string = storage.name 40 | output primaryEndpoints object = storage.properties.primaryEndpoints 41 | output id string = storage.id 42 | -------------------------------------------------------------------------------- /csharp/infra/main.bicep: -------------------------------------------------------------------------------- 1 | targetScope = 'subscription' 2 | 3 | @minLength(1) 4 | @maxLength(64) 5 | @description('Name of the environment that can be used as part of naming resource convention.') 6 | param environmentName string 7 | 8 | @minLength(1) 9 | @allowed([ 10 | 'canadaeast' 11 | 'eastus' 12 | 'eastus2' 13 | 'francecentral' 14 | 'japaneast' 15 | 'norwayeast' 16 | 'polandcentral' 17 | 'southindia' 18 | 'swedencentral' 19 | 'switzerlandnorth' 20 | 'westus3' 21 | ]) 22 | @description('Primary location for all resources.') 23 | param location string 24 | 25 | @description('User Id of the principal to assign database and application roles.') 26 | param principalId string = '' 27 | 28 | // serviceName is sent to functions module and used as value for the tag (azd-service-name) azd uses to identify deployment host 29 | param serviceName string = 'embeddingGenerator' 30 | 31 | //Passed from main.parameters.json 32 | param functionsRuntimeName string = '' 33 | param functionsRuntimeVersion string = '' 34 | 35 | // Optional parameters 36 | param functionAccountName string = '' 37 | param functionAppPlanName string = '' 38 | param openAiAccountName string = '' 39 | param cosmosDbAccountName string = '' 40 | param storageAccountName string = '' 41 | param logAnalyticsName string = '' 42 | param appInsightsName string = '' 43 | param userAssignedIdentityName string = '' 44 | 45 | 46 | var abbreviations = loadJsonContent('abbreviations.json') 47 | var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) 48 | var tags = { 49 | 'azd-env-name': environmentName 50 | repo: 'https://github.com/AzureCosmosDB/cosmos-embeddings-generator' 51 | } 52 | var deploymentStorageContainerName = 'app-package-container' 53 | 54 | 55 | var cosmosSettings = { 56 | database: 'embeddings-db' 57 | container: 'customer' 58 | partitionKey: 'customerId' 59 | vectorProperty: 'vectors' 60 | hashProperty: 'hash' 61 | PropertyToEmbed: 'customerNotes' 62 | } 63 | 64 | var openAiSettings = { 65 | embeddingModelName: 'text-embedding-3-small' 66 | embeddingDeploymentName: 'text-3-small' 67 | dimensions: '1536' 68 | } 69 | 70 | resource resourceGroup 'Microsoft.Resources/resourceGroups@2022-09-01' = { 71 | name: environmentName 72 | location: location 73 | tags: tags 74 | } 75 | 76 | module identity 'app/identity.bicep' = { 77 | name: 'identity' 78 | scope: resourceGroup 79 | params: { 80 | identityName: !empty(userAssignedIdentityName) ? userAssignedIdentityName : '${abbreviations.userAssignedIdentity}-${resourceToken}' 81 | location: location 82 | tags: tags 83 | } 84 | } 85 | 86 | module ai 'app/ai.bicep' = { 87 | name: 'ai' 88 | scope: resourceGroup 89 | params: { 90 | accountName: !empty(openAiAccountName) ? openAiAccountName : '${abbreviations.openAiAccount}-${resourceToken}' 91 | location: location 92 | embeddingsModelName: openAiSettings.embeddingModelName 93 | embeddingsDeploymentName: openAiSettings.embeddingDeploymentName 94 | tags: tags 95 | } 96 | } 97 | 98 | // Backing storage for Azure functions backend processor 99 | module storage 'core/storage/storage-account.bicep' = { 100 | name: 'storage' 101 | scope: resourceGroup 102 | params: { 103 | name: !empty(storageAccountName) ? storageAccountName : '${abbreviations.storageAccount}${resourceToken}' 104 | location: location 105 | tags: tags 106 | containers: [ 107 | {name: deploymentStorageContainerName} 108 | ] 109 | } 110 | } 111 | 112 | module embeddingGenerator 'app/functions.bicep' = { 113 | name: 'embeddingGenerator' 114 | scope: resourceGroup 115 | params: { 116 | name: !empty(functionAccountName) ? functionAccountName : '${abbreviations.functionApp}-${resourceToken}' 117 | location: location 118 | tags: tags 119 | serviceName: serviceName 120 | planName: !empty(functionAppPlanName) ? functionAppPlanName : '${abbreviations.appServicePlan}-${resourceToken}' 121 | runtimeName: functionsRuntimeName 122 | runtimeVersion: functionsRuntimeVersion 123 | storageAccountName: storage.outputs.name 124 | deploymentStorageContainerName: deploymentStorageContainerName 125 | identityId: identity.outputs.resourceId 126 | identityClientId: identity.outputs.clientId 127 | applicationInsightsName: monitoring.outputs.applicationInsightsName 128 | appSettings: { 129 | COSMOS_DATABASE_NAME: cosmosSettings.database 130 | COSMOS_CONTAINER_NAME: cosmosSettings.container 131 | COSMOS_VECTOR_PROPERTY: cosmosSettings.vectorProperty 132 | COSMOS_HASH_PROPERTY: cosmosSettings.hashProperty 133 | COSMOS_PROPERTY_TO_EMBED: cosmosSettings.PropertyToEmbed 134 | COSMOS_CONNECTION__accountEndpoint: database.outputs.endpoint 135 | COSMOS_CONNECTION__credential: 'managedidentity' 136 | COSMOS_CONNECTION__clientId: identity.outputs.clientId 137 | OPENAI_ENDPOINT: ai.outputs.endpoint 138 | OPENAI_DEPLOYMENT_NAME: openAiSettings.embeddingDeploymentName 139 | OPENAI_DIMENSIONS: openAiSettings.dimensions 140 | } 141 | } 142 | } 143 | 144 | module database 'app/database.bicep' = { 145 | name: 'database' 146 | scope: resourceGroup 147 | params: { 148 | accountName: !empty(cosmosDbAccountName) ? cosmosDbAccountName : '${abbreviations.cosmosDbAccount}-${resourceToken}' 149 | location: location 150 | tags: tags 151 | databaseName: cosmosSettings.database 152 | containerNames: [cosmosSettings.container] 153 | partitionKeyName: cosmosSettings.partitionKey 154 | vectorPropertyName: cosmosSettings.vectorProperty 155 | } 156 | } 157 | 158 | // Monitor application with Azure Monitor 159 | module monitoring './core/monitor/monitoring.bicep' = { 160 | name: 'monitoring' 161 | scope: resourceGroup 162 | params: { 163 | location: location 164 | tags: tags 165 | logAnalyticsName: !empty(logAnalyticsName) ? logAnalyticsName : '${abbreviations.operationalInsightsWorkspaces}${resourceToken}' 166 | applicationInsightsName: !empty(appInsightsName) ? appInsightsName : '${abbreviations.insightsComponents}${resourceToken}' 167 | } 168 | } 169 | 170 | var monitoringRoleDefinitionId = '3913510d-42f4-4e42-8a64-420c390055eb' // Monitoring Metrics Publisher role ID 171 | 172 | // Allow access from api to application insights using a managed identity 173 | module appInsightsRoleAssignmentApi './core/monitor/appinsights-access.bicep' = { 174 | name: 'appInsightsRoleAssignmentapi' 175 | scope: resourceGroup 176 | params: { 177 | appInsightsName: monitoring.outputs.applicationInsightsName 178 | roleDefinitionID: monitoringRoleDefinitionId 179 | principalID: identity.outputs.principalId 180 | } 181 | } 182 | 183 | module security 'app/security.bicep' = { 184 | name: 'security' 185 | scope: resourceGroup 186 | params: { 187 | databaseAccountName: database.outputs.accountName 188 | storageAccountName: storage.outputs.name 189 | appPrincipalId: identity.outputs.principalId 190 | userPrincipalId: !empty(principalId) ? principalId : null 191 | } 192 | } 193 | 194 | output COSMOS_CONNECTION__accountEndpoint string = database.outputs.endpoint 195 | output COSMOS_DATABASE_NAME string = cosmosSettings.database 196 | output COSMOS_CONTAINER_NAME string = cosmosSettings.container 197 | output COSMOS_VECTOR_PROPERTY string = cosmosSettings.vectorProperty 198 | output COSMOS_HASH_PROPERTY string = cosmosSettings.hashProperty 199 | output COSMOS_PROPERTY_TO_EMBED string = cosmosSettings.PropertyToEmbed 200 | output OPENAI_ENDPOINT string = ai.outputs.endpoint 201 | output OPENAI_DEPLOYMENT_NAME string = openAiSettings.embeddingDeploymentName 202 | output OPENAI_DIMENSIONS string = openAiSettings.dimensions 203 | -------------------------------------------------------------------------------- /csharp/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 | "environmentName": { 6 | "value": "${AZURE_ENV_NAME}" 7 | }, 8 | "location": { 9 | "value": "${AZURE_LOCATION}" 10 | }, 11 | "principalId": { 12 | "value": "${AZURE_PRINCIPAL_ID}" 13 | }, 14 | "functionsRuntimeName": { 15 | "value": "dotnet-isolated" 16 | }, 17 | "functionsRuntimeVersion": { 18 | "value": "8.0" 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /csharp/infra/main.test.bicep: -------------------------------------------------------------------------------- 1 | // This file is for doing static analysis and contains sensible defaults 2 | // for the bicep analyser to minimise false-positives and provide the best results. 3 | 4 | // This file is not intended to be used as a runtime configuration file. 5 | 6 | targetScope = 'subscription' 7 | 8 | param environmentName string = 'testing' 9 | param location string = 'eastus' 10 | 11 | module main 'main.bicep' = { 12 | name: 'main' 13 | params: { 14 | environmentName: environmentName 15 | location: location 16 | principalId: '' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /csharp/infra/scripts/createlocalsettings.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | 3 | if (-not (Test-Path ".\local.settings.json")) { 4 | 5 | $output = azd env get-values 6 | 7 | # Parse the output to get the endpoint values 8 | $expectedKeys = @( 9 | "COSMOS_CONNECTION__accountEndpoint", 10 | "COSMOS_CONTAINER_NAME", 11 | "COSMOS_DATABASE_NAME", 12 | "COSMOS_HASH_PROPERTY", 13 | "COSMOS_PROPERTY_TO_EMBED", 14 | "COSMOS_VECTOR_PROPERTY", 15 | "OPENAI_DEPLOYMENT_NAME", 16 | "OPENAI_DIMENSIONS", 17 | "OPENAI_ENDPOINT" 18 | ) 19 | 20 | foreach ($line in $output) { 21 | foreach ($key in $expectedKeys) { 22 | if ($line -match $key) { 23 | Set-Variable -Name $key -Value (($line -split "=")[1] -replace '"','') 24 | } 25 | } 26 | } 27 | 28 | @{ 29 | "IsEncrypted" = "false"; 30 | "Values" = @{ 31 | "AzureWebJobsStorage" = "UseDevelopmentStorage=true"; 32 | "FUNCTIONS_WORKER_RUNTIME" = "dotnet-isolated"; 33 | "COSMOS_CONNECTION__accountEndpoint" = "$COSMOS_CONNECTION__accountEndpoint"; 34 | "COSMOS_CONTAINER_NAME" = "$COSMOS_CONTAINER_NAME"; 35 | "COSMOS_DATABASE_NAME" = "$COSMOS_DATABASE_NAME"; 36 | "COSMOS_HASH_PROPERTY" = "$COSMOS_HASH_PROPERTY"; 37 | "COSMOS_PROPERTY_TO_EMBED" = "$COSMOS_PROPERTY_TO_EMBED"; 38 | "COSMOS_VECTOR_PROPERTY" = "$COSMOS_VECTOR_PROPERTY"; 39 | "OPENAI_DEPLOYMENT_NAME" = "$OPENAI_DEPLOYMENT_NAME"; 40 | "OPENAI_DIMENSIONS" = "$OPENAI_DIMENSIONS"; 41 | "OPENAI_ENDPOINT" = "$OPENAI_ENDPOINT"; 42 | } 43 | } | ConvertTo-Json | Out-File -FilePath ".\local.settings.json" -Encoding ascii 44 | } -------------------------------------------------------------------------------- /csharp/infra/scripts/createlocalsettings.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [ ! -f "./local.settings.json" ]; then 6 | 7 | expected_keys=( 8 | "COSMOS_CONNECTION__accountEndpoint" 9 | "COSMOS_CONTAINER_NAME" 10 | "COSMOS_DATABASE_NAME" 11 | "COSMOS_HASH_PROPERTY" 12 | "COSMOS_PROPERTY_TO_EMBED" 13 | "COSMOS_VECTOR_PROPERTY" 14 | "OPENAI_DEPLOYMENT_NAME" 15 | "OPENAI_DIMENSIONS" 16 | "OPENAI_ENDPOINT" 17 | ) 18 | 19 | output=$(azd env get-values) 20 | 21 | while IFS= read -r line; do 22 | for key in "${expected_keys[@]}"; do 23 | if [[ $line == $key=* ]]; then 24 | value="${line#*=}" 25 | value="${value%\"}" # Remove trailing quote if present 26 | value="${value#\"}" # Remove leading quote if present 27 | export "$key"="$value" 28 | fi 29 | done 30 | done <<< "$output" 31 | 32 | cat < ./local.settings.json 33 | { 34 | "IsEncrypted": false, 35 | "Values": { 36 | "AzureWebJobsStorage": "UseDevelopmentStorage=true", 37 | "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", 38 | "COSMOS_CONNECTION__accountEndpoint": "$COSMOS_CONNECTION__accountEndpoint", 39 | "COSMOS_CONTAINER_NAME": "$COSMOS_CONTAINER_NAME", 40 | "COSMOS_DATABASE_NAME": "$COSMOS_DATABASE_NAME", 41 | "COSMOS_HASH_PROPERTY": "$COSMOS_HASH_PROPERTY", 42 | "COSMOS_PROPERTY_TO_EMBED": "$COSMOS_PROPERTY_TO_EMBED", 43 | "COSMOS_VECTOR_PROPERTY": "$COSMOS_VECTOR_PROPERTY", 44 | "OPENAI_DEPLOYMENT_NAME": "$OPENAI_DEPLOYMENT_NAME", 45 | "OPENAI_DIMENSIONS": "$OPENAI_DIMENSIONS", 46 | "OPENAI_ENDPOINT": "$OPENAI_ENDPOINT" 47 | } 48 | } 49 | EOF 50 | 51 | fi -------------------------------------------------------------------------------- /csharp/sample.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "IsEncrypted": false, 3 | "Values": { 4 | "AzureWebJobsStorage": "UseDevelopmentStorage=true", 5 | "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", 6 | "COSMOS_CONNECTION__accountEndpoint": "https://{my-cosmos-account}.documents.azure.com:443/", 7 | "OPENAI_ENDPOINT": "https://{my-open-ai-account}.openai.azure.com/", 8 | "COSMOS_DATABASE_NAME": "embeddings-db", 9 | "COSMOS_CONTAINER_NAME": "customer", 10 | "COSMOS_VECTOR_PROPERTY": "vectors", 11 | "COSMOS_HASH_PROPERTY": "hash", 12 | "COSMOS_PROPERTY_TO_EMBED": "customerNotes", 13 | "OPENAI_DEPLOYMENT_NAME": "text-3-small", 14 | "OPENAI_DIMENSIONS": "1536" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /media/sample-embeddings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AzureCosmosDB/cosmos-embeddings-generator/0d41cee794858418f4100389eb1265b9e84de17a/media/sample-embeddings.png -------------------------------------------------------------------------------- /python/.funcignore: -------------------------------------------------------------------------------- 1 | .git* 2 | .vscode 3 | __azurite_db*__.json 4 | __blobstorage__ 5 | __queuestorage__ 6 | local.settings.json 7 | test 8 | .venv -------------------------------------------------------------------------------- /python/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # Jupyter Notebook 76 | .ipynb_checkpoints 77 | 78 | # IPython 79 | profile_default/ 80 | ipython_config.py 81 | 82 | # pyenv 83 | .python-version 84 | 85 | # pipenv 86 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 87 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 88 | # having no cross-platform support, pipenv may install dependencies that don’t work, or not 89 | # install all needed dependencies. 90 | #Pipfile.lock 91 | 92 | # celery beat schedule file 93 | celerybeat-schedule 94 | 95 | # SageMath parsed files 96 | *.sage.py 97 | 98 | # Environments 99 | .env 100 | .venv 101 | env/ 102 | venv/ 103 | ENV/ 104 | env.bak/ 105 | venv.bak/ 106 | 107 | # Spyder project settings 108 | .spyderproject 109 | .spyproject 110 | 111 | # Rope project settings 112 | .ropeproject 113 | 114 | # mkdocs documentation 115 | /site 116 | 117 | # mypy 118 | .mypy_cache/ 119 | .dmypy.json 120 | dmypy.json 121 | 122 | # Pyre type checker 123 | .pyre/ 124 | 125 | # Azure Functions artifacts 126 | bin 127 | obj 128 | appsettings.json 129 | local.settings.json 130 | 131 | # Azurite artifacts 132 | __blobstorage__ 133 | __queuestorage__ 134 | __azurite_db*__.json 135 | .python_packages -------------------------------------------------------------------------------- /python/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-azuretools.vscode-azurefunctions", 4 | "ms-python.python" 5 | ] 6 | } -------------------------------------------------------------------------------- /python/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Attach to Python Functions", 6 | "type": "debugpy", 7 | "request": "attach", 8 | "connect": { 9 | "host": "localhost", 10 | "port": 9091 11 | }, 12 | "preLaunchTask": "func: host start" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /python/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "azureFunctions.deploySubpath": ".", 3 | "azureFunctions.scmDoBuildDuringDeployment": true, 4 | "azureFunctions.pythonVenv": ".venv", 5 | "azureFunctions.projectLanguage": "Python", 6 | "azureFunctions.projectRuntime": "~4", 7 | "debug.internalConsoleOptions": "neverOpen", 8 | "azureFunctions.projectLanguageModel": 2 9 | } -------------------------------------------------------------------------------- /python/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "func", 6 | "label": "func: host start", 7 | "command": "host start", 8 | "problemMatcher": "$func-python-watch", 9 | "isBackground": true, 10 | "dependsOn": "pip install (functions)" 11 | }, 12 | { 13 | "label": "pip install (functions)", 14 | "type": "shell", 15 | "osx": { 16 | "command": "${config:azureFunctions.pythonVenv}/bin/python -m pip install -r requirements.txt" 17 | }, 18 | "windows": { 19 | "command": "${config:azureFunctions.pythonVenv}/Scripts/python -m pip install -r requirements.txt" 20 | }, 21 | "linux": { 22 | "command": "${config:azureFunctions.pythonVenv}/bin/python -m pip install -r requirements.txt" 23 | }, 24 | "problemMatcher": [] 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /python/azure.yaml: -------------------------------------------------------------------------------- 1 | name: cosmos-embeddings-generator 2 | metadata: 3 | template: cosmos-embeddings-generator 4 | services: 5 | embeddingGenerator: 6 | project: ./ 7 | language: python 8 | host: function 9 | hooks: 10 | postprovision: 11 | windows: 12 | shell: pwsh 13 | run: .\infra\scripts\createlocalsettings.ps1 14 | interactive: true 15 | continueOnError: false 16 | posix: 17 | shell: sh 18 | run: ./infra/scripts/createlocalsettings.sh 19 | interactive: true 20 | continueOnError: false 21 | -------------------------------------------------------------------------------- /python/function_app.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | import hashlib 4 | import json 5 | import azure.functions as func 6 | from openai import AzureOpenAI 7 | from azure.identity import DefaultAzureCredential, get_bearer_token_provider 8 | 9 | 10 | # Retrieve application setings from local.settings.json 11 | OPENAI_ENDPOINT = os.environ["OPENAI_ENDPOINT"] 12 | OPENAI_DEPLOYMENT_NAME = os.environ["OPENAI_DEPLOYMENT_NAME"] 13 | OPENAI_DIMENSIONS = int(os.environ["OPENAI_DIMENSIONS"]) 14 | 15 | COSMOS_DATABASE_NAME = os.environ["COSMOS_DATABASE_NAME"] 16 | COSMOS_CONTAINER_NAME = os.environ["COSMOS_CONTAINER_NAME"] 17 | COSMOS_VECTOR_PROPERTY = os.environ["COSMOS_VECTOR_PROPERTY"] 18 | COSMOS_HASH_PROPERTY = os.environ["COSMOS_HASH_PROPERTY"] 19 | COSMOS_PROPERTY_TO_EMBED = os.environ["COSMOS_PROPERTY_TO_EMBED"] 20 | 21 | token_provider = get_bearer_token_provider(DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default") 22 | 23 | # Initialize OpenAI client 24 | OPENAI_CLIENT = AzureOpenAI( 25 | azure_endpoint = OPENAI_ENDPOINT, 26 | azure_ad_token_provider=token_provider, 27 | api_version = "2024-02-01" 28 | ) 29 | 30 | 31 | # Initialize the function app 32 | app = func.FunctionApp() 33 | 34 | 35 | @app.function_name(name="cosmos-embedding-generator") 36 | @app.cosmos_db_output( 37 | arg_name="output", 38 | database_name="%COSMOS_DATABASE_NAME%", 39 | container_name="%COSMOS_CONTAINER_NAME%", 40 | connection="COSMOS_CONNECTION") 41 | @app.cosmos_db_trigger( 42 | arg_name="input", 43 | database_name="%COSMOS_DATABASE_NAME%", 44 | container_name="%COSMOS_CONTAINER_NAME%", 45 | connection="COSMOS_CONNECTION", 46 | lease_container_name="leases", 47 | create_lease_container_if_not_exists=True) 48 | async def cosmos_embedding_generator(input: func.DocumentList, output: func.Out[func.DocumentList]): 49 | """ 50 | This function listens for changes to new or existing CosmosDb documents/items, 51 | and updates them in place with vector embeddings. 52 | 53 | The expected document/item has at least these 3 properties, and note that 'customerNotes' 54 | is the property that gets embedded. 55 | 56 | Example document: 57 | { 58 | "id": "00001", 59 | "customerId": "10001", 60 | "customerNotes": "lorum ipsum." 61 | } 62 | """ 63 | 64 | if input: 65 | logging.info('Documents modified: %s', len(input)) 66 | updatedDocs = func.DocumentList() 67 | for document in input: 68 | json_document = document.to_dict() 69 | 70 | # Check hash value to see if document is new or modified 71 | is_new, hash_value = is_document_new_or_modified(json_document) 72 | 73 | if is_new: 74 | 75 | # Generate embeddings on the specified document property or document 76 | embeddings = get_embeddings(json_document[COSMOS_PROPERTY_TO_EMBED]) 77 | #embeddings = get_embeddings(json.dumps(json_document)) 78 | 79 | # Add the hash to the document 80 | json_document[COSMOS_HASH_PROPERTY] = hash_value 81 | 82 | # Add the embeddings to the document 83 | json_document[COSMOS_VECTOR_PROPERTY] = embeddings 84 | 85 | # Serialize the result and return it to the output binding 86 | #output.set(func.Document.from_json(json.dumps(json_document))) 87 | updatedDocs.append(func.Document.from_json(json.dumps(json_document))) 88 | 89 | if len(updatedDocs) > 0: 90 | logging.info('Documents to be updated: %s', len(updatedDocs)) 91 | output.set(updatedDocs) 92 | 93 | 94 | def is_document_new_or_modified(json_document: dict) -> tuple[bool, str]: 95 | 96 | # No hash property, document is new 97 | if COSMOS_HASH_PROPERTY not in json_document: 98 | new_hash = compute_json_hash(json_document) 99 | return True, new_hash 100 | 101 | # Save the existing hash 102 | existing_hash = json_document[COSMOS_HASH_PROPERTY] 103 | 104 | # Generate a hash of the document/property 105 | new_hash = compute_json_hash(json_document) 106 | 107 | # Document has changed, process it 108 | if new_hash != existing_hash: 109 | return True, new_hash 110 | 111 | # Document has not changed, skip processing 112 | return False, "" 113 | 114 | def get_embeddings(input_text: str) -> list: 115 | 116 | response = OPENAI_CLIENT.embeddings.create( 117 | input = input_text, 118 | dimensions = OPENAI_DIMENSIONS, 119 | model = OPENAI_DEPLOYMENT_NAME) 120 | 121 | embeddings = response.model_dump() 122 | return embeddings['data'][0]['embedding'] 123 | 124 | def compute_json_hash(json_document: dict) -> str: 125 | 126 | # Cleanse the document of system, vector and hash properties 127 | json_document = cleanse_document_properties(json_document) 128 | 129 | # Generate hash on the property to generate embedding on 130 | property = json_document[COSMOS_PROPERTY_TO_EMBED] 131 | 132 | # Compute a hash on entire document if generating embeddings on the document 133 | # Re-serialize the JSON to canonical form (sorted keys, no extra whitespace) 134 | #canonical_json = json.dumps(json_document, sort_keys=True) 135 | 136 | # Compute SHA256 hash 137 | #hash_object = hashlib.sha256(canonical_json.encode()) 138 | hash_object = hashlib.sha256(property.encode()) 139 | return hash_object.hexdigest() 140 | 141 | def cleanse_document_properties(json_document: dict) -> dict: 142 | 143 | json_document.pop(COSMOS_VECTOR_PROPERTY, None) 144 | json_document.pop(COSMOS_HASH_PROPERTY, None) 145 | json_document.pop("_rid", None) 146 | json_document.pop("_self", None) 147 | json_document.pop("_etag", None) 148 | json_document.pop("_attachments", None) 149 | json_document.pop("_lsn", None) 150 | json_document.pop("_ts", None) 151 | 152 | return json_document 153 | -------------------------------------------------------------------------------- /python/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "logging": { 4 | "applicationInsights": { 5 | "samplingSettings": { 6 | "isEnabled": true, 7 | "excludedTypes": "Request" 8 | } 9 | } 10 | }, 11 | "extensionBundle": { 12 | "id": "Microsoft.Azure.Functions.ExtensionBundle", 13 | "version": "[4.*, 5.0.0)" 14 | } 15 | } -------------------------------------------------------------------------------- /python/infra/abbreviations.json: -------------------------------------------------------------------------------- 1 | { 2 | "cosmosDbAccount": "cosmos", 3 | "openAiAccount": "openai", 4 | "userAssignedIdentity": "ua-id", 5 | "appServiceWebApp": "web", 6 | "appServicePlan": "plan", 7 | "functionApp": "func", 8 | "storageAccount": "strg", 9 | "analysisServicesServers": "as", 10 | "apiManagementService": "apim-", 11 | "appConfigurationConfigurationStores": "appcs-", 12 | "appManagedEnvironments": "cae-", 13 | "appContainerApps": "ca-", 14 | "authorizationPolicyDefinitions": "policy-", 15 | "automationAutomationAccounts": "aa-", 16 | "blueprintBlueprints": "bp-", 17 | "blueprintBlueprintsArtifacts": "bpa-", 18 | "cacheRedis": "redis-", 19 | "cdnProfiles": "cdnp-", 20 | "cdnProfilesEndpoints": "cdne-", 21 | "cognitiveServicesAccounts": "cog-", 22 | "cognitiveServicesFormRecognizer": "cog-fr-", 23 | "cognitiveServicesTextAnalytics": "cog-ta-", 24 | "computeAvailabilitySets": "avail-", 25 | "computeCloudServices": "cld-", 26 | "computeDiskEncryptionSets": "des", 27 | "computeDisks": "disk", 28 | "computeDisksOs": "osdisk", 29 | "computeGalleries": "gal", 30 | "computeSnapshots": "snap-", 31 | "computeVirtualMachines": "vm", 32 | "computeVirtualMachineScaleSets": "vmss-", 33 | "containerInstanceContainerGroups": "ci", 34 | "containerRegistryRegistries": "cr", 35 | "containerServiceManagedClusters": "aks-", 36 | "databricksWorkspaces": "dbw-", 37 | "dataFactoryFactories": "adf-", 38 | "dataLakeAnalyticsAccounts": "dla", 39 | "dataLakeStoreAccounts": "dls", 40 | "dataMigrationServices": "dms-", 41 | "dBforMySQLServers": "mysql-", 42 | "dBforPostgreSQLServers": "psql-", 43 | "devicesIotHubs": "iot-", 44 | "devicesProvisioningServices": "provs-", 45 | "devicesProvisioningServicesCertificates": "pcert-", 46 | "documentDBDatabaseAccounts": "cosmos-", 47 | "eventGridDomains": "evgd-", 48 | "eventGridDomainsTopics": "evgt-", 49 | "eventGridEventSubscriptions": "evgs-", 50 | "eventHubNamespaces": "evhns-", 51 | "eventHubNamespacesEventHubs": "evh-", 52 | "hdInsightClustersHadoop": "hadoop-", 53 | "hdInsightClustersHbase": "hbase-", 54 | "hdInsightClustersKafka": "kafka-", 55 | "hdInsightClustersMl": "mls-", 56 | "hdInsightClustersSpark": "spark-", 57 | "hdInsightClustersStorm": "storm-", 58 | "hybridComputeMachines": "arcs-", 59 | "insightsActionGroups": "ag-", 60 | "insightsComponents": "appi-", 61 | "keyVaultVaults": "kv-", 62 | "kubernetesConnectedClusters": "arck", 63 | "kustoClusters": "dec", 64 | "kustoClustersDatabases": "dedb", 65 | "logicIntegrationAccounts": "ia-", 66 | "logicWorkflows": "logic-", 67 | "machineLearningServicesWorkspaces": "mlw-", 68 | "managedIdentityUserAssignedIdentities": "id-", 69 | "managementManagementGroups": "mg-", 70 | "migrateAssessmentProjects": "migr-", 71 | "networkApplicationGateways": "agw-", 72 | "networkApplicationSecurityGroups": "asg-", 73 | "networkAzureFirewalls": "afw-", 74 | "networkBastionHosts": "bas-", 75 | "networkConnections": "con-", 76 | "networkDnsZones": "dnsz-", 77 | "networkExpressRouteCircuits": "erc-", 78 | "networkFirewallPolicies": "afwp-", 79 | "networkFirewallPoliciesWebApplication": "waf", 80 | "networkFirewallPoliciesRuleGroups": "wafrg", 81 | "networkFrontDoors": "fd-", 82 | "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", 83 | "networkLoadBalancersExternal": "lbe-", 84 | "networkLoadBalancersInternal": "lbi-", 85 | "networkLoadBalancersInboundNatRules": "rule-", 86 | "networkLocalNetworkGateways": "lgw-", 87 | "networkNatGateways": "ng-", 88 | "networkNetworkInterfaces": "nic-", 89 | "networkNetworkSecurityGroups": "nsg-", 90 | "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", 91 | "networkNetworkWatchers": "nw-", 92 | "networkPrivateDnsZones": "pdnsz-", 93 | "networkPrivateLinkServices": "pl-", 94 | "networkPublicIPAddresses": "pip-", 95 | "networkPublicIPPrefixes": "ippre-", 96 | "networkRouteFilters": "rf-", 97 | "networkRouteTables": "rt-", 98 | "networkRouteTablesRoutes": "udr-", 99 | "networkTrafficManagerProfiles": "traf-", 100 | "networkVirtualNetworkGateways": "vgw-", 101 | "networkVirtualNetworks": "vnet-", 102 | "networkVirtualNetworksSubnets": "snet-", 103 | "networkVirtualNetworksVirtualNetworkPeerings": "peer-", 104 | "networkVirtualWans": "vwan-", 105 | "networkVpnGateways": "vpng-", 106 | "networkVpnGatewaysVpnConnections": "vcn-", 107 | "networkVpnGatewaysVpnSites": "vst-", 108 | "notificationHubsNamespaces": "ntfns-", 109 | "notificationHubsNamespacesNotificationHubs": "ntf-", 110 | "operationalInsightsWorkspaces": "log-", 111 | "portalDashboards": "dash-", 112 | "powerBIDedicatedCapacities": "pbi-", 113 | "purviewAccounts": "pview-", 114 | "recoveryServicesVaults": "rsv-", 115 | "resourcesResourceGroups": "rg-", 116 | "searchSearchServices": "srch-", 117 | "serviceBusNamespaces": "sb-", 118 | "serviceBusNamespacesQueues": "sbq-", 119 | "serviceBusNamespacesTopics": "sbt-", 120 | "serviceEndPointPolicies": "se-", 121 | "serviceFabricClusters": "sf-", 122 | "signalRServiceSignalR": "sigr", 123 | "sqlManagedInstances": "sqlmi-", 124 | "sqlServers": "sql-", 125 | "sqlServersDataWarehouse": "sqldw-", 126 | "sqlServersDatabases": "sqldb-", 127 | "sqlServersDatabasesStretch": "sqlstrdb-", 128 | "storageStorageAccountsVm": "stvm", 129 | "storSimpleManagers": "ssimp", 130 | "streamAnalyticsCluster": "asa-", 131 | "synapseWorkspaces": "syn", 132 | "synapseWorkspacesAnalyticsWorkspaces": "synw", 133 | "synapseWorkspacesSqlPoolsDedicated": "syndp", 134 | "synapseWorkspacesSqlPoolsSpark": "synsp", 135 | "timeSeriesInsightsEnvironments": "tsi-", 136 | "webServerFarms": "plan-", 137 | "webSitesAppService": "app-", 138 | "webSitesAppServiceEnvironment": "ase-", 139 | "webSitesFunctions": "func-", 140 | "webStaticSites": "stapp-" 141 | } -------------------------------------------------------------------------------- /python/infra/app/ai.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Create AI accounts.' 2 | 3 | param accountName string 4 | param location string = resourceGroup().location 5 | param tags object = {} 6 | param embeddingsModelName string 7 | param embeddingsDeploymentName string 8 | 9 | var deployments = [ 10 | { 11 | name: embeddingsDeploymentName 12 | skuCapacity: 5 13 | modelName: embeddingsModelName 14 | modelVersion: '1' 15 | } 16 | ] 17 | 18 | module openAiAccount '../core/ai/cognitive-services/account.bicep' = { 19 | name: 'openai-account' 20 | params: { 21 | name: accountName 22 | location: location 23 | tags: tags 24 | kind: 'OpenAI' 25 | sku: 'S0' 26 | enablePublicNetworkAccess: true 27 | } 28 | } 29 | 30 | @batchSize(1) 31 | module openAiModelDeployments '../core/ai/cognitive-services/deployment.bicep' = [ 32 | for (deployment, _) in deployments: { 33 | name: 'openai-model-deployment-${deployment.name}' 34 | params: { 35 | name: deployment.name 36 | parentAccountName: openAiAccount.outputs.name 37 | skuName: 'Standard' 38 | skuCapacity: deployment.skuCapacity 39 | modelName: deployment.modelName 40 | modelVersion: deployment.modelVersion 41 | modelFormat: 'OpenAI' 42 | } 43 | } 44 | ] 45 | 46 | output name string = openAiAccount.outputs.name 47 | output endpoint string = openAiAccount.outputs.endpoint 48 | output key string = openAiAccount.outputs.key 49 | output deployments array = [ 50 | for (_, index) in deployments: { 51 | name: openAiModelDeployments[index].outputs.name 52 | } 53 | ] 54 | 55 | -------------------------------------------------------------------------------- /python/infra/app/database.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Create database accounts.' 2 | 3 | param location string = resourceGroup().location 4 | param tags object = {} 5 | 6 | param accountName string 7 | param databaseName string 8 | param containerNames array 9 | param partitionKeyName string 10 | param vectorPropertyName string 11 | 12 | var database = { 13 | name: databaseName // Database for application 14 | } 15 | 16 | // concatenate / with the partition key and vector property names 17 | var partitionKeyPath = '/${partitionKeyName}' 18 | var vectorPath = '/${vectorPropertyName}' 19 | var vectorExcludedIndexPath = '${vectorPath}/?' 20 | 21 | var containers = [for containerName in containerNames:{ 22 | name: containerName // Container for products 23 | partitionKeyPaths: [ 24 | partitionKeyPath // Partition for product data 25 | ] 26 | indexingPolicy: { 27 | automatic: true 28 | indexingMode: 'consistent' 29 | includedPaths: [ 30 | { 31 | path: '/*' 32 | } 33 | ] 34 | excludedPaths: [ 35 | { 36 | path: vectorExcludedIndexPath 37 | } 38 | ] 39 | vectorIndexes: [ 40 | { 41 | path: vectorPath 42 | type: 'quantizedFlat' //'diskANN' 43 | } 44 | ] 45 | } 46 | vectorEmbeddingPolicy: { 47 | vectorEmbeddings: [ 48 | { 49 | path: vectorPath 50 | dataType: 'float32' 51 | dimensions: 1536 52 | distanceFunction: 'cosine' 53 | } 54 | ] 55 | } 56 | } 57 | ] 58 | 59 | module cosmosDbAccount '../core/database/cosmos-db/nosql/account.bicep' = { 60 | name: 'cosmos-db-account' 61 | params: { 62 | name: accountName 63 | location: location 64 | tags: tags 65 | enableServerless: true 66 | enableVectorSearch: true 67 | enableNoSQLFullTextSearch: false 68 | disableKeyBasedAuth: true 69 | } 70 | } 71 | 72 | module cosmosDbDatabase '../core/database/cosmos-db/nosql/database.bicep' = { 73 | name: 'cosmos-db-database-${database.name}' 74 | params: { 75 | name: database.name 76 | parentAccountName: cosmosDbAccount.outputs.name 77 | tags: tags 78 | setThroughput: false 79 | } 80 | } 81 | 82 | module cosmosDbContainers '../core/database/cosmos-db/nosql/container.bicep' = [ 83 | for (container, _) in containers: { 84 | name: 'cosmos-db-container-${container.name}' 85 | params: { 86 | name: container.name 87 | parentAccountName: cosmosDbAccount.outputs.name 88 | parentDatabaseName: cosmosDbDatabase.outputs.name 89 | tags: tags 90 | setThroughput: false 91 | partitionKeyPaths: container.partitionKeyPaths 92 | indexingPolicy: container.indexingPolicy 93 | vectorEmbeddingPolicy: container.vectorEmbeddingPolicy 94 | } 95 | } 96 | ] 97 | 98 | module cosmosDbLeases '../core/database/cosmos-db/nosql/container.bicep' = { 99 | name: 'cosmos-db-container-leases' 100 | params: { 101 | name: 'leases' 102 | parentAccountName: cosmosDbAccount.outputs.name 103 | parentDatabaseName: cosmosDbDatabase.outputs.name 104 | tags: tags 105 | setThroughput: false 106 | partitionKeyPaths: ['/id'] 107 | } 108 | } 109 | 110 | output endpoint string = cosmosDbAccount.outputs.endpoint 111 | output accountName string = cosmosDbAccount.outputs.name 112 | output connectionString string = cosmosDbAccount.outputs.connectionString 113 | 114 | output database object = { 115 | name: cosmosDbDatabase.outputs.name 116 | } 117 | output containers array = [ 118 | for (_, index) in containers: { 119 | name: cosmosDbContainers[index].outputs.name 120 | } 121 | ] 122 | -------------------------------------------------------------------------------- /python/infra/app/functions.bicep: -------------------------------------------------------------------------------- 1 | param name string 2 | param location string = resourceGroup().location 3 | param tags object = {} 4 | param applicationInsightsName string = '' 5 | param planName string 6 | param appSettings object = {} 7 | param runtimeName string 8 | param runtimeVersion string 9 | param serviceName string 10 | param storageAccountName string 11 | param deploymentStorageContainerName string 12 | param virtualNetworkSubnetId string = '' 13 | param instanceMemoryMB int = 2048 14 | param maximumInstanceCount int = 100 15 | param identityId string = '' 16 | param identityClientId string = '' 17 | 18 | var applicationInsightsIdentity = 'ClientId=${identityClientId};Authorization=AAD' 19 | 20 | module appServicePlan '../core/host/functions/appserviceplan.bicep' = { 21 | name: 'appserviceplan' 22 | params: { 23 | name: planName 24 | location: location 25 | tags: tags 26 | sku: { 27 | name: 'FC1' 28 | tier: 'FlexConsumption' 29 | } 30 | } 31 | } 32 | 33 | module function '../core/host/functions/flexconsumption.bicep' = { 34 | name: '${serviceName}-functions-module' 35 | params: { 36 | name: name 37 | location: location 38 | tags: union(tags, { 'azd-service-name': serviceName }) 39 | identityType: 'UserAssigned' 40 | identityId: identityId 41 | identityClientId: identityClientId 42 | appSettings: union(appSettings, 43 | { 44 | AzureWebJobsStorage__clientId : identityClientId 45 | APPLICATIONINSIGHTS_AUTHENTICATION_STRING: applicationInsightsIdentity 46 | AZURE_CLIENT_ID: identityClientId 47 | }) 48 | applicationInsightsName: applicationInsightsName 49 | appServicePlanId: appServicePlan.outputs.id 50 | runtimeName: runtimeName 51 | runtimeVersion: runtimeVersion 52 | storageAccountName: storageAccountName 53 | deploymentStorageContainerName: deploymentStorageContainerName 54 | instanceMemoryMB: instanceMemoryMB 55 | maximumInstanceCount: maximumInstanceCount 56 | } 57 | } 58 | 59 | output SERVICE_API_NAME string = function.outputs.name 60 | output SERVICE_API_URI string = function.outputs.uri 61 | output SERVICE_API_IDENTITY_PRINCIPAL_ID string = function.outputs.identityPrincipalId 62 | -------------------------------------------------------------------------------- /python/infra/app/identity.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Create identity resources.' 2 | 3 | param identityName string 4 | param location string = resourceGroup().location 5 | param tags object = {} 6 | 7 | module userAssignedIdentity '../core/security/identity/user-assigned.bicep' = { 8 | name: 'user-assigned-identity' 9 | params: { 10 | name: identityName 11 | location: location 12 | tags: tags 13 | } 14 | } 15 | 16 | output name string = userAssignedIdentity.outputs.name 17 | output resourceId string = userAssignedIdentity.outputs.resourceId 18 | output principalId string = userAssignedIdentity.outputs.principalId 19 | output clientId string = userAssignedIdentity.outputs.clientId 20 | -------------------------------------------------------------------------------- /python/infra/app/security.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Create role assignment and definition resources.' 2 | 3 | 4 | @description('Id of the service principals to assign database and application roles.') 5 | param appPrincipalId string = '' 6 | 7 | @description('Id of the user principals to assign database and application roles.') 8 | param userPrincipalId string = '' 9 | 10 | 11 | param storageAccountName string 12 | resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' existing = { 13 | name: storageAccountName 14 | } 15 | 16 | param databaseAccountName string 17 | resource database 'Microsoft.DocumentDB/databaseAccounts@2023-04-15' existing = { 18 | name: databaseAccountName 19 | } 20 | 21 | module nosqlDefinition '../core/database/cosmos-db/nosql/role/definition.bicep' = { 22 | name: 'nosql-role-definition' 23 | params: { 24 | targetAccountName: database.name // Existing account 25 | definitionName: 'Write to Azure Cosmos DB for NoSQL data plane' // Custom role name 26 | permissionsDataActions: [ 27 | 'Microsoft.DocumentDB/databaseAccounts/readMetadata' // Read account metadata 28 | 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*' // Create items 29 | 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*' // Manage items 30 | ] 31 | } 32 | } 33 | 34 | module nosqlAppAssignment '../core/database/cosmos-db/nosql/role/assignment.bicep' = if (!empty(appPrincipalId)) { 35 | name: 'nosql-role-assignment-app' 36 | params: { 37 | targetAccountName: database.name // Existing account 38 | roleDefinitionId: nosqlDefinition.outputs.id // New role definition 39 | principalId: appPrincipalId // Principal to assign role 40 | principalType: 'ServicePrincipal' // Principal type for assigning role 41 | } 42 | } 43 | 44 | module nosqlUserAssignment '../core/database/cosmos-db/nosql/role/assignment.bicep' = if (!empty(userPrincipalId)) { 45 | name: 'nosql-role-assignment-user' 46 | params: { 47 | targetAccountName: database.name // Existing account 48 | roleDefinitionId: nosqlDefinition.outputs.id // New role definition 49 | principalId: userPrincipalId ?? '' // Principal to assign role 50 | principalType: 'User' // Principal type for assigning role 51 | } 52 | } 53 | 54 | module openaiAppAssignment '../core/security/role/assignment.bicep' = if (!empty(appPrincipalId)) { 55 | name: 'openai-role-assignment-read-app' 56 | params: { 57 | roleDefinitionId: subscriptionResourceId( 58 | 'Microsoft.Authorization/roleDefinitions', 59 | '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' 60 | ) // Cognitive Services OpenAI User built-in role 61 | principalId: appPrincipalId // Principal to assign role 62 | principalType: 'ServicePrincipal' // Specify the principal type // was 'None' but this appears to cause issues 63 | } 64 | } 65 | 66 | module openaiUserAssignment '../core/security/role/assignment.bicep' = if (!empty(userPrincipalId)) { 67 | name: 'openai-role-assignment-read-user' 68 | params: { 69 | roleDefinitionId: subscriptionResourceId( 70 | 'Microsoft.Authorization/roleDefinitions', 71 | '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd' 72 | ) // Cognitive Services OpenAI User built-in role 73 | principalId: userPrincipalId // Principal to assign role 74 | principalType: 'User' // Principal type or current deployment user 75 | } 76 | } 77 | 78 | // Allow access from API to storage account to user identity 79 | resource storageUserAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 80 | name: guid(storageAccountName, userPrincipalId, 'storage-blob-owner') 81 | scope: storageAccount 82 | properties: { 83 | roleDefinitionId: subscriptionResourceId( 84 | 'Microsoft.Authorization/roleDefinitions', 85 | 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b') // Storage Blob Data Owner role 86 | principalId: userPrincipalId // Principal to assign role 87 | principalType: 'User' // Principal type or current deployment user 88 | } 89 | } 90 | 91 | // Allow access from API to storage account to user identity 92 | resource storageAppAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 93 | name: guid(storageAccountName, appPrincipalId, 'storage-blob-owner') 94 | scope: storageAccount 95 | properties: { 96 | roleDefinitionId: subscriptionResourceId( 97 | 'Microsoft.Authorization/roleDefinitions', 98 | 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b') // Storage Blob Data Owner role 99 | principalId: appPrincipalId // Principal to assign role 100 | principalType: 'ServicePrincipal' 101 | } 102 | } 103 | 104 | //output roleDefinitions object = { 105 | // nosql: nosqlDefinition.outputs.id 106 | //} 107 | 108 | //output roleAssignments array = union( 109 | // !empty(appPrincipalId) ? [nosqlAppAssignment.outputs.id, openaiAppAssignment.outputs.id] : [], 110 | // !empty(userPrincipalId) ? [nosqlUserAssignment.outputs.id, openaiUserAssignment.outputs.id] : [] 111 | //) 112 | -------------------------------------------------------------------------------- /python/infra/app/web.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Create web apps.' 2 | 3 | param planName string 4 | param appName string 5 | param serviceTag string 6 | param location string = resourceGroup().location 7 | param tags object = {} 8 | 9 | @description('SKU of the App Service Plan.') 10 | param sku string = 'B1' 11 | 12 | @description('Endpoint for Azure Cosmos DB for NoSQL account.') 13 | param databaseAccountEndpoint string 14 | 15 | @description('Endpoint for Azure OpenAI account.') 16 | param openAiAccountEndpoint string 17 | 18 | type openAiOptions = { 19 | completionDeploymentName: string 20 | embeddingDeploymentName: string 21 | maxRagTokens: string 22 | maxContextTokens: string 23 | } 24 | 25 | @description('Application configuration settings for OpenAI.') 26 | param openAiSettings openAiOptions 27 | 28 | type cosmosDbOptions = { 29 | database: string 30 | chatContainer: string 31 | cacheContainer: string 32 | productContainer: string 33 | productDataSourceUri: string 34 | } 35 | @description('Application configuration settings for Azure Cosmos DB.') 36 | param cosmosDbSettings cosmosDbOptions 37 | 38 | type chatOptions = { 39 | maxContextWindow: string 40 | cacheSimilarityScore: string 41 | productMaxResults: string 42 | } 43 | 44 | @description('Application configuration settings for Chat Service.') 45 | param chatSettings chatOptions 46 | 47 | type managedIdentity = { 48 | resourceId: string 49 | clientId: string 50 | } 51 | 52 | @description('Unique identifier for user-assigned managed identity.') 53 | param userAssignedManagedIdentity managedIdentity 54 | 55 | module appServicePlan '../core/host/app-service/plan.bicep' = { 56 | name: 'app-service-plan' 57 | params: { 58 | name: planName 59 | location: location 60 | tags: tags 61 | sku: sku 62 | kind: 'linux' 63 | } 64 | } 65 | 66 | module appServiceWebApp '../core/host/app-service/site.bicep' = { 67 | name: 'app-service-web-app' 68 | params: { 69 | name: appName 70 | location: location 71 | tags: union(tags, { 72 | 'azd-service-name': serviceTag 73 | }) 74 | parentPlanName: appServicePlan.outputs.name 75 | runtimeName: 'dotnetcore' 76 | runtimeVersion: '8.0' 77 | kind: 'app,linux' 78 | enableSystemAssignedManagedIdentity: false 79 | userAssignedManagedIdentityIds: [ 80 | userAssignedManagedIdentity.resourceId 81 | ] 82 | } 83 | } 84 | 85 | module appServiceWebAppConfig '../core/host/app-service/config.bicep' = { 86 | name: 'app-service-config' 87 | params: { 88 | parentSiteName: appServiceWebApp.outputs.name 89 | appSettings: { 90 | OPENAI__ENDPOINT: openAiAccountEndpoint 91 | OPENAI__COMPLETIONDEPLOYMENTNAME: openAiSettings.completionDeploymentName 92 | OPENAI__EMBEDDINGDEPLOYMENTNAME: openAiSettings.embeddingDeploymentName 93 | OPENAI__MAXRAGTOKENS: openAiSettings.maxRagTokens 94 | OPENAI__MAXCONTEXTTOKENS: openAiSettings.maxContextTokens 95 | COSMOSDB__ENDPOINT: databaseAccountEndpoint 96 | COSMOSDB__DATABASE: cosmosDbSettings.database 97 | COSMOSDB__CHATCONTAINER: cosmosDbSettings.chatContainer 98 | COSMOSDB__CACHECONTAINER: cosmosDbSettings.cacheContainer 99 | COSMOSDB__PRODUCTCONTAINER: cosmosDbSettings.productContainer 100 | COSMOSDB__PRODUCTDATASOURCEURI: cosmosDbSettings.productDataSourceUri 101 | CHAT__MAXCONTEXTWINDOW: chatSettings.maxContextWindow 102 | CHAT__CACHESIMILARITYSCORE: chatSettings.cacheSimilarityScore 103 | CHAT__PRODUCTMAXRESULTS: chatSettings.productMaxResults 104 | AZURE_CLIENT_ID: userAssignedManagedIdentity.clientId 105 | } 106 | } 107 | } 108 | 109 | output name string = appServiceWebApp.outputs.name 110 | output endpoint string = appServiceWebApp.outputs.endpoint 111 | -------------------------------------------------------------------------------- /python/infra/core/ai/cognitive-services/account.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates an Azure Cognitive Services account.' 2 | 3 | param name string 4 | param location string = resourceGroup().location 5 | param tags object = {} 6 | 7 | @allowed([ 'OpenAI', 'ComputerVision', 'TextTranslation', 'CognitiveServices' ]) 8 | @description('Sets the kind of account.') 9 | param kind string 10 | 11 | @allowed([ 12 | 'S0' 13 | ]) 14 | @description('SKU for the account. Defaults to "S0".') 15 | param sku string = 'S0' 16 | 17 | @description('Enables access from public networks. Defaults to true.') 18 | param enablePublicNetworkAccess bool = true 19 | 20 | resource account 'Microsoft.CognitiveServices/accounts@2023-05-01' = { 21 | name: name 22 | location: location 23 | tags: tags 24 | kind: kind 25 | sku: { 26 | name: sku 27 | } 28 | properties: { 29 | customSubDomainName: name 30 | publicNetworkAccess: enablePublicNetworkAccess ? 'Enabled' : 'Disabled' 31 | } 32 | } 33 | 34 | output endpoint string = account.properties.endpoint 35 | output key string = account.listKeys().key1 36 | output name string = account.name 37 | -------------------------------------------------------------------------------- /python/infra/core/ai/cognitive-services/deployment.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates an Azure Cognitive Services deployment.' 2 | 3 | param name string 4 | 5 | @description('Name of the parent Azure Cognitive Services account.') 6 | param parentAccountName string 7 | 8 | @description('Name of the SKU for the deployment. Defaults to "Standard".') 9 | param skuName string = 'Standard' 10 | 11 | @description('Capacity of the SKU for the deployment. Defaults to 100.') 12 | param skuCapacity int = 100 13 | 14 | @description('Name of the model to use in the deployment.') 15 | param modelName string 16 | 17 | @description('Format of the model to use in the deployment.') 18 | param modelFormat string 19 | 20 | @description('Version of the model to use in the deployment.') 21 | param modelVersion string 22 | 23 | resource account 'Microsoft.CognitiveServices/accounts@2023-05-01' existing = { 24 | name: parentAccountName 25 | } 26 | 27 | resource deployment 'Microsoft.CognitiveServices/accounts/deployments@2023-05-01' = { 28 | parent: account 29 | name: name 30 | sku: { 31 | name: skuName 32 | capacity: skuCapacity 33 | } 34 | properties: { 35 | model: { 36 | name: modelName 37 | format: modelFormat 38 | version: modelVersion 39 | } 40 | } 41 | } 42 | 43 | output name string = deployment.name 44 | -------------------------------------------------------------------------------- /python/infra/core/database/cosmos-db/account.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Create an Azure Cosmos DB account.' 2 | 3 | param name string 4 | param location string = resourceGroup().location 5 | param tags object = {} 6 | 7 | @allowed(['GlobalDocumentDB']) 8 | @description('Sets the kind of account.') 9 | param kind string = 'GlobalDocumentDB' 10 | 11 | @description('Enables serverless for this account. Defaults to false.') 12 | param enableServerless bool = true 13 | 14 | @description('Enables NoSQL vector search for this account. Defaults to false.') 15 | param enableNoSQLVectorSearch bool = false 16 | 17 | @description('Enables NoSQL full text search for this account. Defaults to false.') 18 | param enableNoSQLFullTextSearch bool = false 19 | 20 | @description('Disables key-based authentication. Defaults to false.') 21 | param disableKeyBasedAuth bool = false 22 | 23 | resource account 'Microsoft.DocumentDB/databaseAccounts@2024-05-15' = { 24 | name: name 25 | location: location 26 | tags: tags 27 | kind: kind 28 | properties: { 29 | consistencyPolicy: { 30 | defaultConsistencyLevel: 'Session' 31 | } 32 | databaseAccountOfferType: 'Standard' 33 | locations: [ 34 | { 35 | locationName: location 36 | failoverPriority: 0 37 | isZoneRedundant: false 38 | } 39 | ] 40 | enableAutomaticFailover: false 41 | enableMultipleWriteLocations: false 42 | disableLocalAuth: disableKeyBasedAuth 43 | capabilities: union( 44 | (enableServerless) 45 | ? [ 46 | { 47 | name: 'EnableServerless' 48 | } 49 | ] 50 | : [], 51 | (enableNoSQLVectorSearch) 52 | ? [ 53 | { 54 | name: 'EnableNoSQLVectorSearch' 55 | } 56 | ] 57 | : [], 58 | (enableNoSQLFullTextSearch) 59 | ? [ 60 | { 61 | name: 'EnableNoSQLFullTextSearch' 62 | } 63 | ] 64 | : [] 65 | ) 66 | } 67 | } 68 | 69 | output endpoint string = account.properties.documentEndpoint 70 | output key string = account.listKeys().primaryMasterKey 71 | output connectionString string = account.listConnectionStrings().connectionStrings[0].connectionString 72 | output name string = account.name 73 | -------------------------------------------------------------------------------- /python/infra/core/database/cosmos-db/nosql/account.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Create an Azure Cosmos DB for NoSQL account.' 2 | 3 | param name string 4 | param location string = resourceGroup().location 5 | param tags object = {} 6 | 7 | @description('Enables serverless for this account. Defaults to false.') 8 | param enableServerless bool = false 9 | 10 | @description('Disables key-based authentication. Defaults to false.') 11 | param disableKeyBasedAuth bool = false 12 | 13 | @description('Enables vector search for this account. Defaults to false.') 14 | param enableVectorSearch bool = false 15 | 16 | @description('Enables NoSQL full text search for this account. Defaults to false.') 17 | param enableNoSQLFullTextSearch bool = false 18 | 19 | module account '../account.bicep' = { 20 | name: 'cosmos-db-nosql-account' 21 | params: { 22 | name: name 23 | location: location 24 | tags: tags 25 | kind: 'GlobalDocumentDB' 26 | enableServerless: enableServerless 27 | enableNoSQLVectorSearch: enableVectorSearch 28 | enableNoSQLFullTextSearch: enableNoSQLFullTextSearch 29 | disableKeyBasedAuth: disableKeyBasedAuth 30 | } 31 | } 32 | 33 | output endpoint string = account.outputs.endpoint 34 | output key string = account.outputs.key 35 | output connectionString string = account.outputs.connectionString 36 | output name string = account.outputs.name 37 | -------------------------------------------------------------------------------- /python/infra/core/database/cosmos-db/nosql/container.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Create an Azure Cosmos DB for NoSQL container.' 2 | 3 | param name string 4 | param tags object = {} 5 | 6 | @description('Name of the parent Azure Cosmos DB account.') 7 | param parentAccountName string 8 | 9 | @description('Name of the parent Azure Cosmos DB database.') 10 | param parentDatabaseName string 11 | 12 | @description('Enables throughput setting at this resource level. Defaults to true.') 13 | param setThroughput bool = false 14 | 15 | @description('Enables autoscale. If setThroughput is enabled, defaults to false.') 16 | param autoscale bool = false 17 | 18 | @description('The amount of throughput set. If setThroughput is enabled, defaults to 400.') 19 | param throughput int = 400 20 | 21 | @description('List of hierarhical partition key paths. Defaults to an array that only contains /id.') 22 | param partitionKeyPaths string[] = [ 23 | '/id' 24 | ] 25 | 26 | @description('Optional custom indexing policy for the container.') 27 | param indexingPolicy object = {} 28 | 29 | @description('Optional vector embedding policy for the container.') 30 | param vectorEmbeddingPolicy object = {} 31 | 32 | @description('Optional full text policy for the container.') 33 | param fullTextPolicy object = {} 34 | 35 | var options = setThroughput 36 | ? autoscale 37 | ? { 38 | autoscaleSettings: { 39 | maxThroughput: throughput 40 | } 41 | } 42 | : { 43 | throughput: throughput 44 | } 45 | : {} 46 | 47 | resource account 'Microsoft.DocumentDB/databaseAccounts@2024-05-15' existing = { 48 | name: parentAccountName 49 | } 50 | 51 | resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2024-05-15' existing = { 52 | name: parentDatabaseName 53 | parent: account 54 | } 55 | 56 | resource container 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2024-05-15' = { 57 | name: name 58 | parent: database 59 | tags: tags 60 | properties: { 61 | options: options 62 | resource: union( 63 | { 64 | id: name 65 | partitionKey: { 66 | paths: partitionKeyPaths 67 | kind: 'MultiHash' 68 | version: 2 69 | } 70 | }, 71 | !empty(indexingPolicy) 72 | ? { 73 | indexingPolicy: indexingPolicy 74 | } 75 | : {}, 76 | !empty(vectorEmbeddingPolicy) 77 | ? { 78 | vectorEmbeddingPolicy: vectorEmbeddingPolicy 79 | } 80 | : {}, 81 | !empty(fullTextPolicy) 82 | ? { 83 | fullTextPolicy: fullTextPolicy 84 | } 85 | : {} 86 | ) 87 | } 88 | } 89 | 90 | output name string = container.name 91 | -------------------------------------------------------------------------------- /python/infra/core/database/cosmos-db/nosql/database.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Create an Azure Cosmos DB for NoSQL database.' 2 | 3 | param name string 4 | param tags object = {} 5 | 6 | @description('Name of the parent Azure Cosmos DB account.') 7 | param parentAccountName string 8 | 9 | @description('Enables throughput setting at this resource level. Defaults to false.') 10 | param setThroughput bool = false 11 | 12 | @description('Enables autoscale. If setThroughput is enabled, defaults to false.') 13 | param autoscale bool = false 14 | 15 | @description('The amount of throughput set. If setThroughput is enabled, defaults to 400.') 16 | param throughput int = 400 17 | 18 | var options = setThroughput 19 | ? autoscale 20 | ? { 21 | autoscaleSettings: { 22 | maxThroughput: throughput 23 | } 24 | } 25 | : { 26 | throughput: throughput 27 | } 28 | : {} 29 | 30 | resource account 'Microsoft.DocumentDB/databaseAccounts@2024-05-15' existing = { 31 | name: parentAccountName 32 | } 33 | 34 | resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2024-05-15' = { 35 | name: name 36 | parent: account 37 | tags: tags 38 | properties: { 39 | options: options 40 | resource: { 41 | id: name 42 | } 43 | } 44 | } 45 | 46 | output name string = database.name 47 | -------------------------------------------------------------------------------- /python/infra/core/database/cosmos-db/nosql/role/assignment.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Create an Azure Cosmos DB for NoSQL role assignment.' 2 | 3 | @description('Name of the target Azure Cosmos DB account.') 4 | param targetAccountName string 5 | 6 | @description('Id of the role definition to assign to the targeted principal and account.') 7 | param roleDefinitionId string 8 | 9 | @description('Id of the principal to assign the role definition for the account.') 10 | param principalId string 11 | 12 | @description('Principal type used for the role assignment.') 13 | param principalType string 14 | 15 | resource account 'Microsoft.DocumentDB/databaseAccounts@2023-04-15' existing = { 16 | name: targetAccountName 17 | } 18 | 19 | resource assignment 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2023-04-15' = { 20 | name: guid(roleDefinitionId, principalId, account.id) 21 | parent: account 22 | properties: { 23 | principalId: principalId 24 | roleDefinitionId: roleDefinitionId 25 | scope: account.id 26 | principalType: principalType 27 | } 28 | } 29 | 30 | output id string = assignment.id 31 | -------------------------------------------------------------------------------- /python/infra/core/database/cosmos-db/nosql/role/definition.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Create an Azure Cosmos DB for NoSQL role definition.' 2 | 3 | @description('Name of the target Azure Cosmos DB account.') 4 | param targetAccountName string 5 | 6 | @description('Name of the role definiton.') 7 | param definitionName string 8 | 9 | @description('An array of data actions that are allowed. Defaults to an empty array.') 10 | param permissionsDataActions string[] = [] 11 | 12 | @description('An array of data actions that are denied. Defaults to an empty array.') 13 | param permissionsNonDataActions string[] = [] 14 | 15 | resource account 'Microsoft.DocumentDB/databaseAccounts@2023-04-15' existing = { 16 | name: targetAccountName 17 | } 18 | 19 | resource definition 'Microsoft.DocumentDB/databaseAccounts/sqlRoleDefinitions@2023-04-15' = { 20 | name: guid('nosql-role-definition', account.id) 21 | parent: account 22 | properties: { 23 | assignableScopes: [ 24 | account.id 25 | ] 26 | permissions: [ 27 | { 28 | dataActions: permissionsDataActions 29 | notDataActions: permissionsNonDataActions 30 | } 31 | ] 32 | roleName: definitionName 33 | type: 'CustomRole' 34 | } 35 | } 36 | 37 | output id string = definition.id 38 | -------------------------------------------------------------------------------- /python/infra/core/host/app-service/config.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates an Azure App Service configuration for a site.' 2 | 3 | @description('Name of the parent App Service site for the configuration.') 4 | param parentSiteName string 5 | 6 | @secure() 7 | param appSettings object = {} 8 | 9 | resource site 'Microsoft.Web/sites@2022-09-01' existing = { 10 | name: parentSiteName 11 | } 12 | 13 | resource config 'Microsoft.Web/sites/config@2022-09-01' = { 14 | name: 'appsettings' 15 | parent: site 16 | kind: 'string' 17 | properties: appSettings 18 | } 19 | -------------------------------------------------------------------------------- /python/infra/core/host/app-service/plan.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates an Azure App Service plan.' 2 | 3 | param name string 4 | param location string = resourceGroup().location 5 | param tags object = {} 6 | 7 | @allowed([ 8 | 'linux' 9 | ]) 10 | @description('OS type of the plan. Defaults to "linux".') 11 | param kind string = 'linux' 12 | 13 | 14 | @description('SKU for the plan. Defaults to "F1".') 15 | param sku string = 'F1' 16 | 17 | resource plan 'Microsoft.Web/serverfarms@2022-09-01' = { 18 | name: name 19 | location: location 20 | tags: tags 21 | sku: { 22 | name: sku 23 | } 24 | kind: kind 25 | properties: { 26 | reserved: kind == 'linux' ? true : null 27 | } 28 | } 29 | 30 | output name string = plan.name 31 | -------------------------------------------------------------------------------- /python/infra/core/host/app-service/site.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates an Azure App Service site.' 2 | 3 | param name string 4 | param location string = resourceGroup().location 5 | param tags object = {} 6 | 7 | @description('Name of the parent plan for the site.') 8 | param parentPlanName string 9 | 10 | @allowed([ 11 | 'dotnet' 12 | 'dotnetcore' 13 | 'dotnet-isolated' 14 | 'node' 15 | 'python' 16 | 'java' 17 | 'powershell' 18 | 'custom' 19 | ]) 20 | @description('Runtime to use for the site.') 21 | param runtimeName string 22 | 23 | @description('Version of the runtime to use for the site.') 24 | param runtimeVersion string 25 | 26 | @description('The OS kind of the site. Defaults to "app, linux"') 27 | param kind string = 'app,linux' 28 | 29 | @description('If the site should be always on. Defaults to true.') 30 | param alwaysOn bool = true 31 | 32 | @description('Allowed origins for client-side CORS request on the site.') 33 | param allowedCorsOrigins string[] = [] 34 | 35 | @description('Enable system-assigned managed identity. Defaults to false.') 36 | param enableSystemAssignedManagedIdentity bool = false 37 | 38 | @description('List of user-assigned managed identities. Defaults to an empty array.') 39 | param userAssignedManagedIdentityIds string[] = [] 40 | 41 | var linuxFxVersion = '${runtimeName}|${runtimeVersion}' 42 | 43 | resource plan 'Microsoft.Web/serverfarms@2022-09-01' existing = { 44 | name: parentPlanName 45 | } 46 | 47 | resource site 'Microsoft.Web/sites@2022-09-01' = { 48 | name: name 49 | location: location 50 | tags: tags 51 | kind: kind 52 | identity: { 53 | type: enableSystemAssignedManagedIdentity 54 | ? !empty(userAssignedManagedIdentityIds) ? 'SystemAssigned, UserAssigned' : 'SystemAssigned' 55 | : !empty(userAssignedManagedIdentityIds) ? 'UserAssigned' : 'None' 56 | userAssignedIdentities: !empty(userAssignedManagedIdentityIds) 57 | ? toObject(userAssignedManagedIdentityIds, uaid => uaid, uaid => {}) 58 | : null 59 | } 60 | properties: { 61 | serverFarmId: plan.id 62 | siteConfig: { 63 | linuxFxVersion: linuxFxVersion 64 | alwaysOn: alwaysOn 65 | http20Enabled: true 66 | minTlsVersion: '1.2' 67 | cors: { 68 | allowedOrigins: union(['https://portal.azure.com', 'https://ms.portal.azure.com'], allowedCorsOrigins) 69 | } 70 | } 71 | httpsOnly: true 72 | } 73 | } 74 | 75 | output endpoint string = 'https://${site.properties.defaultHostName}' 76 | output name string = site.name 77 | output managedIdentityPrincipalId string = enableSystemAssignedManagedIdentity ? site.identity.principalId : '' 78 | -------------------------------------------------------------------------------- /python/infra/core/host/functions/appserviceplan.bicep: -------------------------------------------------------------------------------- 1 | param name string 2 | param location string = resourceGroup().location 3 | param tags object = {} 4 | 5 | @allowed([ 6 | 'linux' 7 | ]) 8 | @description('OS type of the plan. Defaults to "linux".') 9 | param kind string = 'linux' 10 | 11 | param sku object 12 | 13 | param reserved bool = true 14 | 15 | 16 | resource appServicePlan 'Microsoft.Web/serverfarms@2023-12-01' = { 17 | name: name 18 | location: location 19 | tags: tags 20 | sku: sku 21 | kind: kind 22 | properties: { 23 | reserved: reserved 24 | } 25 | } 26 | 27 | output id string = appServicePlan.id 28 | -------------------------------------------------------------------------------- /python/infra/core/host/functions/flexconsumption.bicep: -------------------------------------------------------------------------------- 1 | param name string 2 | param location string = resourceGroup().location 3 | param tags object = {} 4 | 5 | // Reference Properties 6 | param applicationInsightsName string = '' 7 | param appServicePlanId string 8 | param storageAccountName string 9 | param virtualNetworkSubnetId string = '' 10 | @allowed(['SystemAssigned', 'UserAssigned']) 11 | param identityType string 12 | @description('User assigned identity name') 13 | param identityId string 14 | @description('User assigned identity client id') 15 | param identityClientId string 16 | 17 | // Runtime Properties 18 | @allowed([ 19 | 'dotnet-isolated', 'node', 'python', 'java', 'powershell', 'custom' 20 | ]) 21 | param runtimeName string 22 | @allowed(['3.10', '3.11', '7.4', '8.0', '10', '11', '17', '20']) 23 | param runtimeVersion string 24 | param kind string = 'functionapp,linux' 25 | 26 | // Microsoft.Web/sites/config 27 | param appSettings object = {} 28 | param instanceMemoryMB int = 2048 29 | param maximumInstanceCount int = 100 30 | param deploymentStorageContainerName string 31 | 32 | resource stg 'Microsoft.Storage/storageAccounts@2022-09-01' existing = { 33 | name: storageAccountName 34 | } 35 | 36 | resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightsName)) { 37 | name: applicationInsightsName 38 | } 39 | 40 | resource functions 'Microsoft.Web/sites@2023-12-01' = { 41 | name: name 42 | location: location 43 | tags: tags 44 | kind: kind 45 | identity: { 46 | type: identityType 47 | userAssignedIdentities: { 48 | '${identityId}': {} 49 | } 50 | } 51 | properties: { 52 | serverFarmId: appServicePlanId 53 | functionAppConfig: { 54 | deployment: { 55 | storage: { 56 | type: 'blobContainer' 57 | value: '${stg.properties.primaryEndpoints.blob}${deploymentStorageContainerName}' 58 | authentication: { 59 | type: identityType == 'SystemAssigned' ? 'SystemAssignedIdentity' : 'UserAssignedIdentity' 60 | userAssignedIdentityResourceId: identityType == 'UserAssigned' ? identityId : '' 61 | } 62 | } 63 | } 64 | scaleAndConcurrency: { 65 | instanceMemoryMB: instanceMemoryMB 66 | maximumInstanceCount: maximumInstanceCount 67 | } 68 | runtime: { 69 | name: runtimeName 70 | version: runtimeVersion 71 | } 72 | } 73 | virtualNetworkSubnetId: !empty(virtualNetworkSubnetId) ? virtualNetworkSubnetId : null 74 | } 75 | 76 | resource configAppSettings 'config' = { 77 | name: 'appsettings' 78 | properties: union(appSettings, 79 | { 80 | AzureWebJobsStorage__blobServiceUri: stg.properties.primaryEndpoints.blob 81 | AzureWebJobsStorage__credential : 'managedidentity' 82 | AzureWebJobsStorage__clientId : identityClientId 83 | APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights.properties.ConnectionString 84 | }) 85 | } 86 | } 87 | 88 | output name string = functions.name 89 | output uri string = 'https://${functions.properties.defaultHostName}' 90 | output identityPrincipalId string = identityType == 'SystemAssigned' ? functions.identity.principalId : '' 91 | -------------------------------------------------------------------------------- /python/infra/core/monitor/appinsights-access.bicep: -------------------------------------------------------------------------------- 1 | param principalID string 2 | param roleDefinitionID string 3 | param appInsightsName string 4 | 5 | resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { 6 | name: appInsightsName 7 | } 8 | 9 | // Allow access from API to app insights using a managed identity and least priv role 10 | resource appInsightsRoleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { 11 | name: guid(applicationInsights.id, principalID, roleDefinitionID) 12 | scope: applicationInsights 13 | properties: { 14 | roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionID) 15 | principalId: principalID 16 | principalType: 'ServicePrincipal' // Workaround for https://learn.microsoft.com/en-us/azure/role-based-access-control/role-assignments-template#new-service-principal 17 | } 18 | } 19 | 20 | output ROLE_ASSIGNMENT_NAME string = appInsightsRoleAssignment.name 21 | 22 | -------------------------------------------------------------------------------- /python/infra/core/monitor/applicationinsights.bicep: -------------------------------------------------------------------------------- 1 | param name string 2 | param location string = resourceGroup().location 3 | param tags object = {} 4 | 5 | param logAnalyticsWorkspaceId string 6 | param disableLocalAuth bool = false 7 | 8 | resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { 9 | name: name 10 | location: location 11 | tags: tags 12 | kind: 'web' 13 | properties: { 14 | Application_Type: 'web' 15 | WorkspaceResourceId: logAnalyticsWorkspaceId 16 | DisableLocalAuth: disableLocalAuth 17 | } 18 | } 19 | 20 | output connectionString string = applicationInsights.properties.ConnectionString 21 | output instrumentationKey string = applicationInsights.properties.InstrumentationKey 22 | output name string = applicationInsights.name 23 | -------------------------------------------------------------------------------- /python/infra/core/monitor/loganalytics.bicep: -------------------------------------------------------------------------------- 1 | param name string 2 | param location string = resourceGroup().location 3 | param tags object = {} 4 | 5 | resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = { 6 | name: name 7 | location: location 8 | tags: tags 9 | properties: any({ 10 | retentionInDays: 30 11 | features: { 12 | searchVersion: 1 13 | } 14 | sku: { 15 | name: 'PerGB2018' 16 | } 17 | }) 18 | } 19 | 20 | output id string = logAnalytics.id 21 | output name string = logAnalytics.name 22 | -------------------------------------------------------------------------------- /python/infra/core/monitor/monitoring.bicep: -------------------------------------------------------------------------------- 1 | param logAnalyticsName string 2 | param applicationInsightsName string 3 | param location string = resourceGroup().location 4 | param tags object = {} 5 | 6 | 7 | module logAnalytics 'loganalytics.bicep' = { 8 | name: 'loganalytics' 9 | params: { 10 | name: logAnalyticsName 11 | location: location 12 | tags: tags 13 | } 14 | } 15 | 16 | module applicationInsights 'applicationinsights.bicep' = { 17 | name: 'applicationinsights' 18 | params: { 19 | name: applicationInsightsName 20 | location: location 21 | tags: tags 22 | logAnalyticsWorkspaceId: logAnalytics.outputs.id 23 | disableLocalAuth: true 24 | } 25 | } 26 | 27 | output applicationInsightsConnectionString string = applicationInsights.outputs.connectionString 28 | output applicationInsightsInstrumentationKey string = applicationInsights.outputs.instrumentationKey 29 | output applicationInsightsName string = applicationInsights.outputs.name 30 | output logAnalyticsWorkspaceId string = logAnalytics.outputs.id 31 | output logAnalyticsWorkspaceName string = logAnalytics.outputs.name 32 | -------------------------------------------------------------------------------- /python/infra/core/security/identity/user-assigned.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates a Microsoft Entra user-assigned identity.' 2 | 3 | param name string 4 | param location string = resourceGroup().location 5 | param tags object = {} 6 | 7 | resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { 8 | name: name 9 | location: location 10 | tags: tags 11 | } 12 | 13 | output name string = identity.name 14 | output resourceId string = identity.id 15 | output principalId string = identity.properties.principalId 16 | output clientId string = identity.properties.clientId 17 | output tenantId string = identity.properties.tenantId 18 | -------------------------------------------------------------------------------- /python/infra/core/security/role/assignment.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates a role-based access control assignment.' 2 | 3 | @description('Id of the role definition to assign to the targeted principal and account.') 4 | param roleDefinitionId string 5 | 6 | @description('Id of the principal to assign the role definition for the account.') 7 | param principalId string 8 | 9 | @description('Type of principal associated with the principal Id.') 10 | param principalType string 11 | 12 | resource assignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 13 | name: guid(subscription().id, resourceGroup().id, principalId, roleDefinitionId) 14 | scope: resourceGroup() 15 | properties: { 16 | principalId: principalId 17 | roleDefinitionId: roleDefinitionId 18 | principalType: principalType != 'None' ? principalType : null 19 | } 20 | } 21 | 22 | output id string = assignment.id 23 | -------------------------------------------------------------------------------- /python/infra/core/security/role/definition.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates a role-based access control role definition.' 2 | 3 | @description('Name of the role definiton.') 4 | param definitionName string 5 | 6 | @description('Description for the role definition.') 7 | param definitionDescription string 8 | 9 | @description('Array of control-plane actions allowed for the role definition.') 10 | param actions string[] = [] 11 | 12 | @description('Array of control-plane actions disallowed for the role definition.') 13 | param notActions string[] = [] 14 | 15 | @description('Array of data-plane actions allowed for the role definition.') 16 | param dataActions string[] = [] 17 | 18 | @description('Array of data-plane actions disallowed for the role definition.') 19 | param notDataActions string[] = [] 20 | 21 | resource definition 'Microsoft.Authorization/roleDefinitions@2022-04-01' = { 22 | name: guid(subscription().id, resourceGroup().id) 23 | scope: resourceGroup() 24 | properties: { 25 | roleName: definitionName 26 | description: definitionDescription 27 | type: 'CustomRole' 28 | permissions: [ 29 | { 30 | actions: actions 31 | notActions: notActions 32 | dataActions: dataActions 33 | notDataActions: notDataActions 34 | } 35 | ] 36 | assignableScopes: [ 37 | resourceGroup().id 38 | ] 39 | } 40 | } 41 | 42 | output id string = definition.id 43 | -------------------------------------------------------------------------------- /python/infra/core/security/role/storage-Access.bicep: -------------------------------------------------------------------------------- 1 | param principalID string 2 | param principalType string = 'ServicePrincipal' // Workaround for https://learn.microsoft.com/en-us/azure/role-based-access-control/role-assignments-template#new-service-principal 3 | param roleDefinitionID string 4 | param storageAccountName string 5 | 6 | resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' existing = { 7 | name: storageAccountName 8 | } 9 | 10 | // Allow access from API to storage account using a managed identity and least priv Storage roles 11 | resource storageRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 12 | name: guid(storageAccount.id, principalID, roleDefinitionID) 13 | scope: storageAccount 14 | properties: { 15 | roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionID) 16 | principalId: principalID 17 | principalType: principalType 18 | } 19 | } 20 | 21 | output ROLE_ASSIGNMENT_NAME string = storageRoleAssignment.name 22 | -------------------------------------------------------------------------------- /python/infra/core/storage/storage-account.bicep: -------------------------------------------------------------------------------- 1 | param name string 2 | param location string = resourceGroup().location 3 | param tags object = {} 4 | 5 | param allowBlobPublicAccess bool = false 6 | param containers array = [] 7 | param kind string = 'StorageV2' 8 | param minimumTlsVersion string = 'TLS1_2' 9 | param sku object = { name: 'Standard_LRS' } 10 | param networkAcls object = { 11 | bypass: 'AzureServices' 12 | defaultAction: 'Allow' 13 | } 14 | 15 | resource storage 'Microsoft.Storage/storageAccounts@2023-05-01' = { 16 | name: name 17 | location: location 18 | tags: tags 19 | kind: kind 20 | sku: sku 21 | properties: { 22 | minimumTlsVersion: minimumTlsVersion 23 | allowBlobPublicAccess: allowBlobPublicAccess 24 | allowSharedKeyAccess: false 25 | networkAcls: networkAcls 26 | } 27 | 28 | resource blobServices 'blobServices' = if (!empty(containers)) { 29 | name: 'default' 30 | resource container 'containers' = [for container in containers: { 31 | name: container.name 32 | properties: { 33 | publicAccess: container.?publicAccess ?? 'None' 34 | } 35 | }] 36 | } 37 | } 38 | 39 | output name string = storage.name 40 | output primaryEndpoints object = storage.properties.primaryEndpoints 41 | output id string = storage.id 42 | -------------------------------------------------------------------------------- /python/infra/main.bicep: -------------------------------------------------------------------------------- 1 | targetScope = 'subscription' 2 | 3 | @minLength(1) 4 | @maxLength(64) 5 | @description('Name of the environment that can be used as part of naming resource convention.') 6 | param environmentName string 7 | 8 | @minLength(1) 9 | @allowed([ 10 | 'canadaeast' 11 | 'eastus' 12 | 'eastus2' 13 | 'francecentral' 14 | 'japaneast' 15 | 'norwayeast' 16 | 'polandcentral' 17 | 'southindia' 18 | 'swedencentral' 19 | 'switzerlandnorth' 20 | 'westus3' 21 | ]) 22 | @description('Primary location for all resources.') 23 | param location string 24 | 25 | @description('User Id of the principal to assign database and application roles.') 26 | param principalId string = '' 27 | 28 | // serviceName is sent to functions module and used as value for the tag (azd-service-name) azd uses to identify deployment host 29 | param serviceName string = 'embeddingGenerator' 30 | 31 | //Passed from main.parameters.json 32 | param functionsRuntimeName string = '' 33 | param functionsRuntimeVersion string = '' 34 | 35 | // Optional parameters 36 | param functionAccountName string = '' 37 | param functionAppPlanName string = '' 38 | param openAiAccountName string = '' 39 | param cosmosDbAccountName string = '' 40 | param storageAccountName string = '' 41 | param logAnalyticsName string = '' 42 | param appInsightsName string = '' 43 | param userAssignedIdentityName string = '' 44 | 45 | 46 | var abbreviations = loadJsonContent('abbreviations.json') 47 | var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) 48 | var tags = { 49 | 'azd-env-name': environmentName 50 | repo: 'https://github.com/AzureCosmosDB/cosmos-embeddings-generator' 51 | } 52 | var deploymentStorageContainerName = 'app-package-container' 53 | 54 | 55 | var cosmosSettings = { 56 | database: 'embeddings-db' 57 | container: 'customer' 58 | partitionKey: 'customerId' 59 | vectorProperty: 'vectors' 60 | hashProperty: 'hash' 61 | PropertyToEmbed: 'customerNotes' 62 | } 63 | 64 | var openAiSettings = { 65 | embeddingModelName: 'text-embedding-3-small' 66 | embeddingDeploymentName: 'text-3-small' 67 | dimensions: '1536' 68 | } 69 | 70 | resource resourceGroup 'Microsoft.Resources/resourceGroups@2022-09-01' = { 71 | name: environmentName 72 | location: location 73 | tags: tags 74 | } 75 | 76 | module identity 'app/identity.bicep' = { 77 | name: 'identity' 78 | scope: resourceGroup 79 | params: { 80 | identityName: !empty(userAssignedIdentityName) ? userAssignedIdentityName : '${abbreviations.userAssignedIdentity}-${resourceToken}' 81 | location: location 82 | tags: tags 83 | } 84 | } 85 | 86 | module ai 'app/ai.bicep' = { 87 | name: 'ai' 88 | scope: resourceGroup 89 | params: { 90 | accountName: !empty(openAiAccountName) ? openAiAccountName : '${abbreviations.openAiAccount}-${resourceToken}' 91 | location: location 92 | embeddingsModelName: openAiSettings.embeddingModelName 93 | embeddingsDeploymentName: openAiSettings.embeddingDeploymentName 94 | tags: tags 95 | } 96 | } 97 | 98 | // Backing storage for Azure functions backend processor 99 | module storage 'core/storage/storage-account.bicep' = { 100 | name: 'storage' 101 | scope: resourceGroup 102 | params: { 103 | name: !empty(storageAccountName) ? storageAccountName : '${abbreviations.storageAccount}${resourceToken}' 104 | location: location 105 | tags: tags 106 | containers: [ 107 | {name: deploymentStorageContainerName} 108 | ] 109 | } 110 | } 111 | 112 | module embeddingGenerator 'app/functions.bicep' = { 113 | name: 'embeddingGenerator' 114 | scope: resourceGroup 115 | params: { 116 | name: !empty(functionAccountName) ? functionAccountName : '${abbreviations.functionApp}-${resourceToken}' 117 | location: location 118 | tags: tags 119 | serviceName: serviceName //used by azd in azure.yml to tag the deployment host 120 | planName: !empty(functionAppPlanName) ? functionAppPlanName : '${abbreviations.appServicePlan}-${resourceToken}' 121 | runtimeName: functionsRuntimeName 122 | runtimeVersion: functionsRuntimeVersion 123 | storageAccountName: storage.outputs.name 124 | deploymentStorageContainerName: deploymentStorageContainerName 125 | identityId: identity.outputs.resourceId 126 | identityClientId: identity.outputs.clientId 127 | applicationInsightsName: monitoring.outputs.applicationInsightsName 128 | appSettings: { 129 | COSMOS_DATABASE_NAME: cosmosSettings.database 130 | COSMOS_CONTAINER_NAME: cosmosSettings.container 131 | COSMOS_VECTOR_PROPERTY: cosmosSettings.vectorProperty 132 | COSMOS_HASH_PROPERTY: cosmosSettings.hashProperty 133 | COSMOS_PROPERTY_TO_EMBED: cosmosSettings.PropertyToEmbed 134 | COSMOS_CONNECTION__accountEndpoint: database.outputs.endpoint 135 | COSMOS_CONNECTION__credential: 'managedidentity' 136 | COSMOS_CONNECTION__clientId: identity.outputs.clientId 137 | OPENAI_ENDPOINT: ai.outputs.endpoint 138 | OPENAI_DEPLOYMENT_NAME: openAiSettings.embeddingDeploymentName 139 | OPENAI_DIMENSIONS: openAiSettings.dimensions 140 | } 141 | } 142 | } 143 | 144 | module database 'app/database.bicep' = { 145 | name: 'database' 146 | scope: resourceGroup 147 | params: { 148 | accountName: !empty(cosmosDbAccountName) ? cosmosDbAccountName : '${abbreviations.cosmosDbAccount}-${resourceToken}' 149 | location: location 150 | tags: tags 151 | databaseName: cosmosSettings.database 152 | containerNames: [cosmosSettings.container] 153 | partitionKeyName: cosmosSettings.partitionKey 154 | vectorPropertyName: cosmosSettings.vectorProperty 155 | } 156 | } 157 | 158 | // Monitor application with Azure Monitor 159 | module monitoring './core/monitor/monitoring.bicep' = { 160 | name: 'monitoring' 161 | scope: resourceGroup 162 | params: { 163 | location: location 164 | tags: tags 165 | logAnalyticsName: !empty(logAnalyticsName) ? logAnalyticsName : '${abbreviations.operationalInsightsWorkspaces}${resourceToken}' 166 | applicationInsightsName: !empty(appInsightsName) ? appInsightsName : '${abbreviations.insightsComponents}${resourceToken}' 167 | } 168 | } 169 | 170 | var monitoringRoleDefinitionId = '3913510d-42f4-4e42-8a64-420c390055eb' // Monitoring Metrics Publisher role ID 171 | 172 | // Allow access from api to application insights using a managed identity 173 | module appInsightsRoleAssignmentApi './core/monitor/appinsights-access.bicep' = { 174 | name: 'appInsightsRoleAssignmentapi' 175 | scope: resourceGroup 176 | params: { 177 | appInsightsName: monitoring.outputs.applicationInsightsName 178 | roleDefinitionID: monitoringRoleDefinitionId 179 | principalID: identity.outputs.principalId 180 | } 181 | } 182 | 183 | module security 'app/security.bicep' = { 184 | name: 'security' 185 | scope: resourceGroup 186 | params: { 187 | databaseAccountName: database.outputs.accountName 188 | storageAccountName: storage.outputs.name 189 | appPrincipalId: identity.outputs.principalId 190 | userPrincipalId: !empty(principalId) ? principalId : null 191 | } 192 | } 193 | 194 | output COSMOS_CONNECTION__accountEndpoint string = database.outputs.endpoint 195 | output COSMOS_DATABASE_NAME string = cosmosSettings.database 196 | output COSMOS_CONTAINER_NAME string = cosmosSettings.container 197 | output COSMOS_VECTOR_PROPERTY string = cosmosSettings.vectorProperty 198 | output COSMOS_HASH_PROPERTY string = cosmosSettings.hashProperty 199 | output COSMOS_PROPERTY_TO_EMBED string = cosmosSettings.PropertyToEmbed 200 | output OPENAI_ENDPOINT string = ai.outputs.endpoint 201 | output OPENAI_DEPLOYMENT_NAME string = openAiSettings.embeddingDeploymentName 202 | output OPENAI_DIMENSIONS string = openAiSettings.dimensions 203 | -------------------------------------------------------------------------------- /python/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 | "environmentName": { 6 | "value": "${AZURE_ENV_NAME}" 7 | }, 8 | "location": { 9 | "value": "${AZURE_LOCATION}" 10 | }, 11 | "principalId": { 12 | "value": "${AZURE_PRINCIPAL_ID}" 13 | }, 14 | "functionsRuntimeName": { 15 | "value": "python" 16 | }, 17 | "functionsRuntimeVersion": { 18 | "value": "3.11" 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /python/infra/main.test.bicep: -------------------------------------------------------------------------------- 1 | // This file is for doing static analysis and contains sensible defaults 2 | // for the bicep analyser to minimise false-positives and provide the best results. 3 | 4 | // This file is not intended to be used as a runtime configuration file. 5 | 6 | targetScope = 'subscription' 7 | 8 | param environmentName string = 'testing' 9 | param location string = 'eastus' 10 | 11 | module main 'main.bicep' = { 12 | name: 'main' 13 | params: { 14 | environmentName: environmentName 15 | location: location 16 | principalId: '' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /python/infra/scripts/createlocalsettings.ps1: -------------------------------------------------------------------------------- 1 | $ErrorActionPreference = "Stop" 2 | 3 | if (-not (Test-Path ".\local.settings.json")) { 4 | 5 | $output = azd env get-values 6 | 7 | # Parse the output to get the endpoint values 8 | $expectedKeys = @( 9 | "COSMOS_CONNECTION__accountEndpoint", 10 | "COSMOS_CONTAINER_NAME", 11 | "COSMOS_DATABASE_NAME", 12 | "COSMOS_HASH_PROPERTY", 13 | "COSMOS_PROPERTY_TO_EMBED", 14 | "COSMOS_VECTOR_PROPERTY", 15 | "OPENAI_DEPLOYMENT_NAME", 16 | "OPENAI_DIMENSIONS", 17 | "OPENAI_ENDPOINT" 18 | ) 19 | 20 | foreach ($line in $output) { 21 | foreach ($key in $expectedKeys) { 22 | if ($line -match $key) { 23 | Set-Variable -Name $key -Value (($line -split "=")[1] -replace '"','') 24 | } 25 | } 26 | } 27 | 28 | @{ 29 | "IsEncrypted" = "false"; 30 | "Values" = @{ 31 | "AzureWebJobsStorage" = "UseDevelopmentStorage=true"; 32 | "FUNCTIONS_WORKER_RUNTIME" = "python"; 33 | "COSMOS_CONNECTION__accountEndpoint" = "$COSMOS_CONNECTION__accountEndpoint"; 34 | "COSMOS_CONTAINER_NAME" = "$COSMOS_CONTAINER_NAME"; 35 | "COSMOS_DATABASE_NAME" = "$COSMOS_DATABASE_NAME"; 36 | "COSMOS_HASH_PROPERTY" = "$COSMOS_HASH_PROPERTY"; 37 | "COSMOS_PROPERTY_TO_EMBED" = "$COSMOS_PROPERTY_TO_EMBED"; 38 | "COSMOS_VECTOR_PROPERTY" = "$COSMOS_VECTOR_PROPERTY"; 39 | "OPENAI_DEPLOYMENT_NAME" = "$OPENAI_DEPLOYMENT_NAME"; 40 | "OPENAI_DIMENSIONS" = "$OPENAI_DIMENSIONS"; 41 | "OPENAI_ENDPOINT" = "$OPENAI_ENDPOINT"; 42 | } 43 | } | ConvertTo-Json | Out-File -FilePath ".\local.settings.json" -Encoding ascii 44 | } 45 | -------------------------------------------------------------------------------- /python/infra/scripts/createlocalsettings.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [ ! -f "./local.settings.json" ]; then 6 | 7 | expected_keys=( 8 | "COSMOS_CONNECTION__accountEndpoint" 9 | "COSMOS_CONTAINER_NAME" 10 | "COSMOS_DATABASE_NAME" 11 | "COSMOS_HASH_PROPERTY" 12 | "COSMOS_PROPERTY_TO_EMBED" 13 | "COSMOS_VECTOR_PROPERTY" 14 | "OPENAI_DEPLOYMENT_NAME" 15 | "OPENAI_DIMENSIONS" 16 | "OPENAI_ENDPOINT" 17 | ) 18 | 19 | output=$(azd env get-values) 20 | 21 | while IFS= read -r line; do 22 | for key in "${expected_keys[@]}"; do 23 | if [[ $line == $key=* ]]; then 24 | value="${line#*=}" 25 | value="${value%\"}" # Remove trailing quote if present 26 | value="${value#\"}" # Remove leading quote if present 27 | export "$key"="$value" 28 | fi 29 | done 30 | done <<< "$output" 31 | 32 | cat < ./local.settings.json 33 | { 34 | "IsEncrypted": false, 35 | "Values": { 36 | "AzureWebJobsStorage": "UseDevelopmentStorage=true", 37 | "FUNCTIONS_WORKER_RUNTIME": "python", 38 | "COSMOS_CONNECTION__accountEndpoint": "$COSMOS_CONNECTION__accountEndpoint", 39 | "COSMOS_CONTAINER_NAME": "$COSMOS_CONTAINER_NAME", 40 | "COSMOS_DATABASE_NAME": "$COSMOS_DATABASE_NAME", 41 | "COSMOS_HASH_PROPERTY": "$COSMOS_HASH_PROPERTY", 42 | "COSMOS_PROPERTY_TO_EMBED": "$COSMOS_PROPERTY_TO_EMBED", 43 | "COSMOS_VECTOR_PROPERTY": "$COSMOS_VECTOR_PROPERTY", 44 | "OPENAI_DEPLOYMENT_NAME": "$OPENAI_DEPLOYMENT_NAME", 45 | "OPENAI_DIMENSIONS": "$OPENAI_DIMENSIONS", 46 | "OPENAI_ENDPOINT": "$OPENAI_ENDPOINT" 47 | } 48 | } 49 | EOF 50 | 51 | fi -------------------------------------------------------------------------------- /python/requirements.txt: -------------------------------------------------------------------------------- 1 | # DO NOT include azure-functions-worker in this file 2 | # The Python Worker is managed by Azure Functions platform 3 | # Manually managing azure-functions-worker may cause unexpected issues 4 | 5 | azure-functions 6 | azure-cosmos 7 | openai 8 | azure-identity 9 | -------------------------------------------------------------------------------- /python/sample.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "IsEncrypted": false, 3 | "Values": { 4 | "AzureWebJobsStorage": "UseDevelopmentStorage=true", 5 | "FUNCTIONS_WORKER_RUNTIME": "python", 6 | "COSMOS_CONNECTION__accountEndpoint": "https://{my-cosmos-account}.documents.azure.com:443/", 7 | "OPENAI_ENDPOINT": "https://{my-open-ai-account}.openai.azure.com/", 8 | "COSMOS_DATABASE_NAME": "embeddings-db", 9 | "COSMOS_CONTAINER_NAME": "customer", 10 | "COSMOS_VECTOR_PROPERTY": "vectors", 11 | "COSMOS_HASH_PROPERTY": "hash", 12 | "COSMOS_PROPERTY_TO_EMBED": "customerNotes", 13 | "OPENAI_DEPLOYMENT_NAME": "text-3-small", 14 | "OPENAI_DIMENSIONS": "1536" 15 | } 16 | } 17 | --------------------------------------------------------------------------------