├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .funcignore ├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── azure-dev.yml ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── azure.yaml ├── function_app.py ├── host.json ├── infra ├── abbreviations.json ├── app │ ├── ai-Cog-Service-Access.bicep │ ├── api.bicep │ ├── eventgrid.bicep │ ├── storage-Access.bicep │ ├── storage-PrivateEndpoint.bicep │ └── vnet.bicep ├── core │ ├── ai │ │ └── openai.bicep │ ├── host │ │ ├── appservice-appsettings.bicep │ │ ├── appservice.bicep │ │ ├── appserviceplan.bicep │ │ └── functions-flexconsumption.bicep │ ├── identity │ │ └── userAssignedIdentity.bicep │ ├── monitor │ │ ├── appinsights-access.bicep │ │ ├── applicationinsights.bicep │ │ ├── loganalytics.bicep │ │ └── monitoring.bicep │ └── storage │ │ └── storage-account.bicep ├── main.bicep └── main.parameters.json ├── requirements.txt ├── test.http └── testdata.json /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG IMAGE=bullseye 2 | FROM --platform=amd64 mcr.microsoft.com/devcontainers/${IMAGE} 3 | RUN curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg \ 4 | && mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg \ 5 | && sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/debian/$(lsb_release -rs | cut -d'.' -f 1)/prod $(lsb_release -cs) main" > /etc/apt/sources.list.d/dotnetdev.list' \ 6 | && apt-get update && apt-get install -y xdg-utils \ 7 | && apt-get update && apt-get install -y azure-functions-core-tools-4 \ 8 | && apt-get clean -y && rm -rf /var/lib/apt/lists/* -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Azure Developer CLI", 3 | "build": { 4 | "dockerfile": "Dockerfile", 5 | "args": { 6 | "IMAGE": "dotnet:6.0" 7 | } 8 | }, 9 | "features": { 10 | "ghcr.io/devcontainers/features/docker-in-docker:2": {}, 11 | "ghcr.io/devcontainers/features/node:1": { 12 | "version": "16", 13 | "nodeGypDependencies": false 14 | }, 15 | "ghcr.io/azure/azure-dev/azd": { 16 | "version": "latest" 17 | } 18 | }, 19 | "customizations": { 20 | "vscode": { 21 | "extensions": [ 22 | "GitHub.vscode-github-actions", 23 | "ms-azuretools.azure-dev", 24 | "ms-azuretools.vscode-azurefunctions", 25 | "ms-azuretools.vscode-bicep", 26 | "ms-azuretools.vscode-docker", 27 | "ms-dotnettools.csharp", 28 | "ms-dotnettools.vscode-dotnet-runtime", 29 | "ms-vscode.vscode-node-azure-pack" 30 | ] 31 | } 32 | }, 33 | "forwardPorts": [ 34 | 7071 35 | ], 36 | "postCreateCommand": "", 37 | "remoteUser": "vscode", 38 | "hostRequirements": { 39 | "memory": "8gb" 40 | } 41 | } -------------------------------------------------------------------------------- /.funcignore: -------------------------------------------------------------------------------- 1 | .venv -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | > Please provide us with the following information: 5 | > --------------------------------------------------------------- 6 | 7 | ### This issue is for a: (mark with an `x`) 8 | ``` 9 | - [ ] bug report -> please search issues before submitting 10 | - [ ] feature request 11 | - [ ] documentation issue or request 12 | - [ ] regression (a behavior that used to work and stopped in a new release) 13 | ``` 14 | 15 | ### Minimal steps to reproduce 16 | > 17 | 18 | ### Any log messages given by the failure 19 | > 20 | 21 | ### Expected/desired behavior 22 | > 23 | 24 | ### OS and Version? 25 | > Windows 7, 8 or 10. Linux (which distribution). macOS (Yosemite? El Capitan? Sierra?) 26 | 27 | ### Versions 28 | > 29 | 30 | ### Mention any other details that might be useful 31 | 32 | > --------------------------------------------------------------- 33 | > Thanks! We'll be in touch soon. 34 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Purpose 2 | 3 | * ... 4 | 5 | ## Does this introduce a breaking change? 6 | 7 | ``` 8 | [ ] Yes 9 | [ ] No 10 | ``` 11 | 12 | ## Pull Request Type 13 | What kind of change does this Pull Request introduce? 14 | 15 | 16 | ``` 17 | [ ] Bugfix 18 | [ ] Feature 19 | [ ] Code style update (formatting, local variables) 20 | [ ] Refactoring (no functional changes, no api changes) 21 | [ ] Documentation content changes 22 | [ ] Other... Please describe: 23 | ``` 24 | 25 | ## How to Test 26 | * Get the code 27 | 28 | ``` 29 | git clone [repo-address] 30 | cd [repo-name] 31 | git checkout [branch-name] 32 | npm install 33 | ``` 34 | 35 | * Test the code 36 | 37 | ``` 38 | ``` 39 | 40 | ## What to Check 41 | Verify that the following are valid 42 | * ... 43 | 44 | ## Other Information 45 | -------------------------------------------------------------------------------- /.github/workflows/azure-dev.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | push: 4 | # Run when commits are pushed to mainline branch (main or master) 5 | # Set this to the mainline branch you are using 6 | branches: 7 | - main 8 | - master 9 | 10 | # GitHub Actions workflow to deploy to Azure using azd 11 | # To configure required secrets for connecting to Azure, simply run `azd pipeline config` 12 | 13 | # Set up permissions for deploying with secretless Azure federated credentials 14 | # https://learn.microsoft.com/en-us/azure/developer/github/connect-from-azure?tabs=azure-portal%2Clinux#set-up-azure-login-with-openid-connect-authentication 15 | permissions: 16 | id-token: write 17 | contents: read 18 | 19 | jobs: 20 | build: 21 | runs-on: ubuntu-latest 22 | env: 23 | AZURE_CLIENT_ID: ${{ vars.AZURE_CLIENT_ID }} 24 | AZURE_TENANT_ID: ${{ vars.AZURE_TENANT_ID }} 25 | AZURE_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID }} 26 | AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }} 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v3 30 | 31 | - name: Install azd 32 | uses: Azure/setup-azd@v0.1.0 33 | 34 | - name: Log in with Azure (Federated Credentials) 35 | if: ${{ env.AZURE_CLIENT_ID != '' }} 36 | run: | 37 | azd auth login ` 38 | --client-id "$Env:AZURE_CLIENT_ID" ` 39 | --federated-credential-provider "github" ` 40 | --tenant-id "$Env:AZURE_TENANT_ID" 41 | shell: pwsh 42 | 43 | - name: Log in with Azure (Client Credentials) 44 | if: ${{ env.AZURE_CREDENTIALS != '' }} 45 | run: | 46 | $info = $Env:AZURE_CREDENTIALS | ConvertFrom-Json -AsHashtable; 47 | Write-Host "::add-mask::$($info.clientSecret)" 48 | 49 | azd auth login ` 50 | --client-id "$($info.clientId)" ` 51 | --client-secret "$($info.clientSecret)" ` 52 | --tenant-id "$($info.tenantId)" 53 | shell: pwsh 54 | env: 55 | AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }} 56 | 57 | - name: Provision Infrastructure 58 | run: azd provision --no-prompt 59 | env: 60 | AZURE_ENV_NAME: ${{ vars.AZURE_ENV_NAME }} 61 | AZURE_LOCATION: ${{ vars.AZURE_LOCATION }} 62 | AZURE_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID }} 63 | 64 | - name: Deploy Application 65 | run: azd deploy --no-prompt 66 | env: 67 | AZURE_ENV_NAME: ${{ vars.AZURE_ENV_NAME }} 68 | AZURE_LOCATION: ${{ vars.AZURE_LOCATION }} 69 | AZURE_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID }} 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | csx 4 | .vs 5 | edge 6 | Publish 7 | 8 | *.user 9 | *.suo 10 | *.cscfg 11 | *.Cache 12 | project.lock.json 13 | 14 | /packages 15 | /TestResults 16 | 17 | /tools/NuGet.exe 18 | /App_Data 19 | /secrets 20 | /data 21 | .secrets 22 | appsettings.json 23 | local.settings.json 24 | 25 | node_modules 26 | dist 27 | 28 | # Local python packages 29 | .python_packages/ 30 | 31 | # Python Environments 32 | .env 33 | .venv 34 | env/ 35 | venv/ 36 | ENV/ 37 | env.bak/ 38 | venv.bak/ 39 | 40 | # Byte-compiled / optimized / DLL files 41 | __pycache__/ 42 | *.py[cod] 43 | *$py.class 44 | 45 | # Azurite artifacts 46 | __blobstorage__ 47 | __queuestorage__ 48 | __azurite_db*__.json 49 | 50 | # AZD artifacts 51 | .azure/ -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-azuretools.vscode-azurefunctions", 4 | "ms-python.python" 5 | ] 6 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Attach to Python Functions", 6 | "type": "python", 7 | "request": "attach", 8 | "port": 9091, 9 | "preLaunchTask": "func: host start" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /.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 | } -------------------------------------------------------------------------------- /.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 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [project-title] Changelog 2 | 3 | 4 | # x.y.z (yyyy-mm-dd) 5 | 6 | *Features* 7 | * ... 8 | 9 | *Bug Fixes* 10 | * ... 11 | 12 | *Breaking Changes* 13 | * ... 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to [project-title] 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 4 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 5 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 6 | 7 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 8 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 9 | provided by the bot. You will only need to do this once across all repos using our CLA. 10 | 11 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 12 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 13 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 14 | 15 | - [Code of Conduct](#coc) 16 | - [Issues and Bugs](#issue) 17 | - [Feature Requests](#feature) 18 | - [Submission Guidelines](#submit) 19 | 20 | ## Code of Conduct 21 | Help us keep this project open and inclusive. Please read and follow our [Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 22 | 23 | ## Found an Issue? 24 | If you find a bug in the source code or a mistake in the documentation, you can help us by 25 | [submitting an issue](#submit-issue) to the GitHub Repository. Even better, you can 26 | [submit a Pull Request](#submit-pr) with a fix. 27 | 28 | ## Want a Feature? 29 | You can *request* a new feature by [submitting an issue](#submit-issue) to the GitHub 30 | Repository. If you would like to *implement* a new feature, please submit an issue with 31 | a proposal for your work first, to be sure that we can use it. 32 | 33 | * **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). 34 | 35 | ## Submission Guidelines 36 | 37 | ### Submitting an Issue 38 | Before you submit an issue, search the archive, maybe your question was already answered. 39 | 40 | If your issue appears to be a bug, and hasn't been reported, open a new issue. 41 | Help us to maximize the effort we can spend fixing issues and adding new 42 | features, by not reporting duplicate issues. Providing the following information will increase the 43 | chances of your issue being dealt with quickly: 44 | 45 | * **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps 46 | * **Version** - what version is affected (e.g. 0.1.2) 47 | * **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you 48 | * **Browsers and Operating System** - is this a problem with all browsers? 49 | * **Reproduce the Error** - provide a live example or a unambiguous set of steps 50 | * **Related Issues** - has a similar issue been reported before? 51 | * **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be 52 | causing the problem (line of code or commit) 53 | 54 | You can file new issues by providing the above information at the corresponding repository's issues link: https://github.com/[organization-name]/[repository-name]/issues/new]. 55 | 56 | ### Submitting a Pull Request (PR) 57 | Before you submit your Pull Request (PR) consider the following guidelines: 58 | 59 | * Search the repository (https://github.com/[organization-name]/[repository-name]/pulls) for an open or closed PR 60 | that relates to your submission. You don't want to duplicate effort. 61 | 62 | * Make your changes in a new git fork: 63 | 64 | * Commit your changes using a descriptive commit message 65 | * Push your fork to GitHub: 66 | * In GitHub, create a pull request 67 | * If we suggest changes then: 68 | * Make the required updates. 69 | * Rebase your fork and force push to your GitHub repository (this will update your Pull Request): 70 | 71 | ```shell 72 | git rebase master -i 73 | git push -f 74 | ``` 75 | 76 | That's it! Thank you for your contribution! 77 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | page_type: sample 3 | languages: 4 | - azdeveloper 5 | - python 6 | - bicep 7 | products: 8 | - azure 9 | - azure-functions 10 | - azure-openai 11 | urlFragment: function-python-ai-langchain 12 | name: Azure Functions - LangChain with Azure OpenAI and ChatGPT (Python v2 Function) 13 | description: Using human prompt with Python as HTTP Get or Post input, calculates the completions using chains of human input and templates. 14 | --- 15 | 16 | 17 | # Azure Functions 18 | ## LangChain with Azure OpenAI and ChatGPT (Python v2 Function) 19 | 20 | This sample shows how to take a human prompt as HTTP Get or Post input, calculates the completions using chains of human input and templates. This is a starting point that can be used for more sophisticated chains. 21 | 22 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=575770869) 23 | 24 | ## Run on your local environment 25 | 26 | ### Pre-reqs 27 | 1) [Python 3.8+](https://www.python.org/) 28 | 2) [Azure Functions Core Tools](https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local?tabs=v4%2Cmacos%2Ccsharp%2Cportal%2Cbash#install-the-azure-functions-core-tools) 29 | 3) [Azure Developer CLI](https://aka.ms/azd) 30 | 4) Once you have your Azure subscription, run the following in a new terminal window to create Azure OpenAI and other resources needed: 31 | ```bash 32 | azd provision 33 | ``` 34 | 35 | Take note of the value of `AZURE_OPENAI_ENDPOINT` which can be found in `./.azure//.env`. It will look something like: 36 | ```bash 37 | AZURE_OPENAI_ENDPOINT="https://cog-.openai.azure.com/" 38 | ``` 39 | 40 | 5) Add this `local.settings.json` file to the root of the repo folder to simplify local development. Replace `AZURE_OPENAI_ENDPOINT` with your value from step 4. Optionally you can choose a different model deployment in `AZURE_OPENAI_CHATGPT_DEPLOYMENT`. This file will be gitignored to protect secrets from committing to your repo, however by default the sample uses Entra identity (user identity and mananaged identity) so it is secretless. 41 | ```json 42 | { 43 | "IsEncrypted": false, 44 | "Values": { 45 | "FUNCTIONS_WORKER_RUNTIME": "python", 46 | "AzureWebJobsFeatureFlags": "EnableWorkerIndexing", 47 | "AzureWebJobsStorage": "UseDevelopmentStorage=true", 48 | "AZURE_OPENAI_ENDPOINT": "https://.openai.azure.com/", 49 | "AZURE_OPENAI_CHATGPT_DEPLOYMENT": "chat", 50 | "OPENAI_API_VERSION": "2023-05-15" 51 | } 52 | } 53 | ``` 54 | 55 | ### Using Functions CLI 56 | 1) Open a new terminal and do the following: 57 | 58 | ```bash 59 | pip3 install -r requirements.txt 60 | func start 61 | ``` 62 | 2) Using your favorite REST client, e.g. [RestClient in VS Code](https://marketplace.visualstudio.com/items?itemName=humao.rest-client), PostMan, curl, make a post. `test.http` has been provided to run this quickly. 63 | 64 | Terminal: 65 | ```bash 66 | curl -i -X POST http://localhost:7071/api/ask/ \ 67 | -H "Content-Type: text/json" \ 68 | --data-binary "@testdata.json" 69 | ``` 70 | 71 | `testdata.json` 72 | ```json 73 | { 74 | "prompt": "What is a good feature of Azure Functions?" 75 | } 76 | ``` 77 | 78 | `test.http` 79 | ```bash 80 | 81 | POST http://localhost:7071/api/ask HTTP/1.1 82 | content-type: application/json 83 | 84 | { 85 | "prompt": "What is a good feature of Azure Functions?" 86 | } 87 | ``` 88 | 89 | ### Using Visual Studio Code 90 | 1) Open this repo in VS Code: 91 | ```bash 92 | code . 93 | ``` 94 | 95 | 2) Follow the prompts to load Function. It is recommended to Initialize the Functions Project for VS Code, and also to enable a virtual environment for your chosen version of Python. 96 | 97 | 3) Run and Debug `F5` the app 98 | 99 | 4) Test using same REST client steps above 100 | 101 | ## Deploy to Azure 102 | 103 | The easiest way to deploy this app is using the [Azure Dev CLI](https://aka.ms/azd). If you open this repo in GitHub CodeSpaces the AZD tooling is already preinstalled. 104 | 105 | To provision and deploy: 106 | ```bash 107 | azd up 108 | ``` 109 | 110 | ## Source Code 111 | 112 | The key code that makes the prompting and completion work is as follows in [function_app.py](function_app.py). The `/api/ask` function and route expects a prompt to come in the POST body using a standard HTTP Trigger in Python. Then once the environment variables are set to configure OpenAI and LangChain frameworks via `init()` function, we can leverage favorite aspects of LangChain in the `main()` (ask) function. In this simple example we take a prompt, build a better prompt from a template, and then invoke the LLM. By default the LLM deployment is `gpt-35-turbo` as defined in [./infra/main.parameters.json](./infra/main.parameters.json) but you can experiment with other models and other aspects of Langchain's breadth of features. 113 | 114 | ```python 115 | llm = AzureChatOpenAI( 116 | deployment_name=AZURE_OPENAI_CHATGPT_DEPLOYMENT, 117 | temperature=0.3 118 | ) 119 | llm_prompt = PromptTemplate.from_template( 120 | "The following is a conversation with an AI assistant. " + 121 | "The assistant is helpful.\n\n" + 122 | "A:How can I help you today?\n" + 123 | "Human: {human_prompt}?" 124 | ) 125 | formatted_prompt = llm_prompt.format(human_prompt=prompt) 126 | 127 | response = llm.invoke(formatted_prompt) 128 | ``` 129 | -------------------------------------------------------------------------------- /azure.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json 2 | 3 | name: langchain-py-ai-func 4 | metadata: 5 | template: langchain-py-ai-func@1.0.0 6 | services: 7 | api: 8 | project: ./ 9 | language: python 10 | host: function 11 | -------------------------------------------------------------------------------- /function_app.py: -------------------------------------------------------------------------------- 1 | import azure.functions as func 2 | import logging 3 | import os 4 | import openai 5 | from langchain_core.prompts import PromptTemplate 6 | from langchain_openai import AzureChatOpenAI 7 | from azure.identity import DefaultAzureCredential 8 | 9 | app = func.FunctionApp() 10 | 11 | 12 | # Initializes Azure OpenAI environment 13 | def init(): 14 | global credential 15 | global AZURE_OPENAI_ENDPOINT 16 | global AZURE_OPENAI_KEY 17 | global AZURE_OPENAI_CHATGPT_DEPLOYMENT 18 | global OPENAI_API_VERSION 19 | 20 | # Use the Entra Id DefaultAzureCredential to get the token 21 | credential = DefaultAzureCredential() 22 | # Set the API type to `azure_ad` 23 | os.environ["OPENAI_API_TYPE"] = "azure_ad" 24 | # Set the API_KEY to the token from the Azure credential 25 | os.environ["OPENAI_API_KEY"] = credential.get_token( 26 | "https://cognitiveservices.azure.com/.default" 27 | ).token 28 | 29 | # Initialize Azure OpenAI environment 30 | AZURE_OPENAI_ENDPOINT = os.environ.get("AZURE_OPENAI_ENDPOINT") 31 | AZURE_OPENAI_KEY = credential.get_token( 32 | "https://cognitiveservices.azure.com/.default" 33 | ).token 34 | AZURE_OPENAI_CHATGPT_DEPLOYMENT = os.environ.get( 35 | "AZURE_OPENAI_CHATGPT_DEPLOYMENT") or "chat" 36 | OPENAI_API_VERSION = os.environ.get( 37 | "OPENAI_API_VERSION") or "2023-05-15" 38 | 39 | # Configure base OpenAI framework for LangChain and/or llm 40 | openai.api_key = AZURE_OPENAI_KEY 41 | openai.api_base = AZURE_OPENAI_ENDPOINT 42 | openai.api_type = "azure" 43 | openai.api_version = OPENAI_API_VERSION 44 | 45 | 46 | # Initialize Azure OpenAI environment 47 | init() 48 | 49 | 50 | # Function App entry point route for /api/ask 51 | @app.function_name(name="ask") 52 | @app.route(route="ask", auth_level="function", methods=["POST"]) 53 | def main(req): 54 | 55 | try: 56 | req_body = req.get_json() 57 | prompt = req_body.get("prompt") 58 | except ValueError: 59 | raise RuntimeError("prompt data must be set in POST.") 60 | else: 61 | if not prompt: 62 | raise RuntimeError("prompt data must be set in POST.") 63 | 64 | # LangChain user code goes here 65 | llm = AzureChatOpenAI( 66 | deployment_name=AZURE_OPENAI_CHATGPT_DEPLOYMENT, 67 | temperature=0.3 68 | ) 69 | llm_prompt = PromptTemplate.from_template( 70 | "The following is a conversation with an AI assistant. " + 71 | "The assistant is helpful.\n\n" + 72 | "A:How can I help you today?\n" + 73 | "Human: {human_prompt}?" 74 | ) 75 | formatted_prompt = llm_prompt.format(human_prompt=prompt) 76 | 77 | response = llm.invoke(formatted_prompt) 78 | logging.info(response.content) 79 | 80 | return func.HttpResponse(response.content) 81 | -------------------------------------------------------------------------------- /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": "[3.15.0, 4.0.0)" 14 | }, 15 | "concurrency": { 16 | "dynamicConcurrencyEnabled": true, 17 | "snapshotPersistenceEnabled": true 18 | } 19 | } -------------------------------------------------------------------------------- /infra/abbreviations.json: -------------------------------------------------------------------------------- 1 | { 2 | "analysisServicesServers": "as", 3 | "apiManagementService": "apim-", 4 | "appConfigurationConfigurationStores": "appcs-", 5 | "appManagedEnvironments": "cae-", 6 | "appContainerApps": "ca-", 7 | "authorizationPolicyDefinitions": "policy-", 8 | "automationAutomationAccounts": "aa-", 9 | "blueprintBlueprints": "bp-", 10 | "blueprintBlueprintsArtifacts": "bpa-", 11 | "cacheRedis": "redis-", 12 | "cdnProfiles": "cdnp-", 13 | "cdnProfilesEndpoints": "cdne-", 14 | "cognitiveServicesAccounts": "cog-", 15 | "cognitiveServicesFormRecognizer": "cog-fr-", 16 | "cognitiveServicesTextAnalytics": "cog-ta-", 17 | "computeAvailabilitySets": "avail-", 18 | "computeCloudServices": "cld-", 19 | "computeDiskEncryptionSets": "des", 20 | "computeDisks": "disk", 21 | "computeDisksOs": "osdisk", 22 | "computeGalleries": "gal", 23 | "computeSnapshots": "snap-", 24 | "computeVirtualMachines": "vm", 25 | "computeVirtualMachineScaleSets": "vmss-", 26 | "containerInstanceContainerGroups": "ci", 27 | "containerRegistryRegistries": "cr", 28 | "containerServiceManagedClusters": "aks-", 29 | "databricksWorkspaces": "dbw-", 30 | "dataFactoryFactories": "adf-", 31 | "dataLakeAnalyticsAccounts": "dla", 32 | "dataLakeStoreAccounts": "dls", 33 | "dataMigrationServices": "dms-", 34 | "dBforMySQLServers": "mysql-", 35 | "dBforPostgreSQLServers": "psql-", 36 | "devicesIotHubs": "iot-", 37 | "devicesProvisioningServices": "provs-", 38 | "devicesProvisioningServicesCertificates": "pcert-", 39 | "documentDBDatabaseAccounts": "cosmos-", 40 | "eventGridDomains": "evgd-", 41 | "eventGridDomainsTopics": "evgt-", 42 | "eventGridEventSubscriptions": "evgs-", 43 | "eventHubNamespaces": "evhns-", 44 | "eventHubNamespacesEventHubs": "evh-", 45 | "hdInsightClustersHadoop": "hadoop-", 46 | "hdInsightClustersHbase": "hbase-", 47 | "hdInsightClustersKafka": "kafka-", 48 | "hdInsightClustersMl": "mls-", 49 | "hdInsightClustersSpark": "spark-", 50 | "hdInsightClustersStorm": "storm-", 51 | "hybridComputeMachines": "arcs-", 52 | "insightsActionGroups": "ag-", 53 | "insightsComponents": "appi-", 54 | "keyVaultVaults": "kv-", 55 | "kubernetesConnectedClusters": "arck", 56 | "kustoClusters": "dec", 57 | "kustoClustersDatabases": "dedb", 58 | "logicIntegrationAccounts": "ia-", 59 | "logicWorkflows": "logic-", 60 | "machineLearningServicesWorkspaces": "mlw-", 61 | "managedIdentityUserAssignedIdentities": "id-", 62 | "managementManagementGroups": "mg-", 63 | "migrateAssessmentProjects": "migr-", 64 | "networkApplicationGateways": "agw-", 65 | "networkApplicationSecurityGroups": "asg-", 66 | "networkAzureFirewalls": "afw-", 67 | "networkBastionHosts": "bas-", 68 | "networkConnections": "con-", 69 | "networkDnsZones": "dnsz-", 70 | "networkExpressRouteCircuits": "erc-", 71 | "networkFirewallPolicies": "afwp-", 72 | "networkFirewallPoliciesWebApplication": "waf", 73 | "networkFirewallPoliciesRuleGroups": "wafrg", 74 | "networkFrontDoors": "fd-", 75 | "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", 76 | "networkLoadBalancersExternal": "lbe-", 77 | "networkLoadBalancersInternal": "lbi-", 78 | "networkLoadBalancersInboundNatRules": "rule-", 79 | "networkLocalNetworkGateways": "lgw-", 80 | "networkNatGateways": "ng-", 81 | "networkNetworkInterfaces": "nic-", 82 | "networkNetworkSecurityGroups": "nsg-", 83 | "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", 84 | "networkNetworkWatchers": "nw-", 85 | "networkPrivateDnsZones": "pdnsz-", 86 | "networkPrivateLinkServices": "pl-", 87 | "networkPublicIPAddresses": "pip-", 88 | "networkPublicIPPrefixes": "ippre-", 89 | "networkRouteFilters": "rf-", 90 | "networkRouteTables": "rt-", 91 | "networkRouteTablesRoutes": "udr-", 92 | "networkTrafficManagerProfiles": "traf-", 93 | "networkVirtualNetworkGateways": "vgw-", 94 | "networkVirtualNetworks": "vnet-", 95 | "networkVirtualNetworksSubnets": "snet-", 96 | "networkVirtualNetworksVirtualNetworkPeerings": "peer-", 97 | "networkVirtualWans": "vwan-", 98 | "networkVpnGateways": "vpng-", 99 | "networkVpnGatewaysVpnConnections": "vcn-", 100 | "networkVpnGatewaysVpnSites": "vst-", 101 | "notificationHubsNamespaces": "ntfns-", 102 | "notificationHubsNamespacesNotificationHubs": "ntf-", 103 | "operationalInsightsWorkspaces": "log-", 104 | "portalDashboards": "dash-", 105 | "powerBIDedicatedCapacities": "pbi-", 106 | "purviewAccounts": "pview-", 107 | "recoveryServicesVaults": "rsv-", 108 | "resourcesResourceGroups": "rg-", 109 | "searchSearchServices": "srch-", 110 | "serviceBusNamespaces": "sb-", 111 | "serviceBusNamespacesQueues": "sbq-", 112 | "serviceBusNamespacesTopics": "sbt-", 113 | "serviceEndPointPolicies": "se-", 114 | "serviceFabricClusters": "sf-", 115 | "signalRServiceSignalR": "sigr", 116 | "sqlManagedInstances": "sqlmi-", 117 | "sqlServers": "sql-", 118 | "sqlServersDataWarehouse": "sqldw-", 119 | "sqlServersDatabases": "sqldb-", 120 | "sqlServersDatabasesStretch": "sqlstrdb-", 121 | "storageStorageAccounts": "st", 122 | "storageStorageAccountsVm": "stvm", 123 | "storSimpleManagers": "ssimp", 124 | "streamAnalyticsCluster": "asa-", 125 | "synapseWorkspaces": "syn", 126 | "synapseWorkspacesAnalyticsWorkspaces": "synw", 127 | "synapseWorkspacesSqlPoolsDedicated": "syndp", 128 | "synapseWorkspacesSqlPoolsSpark": "synsp", 129 | "timeSeriesInsightsEnvironments": "tsi-", 130 | "webServerFarms": "plan-", 131 | "webSitesAppService": "app-", 132 | "webSitesAppServiceEnvironment": "ase-", 133 | "webSitesFunctions": "func-", 134 | "webStaticSites": "stapp-" 135 | } -------------------------------------------------------------------------------- /infra/app/ai-Cog-Service-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 aiResourceName string 5 | 6 | resource cognitiveService 'Microsoft.CognitiveServices/accounts@2023-05-01' existing = { 7 | name: aiResourceName 8 | } 9 | 10 | // Allow access from API to this resource using a managed identity and least priv role grants 11 | resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 12 | name: guid(cognitiveService.id, principalID, roleDefinitionID) 13 | scope: cognitiveService 14 | properties: { 15 | roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionID) 16 | principalId: principalID 17 | principalType: principalType 18 | } 19 | } 20 | 21 | output ROLE_ASSIGNMENT_NAME string = roleAssignment.name 22 | -------------------------------------------------------------------------------- /infra/app/api.bicep: -------------------------------------------------------------------------------- 1 | param name string 2 | param location string = resourceGroup().location 3 | param tags object = {} 4 | param applicationInsightsName string = '' 5 | param appServicePlanId string 6 | param appSettings object = {} 7 | param runtimeName string 8 | param runtimeVersion string 9 | param serviceName string = 'api' 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 | param aiServiceUrl string = '' 18 | 19 | var applicationInsightsIdentity = 'ClientId=${identityClientId};Authorization=AAD' 20 | 21 | module api '../core/host/functions-flexconsumption.bicep' = { 22 | name: '${serviceName}-functions-module' 23 | params: { 24 | name: name 25 | location: location 26 | tags: union(tags, { 'azd-service-name': serviceName }) 27 | identityType: 'UserAssigned' 28 | identityId: identityId 29 | appSettings: union(appSettings, 30 | { 31 | AzureWebJobsStorage__clientId : identityClientId 32 | APPLICATIONINSIGHTS_AUTHENTICATION_STRING: applicationInsightsIdentity 33 | AZURE_OPENAI_ENDPOINT: aiServiceUrl 34 | AZURE_CLIENT_ID: identityClientId 35 | }) 36 | applicationInsightsName: applicationInsightsName 37 | appServicePlanId: appServicePlanId 38 | runtimeName: runtimeName 39 | runtimeVersion: runtimeVersion 40 | storageAccountName: storageAccountName 41 | deploymentStorageContainerName: deploymentStorageContainerName 42 | virtualNetworkSubnetId: virtualNetworkSubnetId 43 | instanceMemoryMB: instanceMemoryMB 44 | maximumInstanceCount: maximumInstanceCount 45 | } 46 | } 47 | 48 | output SERVICE_API_NAME string = api.outputs.name 49 | output SERVICE_API_URI string = api.outputs.uri 50 | output SERVICE_API_IDENTITY_PRINCIPAL_ID string = api.outputs.identityPrincipalId 51 | -------------------------------------------------------------------------------- /infra/app/eventgrid.bicep: -------------------------------------------------------------------------------- 1 | param location string = resourceGroup().location 2 | param tags object = {} 3 | param storageAccountId string 4 | 5 | resource unprocessedPdfSystemTopic 'Microsoft.EventGrid/systemTopics@2024-06-01-preview' = { 6 | name: 'unprocessed-pdf-topic' 7 | location: location 8 | tags: tags 9 | properties: { 10 | source: storageAccountId 11 | topicType: 'Microsoft.Storage.StorageAccounts' 12 | } 13 | } 14 | 15 | // The actual event grid subscription will be created in the post deployment script as it needs the function to be deployed first 16 | 17 | // resource unprocessedPdfSystemTopicSubscription 'Microsoft.EventGrid/systemTopics/eventSubscriptions@2024-06-01-preview' = { 18 | // parent: unprocessedPdfSystemTopic 19 | // name: 'unprocessed-pdf-topic-subscription' 20 | // properties: { 21 | // destination: { 22 | // endpointType: 'WebHook' 23 | // properties: { 24 | // //Will be set on post-deployment script once the function is created and the blobs extension code is available 25 | // //endpointUrl: 'https://${function_app_blob_event_grid_name}.azurewebsites.net/runtime/webhooks/blobs?functionName=Host.Functions.Trigger_BlobEventGrid&code=${blobs_extension}' 26 | // } 27 | // } 28 | // filter: { 29 | // includedEventTypes: [ 30 | // 'Microsoft.Storage.BlobCreated' 31 | // ] 32 | // subjectBeginsWith: '/blobServices/default/containers/${unprocessedPdfContainerName}/' 33 | // } 34 | // } 35 | // } 36 | 37 | output unprocessedPdfSystemTopicId string = unprocessedPdfSystemTopic.id 38 | output unprocessedPdfSystemTopicName string = unprocessedPdfSystemTopic.name 39 | -------------------------------------------------------------------------------- /infra/app/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 | -------------------------------------------------------------------------------- /infra/app/storage-PrivateEndpoint.bicep: -------------------------------------------------------------------------------- 1 | // Parameters 2 | @description('Specifies the name of the virtual network.') 3 | param virtualNetworkName string 4 | 5 | @description('Specifies the name of the subnet which contains the virtual machine.') 6 | param subnetName string 7 | 8 | @description('Specifies the resource name of the Storage resource with an endpoint.') 9 | param resourceName string 10 | 11 | @description('Specifies the location.') 12 | param location string = resourceGroup().location 13 | 14 | param tags object = {} 15 | 16 | // Virtual Network 17 | resource vnet 'Microsoft.Network/virtualNetworks@2021-08-01' existing = { 18 | name: virtualNetworkName 19 | } 20 | 21 | resource storageAccount 'Microsoft.Storage/storageAccounts@2021-09-01' existing = { 22 | name: resourceName 23 | } 24 | 25 | var blobPrivateDNSZoneName = format('privatelink.blob.{0}', environment().suffixes.storage) 26 | var blobPrivateDnsZoneVirtualNetworkLinkName = format('{0}-link-{1}', resourceName, take(toLower(uniqueString(resourceName, virtualNetworkName)), 4)) 27 | 28 | // Private DNS Zones 29 | resource blobPrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { 30 | name: blobPrivateDNSZoneName 31 | location: 'global' 32 | tags: tags 33 | properties: {} 34 | dependsOn: [ 35 | vnet 36 | ] 37 | } 38 | 39 | // Virtual Network Links 40 | resource blobPrivateDnsZoneVirtualNetworkLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = { 41 | parent: blobPrivateDnsZone 42 | name: blobPrivateDnsZoneVirtualNetworkLinkName 43 | location: 'global' 44 | tags: tags 45 | properties: { 46 | registrationEnabled: false 47 | virtualNetwork: { 48 | id: vnet.id 49 | } 50 | } 51 | } 52 | 53 | // Private Endpoints 54 | resource blobPrivateEndpoint 'Microsoft.Network/privateEndpoints@2021-08-01' = { 55 | name: 'blob-private-endpoint' 56 | location: location 57 | tags: tags 58 | properties: { 59 | privateLinkServiceConnections: [ 60 | { 61 | name: 'blobPrivateLinkConnection' 62 | properties: { 63 | privateLinkServiceId: storageAccount.id 64 | groupIds: [ 65 | 'blob' 66 | ] 67 | } 68 | } 69 | ] 70 | subnet: { 71 | id: '${vnet.id}/subnets/${subnetName}' 72 | } 73 | } 74 | } 75 | 76 | resource blobPrivateDnsZoneGroupName 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2022-01-01' = { 77 | parent: blobPrivateEndpoint 78 | name: 'blobPrivateDnsZoneGroup' 79 | properties: { 80 | privateDnsZoneConfigs: [ 81 | { 82 | name: 'storageBlobARecord' 83 | properties: { 84 | privateDnsZoneId: blobPrivateDnsZone.id 85 | } 86 | } 87 | ] 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /infra/app/vnet.bicep: -------------------------------------------------------------------------------- 1 | @description('Specifies the name of the virtual network.') 2 | param vNetName string 3 | 4 | @description('Specifies the location.') 5 | param location string = resourceGroup().location 6 | 7 | @description('Specifies the name of the subnet for the Service Bus private endpoint.') 8 | param peSubnetName string = 'private-endpoints-subnet' 9 | 10 | @description('Specifies the name of the subnet for Function App virtual network integration.') 11 | param appSubnetName string = 'app' 12 | 13 | param tags object = {} 14 | 15 | resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-05-01' = { 16 | name: vNetName 17 | location: location 18 | tags: tags 19 | properties: { 20 | addressSpace: { 21 | addressPrefixes: [ 22 | '10.0.0.0/16' 23 | ] 24 | } 25 | encryption: { 26 | enabled: false 27 | enforcement: 'AllowUnencrypted' 28 | } 29 | subnets: [ 30 | { 31 | name: peSubnetName 32 | id: resourceId('Microsoft.Network/virtualNetworks/subnets', vNetName, 'private-endpoints-subnet') 33 | properties: { 34 | addressPrefixes: [ 35 | '10.0.1.0/24' 36 | ] 37 | delegations: [] 38 | privateEndpointNetworkPolicies: 'Disabled' 39 | privateLinkServiceNetworkPolicies: 'Enabled' 40 | } 41 | type: 'Microsoft.Network/virtualNetworks/subnets' 42 | } 43 | { 44 | name: appSubnetName 45 | id: resourceId('Microsoft.Network/virtualNetworks/subnets', vNetName, 'app') 46 | properties: { 47 | addressPrefixes: [ 48 | '10.0.2.0/24' 49 | ] 50 | delegations: [ 51 | { 52 | name: 'delegation' 53 | id: resourceId('Microsoft.Network/virtualNetworks/subnets/delegations', vNetName, 'app', 'delegation') 54 | properties: { 55 | //Microsoft.App/environments is the correct delegation for Flex Consumption VNet integration 56 | serviceName: 'Microsoft.App/environments' 57 | } 58 | type: 'Microsoft.Network/virtualNetworks/subnets/delegations' 59 | } 60 | ] 61 | privateEndpointNetworkPolicies: 'Disabled' 62 | privateLinkServiceNetworkPolicies: 'Enabled' 63 | } 64 | type: 'Microsoft.Network/virtualNetworks/subnets' 65 | } 66 | ] 67 | virtualNetworkPeerings: [] 68 | enableDdosProtection: false 69 | } 70 | } 71 | 72 | output peSubnetName string = virtualNetwork.properties.subnets[0].name 73 | output peSubnetID string = virtualNetwork.properties.subnets[0].id 74 | output appSubnetName string = virtualNetwork.properties.subnets[1].name 75 | output appSubnetID string = virtualNetwork.properties.subnets[1].id 76 | -------------------------------------------------------------------------------- /infra/core/ai/openai.bicep: -------------------------------------------------------------------------------- 1 | param name string 2 | param location string = resourceGroup().location 3 | param tags object = {} 4 | 5 | param customSubDomainName string = name 6 | param deployments array = [] 7 | param kind string = 'OpenAI' 8 | param publicNetworkAccess string = 'Enabled' 9 | param sku object = { 10 | name: 'S0' 11 | } 12 | 13 | resource account 'Microsoft.CognitiveServices/accounts@2023-10-01-preview' = { 14 | name: name 15 | location: location 16 | tags: tags 17 | kind: kind 18 | properties: { 19 | customSubDomainName: name 20 | networkAcls : { 21 | defaultAction: publicNetworkAccess == 'Enabled' ? 'Allow' : 'Deny' 22 | virtualNetworkRules: [] 23 | ipRules: [] 24 | } 25 | publicNetworkAccess: publicNetworkAccess 26 | } 27 | sku: sku 28 | } 29 | 30 | @batchSize(1) 31 | resource deployment 'Microsoft.CognitiveServices/accounts/deployments@2023-05-01' = [for deployment in deployments: { 32 | parent: account 33 | name: deployment.name 34 | sku: { 35 | name: 'Standard' 36 | capacity: deployment.capacity 37 | } 38 | properties: { 39 | model: deployment.model 40 | raiPolicyName: contains(deployment, 'raiPolicyName') ? deployment.raiPolicyName : null 41 | } 42 | }] 43 | 44 | output endpoint string = account.properties.endpoint 45 | output id string = account.id 46 | output name string = account.name 47 | output location string = account.location 48 | -------------------------------------------------------------------------------- /infra/core/host/appservice-appsettings.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Updates app settings for an Azure App Service.' 2 | @description('The name of the app service resource within the current resource group scope') 3 | param name string 4 | 5 | @description('The app settings to be applied to the app service') 6 | @secure() 7 | param appSettings object 8 | 9 | resource appService 'Microsoft.Web/sites@2022-03-01' existing = { 10 | name: name 11 | } 12 | 13 | resource settings 'Microsoft.Web/sites/config@2022-03-01' = { 14 | name: 'appsettings' 15 | parent: appService 16 | properties: appSettings 17 | } 18 | -------------------------------------------------------------------------------- /infra/core/host/appservice.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates an Azure App Service in an existing Azure App Service plan.' 2 | param name string 3 | param location string = resourceGroup().location 4 | param tags object = {} 5 | 6 | // Reference Properties 7 | param applicationInsightsName string = '' 8 | param appServicePlanId string 9 | param keyVaultName string = '' 10 | param managedIdentity bool = !empty(keyVaultName) 11 | 12 | // Runtime Properties 13 | @allowed([ 14 | 'dotnet', 'dotnetcore', 'dotnet-isolated', 'node', 'python', 'java', 'powershell', 'custom' 15 | ]) 16 | param runtimeName string 17 | param runtimeNameAndVersion string = '${runtimeName}|${runtimeVersion}' 18 | param runtimeVersion string 19 | 20 | // Microsoft.Web/sites Properties 21 | param kind string = 'app,linux' 22 | 23 | // Microsoft.Web/sites/config 24 | param allowedOrigins array = [] 25 | param alwaysOn bool = true 26 | param appCommandLine string = '' 27 | @secure() 28 | param appSettings object = {} 29 | param clientAffinityEnabled bool = false 30 | param enableOryxBuild bool = contains(kind, 'linux') 31 | param functionAppScaleLimit int = -1 32 | param linuxFxVersion string = runtimeNameAndVersion 33 | param minimumElasticInstanceCount int = -1 34 | param numberOfWorkers int = -1 35 | param scmDoBuildDuringDeployment bool = false 36 | param use32BitWorkerProcess bool = false 37 | param ftpsState string = 'FtpsOnly' 38 | param healthCheckPath string = '' 39 | param virtualNetworkSubnetId string = '' 40 | 41 | resource appService 'Microsoft.Web/sites@2022-03-01' = { 42 | name: name 43 | location: location 44 | tags: tags 45 | kind: kind 46 | properties: { 47 | serverFarmId: appServicePlanId 48 | siteConfig: { 49 | linuxFxVersion: linuxFxVersion 50 | alwaysOn: alwaysOn 51 | ftpsState: ftpsState 52 | minTlsVersion: '1.2' 53 | appCommandLine: appCommandLine 54 | numberOfWorkers: numberOfWorkers != -1 ? numberOfWorkers : null 55 | minimumElasticInstanceCount: minimumElasticInstanceCount != -1 ? minimumElasticInstanceCount : null 56 | use32BitWorkerProcess: use32BitWorkerProcess 57 | functionAppScaleLimit: functionAppScaleLimit != -1 ? functionAppScaleLimit : null 58 | healthCheckPath: healthCheckPath 59 | cors: { 60 | allowedOrigins: union([ 'https://portal.azure.com', 'https://ms.portal.azure.com' ], allowedOrigins) 61 | } 62 | } 63 | clientAffinityEnabled: clientAffinityEnabled 64 | httpsOnly: true 65 | virtualNetworkSubnetId: !empty(virtualNetworkSubnetId) ? virtualNetworkSubnetId : null 66 | } 67 | 68 | identity: { type: managedIdentity ? 'SystemAssigned' : 'None' } 69 | 70 | resource basicPublishingCredentialsPoliciesFtp 'basicPublishingCredentialsPolicies' = { 71 | name: 'ftp' 72 | properties: { 73 | allow: false 74 | } 75 | } 76 | 77 | resource basicPublishingCredentialsPoliciesScm 'basicPublishingCredentialsPolicies' = { 78 | name: 'scm' 79 | properties: { 80 | allow: false 81 | } 82 | } 83 | } 84 | 85 | // Updates to the single Microsoft.sites/web/config resources that need to be performed sequentially 86 | // sites/web/config 'appsettings' 87 | module configAppSettings 'appservice-appsettings.bicep' = { 88 | name: '${name}-appSettings' 89 | params: { 90 | name: appService.name 91 | appSettings: union(appSettings, 92 | { 93 | SCM_DO_BUILD_DURING_DEPLOYMENT: string(scmDoBuildDuringDeployment) 94 | ENABLE_ORYX_BUILD: string(enableOryxBuild) 95 | }, 96 | runtimeName == 'python' && appCommandLine == '' ? { PYTHON_ENABLE_GUNICORN_MULTIWORKERS: 'true'} : {}, 97 | !empty(applicationInsightsName) ? { APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights.properties.ConnectionString } : {}, 98 | !empty(keyVaultName) ? { AZURE_KEY_VAULT_ENDPOINT: keyVault.properties.vaultUri } : {}) 99 | } 100 | } 101 | 102 | // sites/web/config 'logs' 103 | resource configLogs 'Microsoft.Web/sites/config@2022-03-01' = { 104 | name: 'logs' 105 | parent: appService 106 | properties: { 107 | applicationLogs: { fileSystem: { level: 'Verbose' } } 108 | detailedErrorMessages: { enabled: true } 109 | failedRequestsTracing: { enabled: true } 110 | httpLogs: { fileSystem: { enabled: true, retentionInDays: 1, retentionInMb: 35 } } 111 | } 112 | dependsOn: [configAppSettings] 113 | } 114 | 115 | resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = if (!(empty(keyVaultName))) { 116 | name: keyVaultName 117 | } 118 | 119 | resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightsName)) { 120 | name: applicationInsightsName 121 | } 122 | 123 | output identityPrincipalId string = managedIdentity ? appService.identity.principalId : '' 124 | output name string = appService.name 125 | output uri string = 'https://${appService.properties.defaultHostName}' 126 | -------------------------------------------------------------------------------- /infra/core/host/appserviceplan.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates an Azure App Service plan.' 2 | param name string 3 | param location string = resourceGroup().location 4 | param tags object = {} 5 | 6 | param kind string = '' 7 | param reserved bool = true 8 | param sku object 9 | 10 | resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = { 11 | name: name 12 | location: location 13 | tags: tags 14 | sku: sku 15 | kind: kind 16 | properties: { 17 | reserved: reserved 18 | } 19 | } 20 | 21 | output id string = appServicePlan.id 22 | output name string = appServicePlan.name 23 | -------------------------------------------------------------------------------- /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 | param keyVaultName string = '' 15 | 16 | // Runtime Properties 17 | @allowed([ 18 | 'dotnet-isolated', 'node', 'python', 'java', 'powershell', 'custom' 19 | ]) 20 | param runtimeName string 21 | @allowed(['3.10', '3.11', '7.4', '8.0', '10', '11', '17', '20']) 22 | param runtimeVersion string 23 | param kind string = 'functionapp,linux' 24 | 25 | // Microsoft.Web/sites/config 26 | param appSettings object = {} 27 | param instanceMemoryMB int = 2048 28 | param maximumInstanceCount int = 100 29 | param deploymentStorageContainerName string 30 | param appCommandLine string = '' 31 | 32 | resource stg 'Microsoft.Storage/storageAccounts@2022-09-01' existing = { 33 | name: storageAccountName 34 | } 35 | 36 | resource functions 'Microsoft.Web/sites@2023-12-01' = { 37 | name: name 38 | location: location 39 | tags: tags 40 | kind: kind 41 | identity: { 42 | type: identityType 43 | userAssignedIdentities: { 44 | '${identityId}': {} 45 | } 46 | } 47 | properties: { 48 | serverFarmId: appServicePlanId 49 | functionAppConfig: { 50 | deployment: { 51 | storage: { 52 | type: 'blobContainer' 53 | value: '${stg.properties.primaryEndpoints.blob}${deploymentStorageContainerName}' 54 | authentication: { 55 | type: identityType == 'SystemAssigned' ? 'SystemAssignedIdentity' : 'UserAssignedIdentity' 56 | userAssignedIdentityResourceId: identityType == 'UserAssigned' ? identityId : '' 57 | } 58 | } 59 | } 60 | scaleAndConcurrency: { 61 | instanceMemoryMB: instanceMemoryMB 62 | maximumInstanceCount: maximumInstanceCount 63 | } 64 | runtime: { 65 | name: runtimeName 66 | version: runtimeVersion 67 | } 68 | } 69 | virtualNetworkSubnetId: !empty(virtualNetworkSubnetId) ? virtualNetworkSubnetId : null 70 | } 71 | } 72 | // Updates to the single Microsoft.sites/web/config resources that need to be performed sequentially 73 | // sites/web/config 'appsettings' 74 | module configAppSettings 'appservice-appsettings.bicep' = { 75 | name: '${name}-appSettings' 76 | params: { 77 | name: functions.name 78 | appSettings: union(appSettings, 79 | { 80 | AzureWebJobsStorage__accountName: stg.name 81 | AzureWebJobsStorage__credential : 'managedidentity' 82 | APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights.properties.ConnectionString 83 | }, 84 | runtimeName == 'python' && appCommandLine == '' ? { PYTHON_ENABLE_GUNICORN_MULTIWORKERS: 'true'} : {}, 85 | !empty(applicationInsightsName) ? { APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights.properties.ConnectionString } : {}, 86 | !empty(keyVaultName) ? { AZURE_KEY_VAULT_ENDPOINT: keyVault.properties.vaultUri } : {}) 87 | } 88 | } 89 | 90 | resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = if (!(empty(keyVaultName))) { 91 | name: keyVaultName 92 | } 93 | 94 | resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (!empty(applicationInsightsName)) { 95 | name: applicationInsightsName 96 | } 97 | 98 | output name string = functions.name 99 | output uri string = 'https://${functions.properties.defaultHostName}' 100 | output identityPrincipalId string = identityType == 'SystemAssigned' ? functions.identity.principalId : '' 101 | -------------------------------------------------------------------------------- /infra/core/identity/userAssignedIdentity.bicep: -------------------------------------------------------------------------------- 1 | param identityName string 2 | param location string 3 | param tags object = {} 4 | 5 | resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-07-31-preview' = { 6 | name: identityName 7 | location: location 8 | tags: tags 9 | } 10 | 11 | output identityId string = userAssignedIdentity.id 12 | output identityName string = userAssignedIdentity.name 13 | output identityPrincipalId string = userAssignedIdentity.properties.principalId 14 | output identityClientId string = userAssignedIdentity.properties.clientId 15 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /infra/core/monitor/monitoring.bicep: -------------------------------------------------------------------------------- 1 | param logAnalyticsName string 2 | param applicationInsightsName string 3 | param location string = resourceGroup().location 4 | param tags object = {} 5 | param disableLocalAuth bool = false 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: disableLocalAuth 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /infra/main.bicep: -------------------------------------------------------------------------------- 1 | targetScope = 'subscription' 2 | 3 | @minLength(1) 4 | @maxLength(64) 5 | @description('Name of the the environment which is used to generate a short unique hash used in all resources.') 6 | param environmentName string 7 | 8 | @minLength(1) 9 | @description('Primary location for all resources') 10 | @allowed(['australiaeast', 'eastasia', 'eastus', 'eastus2', 'northeurope', 'southcentralus', 'southeastasia', 'uksouth', 'westus2']) 11 | @metadata({ 12 | azd: { 13 | type: 'location' 14 | } 15 | }) 16 | param location string 17 | param skipVnet bool = true 18 | param apiServiceName string = '' 19 | param apiUserAssignedIdentityName string = '' 20 | param applicationInsightsName string = '' 21 | param appServicePlanName string = '' 22 | param logAnalyticsName string = '' 23 | param resourceGroupName string = '' 24 | param storageAccountName string = '' 25 | param vNetName string = '' 26 | param disableLocalAuth bool = true 27 | 28 | @allowed([ 'consumption', 'flexconsumption' ]) 29 | param azFunctionHostingPlanType string = 'flexconsumption' 30 | 31 | param openAiServiceName string = '' 32 | 33 | param openAiSkuName string 34 | @allowed([ 'azure', 'openai', 'azure_custom' ]) 35 | param openAiHost string // Set in main.parameters.json 36 | param openAiApiVersion string = '2023-05-15' 37 | 38 | param chatGptModelName string = '' 39 | param chatGptDeploymentName string = '' 40 | param chatGptDeploymentVersion string = '' 41 | param chatGptDeploymentCapacity int = 0 42 | 43 | var chatGpt = { 44 | modelName: !empty(chatGptModelName) ? chatGptModelName : startsWith(openAiHost, 'azure') ? 'gpt-35-turbo' : 'gpt-3.5-turbo' 45 | deploymentName: !empty(chatGptDeploymentName) ? chatGptDeploymentName : 'chat' 46 | deploymentVersion: !empty(chatGptDeploymentVersion) ? chatGptDeploymentVersion : '0125' 47 | deploymentCapacity: chatGptDeploymentCapacity != 0 ? chatGptDeploymentCapacity : 40 48 | } 49 | 50 | @description('Id of the user or app to assign application roles') 51 | param principalId string = '' 52 | 53 | var abbrs = loadJsonContent('./abbreviations.json') 54 | var resourceToken = toLower(uniqueString(subscription().id, environmentName, location)) 55 | var tags = { 'azd-env-name': environmentName } 56 | var functionAppName = !empty(apiServiceName) ? apiServiceName : '${abbrs.webSitesFunctions}api-${resourceToken}' 57 | var deploymentStorageContainerName = 'app-package-${take(functionAppName, 32)}-${take(toLower(uniqueString(functionAppName, resourceToken)), 7)}' 58 | 59 | // Organize resources in a resource group 60 | resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = { 61 | name: !empty(resourceGroupName) ? resourceGroupName : '${abbrs.resourcesResourceGroups}${environmentName}' 62 | location: location 63 | tags: tags 64 | } 65 | 66 | // User assigned managed identity to be used by the function app to reach storage and service bus 67 | module apiUserAssignedIdentity './core/identity/userAssignedIdentity.bicep' = { 68 | name: 'apiUserAssignedIdentity' 69 | scope: rg 70 | params: { 71 | location: location 72 | tags: tags 73 | identityName: !empty(apiUserAssignedIdentityName) ? apiUserAssignedIdentityName : '${abbrs.managedIdentityUserAssignedIdentities}api-${resourceToken}' 74 | } 75 | } 76 | 77 | // The application backend is a function app 78 | module appServicePlan './core/host/appserviceplan.bicep' = { 79 | name: 'appserviceplan' 80 | scope: rg 81 | params: { 82 | name: !empty(appServicePlanName) ? appServicePlanName : '${abbrs.webServerFarms}${resourceToken}' 83 | location: location 84 | tags: tags 85 | sku: { 86 | name: 'FC1' 87 | tier: 'FlexConsumption' 88 | } 89 | } 90 | } 91 | 92 | module api './app/api.bicep' = { 93 | name: 'api' 94 | scope: rg 95 | params: { 96 | name: functionAppName 97 | location: location 98 | tags: tags 99 | applicationInsightsName: monitoring.outputs.applicationInsightsName 100 | appServicePlanId: appServicePlan.outputs.id 101 | runtimeName: 'python' 102 | runtimeVersion: '3.11' 103 | storageAccountName: storage.outputs.name 104 | deploymentStorageContainerName: deploymentStorageContainerName 105 | identityId: apiUserAssignedIdentity.outputs.identityId 106 | identityClientId: apiUserAssignedIdentity.outputs.identityClientId 107 | appSettings: { 108 | OPENAI_API_VERSION: openAiApiVersion 109 | AZURE_OPENAI_CHATGPT_MODEL: chatGpt.modelName 110 | } 111 | virtualNetworkSubnetId: skipVnet ? '' : serviceVirtualNetwork.outputs.appSubnetID 112 | aiServiceUrl: ai.outputs.endpoint 113 | } 114 | } 115 | 116 | module ai 'core/ai/openai.bicep' = { 117 | name: 'openai' 118 | scope: rg 119 | params: { 120 | name: !empty(openAiServiceName) ? openAiServiceName : '${abbrs.cognitiveServicesAccounts}${resourceToken}' 121 | location: location 122 | tags: tags 123 | publicNetworkAccess: skipVnet == 'false' ? 'Disabled' : 'Enabled' 124 | sku: { 125 | name: openAiSkuName 126 | } 127 | deployments: [ 128 | { 129 | name: chatGpt.deploymentName 130 | capacity: chatGpt.deploymentCapacity 131 | model: { 132 | format: 'OpenAI' 133 | name: chatGpt.modelName 134 | version: chatGpt.deploymentVersion 135 | } 136 | scaleSettings: { 137 | scaleType: 'Standard' 138 | } 139 | } 140 | ] 141 | } 142 | } 143 | 144 | // Backing storage for Azure functions backend processor 145 | module storage 'core/storage/storage-account.bicep' = { 146 | name: 'storage' 147 | scope: rg 148 | params: { 149 | name: !empty(storageAccountName) ? storageAccountName : '${abbrs.storageStorageAccounts}${resourceToken}' 150 | location: location 151 | tags: tags 152 | containers: [ 153 | {name: deploymentStorageContainerName} 154 | ] 155 | networkAcls: skipVnet ? {} : { 156 | defaultAction: 'Deny' 157 | } 158 | } 159 | } 160 | 161 | var storageRoleDefinitionId = 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b' // Storage Blob Data Owner role 162 | 163 | // Allow access from api to storage account using a managed identity 164 | module storageRoleAssignmentApi 'app/storage-Access.bicep' = { 165 | name: 'storageRoleAssignmentapi' 166 | scope: rg 167 | params: { 168 | storageAccountName: storage.outputs.name 169 | roleDefinitionID: storageRoleDefinitionId 170 | principalID: apiUserAssignedIdentity.outputs.identityPrincipalId 171 | principalType: 'ServicePrincipal' 172 | } 173 | } 174 | 175 | module storageRoleAssignmentUserIdentityApi 'app/storage-Access.bicep' = { 176 | name: 'storageRoleAssignmentUserIdentityApi' 177 | scope: rg 178 | params: { 179 | storageAccountName: storage.outputs.name 180 | roleDefinitionID: storageRoleDefinitionId 181 | principalID: principalId 182 | principalType: 'User' 183 | } 184 | } 185 | 186 | var storageQueueDataContributorRoleDefinitionId = '974c5e8b-45b9-4653-ba55-5f855dd0fb88' // Storage Queue Data Contributor 187 | 188 | module storageQueueDataContributorRoleAssignmentprocessor 'app/storage-Access.bicep' = { 189 | name: 'storageQueueDataContributorRoleAssignmentprocessor' 190 | scope: rg 191 | params: { 192 | storageAccountName: storage.outputs.name 193 | roleDefinitionID: storageQueueDataContributorRoleDefinitionId 194 | principalID: apiUserAssignedIdentity.outputs.identityPrincipalId 195 | principalType: 'ServicePrincipal' 196 | } 197 | } 198 | 199 | module storageQueueDataContributorRoleAssignmentUserIdentityprocessor 'app/storage-Access.bicep' = { 200 | name: 'storageQueueDataContributorRoleAssignmentUserIdentityprocessor' 201 | scope: rg 202 | params: { 203 | storageAccountName: storage.outputs.name 204 | roleDefinitionID: storageQueueDataContributorRoleDefinitionId 205 | principalID: principalId 206 | principalType: 'User' 207 | } 208 | } 209 | 210 | var storageTableDataContributorRoleDefinitionId = '0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3' // Storage Table Data Contributor 211 | 212 | module storageTableDataContributorRoleAssignmentprocessor 'app/storage-Access.bicep' = { 213 | name: 'storageTableDataContributorRoleAssignmentprocessor' 214 | scope: rg 215 | params: { 216 | storageAccountName: storage.outputs.name 217 | roleDefinitionID: storageTableDataContributorRoleDefinitionId 218 | principalID: apiUserAssignedIdentity.outputs.identityPrincipalId 219 | principalType: 'ServicePrincipal' 220 | } 221 | } 222 | 223 | module storageTableDataContributorRoleAssignmentUserIdentityprocessor 'app/storage-Access.bicep' = { 224 | name: 'storageTableDataContributorRoleAssignmentUserIdentityprocessor' 225 | scope: rg 226 | params: { 227 | storageAccountName: storage.outputs.name 228 | roleDefinitionID: storageTableDataContributorRoleDefinitionId 229 | principalID: principalId 230 | principalType: 'User' 231 | } 232 | } 233 | 234 | var cogRoleDefinitionId = 'a97b65f3-24c7-4388-baec-2e87135dc908' // Cognitive Services User 235 | 236 | // Allow access from api to storage account using a managed identity 237 | module cogRoleAssignmentApi 'app/ai-Cog-Service-Access.bicep' = { 238 | name: 'cogRoleAssignmentapi' 239 | scope: rg 240 | params: { 241 | aiResourceName: ai.outputs.name 242 | roleDefinitionID: cogRoleDefinitionId 243 | principalID: apiUserAssignedIdentity.outputs.identityPrincipalId 244 | principalType: 'ServicePrincipal' 245 | } 246 | } 247 | 248 | module cogRoleAssignmentUserIdentityApi 'app/ai-Cog-Service-Access.bicep' = { 249 | name: 'cogRoleAssignmentUserIdentityApi' 250 | scope: rg 251 | params: { 252 | aiResourceName: ai.outputs.name 253 | roleDefinitionID: cogRoleDefinitionId 254 | principalID: principalId 255 | principalType: 'User' 256 | } 257 | } 258 | 259 | // Virtual Network & private endpoint to blob storage 260 | module serviceVirtualNetwork 'app/vnet.bicep' = if (!skipVnet) { 261 | name: 'serviceVirtualNetwork' 262 | scope: rg 263 | params: { 264 | location: location 265 | tags: tags 266 | vNetName: !empty(vNetName) ? vNetName : '${abbrs.networkVirtualNetworks}${resourceToken}' 267 | } 268 | } 269 | 270 | module storagePrivateEndpoint 'app/storage-PrivateEndpoint.bicep' = if (!skipVnet) { 271 | name: 'servicePrivateEndpoint' 272 | scope: rg 273 | params: { 274 | location: location 275 | tags: tags 276 | virtualNetworkName: !empty(vNetName) ? vNetName : '${abbrs.networkVirtualNetworks}${resourceToken}' 277 | subnetName: skipVnet ? '' : serviceVirtualNetwork.outputs.peSubnetName 278 | resourceName: storage.outputs.name 279 | } 280 | } 281 | 282 | // Monitor application with Azure Monitor 283 | module monitoring './core/monitor/monitoring.bicep' = { 284 | name: 'monitoring' 285 | scope: rg 286 | params: { 287 | location: location 288 | tags: tags 289 | logAnalyticsName: !empty(logAnalyticsName) ? logAnalyticsName : '${abbrs.operationalInsightsWorkspaces}${resourceToken}' 290 | applicationInsightsName: !empty(applicationInsightsName) ? applicationInsightsName : '${abbrs.insightsComponents}${resourceToken}' 291 | disableLocalAuth: disableLocalAuth 292 | } 293 | } 294 | 295 | var monitoringRoleDefinitionId = '3913510d-42f4-4e42-8a64-420c390055eb' // Monitoring Metrics Publisher role ID 296 | 297 | // Allow access from api to application insights using a managed identity 298 | module appInsightsRoleAssignmentApi './core/monitor/appinsights-access.bicep' = { 299 | name: 'appInsightsRoleAssignmentapi' 300 | scope: rg 301 | params: { 302 | appInsightsName: monitoring.outputs.applicationInsightsName 303 | roleDefinitionID: monitoringRoleDefinitionId 304 | principalID: apiUserAssignedIdentity.outputs.identityPrincipalId 305 | } 306 | } 307 | 308 | // App outputs 309 | output APPLICATIONINSIGHTS_CONNECTION_STRING string = monitoring.outputs.applicationInsightsConnectionString 310 | output AZURE_LOCATION string = location 311 | output AZURE_TENANT_ID string = tenant().tenantId 312 | output SERVICE_API_NAME string = api.outputs.SERVICE_API_NAME 313 | output SERVICE_API_URI string = api.outputs.SERVICE_API_URI 314 | output AZURE_FUNCTION_APP_NAME string = api.outputs.SERVICE_API_NAME 315 | output RESOURCE_GROUP string = rg.name 316 | output AZURE_OPENAI_ENDPOINT string = ai.outputs.endpoint 317 | -------------------------------------------------------------------------------- /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 | "skipVnet": { 6 | "value": "${SKIP_VNET}=true" 7 | }, 8 | "environmentName": { 9 | "value": "${AZURE_ENV_NAME}" 10 | }, 11 | "location": { 12 | "value": "${AZURE_LOCATION}" 13 | }, 14 | "principalId": { 15 | "value": "${AZURE_PRINCIPAL_ID}" 16 | }, 17 | "openAiSkuName": { 18 | "value": "S0" 19 | }, 20 | "openAiServiceName": { 21 | "value": "${AZURE_OPENAI_SERVICE}" 22 | }, 23 | "openAiHost": { 24 | "value": "${OPENAI_HOST=azure}" 25 | }, 26 | "openAiResourceGroupName": { 27 | "value": "${AZURE_OPENAI_RESOURCE_GROUP}" 28 | }, 29 | "openAiApiVersion": { 30 | "value": "${OPENAI_API_VERSION=2023-05-15}" 31 | }, 32 | "chatGptDeploymentName": { 33 | "value": "${AZURE_OPENAI_CHATGPT_DEPLOYMENT=chat}" 34 | }, 35 | "chatGptDeploymentCapacity":{ 36 | "value": "${AZURE_OPENAI_CHATGPT_DEPLOYMENT_CAPACITY}" 37 | }, 38 | "chatGptDeploymentVersion":{ 39 | "value": "${AZURE_OPENAI_CHATGPT_DEPLOYMENT_VERSION}" 40 | }, 41 | "chatGptModelName":{ 42 | "value": "${AZURE_OPENAI_CHATGPT_MODEL=gpt-35-turbo}" 43 | }, 44 | "embeddingDeploymentName": { 45 | "value": "${AZURE_OPENAI_EMB_DEPLOYMENT=embedding}" 46 | }, 47 | "embeddingModelName":{ 48 | "value": "${AZURE_OPENAI_EMB_MODEL_NAME=text-embedding-3-small}" 49 | }, 50 | "embeddingDeploymentVersion":{ 51 | "value": "${AZURE_OPENAI_EMB_DEPLOYMENT_VERSION}" 52 | }, 53 | "embeddingDeploymentCapacity":{ 54 | "value": "${AZURE_OPENAI_EMB_DEPLOYMENT_CAPACITY}" 55 | }, 56 | "searchServiceName": { 57 | "value": "${AZURE_SEARCH_SERVICE}" 58 | }, 59 | "searchServiceResourceGroupName": { 60 | "value": "${AZURE_SEARCH_SERVICE_RESOURCE_GROUP}" 61 | }, 62 | "searchServiceIndexName": { 63 | "value": "${AZURE_SEARCH_INDEX=openai-index}" 64 | }, 65 | "searchServiceSkuName": { 66 | "value": "${AZURE_SEARCH_SERVICE_SKU=standard}" 67 | }, 68 | "storageAccountName": { 69 | "value": "${AZURE_STORAGE_ACCOUNT}" 70 | }, 71 | "storageResourceGroupName": { 72 | "value": "${AZURE_STORAGE_RESOURCE_GROUP}" 73 | }, 74 | "azFunctionHostingPlanType": { 75 | "value": "flexconsumption" 76 | }, 77 | "systemPrompt": { 78 | "value": "${SYSTEM_PROMPT}=You are a helpful assistant. You are responding to requests from a user about internal emails and documents. You can and should refer to the internal documents to help respond to requests. If a user makes a request thats not covered by the documents provided in the query, you must say that you do not have access to the information and not try and get information from other places besides the documents provided. The following is a list of documents that you can refer to when answering questions. The documents are in the format [filename]: [text] and are separated by newlines. If you answer a question by referencing any of the documents, please cite the document in your answer. For example, if you answer a question by referencing info.txt, you should add \"Reference: info.txt\" to the end of your answer on a separate line." 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Do not include azure-functions-worker in this file 2 | # The Python Worker is managed by the Azure Functions platform 3 | # Manually managing azure-functions-worker may cause unexpected issues 4 | 5 | azure-functions 6 | azure-identity 7 | openai 8 | langchain-core 9 | langchain-openai 10 | -------------------------------------------------------------------------------- /test.http: -------------------------------------------------------------------------------- 1 | ### Simple Ask Completion (Local) 2 | POST http://localhost:7071/api/ask HTTP/1.1 3 | content-type: application/json 4 | 5 | { 6 | "prompt": "Tell me two most popular programming features of Azure Functions" 7 | } 8 | 9 | ### Simple Ask Completion (Cloud) 10 | ### .gitignore this file if and when you set key 11 | POST https://.azurewebsites.net/api/ask HTTP/1.1 12 | content-type: application/json 13 | x-functions-key: 14 | 15 | { 16 | "prompt": "Tell me two most popular programming features of Azure Functions" 17 | } 18 | -------------------------------------------------------------------------------- /testdata.json: -------------------------------------------------------------------------------- 1 | { 2 | "prompt": "What is a good feature of Azure Functions?" 3 | } --------------------------------------------------------------------------------