From d85f039a0f966067beec1277f1a2431180c0b115 Mon Sep 17 00:00:00 2001 From: Nicholas Moore <25497602+nfmoore@users.noreply.github.com> Date: Sat, 11 May 2024 18:52:29 +1000 Subject: [PATCH] Add contoso trek product info chatbot custom example scenario (#3) --- .devcontainer/Dockerfile | 25 + .devcontainer/devcontainer.json | 81 + .devcontainer/requirements.txt | 9 + .github/dependabot.yml | 28 + .github/docs/getting-started.md | 59 + .github/docs/images/design-01.png | Bin 0 -> 244558 bytes .github/docs/step-by-step-setup.md | 199 ++ .gitignore | 3 + README.md | 129 +- app/Dockerfile | 29 + {src/app => app}/main.py | 75 +- app/requirements.txt | 8 + .../product_info_1.md | 0 .../product_info_10.md | 0 .../product_info_11.md | 0 .../product_info_12.md | 0 .../product_info_13.md | 0 .../product_info_14.md | 0 .../product_info_15.md | 0 .../product_info_16.md | 0 .../product_info_17.md | 0 .../product_info_18.md | 0 .../product_info_19.md | 0 .../product_info_2.md | 0 .../product_info_20.md | 0 .../product_info_3.md | 0 .../product_info_4.md | 0 .../product_info_5.md | 0 .../product_info_6.md | 0 .../product_info_7.md | 0 .../product_info_8.md | 0 .../product_info_9.md | 0 environment/requirements.txt | 9 - infrastructure/main.bicep | 356 ++-- infrastructure/main.json | 1824 +++++++++++++++-- infrastructure/modules/ai-search.bicep | 81 + infrastructure/modules/ai-services.bicep | 116 ++ .../modules/container-app-environment.bicep | 64 + .../modules/container-registry.bicep | 73 + .../modules/log-analytics-workspace.bicep | 89 + infrastructure/modules/storage-account.bicep | 85 + .../modules/user-assigned-identity.bicep | 30 + .../utilities.py => llms/custom_rag_client.py | 93 +- .../system_messages.yml | 17 +- notebooks/01-populate-index.ipynb | 158 -- notebooks/02-llm-queries.ipynb | 181 -- notebooks/example_scenario_01.ipynb | 412 ++++ .../custom_search_client.py | 10 +- .../templates/product-info/datasource.json | 0 .../templates/product-info/index.json | 2 +- .../templates/product-info/indexer.json | 0 .../templates/product-info/skillset.json | 0 src/app/Dockerfile | 26 - 53 files changed, 3401 insertions(+), 870 deletions(-) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100644 .devcontainer/requirements.txt create mode 100644 .github/dependabot.yml create mode 100644 .github/docs/getting-started.md create mode 100644 .github/docs/images/design-01.png create mode 100644 .github/docs/step-by-step-setup.md create mode 100644 app/Dockerfile rename {src/app => app}/main.py (67%) create mode 100644 app/requirements.txt rename data/{product-info => contoso-trek-product-info}/product_info_1.md (100%) rename data/{product-info => contoso-trek-product-info}/product_info_10.md (100%) rename data/{product-info => contoso-trek-product-info}/product_info_11.md (100%) rename data/{product-info => contoso-trek-product-info}/product_info_12.md (100%) rename data/{product-info => contoso-trek-product-info}/product_info_13.md (100%) rename data/{product-info => contoso-trek-product-info}/product_info_14.md (100%) rename data/{product-info => contoso-trek-product-info}/product_info_15.md (100%) rename data/{product-info => contoso-trek-product-info}/product_info_16.md (100%) rename data/{product-info => contoso-trek-product-info}/product_info_17.md (100%) rename data/{product-info => contoso-trek-product-info}/product_info_18.md (100%) rename data/{product-info => contoso-trek-product-info}/product_info_19.md (100%) rename data/{product-info => contoso-trek-product-info}/product_info_2.md (100%) rename data/{product-info => contoso-trek-product-info}/product_info_20.md (100%) rename data/{product-info => contoso-trek-product-info}/product_info_3.md (100%) rename data/{product-info => contoso-trek-product-info}/product_info_4.md (100%) rename data/{product-info => contoso-trek-product-info}/product_info_5.md (100%) rename data/{product-info => contoso-trek-product-info}/product_info_6.md (100%) rename data/{product-info => contoso-trek-product-info}/product_info_7.md (100%) rename data/{product-info => contoso-trek-product-info}/product_info_8.md (100%) rename data/{product-info => contoso-trek-product-info}/product_info_9.md (100%) delete mode 100644 environment/requirements.txt create mode 100644 infrastructure/modules/ai-search.bicep create mode 100644 infrastructure/modules/ai-services.bicep create mode 100644 infrastructure/modules/container-app-environment.bicep create mode 100644 infrastructure/modules/container-registry.bicep create mode 100644 infrastructure/modules/log-analytics-workspace.bicep create mode 100644 infrastructure/modules/storage-account.bicep create mode 100644 infrastructure/modules/user-assigned-identity.bicep rename src/rag/utilities.py => llms/custom_rag_client.py (82%) rename src/rag/configuration.yaml => llms/system_messages.yml (52%) delete mode 100644 notebooks/01-populate-index.ipynb delete mode 100644 notebooks/02-llm-queries.ipynb create mode 100644 notebooks/example_scenario_01.ipynb rename src/search/utilities.py => search/custom_search_client.py (95%) rename {src/search => search}/templates/product-info/datasource.json (100%) rename {src/search => search}/templates/product-info/index.json (98%) rename {src/search => search}/templates/product-info/indexer.json (100%) rename {src/search => search}/templates/product-info/skillset.json (100%) delete mode 100644 src/app/Dockerfile diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..d29b303 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,25 @@ +FROM mcr.microsoft.com/devcontainers/python:1-3.10-bullseye + +# # Install Databricks CLI +RUN curl -fsSL https://raw.githubusercontent.com/databricks/setup-cli/main/install.sh | sudo sh + +# Copy Python dependencies +COPY ./requirements.txt ./requirements.txt + +# Install Python dependencies +RUN pip install --no-cache-dir --upgrade pip && \ + pip install --no-cache-dir -r ./requirements.txt + +# Install Azure ClI +RUN curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash && az bicep install + +# Connect as root non-root user. More info: https://aka.ms/dev-containers-non-root. +USER vscode + +# Add zsh-autosuggestions +RUN git clone https://github.com/zsh-users/zsh-autosuggestions ~/.zsh/zsh-autosuggestions +RUN printf 'source ~/.zsh/zsh-autosuggestions/zsh-autosuggestions.zsh\n' >> ~/.zshrc + +# Add zsh-syntax-highlighting +RUN git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ~/.zsh/zsh-syntax-highlighting +RUN printf 'source ~/.zsh/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh\n' >> ~/.zshrc diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..9cd97c9 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,81 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/python +{ + "name": "azure-open-ai-example-scenarios", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "build": { + // Path is relative to the devcontainer.json file. + "dockerfile": "Dockerfile" + }, + // Features to add to the dev container. More info: https://containers.dev/features. + "features": { + "ghcr.io/devcontainers/features/docker-in-docker:2.10.2": {}, + "ghcr.io/devcontainers/features/github-cli:1": {}, + "ghcr.io/devcontainers-contrib/features/zsh-plugins:0": {} + }, + // Use 'forwardPorts' to make a list of ports inside the container available locally. + "forwardPorts": [ + 8501 + ], + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "", + // Configure tool-specific properties. + "customizations": { + "vscode": { + "extensions": [ + "ms-azuretools.vscode-docker", + "charliermarsh.ruff", + "ms-vscode.vscode-node-azure-pack", + "ms-vscode.azurecli", + "ms-azuretools.vscode-bicep", + "ms-vscode-remote.remote-containers", + "databricks.databricks", + "github.vscode-github-actions", + "redhat.vscode-yaml", + "ms-toolsai.jupyter", + "ms-python.python", + "yzhang.markdown-all-in-one" + ] + }, + "settings": { + "editor.autoClosingBrackets": "always", + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + }, + "editor.formatOnPaste": true, + "editor.formatOnSave": true, + "editor.inlineSuggest.enabled": true, + "files.autoSave": "afterDelay", + "git.autofetch": true, + "github.copilot.enable": { + "*": true + }, + "notebook.formatOnSave.enabled": true, + "notebook.codeActionsOnSave": { + "notebook.source.fixAll": "explicit", + "notebook.source.organizeImports": "explicit" + }, + "terminal.integrated.defaultProfile.linux": "zsh", + "terminal.integrated.profiles.linux": { + "zsh": { + "path": "/usr/bin/zsh" + } + }, + "[python]": { + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + "source.organizeImports": "explicit" + }, + "editor.defaultFormatter": "charliermarsh.ruff" + } + } + }, + // You can use the mounts property to persist the user profile (to keep things like shell history). + "mounts": [ + "source=profile,target=/root,type=volume", + "target=/root/.vscode-server,type=volume" + ] + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} \ No newline at end of file diff --git a/.devcontainer/requirements.txt b/.devcontainer/requirements.txt new file mode 100644 index 0000000..1160634 --- /dev/null +++ b/.devcontainer/requirements.txt @@ -0,0 +1,9 @@ +azure-identity==1.16.0 +ipykernel==6.29.4 +Jinja2==3.1.4 +openai==1.26.0 +python-dotenv==1.0.1 +requests==2.31.0 +stop-words==2018.7.23 +streamlit==1.34.0 +streamlit-extras==0.4.2 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..9c9489f --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,28 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for more information: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +# https://containers.dev/guide/dependabot + +version: 2 +updates: + + - package-ecosystem: "devcontainers" + directory: ".devcontainer/" + schedule: + interval: weekly + + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: weekly + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/docs/getting-started.md b/.github/docs/getting-started.md new file mode 100644 index 0000000..207ccd1 --- /dev/null +++ b/.github/docs/getting-started.md @@ -0,0 +1,59 @@ +# Getting Started + +The purpose of this section is to provide an overview of each example scenario. + +## Example Scenario: Contoso Trek Product Info Chatbot (Custom) + +### Overview + +This example scenario demonstrates a small-scale proof-of-concept deployment of a chatbot leveraging Azure OpenAI and Azure AI Search. It coveres the following key areas: + +- Pull-based data ingestion with Azure AI Search +- Custom retrieval-augmented generation (RAG) Implementation +- Built using Streamlit +- Deployed using Azure Container Apps + +### Solution Design + +The below diagram shows a high-level design for the chatbot leveraging Azure OpenAI, Azure AI Search, deployed as part of an Azure Container App. + +![Solution Design](./images/design-01.png) + +The solution consists of the following services: + +- **[Azure OpenAI](https://learn.microsoft.com/azure/ai-services/openai/overview)**: Provides the large language model (LLM) capabilities for generating human-like responses based on user queries. +- **[Azure AI Search](https://learn.microsoft.com/azure/search/search-what-is-azure-search)**: Enables efficient retrieval of relevant information from your enterprise knowledge base or data sources. +- **[Container Registry](https://learn.microsoft.com/azure/container-registry/container-registry-intro)**: Used for storing the Docker image. +- **[Container App](https://learn.microsoft.com/azure/container-apps/containers)**: Used for exposing the container as a REST API. + +The following open-source Python framework are used in this project: + +- **[Streamlit](https://streamlit.io/)**: Facilitates a lightweight and user-friendly deployment experience, making the chatbot readily accessible through a web interface. + +### How It Works + +The RAG pattern implemented here utilizes Azure AI Search to retrieve the most relevant information based on the user's query. This retrieved information is then fed into the Azure OpenAI LLM, which generates a comprehensive and informative response tailored to the specific context. + +The chatbot is built using Streamlit, which provides a simple and intuitive interface for users to interact with the chatbot. The chatbot is deployed using Azure Container Apps, which allows for easy scaling and management of the application. + +The [Step-by-Step Setup](.github/docs/step-by-step-setup.md) section of this repository. provides detailed instructions on how to deploy this proof-of-concept. + +> [!CAUTION] +> This solution design is intended for proof-of-concept scenarios and is not recommended for enterprise production scenarios. It is advised to review and adjust the design based on your specific requirements if you plan to use this in a production environment. This could include: +> +> - Securing the solution through network controls. +> - Upflift observability by enabling monitoring and logging for different services. +> - Defining an operational support and lifecycle management plan for the solution. +> - Implementing alerting and notification mechanisms to notify support teams of any issues (e.g. performance, budget, etc.). +> +> The Azure Well-Architected Framework provides guidance on best practices for designing, building, and maintaining cloud solutions. For more information, see the [Azure Well-Architected Framework](https://learn.microsoft.com/azure/well-architected/what-is-well-architected-framework). + +## Related Resources + +- [Azure OpenAI](https://learn.microsoft.com/azure/ai-services/openai/) +- [Azure AI Search](https://learn.microsoft.com/azure/search/) +- [Streamlit](https://streamlit.io/) +- [Azure OpenAI Service REST API reference](https://learn.microsoft.com/azure/ai-services/openai/reference) +- [Securely use Azure OpenAI on your data](https://learn.microsoft.com/azure/ai-services/openai/how-to/use-your-data-securely) +- [Introduction to prompt engineering](https://learn.microsoft.com/azure/ai-services/openai/concepts/prompt-engineering) +- [Prompt engineering techniques](https://learn.microsoft.com/azure/ai-services/openai/concepts/advanced-prompt-engineering?pivots=programming-language-chat-completions) diff --git a/.github/docs/images/design-01.png b/.github/docs/images/design-01.png new file mode 100644 index 0000000000000000000000000000000000000000..afae0f198cdb1ccb537e66961ff28bc6dadcb5a1 GIT binary patch literal 244558 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%0DUglg~xr{cb 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 (1 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 Y [!TIP] +> +> - The following options are recommended to complete the setup: +> 1. Using the [Azure Cloud Shell](https://learn.microsoft.com/azure/cloud-shell/overview) within the Azure Portal. +> 2. Using a [GitHub Codespace](https://docs.github.com/en/codespaces/prebuilding-your-codespaces/about-github-codespaces-prebuilds) +> 3. Using your local VSCode environment with the environment specified in the [development container](https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration/introduction-to-dev-containers). This will be the most efficient way to complete the setup. + +> [!WARNING] +> +> - As with all Azure Deployments, this will incur associated costs. Remember to teardown all related resources after use to avoid unnecessary costs. + +## Prerequisites + +Before implementing this example scenario the following is needed: + +- Azure subscription with Owner permissions. +- GitHub account. + +## 1. Common Setup + +## 1.1. Create a GitHub repository + +1. Log in to your GitHub account and navigate to the [azure-open-ai-example-scenarios](https://github.com/nfmoore/azure-open-ai-example-scenarios) repository and click `Use this template` to create a new repository from this template. + + Rename the template and leave it public. Ensure you click `Include all branches` to copy all branches. + +> [!NOTE] +> +> - You can learn more about creating a repository from a template [here](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-template-repository). + +## 1.2. Deploy Azure resources + +1. Run the following command to get your Object ID of your Azure AD user. + + ```bash + az ad signed-in-user show --query "id" -o tsv + ``` + + This will be used to assign the required permissions needed to interact with the Azure resources during the setup or a development scenario. + +2. Click the `Deploy to Azure` button below to deploy the Azure resources required for these example scenarios. + + [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fnfmoore%2Fazure-open-ai-example-scenarios%2Fmain%2Finfrastructure%2Fmain.json) + +> [!NOTE] +> +> - The Azure AD user requires the `Search Service Contributor ` role and `Search Index Data Contributor` role when creating assets and interacting with Azure AI Search. +> - The Azure AD user requires the `Cognitive Services OpenAI Contributor` role when interacting with Azure Open AI. +> - These roles have been assigned to the Azure AD user in the ARM template used for deployment in step (2). + +## 2. Example Scenario Setup + +## 2.1. Contoso Trek Product Info Chatbot (Custom) + +The [Getting Started](.github/docs/getting-started.md) section of this repository provides an overview of the Contoso Trek Product Info Chatbot (Custom) example scenario. + +### 2.1.1. Set environment variables + +1. To run this project, you need to configure the following environment variables. These must be stored in a .env file in the root of the project. + + ```bash + AZURE_SUBSCRIPTION_ID= + AZURE_RESOURCE_GROUP= + + AZURE_OPENAI_API_BASE=.openai.azure.com + AZURE_AI_SEARCH_ENDPOINT=.search.windows.net + + AZURE_OPENAI_CHAT_DEPLOYMENT=gpt-35-turbo-16k-0613 + AZURE_OPENAI_EMBEDDING_DEPLOYMENT=text-embedding-ada-002-2 + + AZURE_AI_SEARCH_INDEX_NAME=contoso-trek-product-info-01-index + AZURE_AI_SEARCH_INDEXER_NAME=contoso-trek-product-info-01-indexer + AZURE_AI_SEARCH_DATASOURCE_NAME=contoso-trek-product-info-01-datasource + AZURE_AI_SEARCH_SKILLSET_NAME=contoso-trek-product-info-01-skillset + + AZURE_AI_SEARCH_DATASTORE_NAME= + AZURE_AI_SEARCH_DATASTORE_CONTAINER_NAME=data + AZURE_AI_SEARCH_DATASTORE_CONTAINER_PATH=contoso-trek-product-info + ``` + +> [!NOTE] +> +> - The values of the environment variables can be found in the Azure Portal after the deployment of the Azure resources. + +### 2.1.2. Configure the Azure AI Search service and query the Azure Open AI + +1. Before running the notebooks ensure you authenticate with the Azure CLI by running the following command: + + ```bash + az login --tenant + ``` + + This will open a browser window to authenticate with the Azure CLI. +2. To create these artifacts to configure the Azure AI Search service run section `1. Populate Azure AI Search Index` of the the `notebooks/example_scenario_01.ipynb` notebook. +3. After the index, skillset, indexer, and datasource have been created, check that the indexer has run successfully and the index has been populated with data in the Azure Portal under the Azure AI Search service. +4. To query Azure Open AI using the RAG pattern run section `2. LLM Queries with Knowledge Base Integration` of the the `notebooks/example_scenario_01.ipynb` notebook. This section uses the custom RAG implementation and contrasts this with the custom RAG implementation. + +> [!WARNING] +> +> - Ensure that you can complete this section without any errors before proceeding to the next step. + +### 2.1.3. Run the Streamlit app + +1. Before running the streamlit app ensure you authenticate with the Azure CLI by running the following command: + + ```bash + az login --tenant + ``` + + This will open a browser window to authenticate with the Azure CLI. +2. To run the streamlit app locally for testing purposes, you can use the following command: + + ```bash + streamlit run ./app/main.py + ``` +3. Open your web browser and navigate to `http://localhost:8501` to access the chatbot. + +![Streamlit Chat App](./images/image-01.png) + +> [!IMPORTANT] +> +> - Ensure that the Azure AI Search service has been populated with data before running the Streamlit app. + +### 2.1.4. Deploy the Streamlit app to an Azure Container App + +1. Log into the Azure CLI by running the following command: + + ```bash + az login --tenant + ``` + + This will open a browser window to authenticate with the Azure CLI. + +2. Run the following command to authenticate to the Azure Container Registry: + + ```bash + export CONTAINER_REGISTRY_NAME= + az acr login --name $CONTAINER_REGISTRY_NAME -t + ``` +3. Run the following command to build and push the Docker image to the Azure Container Registry: + + ```bash + export CONTAINER_IMAGE_NAME=contoso-trek-product-info-chatbot + + az acr build --image $CONTAINER_IMAGE_NAME --registry $CONTAINER_REGISTRY_NAME --file ./app/Dockerfile . + ``` + +4. Run the following command to deploy the Azure Container App: + + ```bash + export CONTAINER_APP_NAME= # will be created after executing the command + export RESOURCE_GROUP_NAME= # can be found on the Azure Portal + export CONTAINER_APP_ENVIRONMENT_NAME= # can be found on the Azure Portal + export USER_ASSIGNED_IDENTITY_NAME= # can be found on the Azure Portal + + export CONTAINER_REGISTRY_HOSTNAME=$CONTAINER_REGISTRY_NAME.azurecr.io + export USER_ASSIGNED_IDENTITY_ID=$(az identity show --resource-group $RESOURCE_GROUP_NAME --name $USER_ASSIGNED_IDENTITY_NAME | jq '.id' -r) + export USER_ASSIGNED_IDENTITY_CLIENT_ID=$(az identity show --resource-group $RESOURCE_GROUP_NAME --name $USER_ASSIGNED_IDENTITY_NAME | jq '.clientId' -r) + + export AZURE_OPENAI_API_BASE=.openai.azure.com # can be found on the Azure Portal + export AZURE_OPENAI_CHAT_DEPLOYMENT=gpt-35-turbo-16k-0613 + export AZURE_OPENAI_EMBEDDING_DEPLOYMENT=text-embedding-ada-002-2 + + export AZURE_AI_SEARCH_ENDPOINT=.search.windows.net # can be found on the Azure Portal + export AZURE_AI_SEARCH_INDEX_NAME=contoso-trek-product-info-01-index + + az containerapp create \ + --name $CONTAINER_APP_NAME \ + --resource-group $RESOURCE_GROUP_NAME \ + --environment $CONTAINER_APP_ENVIRONMENT_NAME \ + --user-assigned $USER_ASSIGNED_IDENTITY_ID \ + --registry-identity $USER_ASSIGNED_IDENTITY_ID \ + --registry-server $CONTAINER_REGISTRY_HOSTNAME \ + --image $CONTAINER_REGISTRY_HOSTNAME/$CONTAINER_IMAGE_NAME \ + --target-port 8501 \ + --ingress 'external' \ + --max-replicas 2 \ + --env-vars AZURE_OPENAI_API_BASE=$AZURE_OPENAI_API_BASE AZURE_OPENAI_CHAT_DEPLOYMENT=$AZURE_OPENAI_CHAT_DEPLOYMENT AZURE_OPENAI_EMBEDDING_DEPLOYMENT=$AZURE_OPENAI_EMBEDDING_DEPLOYMENT AZURE_AI_SEARCH_ENDPOINT=$AZURE_AI_SEARCH_ENDPOINT AZURE_AI_SEARCH_INDEX_NAME=$AZURE_AI_SEARCH_INDEX_NAME AZURE_CLIENT_ID=$USER_ASSIGNED_IDENTITY_CLIENT_ID + ``` + +5. After the deployment is complete, navigate to the Azure Container App URL to access the chatbot. +6. [Optional] Run the following command to update the Azure Container App after it has been deployed: + + ```bash + az containerapp update \ + --name $CONTAINER_APP_NAME \ + --resource-group $RESOURCE_GROUP_NAME \ + --image $CONTAINER_REGISTRY_HOSTNAME/$CONTAINER_IMAGE_NAME \ + --max-replicas 2 + ``` + +> [!IMPORTANT] +> +> - Ensure that the Azure AI Search service has been populated with data before deploying the Streamlit app to an Azure Container App. +> - Steps (3), (4) and (6) require environment variables that were set in prior steps. diff --git a/.gitignore b/.gitignore index 68bc17f..ee566fa 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,6 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +# Others +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index a72a880..533d799 100644 --- a/README.md +++ b/README.md @@ -1,124 +1,33 @@ -# Example Scenario: Enterprise Chatbot with Azure OpenAI, Azure AI Search, and Streamlit (RAG Pattern) +# Example Scenarios: Generative AI Applications with Azure OpenAI -## Overview +## :books: Overview -This project demonstrates a small-scale proof-of-concept deployment of an enterprise chatbot leveraging the power of Azure OpenAI and Azure AI Search, built and deployed using Streamlit. Utilizing the Retrieval-Augmented Generation (RAG) pattern, the chatbot combines the strengths of both services: +This repository demonstrates small-scale proof-of-concept deployments of Generative AI applications with Azure OpenAI. The repository showcases the capabilities of Azure OpenAI and provides a foundation for enterprises seeking to implement Generative AI applications with Azure OpenAI. -- `Azure OpenAI`: Provides the large language model (LLM) capabilities for generating human-like responses based on user queries. -- `Azure AI Search`: Enables efficient retrieval of relevant information from your enterprise knowledge base or data sources. -- `Streamlit`: Facilitates a lightweight and user-friendly deployment experience, making the chatbot readily accessible through a web interface. +## :computer: Getting Started -The RAG pattern implemented here utilizes Azure AI Search to retrieve the most relevant information based on the user's query. This retrieved information is then fed into the Azure OpenAI LLM, which generates a comprehensive and informative response tailored to the specific context. +This repository will highlight different areas of interest when implementing Generative AI applications with Azure OpenAI. Each example scenario demonstrates a small-scale proof-of-concept that can be used as a foundation for enterprises seeking to implement Generative AI applications with Azure OpenAI. -This project serves as a valuable foundation for small-scale proof-of-concept for enterprises seeking to: +More information on the example scenarios is outlined in the [Getting Started](.github/docs/getting-started.md) section of this repository. -- Implement AI-powered chatbots for improved user engagement and information access. -- Leverage the combined power of Azure OpenAI and Azure AI Search for intelligent text processing and retrieval. -- Build and deploy chatbots efficiently and seamlessly using Streamlit. +### Setup -## Getting Started +Detailed instructions for deploying these proofs-of-concept are outlined in the [Step-by-Step Setup](.github/docs/step-by-step-setup.md) section of this repository. The example scenarios will illustrate concepts such as: -### Create an environment using venv +- Building a search index using Azure AI Search that can be used to retrieve relevant information. +- Implementing the Retrieval-Augmented Generation (RAG) pattern. +- Developing a lightweight web interface for a chatbot using Streamlit. +- Deploying the Generative AI application using Streamlit. +- Deploy the application using Azure Container Apps. -1. Create a python environment using venv. In your terminal, type: +### Example Scenarios - ```bash - python3 -m venv .venv - ``` +This proof-of-concept will cover the following example scenarios: -2. Activate the environment. In your terminal, type: +| Example Scenario | Description | +| ---------------- | ------------ | +| Contoso Trek Product Info Chatbot (Custom) | Small-scale proof-of-concept demonstrating a chatbot leveraging built and deployed using Streamlit and Azure Container Apps. The chatbot uses a custom RAG implementation and Azure AI Search for pull-based data ingestion. | - - On Windows: - - ```bash - .venv\Scripts\activate - ``` - - - On macOS and Linux: - - ```bash - source .venv/bin/activate - ``` - -3. Install the required packages. In your terminal, type: - - ```bash - pip install -r environment/requirements.txt - ``` - -4. Login to Azure using the Azure CLI. In your terminal, type: - - ```bash - az login --tenant --use-device-code - ``` - -### Set environment variables - -To run this project, you need to configure the following environment variables. These can be stored in a .env file in the root of the project. - -- `AZURE_SUBSCRIPTION_ID`: The Azure subscription ID to use for the deployment. For example, `00000000-0000-0000-0000-000000000000`. -- `AZURE_RESOURCE_GROUP_NAME`: The name of the resource group to use for the deployment. For example, `my-resource-group`. -- `AZURE_OPENAI_API_BASE`: The base URL for the Azure OpenAI API. For example, `https://my-resource.openai.azure.com/`. -- `AZURE_OPENAI_API_VERSION`: The version of the Azure OpenAI API. You must set this to `2023-12-01-preview`. -- `AZURE_OPENAI_API_TYPE`: The type of the Azure OpenAI API. You must set this to `azure`. -- `AZURE_OPENAI_CHAT_DEPLOYMENT`: The name of the Azure OpenAI deployment to use for chat. For example, `gpt-35-turbo-16k-0613`. -- `AZURE_OPENAI_CHAT_MODEL`: The name of the Azure OpenAI model to use for chat. For example, `gpt-35-turbo-16k`. -- `AZURE_OPENAI_EMBEDDING_DEPLOYMENT`: The name of the Azure OpenAI deployment to use for embedding. For example, `text-embedding-ada-002-2`. -- `AZURE_OPENAI_EMBEDDING_MODEL`: The name of the Azure OpenAI model to use for embedding. For example, `text-embedding-ada-002`. -- `AZURE_OPENAI_EVALUATION_DEPLOYMENT`: The name of the Azure OpenAI deployment to use for evaluation. For example, `gpt-35-turbo-16k-0613`. -- `AZURE_OPENAI_EVALUATION_MODEL`: The name of the Azure OpenAI model to use for evaluation. For example, `gpt-35-turbo-16k`. -- `AZURE_AI_SEARCH_ENDPOINT`: The endpoint for the Azure AI Search service. For example, `https://my-resource.search.windows.net/`. -- `AZURE_AI_SEARCH_INDEX_NAME`: The name of the Azure AI Search index that will store the vector embeddings of the extracted content. For example, `contoso-index`. -- `AZURE_AI_SEARCH_INDEXER_NAME`: The name of the Azure AI Search indexer that will populate the search index with the extracted content. For example, `contoso-indexer`. -- `AZURE_AI_SEARCH_DATASOURCE_NAME`: The name of the Azure AI Search data source that will connect the search service with the storage container. For example, `contoso-datasource`. -- `AZURE_AI_SEARCH_SKILLSET_NAME`: The name of the Azure AI Search skillset that will chunk documents and generate embeddings. For example, `contoso-skillset`. -- `AZURE_AI_SEARCH_INDEXER_BATCH_SIZE`: The number of documents to process in a single batch. For example, `500`. -- `AZURE_AI_SEARCH_VECTOR_EMBEDDING_DIMENSION`: The dimension of the vector embeddings generated by the skillset. For example, `1536`. -- `AZURE_AI_SEARCH_DATASTORE_NAME`: The name of Azure Storage account that will be registered as an Azure AI Search data source. For example, `contoso-datastore`. -- `AZURE_AI_SEARCH_DATASTORE_CONTAINER_NAME`: The name of the Azure Storage container that stores the data that will be used to populate the index. For example, `contoso-container`. -- `AZURE_AI_SEARCH_DATASTORE_CONTAINER_PATH`: The path to the data that will be used to populate the index. For example, `data/`. - -### Configure the Azure AI Search service - -To run this project, you need to configure the Azure AI Search service. You can do this using the Azure portal or the Azure CLI. This will populate Azure AI Search with a data source, an index, an indexer, and a skillset. - -All templates are provided in the `src/search/templates/product-info` folder and values for the variables, for example `{{ AZURE_OPENAI_API_BASE }}` are populated based on the environment variables. - -To create these artifacts to configure the Azure AI Search service you can run the notebook `src/01-populate-index.ipynb`. - -### Query the Azure AI Search service - -This notebook illistrates two appraoches to query the Azure AI Search service: - -1. Using a custom client implementing the retreival-augmented generation (RAG) pattern. -2. Using the Azure Open AI REST API. - -To query the Azure AI Search service, you can run the notebook `src/02-query-index.ipynb`. - -### Run streamlit app - -To run the streamlit app locally for testing purposes, you can use the following command: - -```bash -streamlit run ./src/app/main.py --client.toolbarMode='minimal' -``` - -Open your web browser and navigate to `http://localhost:8501` to access the chatbot. - -![Streamlit Chat App](./.github/docs/images/image-01.png) - -If you want to deploy this app to Azure, you can containerise it using the `Dockerfile` and deploy it to a suitable Azure service, such as Azure App Service or Azure Container Apps. - -## Resources - -- [Azure OpenAI](https://learn.microsoft.com/azure/ai-services/openai/) -- [Azure AI Search](https://learn.microsoft.com/azure/search/) -- [Streamlit](https://streamlit.io/) -- [Azure OpenAI Service REST API reference](https://learn.microsoft.com/azure/ai-services/openai/reference) -- [Securely use Azure OpenAI on your data](https://learn.microsoft.com/azure/ai-services/openai/how-to/use-your-data-securely) -- [Introduction to prompt engineering](https://learn.microsoft.com/azure/ai-services/openai/concepts/prompt-engineering) -- [Prompt engineering techniques](https://learn.microsoft.com/azure/ai-services/openai/concepts/advanced-prompt-engineering?pivots=programming-language-chat-completions) - -## License +## :balance_scale: License Details on licensing for the project can be found in the [LICENSE](./LICENSE) file. diff --git a/app/Dockerfile b/app/Dockerfile new file mode 100644 index 0000000..290bfaf --- /dev/null +++ b/app/Dockerfile @@ -0,0 +1,29 @@ +FROM python:3.10-slim + +WORKDIR /app + +RUN apt-get update && apt-get install -y \ + build-essential \ + curl \ + software-properties-common \ + git \ + && rm -rf /var/lib/apt/lists/* + +COPY ./app/requirements.txt /app/requirements.txt + +COPY ./llms/custom_rag_client.py /app/llms/custom_rag_client.py +COPY ./llms/system_messages.yml /app/llms/system_messages.yml + +COPY ./app/main.py /app/streamlit_app.py + +ARG AZURE_APP_SYSTEM_PROMPT_CONFIGURATION_FILE=./llms/system_messages.yml +ARG AZURE_APP_TITLE="Contoso Trek Product Info" + +RUN pip install --no-cache-dir --upgrade pip && \ + pip install --no-cache-dir -r ./requirements.txt + +EXPOSE 8501 + +HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health + +ENTRYPOINT ["streamlit", "run", "streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0"] diff --git a/src/app/main.py b/app/main.py similarity index 67% rename from src/app/main.py rename to app/main.py index 7fe8050..64b4f2a 100644 --- a/src/app/main.py +++ b/app/main.py @@ -1,5 +1,5 @@ """ - This module contains the main function that runs the streamlit application. +This module contains the main function that runs the streamlit application. """ import os @@ -7,9 +7,44 @@ import sys import streamlit as st +import yaml +from azure.identity import DefaultAzureCredential, ManagedIdentityCredential from dotenv import load_dotenv -from rag.utilities import RetrievalAugmentedGenerationClient +DEFAULT_APP_TITLE = "Contoso Trek Product Info" +DEFAULT_CONFIGURATION_FILE = "./llms/system_messages.yml" + + +def load_system_messages() -> tuple[str, str]: + """ + Loads system messages from a predefined file. + + Parameters: + answer (str): The text to modify. + references (list[dict]): The list of references. + + Returns: + system_message: The loaded system messages for query and chat. + """ + # Define configuration file + system_prompt_configuration_file = os.getenv( + "AZURE_APP_SYSTEM_PROMPT_CONFIGURATION_FILE", DEFAULT_CONFIGURATION_FILE + ) + + # Load configuration file + with open(system_prompt_configuration_file, "r", encoding="utf-8") as f: + configuration = yaml.safe_load(f) + + # Get system messages + query_system_message = configuration.get("query_system_message") + chat_system_message = configuration.get("product_info_chat_system_message") + + system_messages = { + "query_system_message": query_system_message, + "chat_system_message": chat_system_message, + } + + return system_messages def get_answer(question: str) -> str: @@ -17,7 +52,6 @@ def get_answer(question: str) -> str: Create a completion using the client and message history. Parameters: - client (RetrievalAugmentedGenerationClient): The rag client. question (str): The latest user message. Returns: @@ -35,7 +69,7 @@ def get_answer(question: str) -> str: # Get the assistant message from the chat history formatted_answer = replace_references( answer=message_history[-1]["content"], - references=message_history[-2]["references"], + references=message_history[-1]["context"]["references"], ) return formatted_answer @@ -68,7 +102,23 @@ def replace_references(answer: str, references: list[dict]) -> str: return answer -def main(): +def get_credential() -> ManagedIdentityCredential | DefaultAzureCredential: + """ + Get the Azure credential for the client. + + Parameters: + None + + Returns: + credential: The Azure credential. + """ + if os.getenv("AZURE_CLIENT_ID"): + return ManagedIdentityCredential(client_id=os.getenv("AZURE_CLIENT_ID")) + else: + return DefaultAzureCredential() + + +def main() -> None: """ Main function that runs the application. @@ -82,17 +132,17 @@ def main(): None """ # Set the title of the app - st.title(os.getenv("AZURE_APP_TITLE")) + st.title(os.getenv("APP_TITLE", DEFAULT_APP_TITLE)) # Initialize the orchestration client - st.session_state.client = RetrievalAugmentedGenerationClient( + st.session_state.client = CustomRetrievalAugmentedGenerationClient( open_ai_endpoint=os.getenv("AZURE_OPENAI_API_BASE"), open_ai_chat_deployment=os.getenv("AZURE_OPENAI_CHAT_DEPLOYMENT"), open_ai_embedding_deployment=os.getenv("AZURE_OPENAI_EMBEDDING_DEPLOYMENT"), search_endpoint=os.getenv("AZURE_AI_SEARCH_ENDPOINT"), search_index_name=os.getenv("AZURE_AI_SEARCH_INDEX_NAME"), - system_prompt_configuration_file="src/rag/configuration.yaml" - or os.getenv("AZURE_APP_SYSTEM_PROMPT_CONFIGURATION_FILE"), + credential=get_credential(), + **load_system_messages(), ) # Initialize messages from app session @@ -119,7 +169,6 @@ def main(): if prompt := st.chat_input( "Ask me a question", disabled=st.session_state.is_running ): - # Add user message to app message history st.session_state.app_messages.append({"role": "user", "content": prompt}) @@ -132,7 +181,6 @@ def main(): # Generate a new response if last message is not from assistant if st.session_state.app_messages[-1]["role"] != "assistant": - # Display assistant response in chat message container with st.chat_message("assistant"): with st.spinner("Generating response..."): @@ -149,6 +197,9 @@ def main(): if __name__ == "__main__": - sys.path.append(os.path.join(os.getcwd(), "src")) + sys.path.append(os.path.join(os.getcwd(), ".")) + + from llms.custom_rag_client import CustomRetrievalAugmentedGenerationClient + load_dotenv() main() diff --git a/app/requirements.txt b/app/requirements.txt new file mode 100644 index 0000000..0d05e48 --- /dev/null +++ b/app/requirements.txt @@ -0,0 +1,8 @@ +azure-identity==1.16.0 +ipykernel==6.29.4 +Jinja2==3.1.4 +openai==1.26.0 +python-dotenv==1.0.1 +requests==2.31.0 +streamlit==1.34.0 +streamlit-extras==0.4.2 diff --git a/data/product-info/product_info_1.md b/data/contoso-trek-product-info/product_info_1.md similarity index 100% rename from data/product-info/product_info_1.md rename to data/contoso-trek-product-info/product_info_1.md diff --git a/data/product-info/product_info_10.md b/data/contoso-trek-product-info/product_info_10.md similarity index 100% rename from data/product-info/product_info_10.md rename to data/contoso-trek-product-info/product_info_10.md diff --git a/data/product-info/product_info_11.md b/data/contoso-trek-product-info/product_info_11.md similarity index 100% rename from data/product-info/product_info_11.md rename to data/contoso-trek-product-info/product_info_11.md diff --git a/data/product-info/product_info_12.md b/data/contoso-trek-product-info/product_info_12.md similarity index 100% rename from data/product-info/product_info_12.md rename to data/contoso-trek-product-info/product_info_12.md diff --git a/data/product-info/product_info_13.md b/data/contoso-trek-product-info/product_info_13.md similarity index 100% rename from data/product-info/product_info_13.md rename to data/contoso-trek-product-info/product_info_13.md diff --git a/data/product-info/product_info_14.md b/data/contoso-trek-product-info/product_info_14.md similarity index 100% rename from data/product-info/product_info_14.md rename to data/contoso-trek-product-info/product_info_14.md diff --git a/data/product-info/product_info_15.md b/data/contoso-trek-product-info/product_info_15.md similarity index 100% rename from data/product-info/product_info_15.md rename to data/contoso-trek-product-info/product_info_15.md diff --git a/data/product-info/product_info_16.md b/data/contoso-trek-product-info/product_info_16.md similarity index 100% rename from data/product-info/product_info_16.md rename to data/contoso-trek-product-info/product_info_16.md diff --git a/data/product-info/product_info_17.md b/data/contoso-trek-product-info/product_info_17.md similarity index 100% rename from data/product-info/product_info_17.md rename to data/contoso-trek-product-info/product_info_17.md diff --git a/data/product-info/product_info_18.md b/data/contoso-trek-product-info/product_info_18.md similarity index 100% rename from data/product-info/product_info_18.md rename to data/contoso-trek-product-info/product_info_18.md diff --git a/data/product-info/product_info_19.md b/data/contoso-trek-product-info/product_info_19.md similarity index 100% rename from data/product-info/product_info_19.md rename to data/contoso-trek-product-info/product_info_19.md diff --git a/data/product-info/product_info_2.md b/data/contoso-trek-product-info/product_info_2.md similarity index 100% rename from data/product-info/product_info_2.md rename to data/contoso-trek-product-info/product_info_2.md diff --git a/data/product-info/product_info_20.md b/data/contoso-trek-product-info/product_info_20.md similarity index 100% rename from data/product-info/product_info_20.md rename to data/contoso-trek-product-info/product_info_20.md diff --git a/data/product-info/product_info_3.md b/data/contoso-trek-product-info/product_info_3.md similarity index 100% rename from data/product-info/product_info_3.md rename to data/contoso-trek-product-info/product_info_3.md diff --git a/data/product-info/product_info_4.md b/data/contoso-trek-product-info/product_info_4.md similarity index 100% rename from data/product-info/product_info_4.md rename to data/contoso-trek-product-info/product_info_4.md diff --git a/data/product-info/product_info_5.md b/data/contoso-trek-product-info/product_info_5.md similarity index 100% rename from data/product-info/product_info_5.md rename to data/contoso-trek-product-info/product_info_5.md diff --git a/data/product-info/product_info_6.md b/data/contoso-trek-product-info/product_info_6.md similarity index 100% rename from data/product-info/product_info_6.md rename to data/contoso-trek-product-info/product_info_6.md diff --git a/data/product-info/product_info_7.md b/data/contoso-trek-product-info/product_info_7.md similarity index 100% rename from data/product-info/product_info_7.md rename to data/contoso-trek-product-info/product_info_7.md diff --git a/data/product-info/product_info_8.md b/data/contoso-trek-product-info/product_info_8.md similarity index 100% rename from data/product-info/product_info_8.md rename to data/contoso-trek-product-info/product_info_8.md diff --git a/data/product-info/product_info_9.md b/data/contoso-trek-product-info/product_info_9.md similarity index 100% rename from data/product-info/product_info_9.md rename to data/contoso-trek-product-info/product_info_9.md diff --git a/environment/requirements.txt b/environment/requirements.txt deleted file mode 100644 index d47c386..0000000 --- a/environment/requirements.txt +++ /dev/null @@ -1,9 +0,0 @@ -streamlit==1.31.0 -python-dotenv==1.0.1 -openai==1.11.1 -ipykernel==6.29.2 -Jinja2==3.1.3 -requests==2.31.0 -streamlit-extras==0.4.0 -azure-identity==1.15.0 -nltk==3.8.1 \ No newline at end of file diff --git a/infrastructure/main.bicep b/infrastructure/main.bicep index afc27c0..03c1d57 100644 --- a/infrastructure/main.bicep +++ b/infrastructure/main.bicep @@ -2,202 +2,236 @@ // Parameters //******************************************************** -// Workload identifier used to create unique names for resources. -@description('A unique identifier for the workload.') -@minLength(2) -@maxLength(6) -param workloadIdentifier string = substring(uniqueString(resourceGroup().id), 1, 6) - -// Environment identifier used to create unique names for resources. -@description('A unique identifier for the environment.') -@minLength(2) -@maxLength(8) -param environmentIdentifier string = '01' - -// The location of resource deployments. Defaults to the location of the resource group. -@description('The location of resource deployments.') -param deploymentLocation string = resourceGroup().location +// @description('Resource group name') +// param resourceGroupName string = 'rg-example-scenario-azure-databricks-online-inference-containers' -//******************************************************** -// Variables -//******************************************************** +// @description('Databricks managed resource group name') +// param mrgDatabricksName string = 'rgm-example-scenario-azure-databricks-online-inference-containers-databricks' -// Search Index Data Reader -var azureRbacSearchIndexDataReaderRoleId = '1407120a-92aa-4202-b7e9-c0e197c71c8f' +// @description('Kubernetes managed resource group name') +// param mrgKubernetesName string = 'rgm-example-scenario-azure-databricks-online-inference-containers-kubernetes' -// Search Service Contributor -var azureRbacSearchServiceContributorRoleId = '7ca78c08-252a-4471-8644-bb5ff32d4ba0' +@description('Location for resources') +param location string = az.resourceGroup().location -// Storage Blob Data Contributor -var azureRbacStorageBlobDataContributorRoleId = 'ba92f5b4-2d11-453d-a403-e96b0029c9fe' - -// Cognitive Services OpenAI Contributor -var azureRbacCognitiveServicesOpenAIContributorRoleId = 'a001fd3d-188f-4b5d-821b-7da978bf7442' +@description('User Object ID for authenticated user') +param userObjectId string //******************************************************** -// Resources +// Variables //******************************************************** -// Azure Storage Account -resource r_storageAccount 'Microsoft.Storage/storageAccounts@2022-05-01' = { - name: 'st${workloadIdentifier}${environmentIdentifier}' - location: deploymentLocation - kind: 'StorageV2' - sku: { - name: 'Standard_LRS' - } - properties: { - encryption: { - services: { - blob: { - enabled: true - } - } - keySource: 'Microsoft.Storage' - } - networkAcls: { - bypass: 'AzureServices' - defaultAction: 'Deny' - ipRules: [] - virtualNetworkRules: [] +var serviceSuffix = substring(uniqueString(az.resourceGroup().id), 0, 5) + +var resources = { + applicationInsightsName: 'appi01${serviceSuffix}' + containerRegistryName: 'cr01${serviceSuffix}' + logAnalyticsWorkspaceName: 'log01${serviceSuffix}' + storageAccountName: 'st01${serviceSuffix}' + userAssignedIdentityName: 'id01${serviceSuffix}' + containerAppEnvironmnetName: 'cae01${serviceSuffix}' + aiSearchName: 'srch01${serviceSuffix}' + openAiName: 'oai01${serviceSuffix}' + aiServicesName: 'aisa01${serviceSuffix}' + deploymentScriptName: 'ds01${serviceSuffix}' +} + +// ******************************************************** +// Modules +// ******************************************************** + +module userAssignedIdentity './modules/user-assigned-identity.bicep' = { + name: '${resources.userAssignedIdentityName}-deployment' + params: { + name: resources.userAssignedIdentityName + location: location + tags: { + environment: 'shared' } - publicNetworkAccess: 'Enabled' - supportsHttpsTrafficOnly: true - allowBlobPublicAccess: false - isHnsEnabled: false - minimumTlsVersion: 'TLS1_2' } } -// Azure AI Search -resource r_aiSearch 'Microsoft.Search/searchServices@2023-11-01' = { - name: 'search${workloadIdentifier}${environmentIdentifier}' - location: deploymentLocation - identity: { - type: 'SystemAssigned' - } - sku: { - name: 'standard' - } - properties: { - networkRuleSet: { - ipRules: [] - } - publicNetworkAccess: 'enabled' - disableLocalAuth: false - authOptions: { - aadOrApiKey: { - aadAuthFailureMode: 'http401WithBearerChallenge' - } +module storageAccount './modules/storage-account.bicep' = { + name: '${resources.storageAccountName}-01-deployment' + params: { + name: resources.storageAccountName + location: location + tags: { + environment: 'shared' } } } -// Azure Open AI Account -resource r_aoaiAccount 'Microsoft.CognitiveServices/accounts@2023-10-01-preview' = { - name: 'aoai${workloadIdentifier}${environmentIdentifier}' - location: deploymentLocation - kind: 'OpenAI' - identity: { - type: 'SystemAssigned' +module storageAccountRoleAssignements './modules/storage-account.bicep' = { + name: '${resources.storageAccountName}-02-deployment' + params: { + name: resources.storageAccountName + location: location + roles: [ + { + principalId: aiSearch.outputs.principalId + id: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe' // Storage Blob Data Contributor + } + { + principalId: userObjectId + id: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe' // Storage Blob Data Contributor + type: 'User' + } + ] } - properties: { - customSubDomainName: 'aoai${workloadIdentifier}${environmentIdentifier}' - publicNetworkAccess: 'Enabled' - networkAcls: { - bypass: 'AzureServices' - defaultAction: 'Deny' - ipRules: [] - virtualNetworkRules: [] +} + +module logAnalyticsWorkspace './modules/log-analytics-workspace.bicep' = { + name: '${resources.logAnalyticsWorkspaceName}-deployment' + params: { + name: resources.logAnalyticsWorkspaceName + location: location + tags: { + environment: 'shared' } - } - sku: { - name: 'S0' + storageAccountId: storageAccount.outputs.id } } -// Define Azure Open AI Account Deployments -var modelDeployments = [ - { - name: 'gpt-35-turbo-16k-0613' - modelName: 'gpt-35-turbo-16k' - modelVersion: '0613' - } - { - name: 'text-embedding-ada-002-2' - modelName: 'text-embedding-ada-002' - modelVersion: '2' - } -] - -// Azure Open AI Account Deployments -@batchSize(1) -resource r_aoaiDeploymentsChat 'Microsoft.CognitiveServices/accounts/deployments@2023-05-01' = [for deployment in modelDeployments: { - parent: r_aoaiAccount - name: deployment.name - properties: { - model: { - format: 'OpenAI' - name: deployment.modelName - version: deployment.modelVersion +module containerRegistry './modules/container-registry.bicep' = { + name: '${resources.containerRegistryName}-deployment' + params: { + name: resources.containerRegistryName + location: location + tags: { + environment: 'shared' } + logAnalyticsWorkspaceId: logAnalyticsWorkspace.outputs.id + roles: [ + { + principalId: userAssignedIdentity.outputs.principalId + id: '7f951dda-4ed3-4680-a7ca-43fe172d538d' // ACR Pull role + } + ] } - sku: { - name: 'Standard' - capacity: 30 - } -}] - -//******************************************************** -// RBAC Role Assignments -//******************************************************** +} -resource r_azureSearchIndexDataReaderAzureOpenAiAssignment 'Microsoft.Authorization/roleAssignments@2020-08-01-preview' = { - name: guid(r_aiSearch.name, r_aoaiAccount.name, 'searchIndexDataReader') - scope: r_aiSearch - properties: { - roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', azureRbacSearchIndexDataReaderRoleId) - principalId: r_aoaiAccount.identity.principalId - principalType: 'ServicePrincipal' +module containerAppsEnvironment './modules/container-app-environment.bicep' = { + name: '${resources.containerAppEnvironmnetName}-deployment' + params: { + name: resources.containerAppEnvironmnetName + location: location + tags: { + environment: 'shared' + } + logAnalyticsWorkspaceName: logAnalyticsWorkspace.outputs.name + logAnalyticsWorkspaceResourceGroupName: az.resourceGroup().name } } -resource r_azureSearchServiceContributorAzureOpenAiAssignment 'Microsoft.Authorization/roleAssignments@2020-08-01-preview' = { - name: guid(r_aiSearch.name, r_aoaiAccount.name, 'searchServiceContributor') - scope: r_aiSearch - properties: { - roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', azureRbacSearchServiceContributorRoleId) - principalId: r_aoaiAccount.identity.principalId - principalType: 'ServicePrincipal' +module aiSearch './modules/ai-search.bicep' = { + name: '${resources.aiSearchName}-deployment' + params: { + name: resources.aiSearchName + location: location + tags: { + environment: 'shared' + } + logAnalyticsWorkspaceId: logAnalyticsWorkspace.outputs.id + roles: [ + { + principalId: userAssignedIdentity.outputs.principalId + id: '7ca78c08-252a-4471-8644-bb5ff32d4ba0' // Search Service Contributor + } + { + principalId: userAssignedIdentity.outputs.principalId + id: '1407120a-92aa-4202-b7e9-c0e197c71c8f' // Search Index Data Reader + } + { + principalId: openAi.outputs.principalId + id: '7ca78c08-252a-4471-8644-bb5ff32d4ba0' // Search Service Contributor + } + { + principalId: openAi.outputs.principalId + id: '1407120a-92aa-4202-b7e9-c0e197c71c8f' // Search Index Data Reader + } + { + principalId: userObjectId + id: '7ca78c08-252a-4471-8644-bb5ff32d4ba0' // Search Service Contributor + type: 'User' + } + { + principalId: userObjectId + id: '8ebe5a00-799e-43f5-93ac-243d3dce84a7' // Search Index Data Contributor + type: 'User' + } + ] } } -resource r_storageBlobDataContributorAzureOpenAiAssignment 'Microsoft.Authorization/roleAssignments@2020-08-01-preview' = { - name: guid(r_storageAccount.name, r_aoaiAccount.name, 'storageBlobDataContributor') - scope: r_storageAccount - properties: { - roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', azureRbacStorageBlobDataContributorRoleId) - principalId: r_aoaiAccount.identity.principalId - principalType: 'ServicePrincipal' +module openAi './modules/ai-services.bicep' = { + name: '${resources.openAiName}-01-deployment' + params: { + name: resources.openAiName + location: location + tags: { + environment: 'shared' + } + kind: 'OpenAI' + logAnalyticsWorkspaceId: logAnalyticsWorkspace.outputs.id + roles: [ + { + principalId: userAssignedIdentity.outputs.principalId + id: 'a001fd3d-188f-4b5d-821b-7da978bf7442' // Cognitive Services OpenAI Contributor + } + { + principalId: userObjectId + id: 'a001fd3d-188f-4b5d-821b-7da978bf7442' // Cognitive Services OpenAI Contributor + type: 'User' + } + ] } } -resource r_cognitiveServicesOpenAiContributorAzureAiSearchAssignment 'Microsoft.Authorization/roleAssignments@2020-08-01-preview' = { - name: guid(r_aoaiAccount.name, r_aiSearch.name, 'cognitiveServicesOpenAiContributor') - scope: r_aoaiAccount - properties: { - roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', azureRbacCognitiveServicesOpenAIContributorRoleId) - principalId: r_aiSearch.identity.principalId - principalType: 'ServicePrincipal' +module openAiRoleAssignments './modules/ai-services.bicep' = { + name: '${resources.openAiName}-02-deployment' + params: { + name: resources.openAiName + location: location + tags: { + environment: 'shared' + } + kind: 'OpenAI' + logAnalyticsWorkspaceId: logAnalyticsWorkspace.outputs.id + roles: [ + { + principalId: aiSearch.outputs.principalId + id: 'a001fd3d-188f-4b5d-821b-7da978bf7442' // Cognitive Services OpenAI Contributor + } + ] } } -resource r_storageBlobDataContributorAzureAiSearchAssignment 'Microsoft.Authorization/roleAssignments@2020-08-01-preview' = { - name: guid(r_storageAccount.name, r_aiSearch.name, 'storageBlobDataContributor') - scope: r_storageAccount - properties: { - roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', azureRbacStorageBlobDataContributorRoleId) - principalId: r_aiSearch.identity.principalId - principalType: 'ServicePrincipal' +module aiServices './modules/ai-services.bicep' = { + name: '${resources.aiServicesName}-deployment' + params: { + name: resources.aiServicesName + location: location + tags: { + environment: 'shared' + } + kind: 'CognitiveServices' + logAnalyticsWorkspaceId: logAnalyticsWorkspace.outputs.id + roles: [ + { + principalId: userAssignedIdentity.outputs.principalId + id: '25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68' // Cognitive Services Contributor + } + ] } } + +//******************************************************** +// Outputs +//******************************************************** + +output storageAccountName string = storageAccount.outputs.name +output logAnalyticsWorkspaceName string = logAnalyticsWorkspace.outputs.name +output containerRegistryName string = containerRegistry.outputs.name +output userAssignedIdentityName string = userAssignedIdentity.outputs.name +output containerAppEnvironmnetStagingName string = containerAppsEnvironment.outputs.name diff --git a/infrastructure/main.json b/infrastructure/main.json index 5059ad9..ecc373c 100644 --- a/infrastructure/main.json +++ b/infrastructure/main.json @@ -4,233 +4,1741 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.24.24.22086", - "templateHash": "12446007199015682608" + "version": "0.27.1.19265", + "templateHash": "12164655026923384675" } }, "parameters": { - "workloadIdentifier": { + "location": { "type": "string", - "defaultValue": "[substring(uniqueString(resourceGroup().id), 1, 6)]", - "minLength": 2, - "maxLength": 6, - "metadata": { - "description": "A unique identifier for the workload." - } - }, - "environmentIdentifier": { - "type": "string", - "defaultValue": "01", - "minLength": 2, - "maxLength": 8, + "defaultValue": "[resourceGroup().location]", "metadata": { - "description": "A unique identifier for the environment." + "description": "Location for resources" } }, - "deploymentLocation": { + "userObjectId": { "type": "string", - "defaultValue": "[resourceGroup().location]", "metadata": { - "description": "The location of resource deployments." + "description": "User Object ID for authenticated user" } } }, "variables": { - "azureRbacSearchIndexDataReaderRoleId": "1407120a-92aa-4202-b7e9-c0e197c71c8f", - "azureRbacSearchServiceContributorRoleId": "7ca78c08-252a-4471-8644-bb5ff32d4ba0", - "azureRbacStorageBlobDataContributorRoleId": "ba92f5b4-2d11-453d-a403-e96b0029c9fe", - "azureRbacCognitiveServicesOpenAIContributorRoleId": "a001fd3d-188f-4b5d-821b-7da978bf7442", - "modelDeployments": [ - { - "name": "gpt-35-turbo-16k-0613", - "modelName": "gpt-35-turbo-16k", - "modelVersion": "0613" - }, - { - "name": "text-embedding-ada-002-2", - "modelName": "text-embedding-ada-002", - "modelVersion": "2" - } - ] + "serviceSuffix": "[substring(uniqueString(resourceGroup().id), 0, 5)]", + "resources": { + "applicationInsightsName": "[format('appi01{0}', variables('serviceSuffix'))]", + "containerRegistryName": "[format('cr01{0}', variables('serviceSuffix'))]", + "logAnalyticsWorkspaceName": "[format('log01{0}', variables('serviceSuffix'))]", + "storageAccountName": "[format('st01{0}', variables('serviceSuffix'))]", + "userAssignedIdentityName": "[format('id01{0}', variables('serviceSuffix'))]", + "containerAppEnvironmnetName": "[format('cae01{0}', variables('serviceSuffix'))]", + "aiSearchName": "[format('srch01{0}', variables('serviceSuffix'))]", + "openAiName": "[format('oai01{0}', variables('serviceSuffix'))]", + "aiServicesName": "[format('aisa01{0}', variables('serviceSuffix'))]", + "deploymentScriptName": "[format('ds01{0}', variables('serviceSuffix'))]" + } }, "resources": [ { - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-05-01", - "name": "[format('st{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier'))]", - "location": "[parameters('deploymentLocation')]", - "kind": "StorageV2", - "sku": { - "name": "Standard_LRS" - }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-deployment', variables('resources').userAssignedIdentityName)]", "properties": { - "encryption": { - "services": { - "blob": { - "enabled": true - } - }, - "keySource": "Microsoft.Storage" + "expressionEvaluationOptions": { + "scope": "inner" }, - "networkAcls": { - "bypass": "AzureServices", - "defaultAction": "Deny", - "ipRules": [], - "virtualNetworkRules": [] + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('resources').userAssignedIdentityName]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": { + "environment": "shared" + } + } }, - "publicNetworkAccess": "Enabled", - "supportsHttpsTrafficOnly": true, - "allowBlobPublicAccess": false, - "isHnsEnabled": false, - "minimumTlsVersion": "TLS1_2" + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.27.1.19265", + "templateHash": "3718297678080327579" + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the User Assigned Identity service" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for User Assigned Identity service" + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags for the User Assigned Identity service" + } + } + }, + "resources": [ + { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]" + } + ], + "outputs": { + "id": { + "type": "string", + "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name'))]" + }, + "name": { + "type": "string", + "value": "[parameters('name')]" + }, + "principalId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('name')), '2023-01-31').principalId]" + } + } + } } }, { - "type": "Microsoft.Search/searchServices", - "apiVersion": "2023-11-01", - "name": "[format('search{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier'))]", - "location": "[parameters('deploymentLocation')]", - "identity": { - "type": "SystemAssigned" - }, - "sku": { - "name": "standard" - }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-01-deployment', variables('resources').storageAccountName)]", "properties": { - "networkRuleSet": { - "ipRules": [] + "expressionEvaluationOptions": { + "scope": "inner" }, - "publicNetworkAccess": "enabled", - "disableLocalAuth": false, - "authOptions": { - "aadOrApiKey": { - "aadAuthFailureMode": "http401WithBearerChallenge" + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('resources').storageAccountName]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": { + "environment": "shared" + } + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.27.1.19265", + "templateHash": "14075893523994139509" + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the Storage service" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for Storage service" + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags for the Storage service" + } + }, + "roles": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Role assignments for the Storage service" + } + }, + "enableHns": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Enable hierarchical namespace" + } + } + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-05-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": { + "name": "Standard_LRS" + }, + "kind": "StorageV2", + "properties": { + "isHnsEnabled": "[parameters('enableHns')]", + "minimumTlsVersion": "TLS1_2" + } + }, + { + "copy": { + "name": "roleAssignments", + "count": "[length(parameters('roles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('name'))]", + "name": "[guid(parameters('name'), parameters('roles')[copyIndex()].principalId, parameters('roles')[copyIndex()].id)]", + "properties": { + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()].id)]", + "principalId": "[parameters('roles')[copyIndex()].principalId]", + "principalType": "[if(contains(parameters('roles')[copyIndex()], 'type'), parameters('roles')[copyIndex()].type, 'ServicePrincipal')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]" + ] + }, + { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2020-10-01", + "name": "[format('ds{0}', parameters('name'))]", + "location": "[parameters('location')]", + "kind": "AzureCLI", + "properties": { + "azCliVersion": "2.30.0", + "timeout": "PT5M", + "retentionInterval": "PT1H", + "environmentVariables": [ + { + "name": "AZURE_STORAGE_ACCOUNT", + "value": "[parameters('name')]" + }, + { + "name": "AZURE_STORAGE_KEY", + "secureValue": "[listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), '2022-05-01').keys[0].value]" + } + ], + "scriptContent": " git clone https://github.com/nfmoore/azure-open-ai-example-scenarios.git\n az storage container create --name data --account-name $AZURE_STORAGE_ACCOUNT --account-key $AZURE_STORAGE_KEY \n az storage blob upload-batch --destination data --account-name $AZURE_STORAGE_ACCOUNT --account-key $AZURE_STORAGE_KEY --destination-path ./ --source ./azure-open-ai-example-scenarios/data\n " + }, + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]" + ] + } + ], + "outputs": { + "name": { + "type": "string", + "value": "[parameters('name')]" + }, + "id": { + "type": "string", + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]" + } } } } }, { - "type": "Microsoft.CognitiveServices/accounts", - "apiVersion": "2023-10-01-preview", - "name": "[format('aoai{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier'))]", - "location": "[parameters('deploymentLocation')]", - "kind": "OpenAI", - "identity": { - "type": "SystemAssigned" - }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-02-deployment', variables('resources').storageAccountName)]", "properties": { - "customSubDomainName": "[format('aoai{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier'))]", - "publicNetworkAccess": "Enabled", - "networkAcls": { - "bypass": "AzureServices", - "defaultAction": "Deny", - "ipRules": [], - "virtualNetworkRules": [] + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('resources').storageAccountName]" + }, + "location": { + "value": "[parameters('location')]" + }, + "roles": { + "value": [ + { + "principalId": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').aiSearchName)), '2022-09-01').outputs.principalId.value]", + "id": "ba92f5b4-2d11-453d-a403-e96b0029c9fe" + }, + { + "principalId": "[parameters('userObjectId')]", + "id": "ba92f5b4-2d11-453d-a403-e96b0029c9fe", + "type": "User" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.27.1.19265", + "templateHash": "14075893523994139509" + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the Storage service" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for Storage service" + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags for the Storage service" + } + }, + "roles": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Role assignments for the Storage service" + } + }, + "enableHns": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Enable hierarchical namespace" + } + } + }, + "resources": [ + { + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-05-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": { + "name": "Standard_LRS" + }, + "kind": "StorageV2", + "properties": { + "isHnsEnabled": "[parameters('enableHns')]", + "minimumTlsVersion": "TLS1_2" + } + }, + { + "copy": { + "name": "roleAssignments", + "count": "[length(parameters('roles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('name'))]", + "name": "[guid(parameters('name'), parameters('roles')[copyIndex()].principalId, parameters('roles')[copyIndex()].id)]", + "properties": { + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()].id)]", + "principalId": "[parameters('roles')[copyIndex()].principalId]", + "principalType": "[if(contains(parameters('roles')[copyIndex()], 'type'), parameters('roles')[copyIndex()].type, 'ServicePrincipal')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]" + ] + }, + { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2020-10-01", + "name": "[format('ds{0}', parameters('name'))]", + "location": "[parameters('location')]", + "kind": "AzureCLI", + "properties": { + "azCliVersion": "2.30.0", + "timeout": "PT5M", + "retentionInterval": "PT1H", + "environmentVariables": [ + { + "name": "AZURE_STORAGE_ACCOUNT", + "value": "[parameters('name')]" + }, + { + "name": "AZURE_STORAGE_KEY", + "secureValue": "[listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('name')), '2022-05-01').keys[0].value]" + } + ], + "scriptContent": " git clone https://github.com/nfmoore/azure-open-ai-example-scenarios.git\n az storage container create --name data --account-name $AZURE_STORAGE_ACCOUNT --account-key $AZURE_STORAGE_KEY \n az storage blob upload-batch --destination data --account-name $AZURE_STORAGE_ACCOUNT --account-key $AZURE_STORAGE_KEY --destination-path ./ --source ./azure-open-ai-example-scenarios/data\n " + }, + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]" + ] + } + ], + "outputs": { + "name": { + "type": "string", + "value": "[parameters('name')]" + }, + "id": { + "type": "string", + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('name'))]" + } + } } }, - "sku": { - "name": "S0" - } + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').aiSearchName))]" + ] }, { - "copy": { - "name": "r_aoaiDeploymentsChat", - "count": "[length(variables('modelDeployments'))]", - "mode": "serial", - "batchSize": 1 - }, - "type": "Microsoft.CognitiveServices/accounts/deployments", - "apiVersion": "2023-05-01", - "name": "[format('{0}/{1}', format('aoai{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')), variables('modelDeployments')[copyIndex()].name)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-deployment', variables('resources').logAnalyticsWorkspaceName)]", "properties": { - "model": { - "format": "OpenAI", - "name": "[variables('modelDeployments')[copyIndex()].modelName]", - "version": "[variables('modelDeployments')[copyIndex()].modelVersion]" + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('resources').logAnalyticsWorkspaceName]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": { + "environment": "shared" + } + }, + "storageAccountId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-01-deployment', variables('resources').storageAccountName)), '2022-09-01').outputs.id.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.27.1.19265", + "templateHash": "8716779443418268881" + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the Log Analytics workspace" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for Log Analytics workspace" + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags for the Log Analytics workspace" + } + }, + "roles": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Role assignments for the Log Analytics workspace" + } + }, + "storageAccountId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Storage account ID to link to the Log Analytics workspace" + } + } + }, + "resources": [ + { + "type": "Microsoft.OperationalInsights/workspaces/linkedStorageAccounts", + "apiVersion": "2020-08-01", + "name": "[format('{0}/{1}', parameters('name'), 'Alerts')]", + "properties": { + "storageAccountIds": [ + "[parameters('storageAccountId')]" + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('name'))]" + ] + }, + { + "type": "Microsoft.OperationalInsights/workspaces/linkedStorageAccounts", + "apiVersion": "2020-08-01", + "name": "[format('{0}/{1}', parameters('name'), 'CustomLogs')]", + "properties": { + "storageAccountIds": [ + "[parameters('storageAccountId')]" + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('name'))]" + ] + }, + { + "type": "Microsoft.OperationalInsights/workspaces/linkedStorageAccounts", + "apiVersion": "2020-08-01", + "name": "[format('{0}/{1}', parameters('name'), 'Ingestion')]", + "properties": { + "storageAccountIds": [ + "[parameters('storageAccountId')]" + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('name'))]" + ] + }, + { + "type": "Microsoft.OperationalInsights/workspaces/linkedStorageAccounts", + "apiVersion": "2020-08-01", + "name": "[format('{0}/{1}', parameters('name'), 'Query')]", + "properties": { + "storageAccountIds": [ + "[parameters('storageAccountId')]" + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('name'))]" + ] + }, + { + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2022-10-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "retentionInDays": 30, + "sku": { + "name": "Standalone" + } + } + }, + { + "copy": { + "name": "roleAssignments", + "count": "[length(parameters('roles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.OperationalInsights/workspaces/{0}', parameters('name'))]", + "name": "[guid(parameters('name'), parameters('roles')[copyIndex()].principalId, parameters('roles')[copyIndex()].id)]", + "properties": { + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()].id)]", + "principalId": "[parameters('roles')[copyIndex()].principalId]", + "principalType": "[if(contains(parameters('roles')[copyIndex()], 'type'), parameters('roles')[copyIndex()].type, 'ServicePrincipal')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('name'))]" + ] + } + ], + "outputs": { + "name": { + "type": "string", + "value": "[parameters('name')]" + }, + "id": { + "type": "string", + "value": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('name'))]" + } + } } }, - "sku": { - "name": "Standard", - "capacity": 30 + "dependsOn": [ + "[resourceId('Microsoft.Resources/deployments', format('{0}-01-deployment', variables('resources').storageAccountName))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-deployment', variables('resources').containerRegistryName)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('resources').containerRegistryName]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": { + "environment": "shared" + } + }, + "logAnalyticsWorkspaceId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').logAnalyticsWorkspaceName)), '2022-09-01').outputs.id.value]" + }, + "roles": { + "value": [ + { + "principalId": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').userAssignedIdentityName)), '2022-09-01').outputs.principalId.value]", + "id": "7f951dda-4ed3-4680-a7ca-43fe172d538d" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.27.1.19265", + "templateHash": "2880683818389779110" + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the Container Registry service" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for Container Registry service" + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags for the Container Registry service" + } + }, + "roles": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Role assignments for the Container Registry service" + } + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Log Analytics workspace ID for diagnostics" + } + } + }, + "resources": [ + { + "type": "Microsoft.ContainerRegistry/registries", + "apiVersion": "2022-12-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": { + "name": "Standard" + }, + "properties": { + "adminUserEnabled": true + } + }, + { + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.ContainerRegistry/registries/{0}', parameters('name'))]", + "name": "all-logs-all-metrics", + "properties": { + "workspaceId": "[parameters('logAnalyticsWorkspaceId')]", + "logs": [ + { + "categoryGroup": "allLogs", + "enabled": true + } + ], + "metrics": [ + { + "category": "AllMetrics", + "enabled": true + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.ContainerRegistry/registries', parameters('name'))]" + ] + }, + { + "copy": { + "name": "roleAssignments", + "count": "[length(parameters('roles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.ContainerRegistry/registries/{0}', parameters('name'))]", + "name": "[guid(parameters('name'), parameters('roles')[copyIndex()].principalId, parameters('roles')[copyIndex()].id)]", + "properties": { + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()].id)]", + "principalId": "[parameters('roles')[copyIndex()].principalId]", + "principalType": "[if(contains(parameters('roles')[copyIndex()], 'type'), parameters('roles')[copyIndex()].type, 'ServicePrincipal')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.ContainerRegistry/registries', parameters('name'))]" + ] + } + ], + "outputs": { + "name": { + "type": "string", + "value": "[parameters('name')]" + }, + "id": { + "type": "string", + "value": "[resourceId('Microsoft.ContainerRegistry/registries', parameters('name'))]" + } + } + } }, "dependsOn": [ - "[resourceId('Microsoft.CognitiveServices/accounts', format('aoai{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')))]" + "[resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').logAnalyticsWorkspaceName))]", + "[resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').userAssignedIdentityName))]" ] }, { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2020-08-01-preview", - "scope": "[format('Microsoft.Search/searchServices/{0}', format('search{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')))]", - "name": "[guid(format('search{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')), format('aoai{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')), 'searchIndexDataReader')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-deployment', variables('resources').containerAppEnvironmnetName)]", "properties": { - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', variables('azureRbacSearchIndexDataReaderRoleId'))]", - "principalId": "[reference(resourceId('Microsoft.CognitiveServices/accounts', format('aoai{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier'))), '2023-10-01-preview', 'full').identity.principalId]", - "principalType": "ServicePrincipal" + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('resources').containerAppEnvironmnetName]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": { + "environment": "shared" + } + }, + "logAnalyticsWorkspaceName": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').logAnalyticsWorkspaceName)), '2022-09-01').outputs.name.value]" + }, + "logAnalyticsWorkspaceResourceGroupName": { + "value": "[resourceGroup().name]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.27.1.19265", + "templateHash": "12781803292218525281" + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the Application Insights service" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for Application Insights service" + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags for the Application Insights service" + } + }, + "roles": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Role assignments for the Application Insights service" + } + }, + "logAnalyticsWorkspaceName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Log Analytics workspace name" + } + }, + "logAnalyticsWorkspaceResourceGroupName": { + "type": "string", + "defaultValue": "[resourceGroup().name]", + "metadata": { + "description": "Log Analytics workspace resource group name" + } + } + }, + "resources": [ + { + "type": "Microsoft.App/managedEnvironments", + "apiVersion": "2023-05-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "properties": { + "appLogsConfiguration": { + "destination": "log-analytics", + "logAnalyticsConfiguration": { + "customerId": "[reference(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('logAnalyticsWorkspaceResourceGroupName')), 'Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsWorkspaceName')), '2022-10-01').customerId]", + "sharedKey": "[listKeys(extensionResourceId(format('/subscriptions/{0}/resourceGroups/{1}', subscription().subscriptionId, parameters('logAnalyticsWorkspaceResourceGroupName')), 'Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsWorkspaceName')), '2022-10-01').primarySharedKey]" + } + } + } + }, + { + "copy": { + "name": "roleAssignments", + "count": "[length(parameters('roles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.App/managedEnvironments/{0}', parameters('name'))]", + "name": "[guid(parameters('name'), parameters('roles')[copyIndex()].principalId, parameters('roles')[copyIndex()].id)]", + "properties": { + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()].id)]", + "principalId": "[parameters('roles')[copyIndex()].principalId]", + "principalType": "[if(contains(parameters('roles')[copyIndex()], 'type'), parameters('roles')[copyIndex()].type, 'ServicePrincipal')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.App/managedEnvironments', parameters('name'))]" + ] + } + ], + "outputs": { + "name": { + "type": "string", + "value": "[parameters('name')]" + }, + "id": { + "type": "string", + "value": "[resourceId('Microsoft.App/managedEnvironments', parameters('name'))]" + } + } + } }, "dependsOn": [ - "[resourceId('Microsoft.Search/searchServices', format('search{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')))]", - "[resourceId('Microsoft.CognitiveServices/accounts', format('aoai{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')))]" + "[resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').logAnalyticsWorkspaceName))]" ] }, { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2020-08-01-preview", - "scope": "[format('Microsoft.Search/searchServices/{0}', format('search{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')))]", - "name": "[guid(format('search{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')), format('aoai{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')), 'searchServiceContributor')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-deployment', variables('resources').aiSearchName)]", "properties": { - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', variables('azureRbacSearchServiceContributorRoleId'))]", - "principalId": "[reference(resourceId('Microsoft.CognitiveServices/accounts', format('aoai{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier'))), '2023-10-01-preview', 'full').identity.principalId]", - "principalType": "ServicePrincipal" + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('resources').aiSearchName]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": { + "environment": "shared" + } + }, + "logAnalyticsWorkspaceId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').logAnalyticsWorkspaceName)), '2022-09-01').outputs.id.value]" + }, + "roles": { + "value": [ + { + "principalId": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').userAssignedIdentityName)), '2022-09-01').outputs.principalId.value]", + "id": "7ca78c08-252a-4471-8644-bb5ff32d4ba0" + }, + { + "principalId": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').userAssignedIdentityName)), '2022-09-01').outputs.principalId.value]", + "id": "1407120a-92aa-4202-b7e9-c0e197c71c8f" + }, + { + "principalId": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-01-deployment', variables('resources').openAiName)), '2022-09-01').outputs.principalId.value]", + "id": "7ca78c08-252a-4471-8644-bb5ff32d4ba0" + }, + { + "principalId": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-01-deployment', variables('resources').openAiName)), '2022-09-01').outputs.principalId.value]", + "id": "1407120a-92aa-4202-b7e9-c0e197c71c8f" + }, + { + "principalId": "[parameters('userObjectId')]", + "id": "7ca78c08-252a-4471-8644-bb5ff32d4ba0", + "type": "User" + }, + { + "principalId": "[parameters('userObjectId')]", + "id": "8ebe5a00-799e-43f5-93ac-243d3dce84a7", + "type": "User" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.27.1.19265", + "templateHash": "10031389011035315454" + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the Azure AI Search service" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for the Azure AI Search service" + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags for the Azure AI Search service" + } + }, + "roles": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Role assignments for the Azure AI Search service" + } + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Log Analytics workspace ID for diagnostics" + } + } + }, + "resources": [ + { + "type": "Microsoft.Search/searchServices", + "apiVersion": "2023-11-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": { + "name": "standard" + }, + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "authOptions": { + "aadOrApiKey": { + "aadAuthFailureMode": "http401WithBearerChallenge" + } + } + } + }, + { + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.Search/searchServices/{0}', parameters('name'))]", + "name": "all-logs-all-metrics", + "properties": { + "workspaceId": "[parameters('logAnalyticsWorkspaceId')]", + "logs": [ + { + "categoryGroup": "allLogs", + "enabled": true + } + ], + "metrics": [ + { + "category": "AllMetrics", + "enabled": true + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Search/searchServices', parameters('name'))]" + ] + }, + { + "copy": { + "name": "roleAssignments", + "count": "[length(parameters('roles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Search/searchServices/{0}', parameters('name'))]", + "name": "[guid(parameters('name'), parameters('roles')[copyIndex()].principalId, parameters('roles')[copyIndex()].id)]", + "properties": { + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()].id)]", + "principalId": "[parameters('roles')[copyIndex()].principalId]", + "principalType": "[if(contains(parameters('roles')[copyIndex()], 'type'), parameters('roles')[copyIndex()].type, 'ServicePrincipal')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Search/searchServices', parameters('name'))]" + ] + } + ], + "outputs": { + "name": { + "type": "string", + "value": "[parameters('name')]" + }, + "id": { + "type": "string", + "value": "[resourceId('Microsoft.Search/searchServices', parameters('name'))]" + }, + "principalId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Search/searchServices', parameters('name')), '2023-11-01', 'full').identity.principalId]" + } + } + } }, "dependsOn": [ - "[resourceId('Microsoft.Search/searchServices', format('search{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')))]", - "[resourceId('Microsoft.CognitiveServices/accounts', format('aoai{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')))]" + "[resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').logAnalyticsWorkspaceName))]", + "[resourceId('Microsoft.Resources/deployments', format('{0}-01-deployment', variables('resources').openAiName))]", + "[resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').userAssignedIdentityName))]" ] }, { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2020-08-01-preview", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', format('st{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')))]", - "name": "[guid(format('st{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')), format('aoai{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')), 'storageBlobDataContributor')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-01-deployment', variables('resources').openAiName)]", "properties": { - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', variables('azureRbacStorageBlobDataContributorRoleId'))]", - "principalId": "[reference(resourceId('Microsoft.CognitiveServices/accounts', format('aoai{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier'))), '2023-10-01-preview', 'full').identity.principalId]", - "principalType": "ServicePrincipal" + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('resources').openAiName]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": { + "environment": "shared" + } + }, + "kind": { + "value": "OpenAI" + }, + "logAnalyticsWorkspaceId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').logAnalyticsWorkspaceName)), '2022-09-01').outputs.id.value]" + }, + "roles": { + "value": [ + { + "principalId": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').userAssignedIdentityName)), '2022-09-01').outputs.principalId.value]", + "id": "a001fd3d-188f-4b5d-821b-7da978bf7442" + }, + { + "principalId": "[parameters('userObjectId')]", + "id": "a001fd3d-188f-4b5d-821b-7da978bf7442", + "type": "User" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.27.1.19265", + "templateHash": "11553281893512969426" + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the AI Services account" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for AI Services account" + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags for the AI Services account" + } + }, + "kind": { + "type": "string", + "defaultValue": "CognitiveServices", + "metadata": { + "description": "Kind of AI Services account" + } + }, + "roles": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Role assignments for the Azure Search account" + } + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Log Analytics workspace ID for diagnostics" + } + }, + "deployments": { + "type": "array", + "defaultValue": [ + { + "name": "gpt-4-32k", + "version": "0613" + }, + { + "name": "gpt-35-turbo-16k", + "version": "0613" + }, + { + "name": "text-embedding-ada-002", + "version": "2" + } + ], + "metadata": { + "description": "Deployments for the AI Services account" + } + } + }, + "resources": [ + { + "copy": { + "name": "aisaDeployments", + "count": "[length(parameters('deployments'))]", + "mode": "serial", + "batchSize": 1 + }, + "condition": "[equals(parameters('kind'), 'OpenAI')]", + "type": "Microsoft.CognitiveServices/accounts/deployments", + "apiVersion": "2023-05-01", + "name": "[format('{0}/{1}', parameters('name'), format('{0}-{1}', parameters('deployments')[copyIndex()].name, parameters('deployments')[copyIndex()].version))]", + "properties": { + "model": { + "format": "OpenAI", + "name": "[parameters('deployments')[copyIndex()].name]", + "version": "[parameters('deployments')[copyIndex()].version]" + }, + "versionUpgradeOption": "NoAutoUpgrade" + }, + "sku": { + "name": "Standard", + "capacity": "[if(contains(parameters('deployments')[copyIndex()], 'capacity'), parameters('deployments')[copyIndex()].capacity, 20)]" + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', parameters('name'))]" + ] + }, + { + "type": "Microsoft.CognitiveServices/accounts", + "apiVersion": "2023-05-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": { + "name": "S0" + }, + "kind": "[parameters('kind')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "customSubDomainName": "[parameters('name')]" + } + }, + { + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('name'))]", + "name": "all-logs-all-metrics", + "properties": { + "workspaceId": "[parameters('logAnalyticsWorkspaceId')]", + "logs": [ + { + "categoryGroup": "allLogs", + "enabled": true + } + ], + "metrics": [ + { + "category": "AllMetrics", + "enabled": true + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', parameters('name'))]" + ] + }, + { + "copy": { + "name": "roleAssignments", + "count": "[length(parameters('roles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('name'))]", + "name": "[guid(parameters('name'), parameters('roles')[copyIndex()].principalId, parameters('roles')[copyIndex()].id)]", + "properties": { + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()].id)]", + "principalId": "[parameters('roles')[copyIndex()].principalId]", + "principalType": "[if(contains(parameters('roles')[copyIndex()], 'type'), parameters('roles')[copyIndex()].type, 'ServicePrincipal')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', parameters('name'))]" + ] + } + ], + "outputs": { + "name": { + "type": "string", + "value": "[parameters('name')]" + }, + "id": { + "type": "string", + "value": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('name'))]" + }, + "principalId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), '2023-05-01', 'full').identity.principalId]" + } + } + } }, "dependsOn": [ - "[resourceId('Microsoft.CognitiveServices/accounts', format('aoai{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')))]", - "[resourceId('Microsoft.Storage/storageAccounts', format('st{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')))]" + "[resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').logAnalyticsWorkspaceName))]", + "[resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').userAssignedIdentityName))]" ] }, { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2020-08-01-preview", - "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', format('aoai{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')))]", - "name": "[guid(format('aoai{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')), format('search{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')), 'cognitiveServicesOpenAiContributor')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-02-deployment', variables('resources').openAiName)]", "properties": { - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', variables('azureRbacCognitiveServicesOpenAIContributorRoleId'))]", - "principalId": "[reference(resourceId('Microsoft.Search/searchServices', format('search{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier'))), '2023-11-01', 'full').identity.principalId]", - "principalType": "ServicePrincipal" + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('resources').openAiName]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": { + "environment": "shared" + } + }, + "kind": { + "value": "OpenAI" + }, + "logAnalyticsWorkspaceId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').logAnalyticsWorkspaceName)), '2022-09-01').outputs.id.value]" + }, + "roles": { + "value": [ + { + "principalId": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').aiSearchName)), '2022-09-01').outputs.principalId.value]", + "id": "a001fd3d-188f-4b5d-821b-7da978bf7442" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.27.1.19265", + "templateHash": "11553281893512969426" + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the AI Services account" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for AI Services account" + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags for the AI Services account" + } + }, + "kind": { + "type": "string", + "defaultValue": "CognitiveServices", + "metadata": { + "description": "Kind of AI Services account" + } + }, + "roles": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Role assignments for the Azure Search account" + } + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Log Analytics workspace ID for diagnostics" + } + }, + "deployments": { + "type": "array", + "defaultValue": [ + { + "name": "gpt-4-32k", + "version": "0613" + }, + { + "name": "gpt-35-turbo-16k", + "version": "0613" + }, + { + "name": "text-embedding-ada-002", + "version": "2" + } + ], + "metadata": { + "description": "Deployments for the AI Services account" + } + } + }, + "resources": [ + { + "copy": { + "name": "aisaDeployments", + "count": "[length(parameters('deployments'))]", + "mode": "serial", + "batchSize": 1 + }, + "condition": "[equals(parameters('kind'), 'OpenAI')]", + "type": "Microsoft.CognitiveServices/accounts/deployments", + "apiVersion": "2023-05-01", + "name": "[format('{0}/{1}', parameters('name'), format('{0}-{1}', parameters('deployments')[copyIndex()].name, parameters('deployments')[copyIndex()].version))]", + "properties": { + "model": { + "format": "OpenAI", + "name": "[parameters('deployments')[copyIndex()].name]", + "version": "[parameters('deployments')[copyIndex()].version]" + }, + "versionUpgradeOption": "NoAutoUpgrade" + }, + "sku": { + "name": "Standard", + "capacity": "[if(contains(parameters('deployments')[copyIndex()], 'capacity'), parameters('deployments')[copyIndex()].capacity, 20)]" + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', parameters('name'))]" + ] + }, + { + "type": "Microsoft.CognitiveServices/accounts", + "apiVersion": "2023-05-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": { + "name": "S0" + }, + "kind": "[parameters('kind')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "customSubDomainName": "[parameters('name')]" + } + }, + { + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('name'))]", + "name": "all-logs-all-metrics", + "properties": { + "workspaceId": "[parameters('logAnalyticsWorkspaceId')]", + "logs": [ + { + "categoryGroup": "allLogs", + "enabled": true + } + ], + "metrics": [ + { + "category": "AllMetrics", + "enabled": true + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', parameters('name'))]" + ] + }, + { + "copy": { + "name": "roleAssignments", + "count": "[length(parameters('roles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('name'))]", + "name": "[guid(parameters('name'), parameters('roles')[copyIndex()].principalId, parameters('roles')[copyIndex()].id)]", + "properties": { + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()].id)]", + "principalId": "[parameters('roles')[copyIndex()].principalId]", + "principalType": "[if(contains(parameters('roles')[copyIndex()], 'type'), parameters('roles')[copyIndex()].type, 'ServicePrincipal')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', parameters('name'))]" + ] + } + ], + "outputs": { + "name": { + "type": "string", + "value": "[parameters('name')]" + }, + "id": { + "type": "string", + "value": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('name'))]" + }, + "principalId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), '2023-05-01', 'full').identity.principalId]" + } + } + } }, "dependsOn": [ - "[resourceId('Microsoft.Search/searchServices', format('search{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')))]", - "[resourceId('Microsoft.CognitiveServices/accounts', format('aoai{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')))]" + "[resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').aiSearchName))]", + "[resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').logAnalyticsWorkspaceName))]" ] }, { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2020-08-01-preview", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', format('st{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')))]", - "name": "[guid(format('st{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')), format('search{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')), 'storageBlobDataContributor')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}-deployment', variables('resources').aiServicesName)]", "properties": { - "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', variables('azureRbacStorageBlobDataContributorRoleId'))]", - "principalId": "[reference(resourceId('Microsoft.Search/searchServices', format('search{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier'))), '2023-11-01', 'full').identity.principalId]", - "principalType": "ServicePrincipal" + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "name": { + "value": "[variables('resources').aiServicesName]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": { + "environment": "shared" + } + }, + "kind": { + "value": "CognitiveServices" + }, + "logAnalyticsWorkspaceId": { + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').logAnalyticsWorkspaceName)), '2022-09-01').outputs.id.value]" + }, + "roles": { + "value": [ + { + "principalId": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').userAssignedIdentityName)), '2022-09-01').outputs.principalId.value]", + "id": "25fbc0a9-bd7c-42a3-aa1a-3b75d497ee68" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.27.1.19265", + "templateHash": "11553281893512969426" + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Name of the AI Services account" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Location for AI Services account" + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Tags for the AI Services account" + } + }, + "kind": { + "type": "string", + "defaultValue": "CognitiveServices", + "metadata": { + "description": "Kind of AI Services account" + } + }, + "roles": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Role assignments for the Azure Search account" + } + }, + "logAnalyticsWorkspaceId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Log Analytics workspace ID for diagnostics" + } + }, + "deployments": { + "type": "array", + "defaultValue": [ + { + "name": "gpt-4-32k", + "version": "0613" + }, + { + "name": "gpt-35-turbo-16k", + "version": "0613" + }, + { + "name": "text-embedding-ada-002", + "version": "2" + } + ], + "metadata": { + "description": "Deployments for the AI Services account" + } + } + }, + "resources": [ + { + "copy": { + "name": "aisaDeployments", + "count": "[length(parameters('deployments'))]", + "mode": "serial", + "batchSize": 1 + }, + "condition": "[equals(parameters('kind'), 'OpenAI')]", + "type": "Microsoft.CognitiveServices/accounts/deployments", + "apiVersion": "2023-05-01", + "name": "[format('{0}/{1}', parameters('name'), format('{0}-{1}', parameters('deployments')[copyIndex()].name, parameters('deployments')[copyIndex()].version))]", + "properties": { + "model": { + "format": "OpenAI", + "name": "[parameters('deployments')[copyIndex()].name]", + "version": "[parameters('deployments')[copyIndex()].version]" + }, + "versionUpgradeOption": "NoAutoUpgrade" + }, + "sku": { + "name": "Standard", + "capacity": "[if(contains(parameters('deployments')[copyIndex()], 'capacity'), parameters('deployments')[copyIndex()].capacity, 20)]" + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', parameters('name'))]" + ] + }, + { + "type": "Microsoft.CognitiveServices/accounts", + "apiVersion": "2023-05-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "sku": { + "name": "S0" + }, + "kind": "[parameters('kind')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "customSubDomainName": "[parameters('name')]" + } + }, + { + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('name'))]", + "name": "all-logs-all-metrics", + "properties": { + "workspaceId": "[parameters('logAnalyticsWorkspaceId')]", + "logs": [ + { + "categoryGroup": "allLogs", + "enabled": true + } + ], + "metrics": [ + { + "category": "AllMetrics", + "enabled": true + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', parameters('name'))]" + ] + }, + { + "copy": { + "name": "roleAssignments", + "count": "[length(parameters('roles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.CognitiveServices/accounts/{0}', parameters('name'))]", + "name": "[guid(parameters('name'), parameters('roles')[copyIndex()].principalId, parameters('roles')[copyIndex()].id)]", + "properties": { + "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()].id)]", + "principalId": "[parameters('roles')[copyIndex()].principalId]", + "principalType": "[if(contains(parameters('roles')[copyIndex()], 'type'), parameters('roles')[copyIndex()].type, 'ServicePrincipal')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.CognitiveServices/accounts', parameters('name'))]" + ] + } + ], + "outputs": { + "name": { + "type": "string", + "value": "[parameters('name')]" + }, + "id": { + "type": "string", + "value": "[resourceId('Microsoft.CognitiveServices/accounts', parameters('name'))]" + }, + "principalId": { + "type": "string", + "value": "[reference(resourceId('Microsoft.CognitiveServices/accounts', parameters('name')), '2023-05-01', 'full').identity.principalId]" + } + } + } }, "dependsOn": [ - "[resourceId('Microsoft.Search/searchServices', format('search{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')))]", - "[resourceId('Microsoft.Storage/storageAccounts', format('st{0}{1}', parameters('workloadIdentifier'), parameters('environmentIdentifier')))]" + "[resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').logAnalyticsWorkspaceName))]", + "[resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').userAssignedIdentityName))]" ] } - ] + ], + "outputs": { + "storageAccountName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-01-deployment', variables('resources').storageAccountName)), '2022-09-01').outputs.name.value]" + }, + "logAnalyticsWorkspaceName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').logAnalyticsWorkspaceName)), '2022-09-01').outputs.name.value]" + }, + "containerRegistryName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').containerRegistryName)), '2022-09-01').outputs.name.value]" + }, + "userAssignedIdentityName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').userAssignedIdentityName)), '2022-09-01').outputs.name.value]" + }, + "containerAppEnvironmnetStagingName": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Resources/deployments', format('{0}-deployment', variables('resources').containerAppEnvironmnetName)), '2022-09-01').outputs.name.value]" + } + } } \ No newline at end of file diff --git a/infrastructure/modules/ai-search.bicep b/infrastructure/modules/ai-search.bicep new file mode 100644 index 0000000..ce17abe --- /dev/null +++ b/infrastructure/modules/ai-search.bicep @@ -0,0 +1,81 @@ +//******************************************************** +// Parameters +//******************************************************** + +@description('Name of the Azure AI Search service') +param name string + +@description('Location for the Azure AI Search service') +param location string = resourceGroup().location + +@description('Tags for the Azure AI Search service') +param tags object = {} + +@description('Role assignments for the Azure AI Search service') +param roles array = [] + +@description('Log Analytics workspace ID for diagnostics') +param logAnalyticsWorkspaceId string = '' + +resource srchNew 'Microsoft.Search/searchServices@2023-11-01' = { + name: name + location: location + tags: tags + sku: { + name: 'standard' + } + identity: { + type: 'SystemAssigned' + } + properties: { + authOptions: { + aadOrApiKey: { + aadAuthFailureMode: 'http401WithBearerChallenge' + } + } + } +} + +//******************************************************** +// Resources +//******************************************************** + +resource diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { + name: 'all-logs-all-metrics' + scope: srchNew + properties: { + workspaceId: logAnalyticsWorkspaceId + logs: [ + { + categoryGroup: 'allLogs' + enabled: true + } + ] + metrics: [ + { + category: 'AllMetrics' + enabled: true + } + ] + } +} + +resource roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ + for role in roles: { + name: guid(name, role.principalId, role.id) + scope: srchNew + properties: { + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', role.id) + principalId: role.principalId + principalType: contains(role, 'type') ? role.type : 'ServicePrincipal' + } + } +] + +//******************************************************** +// Outputs +//******************************************************** + +output name string = srchNew.name +output id string = srchNew.id +output principalId string = srchNew.identity.principalId diff --git a/infrastructure/modules/ai-services.bicep b/infrastructure/modules/ai-services.bicep new file mode 100644 index 0000000..75286e4 --- /dev/null +++ b/infrastructure/modules/ai-services.bicep @@ -0,0 +1,116 @@ +//******************************************************** +// Parameters +//******************************************************** + +@description('Name of the AI Services account') +param name string + +@description('Location for AI Services account') +param location string = resourceGroup().location + +@description('Tags for the AI Services account') +param tags object = {} + +@description('Kind of AI Services account') +param kind string = 'CognitiveServices' + +@description('Role assignments for the Azure Search account') +param roles array = [] + +@description('Log Analytics workspace ID for diagnostics') +param logAnalyticsWorkspaceId string = '' + +@description('Deployments for the AI Services account') +param deployments array = [ + { + name: 'gpt-4-32k' + version: '0613' + } + { + name: 'gpt-35-turbo-16k' + version: '0613' + } + { + name: 'text-embedding-ada-002' + version: '2' + } +] + +//******************************************************** +// Resources +//******************************************************** + +resource aisaNew 'Microsoft.CognitiveServices/accounts@2023-05-01' = { + name: name + location: location + tags: tags + sku: { + name: 'S0' + } + kind: kind + identity: { + type: 'SystemAssigned' + } + properties: { + customSubDomainName: name + } + + @batchSize(1) + resource aisaDeployments 'deployments@2023-05-01' = [ + for deployment in deployments: if (kind == 'OpenAI') { + name: '${deployment.name}-${deployment.version}' + properties: { + model: { + format: 'OpenAI' + name: deployment.name + version: deployment.version + } + versionUpgradeOption: 'NoAutoUpgrade' + } + sku: { + name: 'Standard' + capacity: contains(deployment, 'capacity') ? deployment.capacity : 20 + } + } + ] +} + +resource diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { + name: 'all-logs-all-metrics' + scope: aisaNew + properties: { + workspaceId: logAnalyticsWorkspaceId + logs: [ + { + categoryGroup: 'allLogs' + enabled: true + } + ] + metrics: [ + { + category: 'AllMetrics' + enabled: true + } + ] + } +} + +resource roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ + for role in roles: { + name: guid(name, role.principalId, role.id) + scope: aisaNew + properties: { + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', role.id) + principalId: role.principalId + principalType: contains(role, 'type') ? role.type : 'ServicePrincipal' + } + } +] + +//******************************************************** +// Outputs +//******************************************************** + +output name string = aisaNew.name +output id string = aisaNew.id +output principalId string = aisaNew.identity.principalId diff --git a/infrastructure/modules/container-app-environment.bicep b/infrastructure/modules/container-app-environment.bicep new file mode 100644 index 0000000..ec4b999 --- /dev/null +++ b/infrastructure/modules/container-app-environment.bicep @@ -0,0 +1,64 @@ +//******************************************************** +// Parameters +//******************************************************** + +@description('Name of the Application Insights service') +param name string + +@description('Location for Application Insights service') +param location string = resourceGroup().location + +@description('Tags for the Application Insights service') +param tags object = {} + +@description('Role assignments for the Application Insights service') +param roles array = [] + +@description('Log Analytics workspace name') +param logAnalyticsWorkspaceName string = '' + +@description('Log Analytics workspace resource group name') +param logAnalyticsWorkspaceResourceGroupName string = resourceGroup().name + +//******************************************************** +// Resources +//******************************************************** + +resource logExisting 'Microsoft.OperationalInsights/workspaces@2022-10-01' existing = { + scope: resourceGroup(logAnalyticsWorkspaceResourceGroupName) + name: logAnalyticsWorkspaceName +} + +resource caeNew 'Microsoft.App/managedEnvironments@2023-05-01' = { + name: name + location: location + tags: tags + properties: { + appLogsConfiguration: { + destination: 'log-analytics' + logAnalyticsConfiguration: { + customerId: logExisting.properties.customerId + sharedKey: logExisting.listKeys().primarySharedKey + } + } + } +} + +resource roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ + for role in roles: { + name: guid(name, role.principalId, role.id) + scope: caeNew + properties: { + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', role.id) + principalId: role.principalId + principalType: contains(role, 'type') ? role.type : 'ServicePrincipal' + } + } +] + +//******************************************************** +// Outputs +//******************************************************** + +output name string = caeNew.name +output id string = caeNew.id diff --git a/infrastructure/modules/container-registry.bicep b/infrastructure/modules/container-registry.bicep new file mode 100644 index 0000000..499b60b --- /dev/null +++ b/infrastructure/modules/container-registry.bicep @@ -0,0 +1,73 @@ +//******************************************************** +// Parameters +//******************************************************** + +@description('Name of the Container Registry service') +param name string + +@description('Location for Container Registry service') +param location string = resourceGroup().location + +@description('Tags for the Container Registry service') +param tags object = {} + +@description('Role assignments for the Container Registry service') +param roles array = [] + +@description('Log Analytics workspace ID for diagnostics') +param logAnalyticsWorkspaceId string = '' + +//******************************************************** +// Resources +//******************************************************** + +resource crNew 'Microsoft.ContainerRegistry/registries@2022-12-01' = { + name: name + location: location + tags: tags + sku: { + name: 'Standard' + } + properties: { + adminUserEnabled: true + } +} + +resource diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { + name: 'all-logs-all-metrics' + scope: crNew + properties: { + workspaceId: logAnalyticsWorkspaceId + logs: [ + { + categoryGroup: 'allLogs' + enabled: true + } + ] + metrics: [ + { + category: 'AllMetrics' + enabled: true + } + ] + } +} + +resource roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ + for role in roles: { + name: guid(name, role.principalId, role.id) + scope: crNew + properties: { + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', role.id) + principalId: role.principalId + principalType: contains(role, 'type') ? role.type : 'ServicePrincipal' + } + } +] + +//******************************************************** +// Outputs +//******************************************************** + +output name string = crNew.name +output id string = crNew.id diff --git a/infrastructure/modules/log-analytics-workspace.bicep b/infrastructure/modules/log-analytics-workspace.bicep new file mode 100644 index 0000000..3644308 --- /dev/null +++ b/infrastructure/modules/log-analytics-workspace.bicep @@ -0,0 +1,89 @@ +//******************************************************** +// Parameters +//******************************************************** + +@description('Name of the Log Analytics workspace') +param name string + +@description('Location for Log Analytics workspace') +param location string = resourceGroup().location + +@description('Tags for the Log Analytics workspace') +param tags object = {} + +@description('Role assignments for the Log Analytics workspace') +param roles array = [] + +@description('Storage account ID to link to the Log Analytics workspace') +param storageAccountId string = '' + +//******************************************************** +// Resources +//******************************************************** + +resource logNew 'Microsoft.OperationalInsights/workspaces@2022-10-01' = { + name: name + location: location + tags: tags + properties: { + retentionInDays: 30 + sku: { + name: 'Standalone' + } + } + + resource linkedStAlerts 'linkedStorageAccounts@2020-08-01' = { + name: 'Alerts' + properties: { + storageAccountIds: [ + storageAccountId + ] + } + } + + resource linkedStCustomLogs 'linkedStorageAccounts@2020-08-01' = { + name: 'CustomLogs' + properties: { + storageAccountIds: [ + storageAccountId + ] + } + } + + resource linkedStIngestion 'linkedStorageAccounts@2020-08-01' = { + name: 'Ingestion' + properties: { + storageAccountIds: [ + storageAccountId + ] + } + } + + resource linkedStQuery 'linkedStorageAccounts@2020-08-01' = { + name: 'Query' + properties: { + storageAccountIds: [ + storageAccountId + ] + } + } +} + +resource roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ + for role in roles: { + name: guid(name, role.principalId, role.id) + scope: logNew + properties: { + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', role.id) + principalId: role.principalId + principalType: contains(role, 'type') ? role.type : 'ServicePrincipal' + } + } +] + +//******************************************************** +// Outputs +//******************************************************** + +output name string = logNew.name +output id string = logNew.id diff --git a/infrastructure/modules/storage-account.bicep b/infrastructure/modules/storage-account.bicep new file mode 100644 index 0000000..a6b79e3 --- /dev/null +++ b/infrastructure/modules/storage-account.bicep @@ -0,0 +1,85 @@ +//******************************************************** +// Parameters +//******************************************************** + +@description('Name of the Storage service') +param name string + +@description('Location for Storage service') +param location string = resourceGroup().location + +@description('Tags for the Storage service') +param tags object = {} + +@description('Role assignments for the Storage service') +param roles array = [] + +@description('Enable hierarchical namespace') +param enableHns bool = false + +//******************************************************** +// Resources +//******************************************************** + +resource stNew 'Microsoft.Storage/storageAccounts@2022-05-01' = { + name: name + location: location + tags: tags + sku: { + name: 'Standard_LRS' + } + kind: 'StorageV2' + properties: { + isHnsEnabled: enableHns + minimumTlsVersion: 'TLS1_2' + } +} + +resource roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ + for role in roles: { + name: guid(name, role.principalId, role.id) + scope: stNew + properties: { + roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', role.id) + principalId: role.principalId + principalType: contains(role, 'type') ? role.type : 'ServicePrincipal' + } + } +] + +//******************************************************** +// Deployment Scripts +//******************************************************** + +resource deploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = { + name: 'ds${name}' + location: location + kind: 'AzureCLI' + properties: { + azCliVersion: '2.30.0' + timeout: 'PT5M' + retentionInterval: 'PT1H' + environmentVariables: [ + { + name: 'AZURE_STORAGE_ACCOUNT' + value: stNew.name + } + { + name: 'AZURE_STORAGE_KEY' + secureValue: stNew.listKeys().keys[0].value + } + ] + scriptContent: ''' + git clone https://github.com/nfmoore/azure-open-ai-example-scenarios.git + az storage container create --name data --account-name $AZURE_STORAGE_ACCOUNT --account-key $AZURE_STORAGE_KEY + az storage blob upload-batch --destination data --account-name $AZURE_STORAGE_ACCOUNT --account-key $AZURE_STORAGE_KEY --destination-path ./ --source ./azure-open-ai-example-scenarios/data + ''' + } +} + +//******************************************************** +// Outputs +//******************************************************** + +output name string = stNew.name +output id string = stNew.id diff --git a/infrastructure/modules/user-assigned-identity.bicep b/infrastructure/modules/user-assigned-identity.bicep new file mode 100644 index 0000000..9c1c573 --- /dev/null +++ b/infrastructure/modules/user-assigned-identity.bicep @@ -0,0 +1,30 @@ +//******************************************************** +// Parameters +//******************************************************** + +@description('Name of the User Assigned Identity service') +param name string + +@description('Location for User Assigned Identity service') +param location string = resourceGroup().location + +@description('Tags for the User Assigned Identity service') +param tags object = {} + +//******************************************************** +// Resources +//******************************************************** + +resource idNew 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { + name: name + location: location + tags: tags +} + +//******************************************************** +// Outputs +//******************************************************** + +output id string = idNew.id +output name string = idNew.name +output principalId string = idNew.properties.principalId diff --git a/src/rag/utilities.py b/llms/custom_rag_client.py similarity index 82% rename from src/rag/utilities.py rename to llms/custom_rag_client.py index 82f8ca9..c646b29 100644 --- a/src/rag/utilities.py +++ b/llms/custom_rag_client.py @@ -1,21 +1,17 @@ """ -This module contains the RetrievalAugmentedGenerationClient class for +This module contains the CustomRetrievalAugmentedGenerationClient class for orchestrating various operations related to AI search and chat. Classes: - RetrievalAugmentedGenerationClient: A client class for orchestrating various + CustomRetrievalAugmentedGenerationClient: A client class for orchestrating various operations related to AI search and chat. """ -import string - import requests -import yaml from azure.identity import DefaultAzureCredential -from nltk.corpus import stopwords -class RetrievalAugmentedGenerationClient: +class CustomRetrievalAugmentedGenerationClient: """ A client class for orchestrating various operations related to AI search and chat. @@ -38,9 +34,10 @@ def __init__( open_ai_embedding_deployment: str, search_endpoint: str, search_index_name: str, - system_prompt_configuration_file: str, - open_ai_api_version="2023-12-01-preview", - search_api_version="2023-11-01", + query_system_message: str, + chat_system_message: str, + open_ai_api_version="2024-02-01", + search_api_version="2024-03-01-Preview", credential=DefaultAzureCredential(), ): self.open_ai_endpoint = open_ai_endpoint @@ -62,15 +59,8 @@ def __init__( self.embedding_endpoint = f"{self.open_ai_endpoint}/openai/deployments/{self.open_ai_embedding_deployment}/embeddings?api-version={self.open_ai_api_version}" self.query_search_endpoint = f"{self.search_endpoint}/indexes/{self.search_index_name}/docs/search?api-version={self.search_api_version}" - with open(system_prompt_configuration_file, "r", encoding="utf-8") as f: - configuration = yaml.safe_load(f) - - self.search_query_system_message = configuration.get( - "search_query_system_message" - ) - self.chat_response_system_message = configuration.get( - "chat_response_system_message" - ) + self.query_system_message = query_system_message + self.chat_system_message = chat_system_message def get_request_headers(self, token: str) -> dict[str, str]: """ @@ -113,7 +103,7 @@ def retrieve_documents( headers=self.get_request_headers(self.open_ai_access_token), json={ "messages": [ - {"role": "system", "content": self.search_query_system_message}, + {"role": "system", "content": self.query_system_message}, {"role": "user", "content": question}, ], }, @@ -143,8 +133,9 @@ def retrieve_documents( "select": selected_fields, "queryType": "semantic", "semanticConfiguration": f"{self.search_index_name}-semantic-configuration", - "captions": "extractive", - "answers": "extractive", + "captions": "extractive|highlight-true", + "answers": f"extractive|count-{number_of_documents}", + "count": "true", "top": number_of_documents, "vectorQueries": [ { @@ -163,7 +154,11 @@ def retrieve_documents( # Filter search documents filtered_search_documents = [ - {"title": doc["title"], "path": doc["path"], "chunk": doc["chunk"]} + { + "title": doc["title"], + "path": doc["path"], + "content": doc["@search.captions"][0]["text"], + } for doc in search_documents ] @@ -182,23 +177,15 @@ def augment_prompt(self, question: str, retrieved_documents: list[any]) -> str: str: The augmented prompt with the retrieved documents. """ - # Function to pre-process document content - remove punctuation and stopwords - def preprocess_text(text: str): - text = text.lower().translate(str.maketrans("", "", string.punctuation)) - stop_words = set(stopwords.words("english")) - text = " ".join([word for word in text.split() if word not in stop_words]) - return text - # Generate prompt sources string prompt_sources = "".join( - [ - f"{doc['title']} :: {doc['path']} :: {preprocess_text(doc['chunk'])} ||\n" - for doc in retrieved_documents - ] + [f"{doc['title']} :: {doc['content']} ||\n" for doc in retrieved_documents] ) # Embed the sources in the prompt - augmented_prompt = f"{question}\nSources:\n{prompt_sources}" + augmented_prompt = ( + f"#question:```{question}```\n#sources:```{prompt_sources}```" + ) return augmented_prompt @@ -230,9 +217,7 @@ def generate_response( self.chat_endpoint, headers=self.get_request_headers(self.open_ai_access_token), json={ - "messages": [ - {"role": "system", "content": self.chat_response_system_message} - ] + "messages": [{"role": "system", "content": self.chat_system_message}] + message_history_filtered + [{"role": "user", "content": augmented_prompt}], }, @@ -246,16 +231,18 @@ def generate_response( def update_message_history( self, + question: str, message_history: list[dict[str, any]], augmented_prompt: str, response: str, retrieved_documents: list[any], ) -> list[dict[str, any]]: """ - Updates the message history with the user and agent messages. + Updates the message history with the user and assistant messages. Parameters: self (object): An instance of the class that this method belongs to. + question (str): The user question. message_history (list[dict[str, any]]): The message history containing the user prompt. augmented_prompt (str): The user message. response (str): The assistant message. @@ -272,14 +259,32 @@ def update_message_history( ) ) + # Remove duplicate references from retrieved documents + references_without_duplicates = [ + dict(referenceas_tuple) + for referenceas_tuple in { + tuple(reference.items()) for reference in references + } + ] + # Update message history user_message = { "role": "user", - "content": augmented_prompt, - "references": references, + "content": question, + "context": { + "augmented_prompt": augmented_prompt, + }, } - agent_message = {"role": "assistant", "content": response} - updated_message_history = message_history + [user_message, agent_message] + + assistant_message = { + "role": "assistant", + "content": response, + "context": { + "references": references_without_duplicates, + }, + } + + updated_message_history = message_history + [user_message, assistant_message] return updated_message_history @@ -309,7 +314,7 @@ def get_answer(self, question: str, message_history: list[dict[str, any]]) -> st # Update message history updated_message_history = self.update_message_history( - message_history, augmented_prompt, response, retrieved_documents + question, message_history, augmented_prompt, response, retrieved_documents ) return updated_message_history diff --git a/src/rag/configuration.yaml b/llms/system_messages.yml similarity index 52% rename from src/rag/configuration.yaml rename to llms/system_messages.yml index e6e64a2..9af6943 100644 --- a/src/rag/configuration.yaml +++ b/llms/system_messages.yml @@ -1,14 +1,21 @@ -search_query_system_message: | +# ----------------------------------- +# Example Scenario 1 +# ----------------------------------- + +query_system_message: | You are a bot that translates user queries into an effective search query for Azure AI Search. Ensure the user's intent is captured by including relevant keywords or phrases from their query. Ensure you ownly return the search query and nothing else in your response. -chat_response_system_message: | +product_info_chat_system_message: | You are an customer service bot designed to answer questions on products. Keep your answers short and to the point. Try to use dot points as much as possible. Answer ONLY with the contnet listed in the list of sources below. If there isn't enough information below, say you don't know. - Do not generate answers that don't use the sources below. If asking a clarifying question to the user would help, ask the question. + The question will enclosed by three back ticks (```) after a # followed by the word question, for example "#question" + The sources will enclosed by three back ticks (```) after a # followed by the word sources, for example "#sources" + Do not generate answers that don't use information in the listed sources. + If asking a clarifying question to the user would help, ask the question. For tabular information return it as an html table. Do not return markdown format. - If the question is not in English, answer in the language used in the question. - Each source has a name, path, and contnet seperated by a double colon, always include the source name for each fact you use in the response. + Each source has a name and content seperated by a double colon, always include the source name for each fact you use in the response. Use square brackets to reference the source, for example [info1.txt]. Don't combine sources, list each source separately, for example [info1.txt][info2.pdf]. +# ----------------------------------- diff --git a/notebooks/01-populate-index.ipynb b/notebooks/01-populate-index.ipynb deleted file mode 100644 index c1b0fd3..0000000 --- a/notebooks/01-populate-index.ipynb +++ /dev/null @@ -1,158 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Populate Azure AI Search Index" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "import dotenv\n", - "import sys\n", - "\n", - "dotenv.load_dotenv(\".env\")\n", - "sys.path.append(os.path.join(os.getcwd(), \"..\", \"src\"))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Approach 1: Pull-based\n", - "\n", - "The pull model uses indexers connecting to a supported data source, automatically uploading the data into your index. This is the recommended approach for data sources that are frequently updated." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "from search.utilities import SearchClient\n", - "\n", - "# Create search client\n", - "search_client = SearchClient(\n", - " search_endpoint=os.environ[\"AZURE_AI_SEARCH_ENDPOINT\"],\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "# Generate list of variables to be used in templates\n", - "template_variables = {\n", - " key: value for key, value in os.environ.items() if key.startswith((\"AZURE\"))\n", - "}\n", - "\n", - "# Define template paths\n", - "base_path = os.path.join(os.getcwd(), \"..\", \"src\", \"search\", \"templates\")\n", - "datasource_template_path = os.path.join(base_path, \"product-info\", \"datasource.json\")\n", - "index_template_path = os.path.join(base_path, \"product-info\", \"index.json\")\n", - "skillset_template_path = os.path.join(base_path, \"product-info\", \"skillset.json\")\n", - "indexer_template_path = os.path.join(base_path, \"product-info\", \"indexer.json\")\n", - "\n", - "# List of search assets\n", - "assets = [\n", - " {\n", - " \"type\": \"indexes\",\n", - " \"name\": os.environ[\"AZURE_AI_SEARCH_INDEX_NAME\"],\n", - " \"template_path\": index_template_path,\n", - " \"template_variables\": template_variables,\n", - " },\n", - " {\n", - " \"type\": \"datasources\",\n", - " \"name\": os.environ[\"AZURE_AI_SEARCH_DATASOURCE_NAME\"],\n", - " \"template_path\": datasource_template_path,\n", - " \"template_variables\": template_variables,\n", - " },\n", - " {\n", - " \"type\": \"skillsets\",\n", - " \"name\": os.environ[\"AZURE_AI_SEARCH_SKILLSET_NAME\"],\n", - " \"template_path\": skillset_template_path,\n", - " \"template_variables\": template_variables,\n", - " },\n", - " {\n", - " \"type\": \"indexers\",\n", - " \"name\": os.environ[\"AZURE_AI_SEARCH_INDEXER_NAME\"],\n", - " \"template_path\": indexer_template_path,\n", - " \"template_variables\": template_variables,\n", - " },\n", - "]\n", - "\n", - "# Load search asset templates\n", - "search_client.load_search_management_asset_templates(assets)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "# Create the index\n", - "index_response = search_client.create_search_management_asset(asset_type=\"indexes\")\n", - "\n", - "# Create the data source\n", - "datasource_response = search_client.create_search_management_asset(asset_type=\"datasources\")\n", - "\n", - "# Create skillset to enhance the indexer\n", - "skillset_response = search_client.create_search_management_asset(asset_type=\"skillsets\")\n", - "\n", - "# Create the indexer\n", - "indexer_response = search_client.create_search_management_asset(asset_type=\"indexers\")" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "# Run the indexer\n", - "indexer_run_response = search_client.run_indexer()" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "# Run the indexer with reset\n", - "indexer_run_reset_response = search_client.run_indexer(reset_flag=True)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "base", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.4" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/notebooks/02-llm-queries.ipynb b/notebooks/02-llm-queries.ipynb deleted file mode 100644 index bcb9582..0000000 --- a/notebooks/02-llm-queries.ipynb +++ /dev/null @@ -1,181 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# LLM Queries with Knowledge Base Integration" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "import dotenv\n", - "import sys\n", - "\n", - "dotenv.load_dotenv(\".env\")\n", - "sys.path.append(os.path.join(os.getcwd(), \"..\", \"src\"))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Approach 1: Custom Client\n", - "\n", - "This approach will use the `RetrievalAugmentedGenerationClient` class defined in `src/rag/utilities.py`. This will NOT require a Microsoft managed private endpoint for private access." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from rag.utilities import RetrievalAugmentedGenerationClient\n", - "\n", - "# Create orchestration client\n", - "rag_client = RetrievalAugmentedGenerationClient(\n", - " open_ai_endpoint=os.getenv(\"AZURE_OPENAI_API_BASE\"),\n", - " open_ai_chat_deployment=os.getenv(\"AZURE_OPENAI_CHAT_DEPLOYMENT\"),\n", - " open_ai_embedding_deployment=os.getenv(\"AZURE_OPENAI_EMBEDDING_DEPLOYMENT\"),\n", - " search_endpoint=os.getenv(\"AZURE_AI_SEARCH_ENDPOINT\"),\n", - " search_index_name=os.getenv(\"AZURE_AI_SEARCH_INDEX_NAME\"),\n", - " system_prompt_configuration_file=\"../src/rag/configuration.yaml\"\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "message_history = []\n", - "message_history = rag_client.get_answer(\"Which tent is the most waterproof?\", message_history=message_history)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for message in message_history:\n", - " content = message['content'].split(\"Sources:\")[0].strip()\n", - " print(f\"{message['role'].title()}: {content}\\n\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "message_history = rag_client.get_answer(\"Tell me more about the Alpine Explorer Tent?\", message_history=message_history)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for message in message_history:\n", - " content = message['content'].split(\"Sources:\")[0].strip()\n", - " print(f\"{message['role'].title()}: {content}\\n\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Approach 2: Azure OpenAI Service REST API\n", - "\n", - "This will require public access on Azure AI Search or a Microsoft managed private endpoint for private access." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from azure.identity import DefaultAzureCredential\n", - "import requests\n", - "\n", - "credential = DefaultAzureCredential()\n", - "access_token = credential.get_token(\"https://cognitiveservices.azure.com/.default\")\n", - "\n", - "open_ai_endpoint = os.getenv(\"AZURE_OPENAI_API_BASE\")\n", - "open_ai_chat_deployment = os.getenv(\"AZURE_OPENAI_CHAT_DEPLOYMENT\")\n", - "open_ai_api_version = os.getenv(\"AZURE_OPENAI_API_VERSION\")\n", - "\n", - "chat_endpoint = f\"{open_ai_endpoint}/openai/deployments/{open_ai_chat_deployment}/extensions/chat/completions?api-version={open_ai_api_version}\"\n", - "\n", - "request_headers = {\n", - " \"Content-Type\": \"application/json\",\n", - " \"Authorization\": f\"Bearer {access_token.token}\",\n", - "}\n", - "\n", - "request_payload = {\n", - " \"dataSources\": [\n", - " {\n", - " \"type\": \"AzureCognitiveSearch\",\n", - " \"parameters\": {\n", - " \"endpoint\": os.getenv(\"AZURE_AI_SEARCH_ENDPOINT\"),\n", - " \"indexName\": os.getenv(\"AZURE_AI_SEARCH_INDEX_NAME\"),\n", - " \"queryType\": \"vectorSemanticHybrid\",\n", - " \"embeddingDeploymentName\": os.getenv(\n", - " \"AZURE_OPENAI_EMBEDDING_DEPLOYMENT\"\n", - " ),\n", - " \"fieldsMapping\": {\"titleField\": \"title\", \"urlField\": \"path\"},\n", - " },\n", - " }\n", - " ],\n", - " \"messages\": [{\"role\": \"user\", \"content\": \"Which tent is the most waterproof?\"}],\n", - "}\n", - "\n", - "response = requests.post(\n", - " chat_endpoint,\n", - " headers=request_headers,\n", - " json=request_payload,\n", - ")\n", - "\n", - "print(response.json()[\"choices\"][0][\"message\"][\"content\"])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": ".venv", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.4" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/notebooks/example_scenario_01.ipynb b/notebooks/example_scenario_01.ipynb new file mode 100644 index 0000000..4eff419 --- /dev/null +++ b/notebooks/example_scenario_01.ipynb @@ -0,0 +1,412 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Example Scenario 1: Pull-Based Data Ingestion with Azure AI Search & Custom Retrieval-Augmented Generation (RAG) Pattern\n", + "\n", + "- **Populate Azure AI Search Index**: A pull-based approach is used to create a search index in Azure AI Search. A pull model uses indexers connecting to a supported data source, automatically uploading the data into your index. This is the recommended approach for data sources that are frequently updated.\n", + "\n", + "- **LLM Queries with Knowledge Base Integration**: A custom implementation for Retrieval Augmented Generation (RAG) will be used to chat with an LLM. This approach will be contrasted with an out-of-the box approach using the Azure OpenAI Service REST API." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import dotenv\n", + "import sys\n", + "\n", + "# common setup\n", + "dotenv.load_dotenv(\".env\")\n", + "sys.path.append(os.path.join(os.getcwd(), \"..\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Populate Azure AI Search Index\n", + "\n", + "### Approach: Pull-Based Custom Client\n", + "\n", + "This approach will use the `CustomSearchClient` class defined in `search/custom_search_client_pull.py`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from search.custom_search_client import CustomSearchClient\n", + "\n", + "# Create search client\n", + "search_client = CustomSearchClient(\n", + " search_endpoint=os.environ[\"AZURE_AI_SEARCH_ENDPOINT\"],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Generate list of variables to be used in templates\n", + "template_variables = {\n", + " key: value for key, value in os.environ.items() if key.startswith((\"AZURE\"))\n", + "}\n", + "\n", + "# Define template paths\n", + "base_path = os.path.join(os.getcwd(), \"..\", \"search\", \"templates\")\n", + "datasource_template_path = os.path.join(base_path, \"product-info\", \"datasource.json\")\n", + "index_template_path = os.path.join(base_path, \"product-info\", \"index.json\")\n", + "skillset_template_path = os.path.join(base_path, \"product-info\", \"skillset.json\")\n", + "indexer_template_path = os.path.join(base_path, \"product-info\", \"indexer.json\")\n", + "\n", + "# List of search assets\n", + "assets = [\n", + " {\n", + " \"type\": \"indexes\",\n", + " \"name\": os.environ[\"AZURE_AI_SEARCH_INDEX_NAME\"],\n", + " \"template_path\": index_template_path,\n", + " \"template_variables\": template_variables,\n", + " },\n", + " {\n", + " \"type\": \"datasources\",\n", + " \"name\": os.environ[\"AZURE_AI_SEARCH_DATASOURCE_NAME\"],\n", + " \"template_path\": datasource_template_path,\n", + " \"template_variables\": template_variables,\n", + " },\n", + " {\n", + " \"type\": \"skillsets\",\n", + " \"name\": os.environ[\"AZURE_AI_SEARCH_SKILLSET_NAME\"],\n", + " \"template_path\": skillset_template_path,\n", + " \"template_variables\": template_variables,\n", + " },\n", + " {\n", + " \"type\": \"indexers\",\n", + " \"name\": os.environ[\"AZURE_AI_SEARCH_INDEXER_NAME\"],\n", + " \"template_path\": indexer_template_path,\n", + " \"template_variables\": template_variables,\n", + " },\n", + "]\n", + "\n", + "# Load search asset templates\n", + "search_client.load_search_management_asset_templates(assets)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create the index\n", + "index_response = search_client.create_search_management_asset(asset_type=\"indexes\")\n", + "\n", + "# Create the data source\n", + "datasource_response = search_client.create_search_management_asset(\n", + " asset_type=\"datasources\"\n", + ")\n", + "\n", + "# Create skillset to enhance the indexer\n", + "skillset_response = search_client.create_search_management_asset(asset_type=\"skillsets\")\n", + "\n", + "# Create the indexer\n", + "indexer_response = search_client.create_search_management_asset(asset_type=\"indexers\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Run the indexer\n", + "indexer_run_response = search_client.run_indexer()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# [Optional] Run the indexer with reset\n", + "# indexer_run_reset_response = search_client.run_indexer(reset_flag=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. LLM Queries with Knowledge Base Integration\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import dotenv\n", + "import sys\n", + "\n", + "dotenv.load_dotenv(\".env\")\n", + "sys.path.append(os.path.join(os.getcwd(), \"..\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Approach 1: Custom Client\n", + "\n", + "This approach will use the `CustomRetrievalAugmentedGenerationClient` class defined in `open_ai/custom_rag_client.py`. This will NOT require a Microsoft managed private endpoint for private access." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import yaml\n", + "\n", + "# Load configuration file\n", + "system_prompt_configuration_file = \"../llms/system_messages.yml\"\n", + "with open(system_prompt_configuration_file, \"r\", encoding=\"utf-8\") as f:\n", + " configuration = yaml.safe_load(f)\n", + "\n", + "# Get system messages\n", + "query_system_message = configuration.get(\"query_system_message\")\n", + "chat_system_message = configuration.get(\"product_info_chat_system_message\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from llms.custom_rag_client import CustomRetrievalAugmentedGenerationClient\n", + "\n", + "# Create orchestration client\n", + "rag_client = CustomRetrievalAugmentedGenerationClient(\n", + " open_ai_endpoint=os.getenv(\"AZURE_OPENAI_API_BASE\"),\n", + " open_ai_chat_deployment=os.getenv(\"AZURE_OPENAI_CHAT_DEPLOYMENT\"),\n", + " open_ai_embedding_deployment=os.getenv(\"AZURE_OPENAI_EMBEDDING_DEPLOYMENT\"),\n", + " search_endpoint=os.getenv(\"AZURE_AI_SEARCH_ENDPOINT\"),\n", + " search_index_name=os.getenv(\"AZURE_AI_SEARCH_INDEX_NAME\"),\n", + " query_system_message=query_system_message,\n", + " chat_system_message=chat_system_message,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "message_history = []\n", + "message_history = rag_client.get_answer(\n", + " \"Which tent is the most waterproof?\", message_history=message_history\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for message in message_history:\n", + " print(f\"{message['role'].title()}: {message['content']}\\n\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "message_history = rag_client.get_answer(\n", + " \"Tell me more about the Alpine Explorer Tent?\", message_history=message_history\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for message in message_history:\n", + " print(f\"{message['role'].title()}: {message['content']}\\n\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Approach 2: Azure OpenAI Service REST API\n", + "\n", + "This will require public access on Azure AI Search or a Microsoft managed private endpoint for private access." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from azure.identity import DefaultAzureCredential\n", + "import requests\n", + "\n", + "credential = DefaultAzureCredential()\n", + "access_token = credential.get_token(\"https://cognitiveservices.azure.com/.default\")\n", + "\n", + "open_ai_endpoint = os.getenv(\"AZURE_OPENAI_API_BASE\")\n", + "open_ai_chat_deployment = os.getenv(\"AZURE_OPENAI_CHAT_DEPLOYMENT\")\n", + "open_ai_api_version = \"2024-02-01\"\n", + "\n", + "chat_endpoint = f\"{open_ai_endpoint}/openai/deployments/{open_ai_chat_deployment}/chat/completions?api-version={open_ai_api_version}\"\n", + "\n", + "request_headers = {\n", + " \"Content-Type\": \"application/json\",\n", + " \"Authorization\": f\"Bearer {access_token.token}\",\n", + "}\n", + "\n", + "\n", + "def get_answer(message_history: list):\n", + " request_payload = {\n", + " \"data_sources\": [\n", + " {\n", + " \"type\": \"azure_search\",\n", + " \"parameters\": {\n", + " \"endpoint\": os.getenv(\"AZURE_AI_SEARCH_ENDPOINT\"),\n", + " \"index_name\": os.getenv(\"AZURE_AI_SEARCH_INDEX_NAME\"),\n", + " \"query_type\": \"vector_semantic_hybrid\",\n", + " \"embedding_dependency\": {\n", + " \"deployment_name\": os.getenv(\n", + " \"AZURE_OPENAI_EMBEDDING_DEPLOYMENT\"\n", + " ),\n", + " \"type\": \"deployment_name\",\n", + " },\n", + " \"fields_mapping\": {\"title_field\": \"title\", \"url_field\": \"path\"},\n", + " \"authentication\": {\"type\": \"system_assigned_managed_identity\"},\n", + " },\n", + " }\n", + " ],\n", + " \"messages\": message_history,\n", + " }\n", + "\n", + " response = requests.post(\n", + " chat_endpoint,\n", + " headers=request_headers,\n", + " json=request_payload,\n", + " )\n", + "\n", + " return response.json()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "inital_user_message = \"Which tent is the most waterproof?\"\n", + "message_history = [\n", + " {\"role\": \"system\", \"content\": chat_system_message},\n", + " {\"role\": \"user\", \"content\": inital_user_message},\n", + "]\n", + "\n", + "response = get_answer(message_history)\n", + "message_history.append(\n", + " {\"role\": \"assistant\", \"content\": response[\"choices\"][0][\"message\"][\"content\"]}\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for message in message_history:\n", + " if message[\"role\"] in [\"user\", \"assistant\"]:\n", + " print(f\"{message['role'].title()}: {message['content']}\\n\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "follow_up_user_message = \"Tell me more about the Alpine Explorer Tent?\"\n", + "message_history.append({\"role\": \"user\", \"content\": follow_up_user_message})\n", + "\n", + "response = get_answer(message_history)\n", + "message_history.append(\n", + " {\"role\": \"assistant\", \"content\": response[\"choices\"][0][\"message\"][\"content\"]}\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for message in message_history:\n", + " if message[\"role\"] in [\"user\", \"assistant\"]:\n", + " print(f\"{message['role'].title()}: {message['content']}\\n\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.14" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/src/search/utilities.py b/search/custom_search_client.py similarity index 95% rename from src/search/utilities.py rename to search/custom_search_client.py index 4c32055..9055d42 100644 --- a/src/search/utilities.py +++ b/search/custom_search_client.py @@ -1,9 +1,9 @@ """ -This module contains the AISearchClient class which is used for +This module contains the SearchClient class which is used for interacting with the AI Search service. Classes: - AISearchClient: A client for interacting with the AI Search service. + CustomSearchClient: A client for interacting with the AI Search service. """ import json @@ -15,7 +15,7 @@ from jinja2 import Template -class SearchClient: +class CustomSearchClient: """A client for interacting with the AI Search service. This class provides methods for loading templates, checking if resources exist, @@ -30,11 +30,11 @@ class SearchClient: def __init__( self, search_endpoint: str, - api_version="2023-10-01-Preview", + search_api_version="2024-03-01-Preview", credential=DefaultAzureCredential(), ): self.search_endpoint: str = search_endpoint - self.api_version: str = api_version + self.api_version: str = search_api_version self.search_management_assets = { "indexers": {"name": None, "payload": None}, diff --git a/src/search/templates/product-info/datasource.json b/search/templates/product-info/datasource.json similarity index 100% rename from src/search/templates/product-info/datasource.json rename to search/templates/product-info/datasource.json diff --git a/src/search/templates/product-info/index.json b/search/templates/product-info/index.json similarity index 98% rename from src/search/templates/product-info/index.json rename to search/templates/product-info/index.json index 9da1ec0..e00cef5 100644 --- a/src/search/templates/product-info/index.json +++ b/search/templates/product-info/index.json @@ -116,7 +116,7 @@ "searchAnalyzer": null, "analyzer": null, "normalizer": null, - "dimensions": "{{ AZURE_AI_SEARCH_VECTOR_EMBEDDING_DIMENSION }}", + "dimensions": "1536", "vectorSearchProfile": "{{ AZURE_AI_SEARCH_INDEX_NAME }}-profile", "synonymMaps": [] } diff --git a/src/search/templates/product-info/indexer.json b/search/templates/product-info/indexer.json similarity index 100% rename from src/search/templates/product-info/indexer.json rename to search/templates/product-info/indexer.json diff --git a/src/search/templates/product-info/skillset.json b/search/templates/product-info/skillset.json similarity index 100% rename from src/search/templates/product-info/skillset.json rename to search/templates/product-info/skillset.json diff --git a/src/app/Dockerfile b/src/app/Dockerfile deleted file mode 100644 index 78cdcab..0000000 --- a/src/app/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -# app/Dockerfile - -FROM python:3.9-slim - -WORKDIR /app - -RUN apt-get update && apt-get install -y \ - build-essential \ - curl \ - software-properties-common \ - git \ - && rm -rf /var/lib/apt/lists/* - -COPY ../environment/requirements.txt /app/requirements.txt - -COPY ../orchestration/utilities.py /app/orchestration/utilities.py -COPY ./main.py /app/streamlit_app.py - -RUN pip3 install -r requirements.txt -RUN python3 -m nltk.downloader stopwords - -EXPOSE 8501 - -HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health - -ENTRYPOINT ["streamlit", "run", "streamlit_app.py", "--server.port=8501", "--server.address=0.0.0.0", "--client.toolbarMode='minimal'"]