├── .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 | [](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 | }
--------------------------------------------------------------------------------