├── .devcontainer └── devcontainer.json ├── .editorconfig ├── .github └── workflows │ ├── deploy.yml │ ├── docs.yml │ ├── packages.yml │ └── slides.yml ├── .gitignore ├── .prettierignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── azure.yaml ├── data ├── privacy-policy.pdf ├── support.pdf └── terms-of-service.pdf ├── docker-compose.yml ├── docs ├── _workshop-java-quarkus.md ├── assets │ ├── aca-environment.png │ ├── architecture.excalidraw │ ├── architecture.png │ ├── azd-deploy-output.png │ ├── azure-ai-search-indexes.png │ ├── azure-ai-search-logo.png │ ├── azure-ai-search-results.png │ ├── azure-ai-search.png │ ├── azure-architecture-generic-db.excalidraw │ ├── azure-architecture-generic-db.png │ ├── azure-architecture.excalidraw │ ├── azure-architecture.png │ ├── azure-compute-services.png │ ├── azure-container-apps.png │ ├── azure-portal-azd.png │ ├── azure-resource-manager.png │ ├── banner.jpg │ ├── chat-streaming.gif │ ├── chatbot-answer.png │ ├── class-diagram-model.png │ ├── class-diagram-rest.png │ ├── create-codespaces.png │ ├── deployed-app.png │ ├── follow-up-questions.png │ ├── fork-project.png │ ├── gh-actions.png │ ├── gh-workflow-details.png │ ├── github-clone.png │ ├── ingestion-cli.png │ ├── ingestion-deployement.png │ ├── portal-burger.png │ ├── qdrant-dashboard-collection.png │ ├── qdrant-dashboard.png │ ├── qdrant-logo.png │ ├── rag.png │ ├── vscode-dev-container-status.png │ └── vscode-reopen-in-container.png ├── sections │ ├── 00-welcome.md │ ├── 01-intro.md │ ├── 02-preparation.md │ ├── 02.1-additional-setup.md │ ├── 03-overview.md │ ├── 04-vector-db.md │ ├── 05-ingestion.md │ ├── 06-chat-api.md │ ├── 08-website.md │ ├── 09-azure.md │ ├── 10-deployment.md │ ├── 10.1-ci-cd.md │ ├── 12-conclusion.md │ └── _old │ │ ├── 09-azure-llm.md │ │ └── 11-improvements.md └── slides │ ├── README.md │ ├── images │ ├── agent.png │ ├── ai.jpg │ ├── antonio.png │ ├── book-langchain4j.png │ ├── chatgpt.png │ ├── chris-dive.jpg │ ├── chris.jpg │ ├── embedding.png │ ├── julien.jpg │ ├── llm-magazine.jpg │ ├── llm-training.png │ ├── microsoft-azure.png │ ├── ms-full-logo.svg │ ├── olivier.jpg │ ├── openai.png │ ├── rag.png │ ├── sandra.jpg │ ├── tokens.png │ ├── tokens2.png │ └── yohan.jpg │ ├── slides-java-quarkus-reactor.md │ ├── slides-java-quarkus.md │ └── template │ ├── Archivo-Variable.ttf │ ├── Inconsolata-Regular.ttf │ ├── Saira-Variable.ttf │ ├── animate.css │ ├── code.js │ ├── fa-brands-400.woff2 │ ├── fa-regular-400.woff2 │ ├── fa-solid-900.woff2 │ ├── fontawesome-all.css │ ├── fonts.css │ ├── index.html │ ├── remark.min.js │ ├── rough-notation.js │ └── style.scss ├── infra ├── abbreviations.json ├── azure │ └── deploy-azure-openai-models.sh ├── core │ ├── ai │ │ └── cognitiveservices.bicep │ ├── host │ │ ├── container-app.bicep │ │ ├── container-apps-environment.bicep │ │ ├── container-apps.bicep │ │ ├── container-registry.bicep │ │ └── staticwebapp.bicep │ ├── monitor │ │ ├── applicationinsights-dashboard.bicep │ │ ├── applicationinsights.bicep │ │ ├── loganalytics.bicep │ │ └── monitoring.bicep │ ├── search │ │ └── search-services.bicep │ └── security │ │ ├── managed-identity.bicep │ │ ├── registry-access.bicep │ │ └── role.bicep ├── main.bicep └── main.parameters.json ├── package-lock.json ├── package.json ├── pom.xml ├── scripts ├── ingest-data.ps1 ├── ingest-data.sh ├── repo │ ├── build-docs.sh │ └── create-packages.sh └── setup-template.sh ├── src ├── backend │ ├── .dockerignore │ ├── .gitignore │ ├── .mvn │ │ └── wrapper │ │ │ ├── .gitignore │ │ │ ├── MavenWrapperDownloader.java │ │ │ └── maven-wrapper.properties │ ├── Dockerfile │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ └── src │ │ └── main │ │ ├── docker │ │ ├── Dockerfile.jvm │ │ ├── Dockerfile.legacy-jar │ │ ├── Dockerfile.native │ │ └── Dockerfile.native-micro │ │ ├── java │ │ └── ai │ │ │ └── azure │ │ │ └── openai │ │ │ └── rag │ │ │ └── workshop │ │ │ └── backend │ │ │ ├── configuration │ │ │ ├── ChatLanguageModelAzureOpenAiProducer.java │ │ │ ├── ChatLanguageModelOllamaProducer.java │ │ │ ├── EmbeddingModelProducer.java │ │ │ └── EmbeddingStoreProducer.java │ │ │ └── rest │ │ │ ├── ChatMessage.java │ │ │ ├── ChatRequest.java │ │ │ ├── ChatResource.java │ │ │ └── ChatResponse.java │ │ ├── plantuml │ │ ├── class-diagram-model.puml │ │ └── class-diagram-rest.puml │ │ ├── resources │ │ ├── META-INF │ │ │ └── resources │ │ │ │ ├── assets │ │ │ │ ├── index-4LLPZTDq.js │ │ │ │ ├── index-4LLPZTDq.js.map │ │ │ │ ├── vendor-9AMKf4B_.js │ │ │ │ └── vendor-9AMKf4B_.js.map │ │ │ │ ├── favicon.ico │ │ │ │ └── index.html │ │ ├── application.properties │ │ └── default_banner.txt │ │ └── script │ │ └── curl-chat-endpoint.sh ├── frontend │ ├── .lintstagedrc │ ├── README.md │ ├── assets │ │ ├── lightbulb.svg │ │ ├── new-chat.svg │ │ ├── question.svg │ │ └── send.svg │ ├── index.html │ ├── package.json │ ├── public │ │ └── favicon.ico │ ├── src │ │ ├── api.ts │ │ ├── components │ │ │ ├── chat.ts │ │ │ └── debug.ts │ │ ├── index.ts │ │ ├── message-parser.ts │ │ ├── models.ts │ │ └── vite-env.d.ts │ ├── tsconfig.json │ └── vite.config.ts └── ingestion │ ├── .dockerignore │ ├── .gitignore │ ├── .mvn │ └── wrapper │ │ ├── .gitignore │ │ ├── MavenWrapperDownloader.java │ │ └── maven-wrapper.properties │ ├── Dockerfile │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ └── src │ ├── main │ ├── java │ │ └── ai │ │ │ └── azure │ │ │ └── openai │ │ │ └── rag │ │ │ └── workshop │ │ │ └── ingestion │ │ │ ├── configuration │ │ │ ├── EmbeddingModelProducer.java │ │ │ └── EmbeddingStoreProducer.java │ │ │ └── rest │ │ │ └── DocumentIngestor.java │ └── resources │ │ ├── application.properties │ │ ├── default_banner.txt │ │ └── tinylog.properties │ └── test │ └── java │ └── ai │ └── azure │ └── openai │ └── rag │ └── workshop │ └── ingestion │ └── .gitkeep └── trainer ├── Dockerfile ├── README.md ├── azure.yaml ├── infra ├── abbreviations.json ├── core │ ├── ai │ │ └── cognitiveservices.bicep │ ├── host │ │ ├── container-app.bicep │ │ ├── container-apps-environment.bicep │ │ ├── container-apps.bicep │ │ └── container-registry.bicep │ ├── monitor │ │ └── loganalytics.bicep │ └── security │ │ ├── managed-identity.bicep │ │ ├── registry-access.bicep │ │ └── role.bicep ├── main.bicep └── main.parameters.json ├── package-lock.json ├── package.json ├── src ├── app.ts ├── plugins │ ├── config.ts │ ├── proxy.ts │ └── sensible.ts └── routes │ └── root.ts ├── test.http └── tsconfig.json /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node 3 | { 4 | "name": "OpenAI Workshop", 5 | 6 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 7 | "image": "mcr.microsoft.com/devcontainers/javascript-node:20-bullseye", 8 | 9 | // Features to add to the dev container. More info: https://containers.dev/features. 10 | "features": { 11 | "ghcr.io/devcontainers/features/node:1": { 12 | "version": "20" 13 | }, 14 | "ghcr.io/devcontainers/features/java:1": { 15 | "version": "21", 16 | "installMaven": "true", 17 | "installGradle": "false" 18 | }, 19 | "ghcr.io/devcontainers/features/docker-in-docker:2": { 20 | "version": 20, 21 | "moby": "false", 22 | "dockerDashComposeVersion": "v2" 23 | }, 24 | "ghcr.io/devcontainers/features/azure-cli:1": { 25 | "version": "latest", 26 | "installBicep": true 27 | }, 28 | "ghcr.io/devcontainers/features/github-cli:1": {}, 29 | // "ghcr.io/devcontainers/features/powershell:1": {}, 30 | "ghcr.io/azure/azure-dev/azd:latest": {}, 31 | "ghcr.io/prulloac/devcontainer-features/ollama:1": { 32 | "pull": "mistral" 33 | } 34 | }, 35 | 36 | // Configure tool-specific properties. 37 | "customizations": { 38 | "vscode": { 39 | "extensions": [ 40 | "ms-azuretools.azure-dev", 41 | "ms-azuretools.vscode-bicep", 42 | "ms-azuretools.vscode-docker", 43 | "vscjava.vscode-java-pack", 44 | "esbenp.prettier-vscode", 45 | "humao.rest-client", 46 | "runem.lit-plugin" 47 | ] 48 | } 49 | }, 50 | 51 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 52 | "forwardPorts": [3000, 3001, 8000, 6333], 53 | 54 | // Use 'postCreateCommand' to run commands after the container is created. 55 | "postCreateCommand": "npm install -g @moaw/cli fuzz-run", 56 | 57 | // Set minimal host requirements for the container. 58 | "hostRequirements": { 59 | "memory": "8gb" 60 | } 61 | 62 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 63 | // "remoteUser": "root" 64 | } 65 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_style = space 8 | indent_size = 2 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | max_line_length = off 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to Azure 2 | on: 3 | push: 4 | # Run only when commits are pushed to main branch 5 | branches: 6 | - main 7 | 8 | # Set up permissions for deploying with secretless Azure federated credentials 9 | # https://learn.microsoft.com/en-us/azure/developer/github/connect-from-azure?tabs=azure-portal%2Clinux#set-up-azure-login-with-openid-connect-authentication 10 | permissions: 11 | id-token: write 12 | contents: read 13 | 14 | jobs: 15 | deploy: 16 | runs-on: ubuntu-latest 17 | env: 18 | AZURE_CLIENT_ID: ${{ vars.AZURE_CLIENT_ID }} 19 | AZURE_TENANT_ID: ${{ vars.AZURE_TENANT_ID }} 20 | AZURE_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID }} 21 | AZURE_OPENAI_URL: ${{ vars.AZURE_OPENAI_URL }} 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | 26 | - name: Install azd 27 | uses: Azure/setup-azd@v1.0.0 28 | 29 | - name: Log in with Azure (Federated Credentials) 30 | if: ${{ env.AZURE_CLIENT_ID != '' }} 31 | run: | 32 | azd auth login ` 33 | --client-id "$Env:AZURE_CLIENT_ID" ` 34 | --federated-credential-provider "github" ` 35 | --tenant-id "$Env:AZURE_TENANT_ID" 36 | shell: pwsh 37 | 38 | - name: Build and deploy application 39 | if: ${{ env.AZURE_CLIENT_ID != '' }} 40 | run: | 41 | azd up --no-prompt 42 | env: 43 | AZURE_ENV_NAME: ${{ vars.AZURE_ENV_NAME }} 44 | AZURE_LOCATION: ${{ vars.AZURE_LOCATION }} 45 | AZURE_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID }} 46 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Build workshop docs 2 | on: 3 | push: 4 | branches: [main] 5 | 6 | jobs: 7 | docs: 8 | name: Build docs 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | - name: Setup Node.js 14 | uses: actions/setup-node@v4 15 | with: 16 | node-version: 20 17 | - name: Install tools 18 | run: npm i -g @moaw/cli 19 | - name: Build and update docs 20 | run: | 21 | git config --global user.name "sinedied" 22 | git config --global user.email "noda@free.fr" 23 | ./scripts/repo/build-docs.sh 24 | env: 25 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 26 | -------------------------------------------------------------------------------- /.github/workflows/packages.yml: -------------------------------------------------------------------------------- 1 | name: Update workshop packages 2 | on: 3 | push: 4 | branches: [main] 5 | 6 | jobs: 7 | update_packages: 8 | name: Update 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | - name: Create packages 16 | run: ./scripts/repo/create-packages.sh 17 | - name: Update release 18 | uses: ncipollo/release-action@v1.12.0 19 | with: 20 | name: Workshop packages 21 | tag: latest 22 | artifacts: 'dist/*.tar.gz' 23 | allowUpdates: true 24 | removeArtifacts: true 25 | -------------------------------------------------------------------------------- /.github/workflows/slides.yml: -------------------------------------------------------------------------------- 1 | name: Deploy slides 2 | on: 3 | push: 4 | branches: [main] 5 | workflow_dispatch: 6 | 7 | concurrency: 8 | group: 'pages' 9 | cancel-in-progress: true 10 | 11 | jobs: 12 | deploy_slides: 13 | name: Build and Deploy 14 | permissions: 15 | pages: write 16 | id-token: write 17 | environment: 18 | name: github-pages 19 | url: ${{ steps.deployment.outputs.page_url }} 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v4 24 | - name: Setup Node.js 25 | uses: actions/setup-node@v4 26 | with: 27 | node-version: 20 28 | - name: Install tools 29 | run: npm i -g backslide 30 | - name: Build slides 31 | run: | 32 | cd docs/slides 33 | bs export --web slides-java-quarkus.md --output dist/java-quarkus 34 | - name: Setup GitHub Pages 35 | uses: actions/configure-pages@v5 36 | - name: Upload artifact 37 | uses: actions/upload-pages-artifact@v3 38 | with: 39 | path: docs/slides/dist 40 | - name: Deploy to GitHub Pages 41 | id: deployment 42 | uses: actions/deploy-pages@v4 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled output 2 | node_modules/ 3 | dist/ 4 | .tmp/ 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | pnpm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # IntelliJ 16 | .idea 17 | *.iml 18 | 19 | # Deployment 20 | *.env* 21 | .azure 22 | 23 | # DB Storage 24 | .qdrant/ 25 | 26 | # Misc 27 | .DS_Store 28 | .current 29 | *dummy* 30 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | docs -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | - Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | - Full paths of source file(s) related to the manifestation of the issue 23 | - The location of the affected source code (tag/branch/commit or direct URL) 24 | - Any special configuration required to reproduce the issue 25 | - Step-by-step instructions to reproduce the issue 26 | - Proof-of-concept or exploit code (if possible) 27 | - Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## How to file issues and get help 4 | 5 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 6 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 7 | feature request as a new Issue. 8 | 9 | For help and questions about using this project, please use GitHub Issues and tag them with the 10 | **question** label. 11 | 12 | ## Microsoft Support Policy 13 | 14 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above. 15 | -------------------------------------------------------------------------------- /azure.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json 2 | 3 | name: azure-openai-rag-workshop-java 4 | metadata: 5 | template: azure-openai-rag-workshop-java@1.0.0 6 | 7 | services: 8 | frontend: 9 | project: ./src/frontend 10 | dist: dist 11 | language: ts 12 | host: staticwebapp 13 | hooks: 14 | predeploy: 15 | windows: 16 | shell: pwsh 17 | run: Export-ModuleMember -Variable BACKEND_API_URI && npm run build 18 | interactive: true 19 | continueOnError: false 20 | posix: 21 | shell: sh 22 | run: export BACKEND_API_URI && npm run build 23 | interactive: true 24 | continueOnError: false 25 | 26 | backend: 27 | project: ./src/backend 28 | language: java 29 | host: containerapp 30 | 31 | ingestion: 32 | project: ./src/ingestion 33 | language: java 34 | host: containerapp 35 | 36 | hooks: 37 | postup: 38 | windows: 39 | shell: pwsh 40 | run: ./scripts/ingest-data.ps1 41 | interactive: true 42 | continueOnError: false 43 | posix: 44 | shell: sh 45 | run: ./scripts/ingest-data.sh 46 | interactive: true 47 | continueOnError: false 48 | -------------------------------------------------------------------------------- /data/privacy-policy.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/data/privacy-policy.pdf -------------------------------------------------------------------------------- /data/support.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/data/support.pdf -------------------------------------------------------------------------------- /data/terms-of-service.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/data/terms-of-service.pdf -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | backend: 3 | build: 4 | dockerfile: ./src/backend/Dockerfile 5 | environment: 6 | - AZURE_OPENAI_URL=${AZURE_OPENAI_URL} 7 | - QDRANT_URL=http://qdrant:6334 8 | - LOCAL=true 9 | ports: 10 | - 3000:3000 11 | 12 | ingestion: 13 | build: 14 | dockerfile: ./src/ingestion/Dockerfile 15 | environment: 16 | - AZURE_OPENAI_URL=${AZURE_OPENAI_URL} 17 | - QDRANT_URL=http://qdrant:6334 18 | ports: 19 | - 3001:3001 20 | 21 | qdrant: 22 | image: docker.io/qdrant/qdrant:v1.12.4 23 | ports: 24 | - 6333:6333 25 | - 6334:6334 26 | volumes: 27 | - .qdrant:/qdrant/storage:z 28 | -------------------------------------------------------------------------------- /docs/_workshop-java-quarkus.md: -------------------------------------------------------------------------------- 1 | include::sections/00-welcome.md[] 2 | 3 | --- 4 | 5 | include::sections/01-intro.md[] 6 | 7 | --- 8 | 9 | include::sections/02-preparation.md[] 10 | 11 | --- 12 | 13 | include::sections/02.1-additional-setup.md[] 14 | 15 | --- 16 | 17 | include::sections/03-overview.md[] 18 | 19 | --- 20 | 21 | include::sections/04-vector-db.md[] 22 | 23 | --- 24 | 25 | include::sections/05-ingestion.md[] 26 | 27 | --- 28 | 29 | include::sections/06-chat-api.md[] 30 | 31 | --- 32 | 33 | include::sections/08-website.md[] 34 | 35 | --- 36 | 37 | include::sections/09-azure.md[] 38 | 39 | --- 40 | 41 | include::sections/10-deployment.md[] 42 | 43 | --- 44 | 45 | include::sections/12-conclusion.md[] 46 | -------------------------------------------------------------------------------- /docs/assets/aca-environment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/aca-environment.png -------------------------------------------------------------------------------- /docs/assets/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/architecture.png -------------------------------------------------------------------------------- /docs/assets/azd-deploy-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/azd-deploy-output.png -------------------------------------------------------------------------------- /docs/assets/azure-ai-search-indexes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/azure-ai-search-indexes.png -------------------------------------------------------------------------------- /docs/assets/azure-ai-search-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/azure-ai-search-logo.png -------------------------------------------------------------------------------- /docs/assets/azure-ai-search-results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/azure-ai-search-results.png -------------------------------------------------------------------------------- /docs/assets/azure-ai-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/azure-ai-search.png -------------------------------------------------------------------------------- /docs/assets/azure-architecture-generic-db.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/azure-architecture-generic-db.png -------------------------------------------------------------------------------- /docs/assets/azure-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/azure-architecture.png -------------------------------------------------------------------------------- /docs/assets/azure-compute-services.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/azure-compute-services.png -------------------------------------------------------------------------------- /docs/assets/azure-container-apps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/azure-container-apps.png -------------------------------------------------------------------------------- /docs/assets/azure-portal-azd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/azure-portal-azd.png -------------------------------------------------------------------------------- /docs/assets/azure-resource-manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/azure-resource-manager.png -------------------------------------------------------------------------------- /docs/assets/banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/banner.jpg -------------------------------------------------------------------------------- /docs/assets/chat-streaming.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/chat-streaming.gif -------------------------------------------------------------------------------- /docs/assets/chatbot-answer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/chatbot-answer.png -------------------------------------------------------------------------------- /docs/assets/class-diagram-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/class-diagram-model.png -------------------------------------------------------------------------------- /docs/assets/class-diagram-rest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/class-diagram-rest.png -------------------------------------------------------------------------------- /docs/assets/create-codespaces.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/create-codespaces.png -------------------------------------------------------------------------------- /docs/assets/deployed-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/deployed-app.png -------------------------------------------------------------------------------- /docs/assets/follow-up-questions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/follow-up-questions.png -------------------------------------------------------------------------------- /docs/assets/fork-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/fork-project.png -------------------------------------------------------------------------------- /docs/assets/gh-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/gh-actions.png -------------------------------------------------------------------------------- /docs/assets/gh-workflow-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/gh-workflow-details.png -------------------------------------------------------------------------------- /docs/assets/github-clone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/github-clone.png -------------------------------------------------------------------------------- /docs/assets/ingestion-cli.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/ingestion-cli.png -------------------------------------------------------------------------------- /docs/assets/ingestion-deployement.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/ingestion-deployement.png -------------------------------------------------------------------------------- /docs/assets/portal-burger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/portal-burger.png -------------------------------------------------------------------------------- /docs/assets/qdrant-dashboard-collection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/qdrant-dashboard-collection.png -------------------------------------------------------------------------------- /docs/assets/qdrant-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/qdrant-dashboard.png -------------------------------------------------------------------------------- /docs/assets/qdrant-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/qdrant-logo.png -------------------------------------------------------------------------------- /docs/assets/rag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/rag.png -------------------------------------------------------------------------------- /docs/assets/vscode-dev-container-status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/vscode-dev-container-status.png -------------------------------------------------------------------------------- /docs/assets/vscode-reopen-in-container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/assets/vscode-reopen-in-container.png -------------------------------------------------------------------------------- /docs/sections/00-welcome.md: -------------------------------------------------------------------------------- 1 | --- 2 | short_title: Create your own ChatGPT with RAG 3 | description: Discover how to create and populate a vector database, create a Web chat interface and an API to expose your agent to the Web interface. 4 | type: workshop 5 | authors: 6 | - Yohan Lasorsa 7 | - Julien Dubois 8 | - Christopher Maneu 9 | - Sandra Ahlgrimm 10 | - Antonio Goncalves 11 | contacts: 12 | - '@sinedied' 13 | - '@juliendubois' 14 | - '@cmaneu' 15 | - '@sKriemhild' 16 | - '@agoncal' 17 | banner_url: assets/banner.jpg 18 | duration_minutes: 120 19 | audience: students, devs 20 | level: intermediate 21 | tags: chatgpt, openai, langchain4j, retrieval-augmented-generation, azure, containers, docker, static web apps, java, quarkus, azure ai search, azure container apps, qdrant, vector database 22 | published: false 23 | wt_id: java-0000-cxa 24 | sections_title: 25 | - Welcome 26 | --- 27 | 28 | # Create your own ChatGPT with Retrieval-Augmented-Generation 29 | 30 | In this workshop, we'll explore the fundamentals of custom ChatGPT experiences based on a corpus of documents. We will create a vector database and fill-in with data from PDF documents, and then build a chat website and API to be able to ask questions about information contained in these documents. 31 | 32 | ## You'll learn how to... 33 | 34 | - Create a knowledge base using a vector database. 35 | - Ingest documents in a vector database. 36 | - Create a Web API with [Quarkus](https://quarkus.io/). 37 | - Use [Azure OpenAI](https://azure.microsoft.com/products/ai-services/openai-service) models and [LangChain4j](https://langchain4j.github.io/langchain4j/) to generate answers based on a prompt. 38 | - Query a vector database and augment a prompt to generate responses. 39 | - Connect your Web API to a ChatGPT-like website. 40 | - (optionally) Deploy your application to Azure. 41 | 42 | ## Prerequisites 43 | 44 |
45 | 46 | | | | 47 | |-------------------|----------------------------------------------------------------------| 48 | | GitHub account | [Get a free GitHub account](https://github.com/join) | 49 | | Azure account | [Get a free Azure account](https://azure.microsoft.com/free) | 50 | | Access to Azure OpenAI API | [Request access to Azure OpenAI](https://aka.ms/oaiapply) | 51 | | A Web browser | [Get Microsoft Edge](https://www.microsoft.com/edge) | 52 | | An HTTP client | [For example curl](https://curl.se/) | 53 | | Java knowledge | [Java tutorial on W3schools](https://www.w3schools.com/java/) | 54 | | Quarkus knowledge | [Quarkus Getting Started](https://quarkus.io/guides/getting-started) | 55 | 56 |
57 | 58 |
59 | 60 | | | | 61 | |-------------------|----------------------------------------------------------------------| 62 | | GitHub account | [Get a free GitHub account](https://github.com/join) | 63 | | A Web browser | [Get Microsoft Edge](https://www.microsoft.com/edge) | 64 | | An HTTP client | [For example curl](https://curl.se/) | 65 | | Java knowledge | [Java tutorial on W3schools](https://www.w3schools.com/java/) | 66 | | Quarkus knowledge | [Quarkus Getting Started](https://quarkus.io/guides/getting-started) | 67 | 68 |
69 | 70 | We'll use [GitHub Codespaces](https://github.com/features/codespaces) to have an instant dev environment already prepared for this workshop. 71 | 72 | If you prefer to work locally, we'll also provide instructions to setup a local dev environment using either VS Code with a [dev container](https://aka.ms/vscode/ext/devcontainer) or a manual install of the needed tools with your favourite IDE (Intellij IDEA, VS Code, etc.). 73 | 74 |
75 | 76 | > Your Azure account must have `Microsoft.Authorization/roleAssignments/write` permissions, such as [Role Based Access Control Administrator](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#role-based-access-control-administrator-preview), [User Access Administrator](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#user-access-administrator), or [Owner](https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#owner). Your account also needs `Microsoft.Resources/deployments/write` permissions at a subscription level to allow deployment of Azure resources. 77 | > 78 | > If you have your own personal Azure subscription, you should be good to go. If you're using an Azure subscription provided by your company, you may need to contact your IT department to ensure you have the necessary permissions. 79 | 80 |
-------------------------------------------------------------------------------- /docs/sections/01-intro.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | Organizations of all sizes have amassed a plethora of documents over time. While generative AI, such as ChatGPT, can provide answers about general knowledge and historical events with reasonable accuracy, they can also be tailored to answer questions based on a company's internal documents. 4 | 5 |
6 | 7 | > **Accuracy in Generative AI** 8 | > Large Language Models (LLMs), like the ones powering ChatGPT, aren't designed for high-precision answers. They may produce "hallucinations", offering responses that seem authoritative but are actually incorrect. It's crucial to **inform users that the responses are AI-generated**. In this workshop, we'll explore how to generate answers that link to their information sources — this is what we call *grounding* — enabling users to verify the accuracy of the AI's responses. 9 | 10 |
11 | 12 | In this workshop, we'll guide you through building a chat application that generates responses based on your documents and deploy it to Azure. We'll touch on many different topics, but we'll take it one step at a time. 13 | 14 | ### Application architecture 15 | 16 | Below is the architecture of the application we're going to build: 17 | 18 | ![Application architecture](./assets/architecture.png) 19 | 20 | Our application consists of five main components: 21 | 22 | 1. **Vector Database**: The vector database stores mathematical representations of our documents, known as _embeddings_. These are used by the Chat API to find documents relevant to a user's question. 23 | 24 | 2. **Ingestion Service**: The ingestion service feeds data from your documents into this vector database. 25 | 26 | 3. **Chat API**: This API enables a client application to send chat messages and receive answers generated from the documents in the vector database. 27 | 28 | 4. **Chat Website**: This site offers a ChatGPT-like interface for users to ask questions and receive answers about the ingested documents. 29 | 30 | 5. **OpenAI Model Deployment**: We will use the `gpt-4o-mini` model, hosted on Azure, for this workshop. The code can also be adapted to work with OpenAI's APIs or Ollame with minimal changes. 31 | 32 | ### What is Retrievial-Augmented Generation? 33 | 34 | Retrieval-Augmented generation (RAG) is a powerful technique that combines the strengths of two different approaches in natural language processing: retrieval-based methods and generative models. This hybrid approach allows for the generation of responses that are both contextually relevant and rich in content. Let's break down how this works in the context of creating a custom ChatGPT-like model. 35 | 36 | At its core, RAG involves two main components: 37 | 38 | - **Retriever**: Think "_like a search engine_", finding relevant information from a database. The retriever usually searches in a vector database. It could also - for some use cases - search on application dabases, APIs and other sources of information. In this workshop, we will implement this logic in the _Chat API_. 39 | 40 | - **Generator**: Acts like a writer, taking the prompt and information retrieved to craft a response. In this workshop, OpenAI `gpt-4o-mini` will be our generator. 41 | 42 | ![](./assets/rag.png) 43 | 44 | The RAG process involves the following steps: 45 | 46 | 1. **Embedding Computation**: Converts a user's prompt into an embedding for similarity comparisons. 47 | 48 | 2. **Document Retrieval**: Finds the most relevant documents using the prompt's embedding. This is where systems like Azure AI Search come into play, allowing for efficient vector similarity searches. 49 | 50 | 3. **Contextual Augmentation**: Enhances the user prompt with information from retrieved documents. This step is crucial as it provides additional context and information to the generator. 51 | 52 | 4. **Response Generation**: Use the model to generate a response using the augmented prompt. The model uses the additional context provided by the retrieved documents to produce a more informed and accurate output. 53 | 54 | -------------------------------------------------------------------------------- /docs/sections/02-preparation.md: -------------------------------------------------------------------------------- 1 | ## Preparation 2 | 3 | Before diving into development, let's set up your project environment. This includes: 4 | 5 | - Creating a new project on GitHub based on a template 6 | - Using a prepared dev container environment on either [GitHub Codespaces](https://github.com/features/codespaces) or [VS Code with Dev Containers extension](https://aka.ms/vscode/ext/devcontainer) (or a manual install of the needed tools) 7 | 8 | ### Creating your project 9 | 10 | 1. Open [this GitHub repository](https://github.com/Azure-Samples/azure-openai-rag-workshop-java) 11 | 2. Click the **Fork** button and click on **Create fork** to create a copy of the project in your own GitHub account. 12 | 13 | ![Screenshot of GitHub showing the Fork button](./assets/fork-project.png) 14 | 15 | Once the fork is created, select the **Code** button, then the **Codespaces** tab and click on **Create Codespaces on main**. 16 | 17 | ![Screenshot of GitHub showing the Codespaces creation](./assets/create-codespaces.png) 18 | 19 | This will initialize a development container with all necessary tools pre-installed. Once it's ready, you have everything you need to start coding. Wait a few minutes after the UI is loaded to ensure everything is ready, as some tasks will be triggered after everything is fully loaded, such as the installation of the npm packages with `npm install`. 20 | 21 |
22 | 23 | > GitHub Codespaces provides up to 60 hours of free usage monthly for all GitHub users. You can check out [GitHub's pricing details](https://github.com/features/codespaces) for more information. 24 | 25 |
26 | 27 | #### [optional] Local Development with the dev container 28 | 29 | If you prefer working on your local machine, you can also run the dev container on your machine. If you're fine with using Codespaces, you can skip directly to the next section. 30 | 31 | 32 | 1. Ensure you have [Docker](https://www.docker.com/products/docker-desktop), [VS Code](https://code.visualstudio.com/), and the [Dev Containers extension](https://aka.ms/vscode/ext/devcontainer) installed. 33 | 34 |
35 | 36 | > You can learn more about Dev Containers in [this video series](https://learn.microsoft.com/shows/beginners-series-to-dev-containers/). You can also [check the website](https://containers.dev) and [the specification](https://github.com/devcontainers/spec). 37 | 38 |
39 | 40 | 2. In GitHub website, select the **Code** button, then the **Local** tab and copy your repository url. 41 | 42 | ![Screenshot of GitHub showing the repository URL](./assets/github-clone.png) 43 | 3. Clone your forked repository and then open the folder in VS Code: 44 | 45 | ```bash 46 | git clone 47 | ``` 48 | 49 | 3. In VS Code, use `Ctrl+Shift+P` (or `Command+Shift+P` on macOS) to open the **command palette** and type **Reopen in Container**. 50 | 51 | ![Reopen in container command in VS Code](./assets/vscode-reopen-in-container.png) 52 | 53 | *Alt text: Screenshot of VS Code showing the "Reopen in Container" command.* 54 | 55 | The first time it will take some time to download and setup the container image, meanwhile you can go ahead and read the next sections. 56 | 57 | Once the container is ready, you will see "Dev Container: OpenAI Workshop" in the bottom left corner of VSCode: 58 | 59 | ![Dev Container status in VS Code](./assets/vscode-dev-container-status.png) 60 | 61 | 62 | #### [optional] Working locally without the dev container 63 | 64 | If you want to work locally without using a dev container, you need to clone the project and install the following tools: 65 | 66 | | | | 67 | |---------------|--------------------------------| 68 | | Git | [Get Git](https://git-scm.com) | 69 | | Docker v20+ | [Get Docker](https://docs.docker.com/get-docker) | 70 | | Java v17+ | [Get Java](https://www.java.com/download/) | 71 | | Node.js v20+ | [Get Node.js](https://nodejs.org) | 72 | | GitHub CLI | [Get GitHub CLI](https://cli.github.com/manual/installation) | 73 | | Azure Developer CLI | [Get Azure Developer CLI](https://learn.microsoft.com/azure/developer/azure-developer-cli/install-azd) | 74 | | Bash v3+ | [Get bash](https://www.gnu.org/software/bash/) (Windows users can use **Git bash** that comes with Git) | 75 | | A code editor | [Get VS Code](https://aka.ms/get-vscode) | 76 | 77 | You can test your setup by opening a terminal and typing: 78 | 79 | ```sh 80 | git --version 81 | docker --version 82 | java --version 83 | node --version 84 | gh --version 85 | azd version 86 | bash --version 87 | ``` 88 | -------------------------------------------------------------------------------- /docs/sections/02.1-additional-setup.md: -------------------------------------------------------------------------------- 1 | ## Complete the setup 2 | 3 | To complete the template setup, please run the following command in a terminal, at the root of the project: 4 | 5 | ```bash 6 | ./scripts/setup-template.sh quarkus 7 | ``` 8 | 9 | ### Preparing the environment 10 | 11 |
12 | 13 | We have deployed an Open AI proxy service for you, so you can use it to work on this workshop locally before deploying anything to Azure. 14 | 15 | Create a `.env` file at the root of the project, and add the following content: 16 | 17 | ```bash 18 | AZURE_OPENAI_URL=$$proxy$$ 19 | QDRANT_URL=http://localhost:6334 20 | ``` 21 | 22 |
23 | 24 |
25 | 26 | Now you either have to deploy an Azure Open AI service to use the OpenAI API, or you can use a local LLM with Ollama. 27 | 28 | #### Using Azure Open AI 29 | 30 | You first need to deploy an Azure Open AI service to use the OpenAI API. 31 | 32 | Before moving to the next section, go to the **Azure setup** section (either on the left or using the "hamburger" menu depending of your device) to deploy the necessary resources and create your `.env` file needed. 33 | 34 | After you completed the Azure setup, you can come back here to continue the workshop. 35 | 36 |
37 | 38 | #### (Optional) Using Ollama 39 | 40 | If you have a machine with enough resources, you can run this workshop entirely locally without using any cloud resources. To do that, you first have to install [Ollama](https://ollama.com) and then run the following commands to download the models on your machine: 41 | 42 | ```bash 43 | ollama pull mistral 44 | ``` 45 | 46 |
47 | 48 | > The `mistral` model with download a few gigabytes of data, so it can take some time depending on your internet connection. Using Codespaces will provide you a fast connection. 49 | 50 |
51 | 52 |
53 | 54 | > Ollama won't work in GitHub Codespaces currently, so it will only work if you are working on the workshop locally. 55 | 56 |
57 | 58 |
59 | 60 |
61 | 62 | Finally, you can start the Ollama server with the following command: 63 | 64 | ```bash 65 | ollama run mistral 66 | ``` 67 | -------------------------------------------------------------------------------- /docs/sections/03-overview.md: -------------------------------------------------------------------------------- 1 | ## Overview of the project 2 | 3 | The project template you've forked is a monorepo, which means it's a single repository that houses multiple projects. Here's how it's organized, focusing on the key files and directories: 4 | 5 | ```sh 6 | .devcontainer/ # Configuration for the development container 7 | data/ # Sample PDFs to serve as custom data 8 | infra/ # Templates and scripts for Docker and Azure infrastructure 9 | scripts/ # Utility scripts for document ingestion 10 | src/ # Source code for the application's services 11 | ├── backend/ # The Chat API developed with Quarkus 12 | ├── frontend/ # The Chat website 13 | ├── ingestion/ # The service for document ingestion developed with Quarkus 14 | pom.xml # Main Maven parent POM 15 | .env # File that you created for environment variables 16 | ``` 17 | 18 | We're using Java and Quarkus for our APIs and Node.js for our website, and have set up a Maven parent POM to manage dependencies across all projects from a single place. Running `mvn install` at the root installs dependencies for all backend projects ( `npm install` for the frontend), simplifying monorepo management. 19 | 20 | Otherwise, you can use your regular `mvn` commands in any project folder and it will work as usual. 21 | 22 | ### About the services 23 | 24 | We generated the base code of our differents services with the respective CLI or generator of the frameworks we'll be using, and we've pre-written several service components so you can jump straight into the most interesting parts. 25 | 26 | ### The Chat API specification 27 | 28 | Creating a chat-like experience requires two main components: a user interface and a service API. The [ChatBootAI OpenAPI specification](https://editor.swagger.io/?url=https://raw.githubusercontent.com/ChatBootAI/chatbootai-openapi/main/openapi/openapi-chatbootai.yml) standardizes their interactions. This standardization allows for the development of different client applications (like mobile apps) that can interact seamlessly with chat services written in various programming languages. 29 | 30 | #### The Chat request 31 | 32 | A chat request is sent in JSON format, and must contain at least the user's message. Other optional parameters include a flag indicating if the response should be streamed, context-specific options that can tailor the chat service's behavior and a session state object that can be used to maintain state between requests. 33 | 34 | ```json 35 | { 36 | "messages": [ 37 | { 38 | "content": "Can I do some Scuba diving?", 39 | "role": "user" 40 | } 41 | ], 42 | "stream": false, 43 | "context": { ... }, 44 | "session_state": null 45 | } 46 | ``` 47 | 48 | #### The chat response 49 | 50 | The chat service responds with a JSON object representing the generated response. The answer is located under the message's `content` property. 51 | 52 | ```json 53 | { 54 | "choices": [ 55 | { 56 | "index": 0, 57 | "message": { 58 | "content": "There is no information available about Scuba diving in the provided sources.", 59 | "role": "assistant", 60 | "context": { ... } 61 | } 62 | } 63 | ], 64 | } 65 | ``` 66 | 67 | You can learn more about the [ChatBootAI OpenAPI specification here](https://editor.swagger.io/?url=https://raw.githubusercontent.com/ChatBootAI/chatbootai-openapi/main/openapi/openapi-chatbootai.yml) and on [the GitHub repo](https://github.com/ChatBootAI/chatbootai-openapi). 68 | 69 |
70 | 71 | > If streaming is enabled, the response will be a stream of JSON objects, each representing a chunk of the response. This format allows for a dynamic and real-time messaging experience, as each chunk can be sent and rendered as soon as it's ready. In that case, the response format follows the [Newline Delimited JSON (NDJSON)](https://github.com/ndjson/ndjson-spec) specification, which is a convenient way of sending structured data that may be processed one record at a time. 72 | 73 |
74 | -------------------------------------------------------------------------------- /docs/sections/04-vector-db.md: -------------------------------------------------------------------------------- 1 | ## The vector database 2 | 3 | We'll start by creating a vector database. Vectors are arrays of numbers that represent the features or characteristics of the data. For example, an image can be converted into a vector of pixels, or a word can be converted into a vector of semantic meaning. A vector database can perform fast and accurate searches based on the similarity or distance between the vectors, rather than exact matches. This enables applications such as image recognition, natural language processing, recommendation systems, and more. 4 | 5 | ### Ingestion and retrieval 6 | 7 | In our use-case, text will be extracted out of PDF files, and this text will be *tokenized*. Tokenization is the process of splitting our text into different tokens, which will be short portions of text. Those tokens will then be converted into a *vector* and added to the database. The vector database is then able to search for similar vectors based on the distance between them. 8 | 9 | That's how our system will be able to find the most relevant data, coming from the original PDF files. 10 | 11 | This will be used in the first component (the *Retriever*) of the Retrieval Augmented Generation (RAG) pattern that we will use to build our custom ChatGPT. 12 | 13 | ### About vector databases 14 | 15 | There are many available vector databases, and a good list can be found in the supported Vector stores list from the LangChain4j project: [https://github.com/langchain4j/langchain4j](https://github.com/langchain4j/langchain4j). 16 | 17 | Some of the popular ones are: 18 | 19 | - [MemoryVectorStore](https://js.langchain.com/docs/integrations/vectorstores/memory) which is an in-memory vector store, which is great for testing and development, but not for production. 20 | - [Qdrant](https://qdrant.tech/) 21 | - [pgvector](https://github.com/pgvector/pgvector) 22 | - [Redis](https://redis.io) 23 | 24 | On Azure, you can run the vector databases listed above, or use specific Azure services that also provide this functionality, such as: 25 | 26 | - [Azure AI Search](https://azure.microsoft.com/services/search/) 27 | - [Azure Cosmos DB for MongoDB vCore](https://learn.microsoft.com/azure/cosmos-db/mongodb/vcore/) 28 | 29 | ### Introducing Qdrant 30 | 31 | ![Qdrant Logo](./assets/qdrant-logo.png) 32 | 33 | [Qdrant](https://qdrant.tech/) is an open-source vector database that is easy to use and deploy. The core of Qdrant is a vector similarity search engine that provides a production-ready service with a convenient API to store, search, and manage vectors with an additional payload. You can think of the payloads as additional pieces of information that can help you hone in on your search and also receive useful information that you can give to your users. 34 | 35 | For this workshop, we'll use Qdrant as our vector database as it works well with JavaScript and can run locally in Docker. For the RAG use-case, most vector databases will work in a similar way. 36 | 37 | ### Running Qdrant locally 38 | 39 | To start Qdrant locally we have setup a Docker Compose file. You can use the following command from the root of the project: 40 | 41 | ```bash 42 | docker compose up qdrant 43 | ``` 44 | 45 | This will pull the Docker image, start Qdrant on port `6333` and mount a volume to store the data in the `.qdrant` folder. You should see logs that look like: 46 | 47 | ```text 48 | qdrant-1 | INFO qdrant::actix: Qdrant HTTP listening on 6333 49 | qdrant-1 | INFO actix_server::builder: Starting 9 workers 50 | qdrant-1 | INFO qdrant::tonic: Qdrant gRPC listening on 51 | qdrant-1 | INFO actix_server::server: Actix runtime found; starting in Actix runtime 52 | ``` 53 | 54 | You can test that Qdrant is running by opening the following URL in your browser: [http://localhost:6333/dashboard](http://localhost:6333/dashboard). 55 | 56 |
57 | 58 | > In Codespaces, once the servce is running, you click on the **Open in browser** button when prompted and add `/dashboard` at the end of the URL. 59 | > You can also select the **Ports** tab in the bottom panel, right click on the URL in the **Forwarded Address** column next to the `6333` port, and select **Open in browser**. 60 | 61 |
62 | 63 | Once you tested that Qdrant is running correctly, you can stop it by pressing `CTRL+C` in your terminal or executing the following command from the root directory of the project: 64 | 65 | ```bash 66 | docker compose down qdrant 67 | ``` 68 | -------------------------------------------------------------------------------- /docs/sections/08-website.md: -------------------------------------------------------------------------------- 1 | ## Chat website 2 | 3 | Now that we have our Chat API, it's time to build the website that will use. 4 | Notice that you don't have to develop the frontend part, it's already done for you. But you need to build it and, of course, if you want to understand how it works, you can follow the instructions below. 5 | 6 | ### Introducing Vite and Lit 7 | 8 | We use [Vite](https://vitejs.dev/) as a frontend build tool, and [Lit](https://lit.dev/) as a Web components library. 9 | 10 | This frontend is built as a Single Page Application (SPA), which is similar to the well-known ChatGPT website. The main difference is that it will get its data from the Chat API that we described in the previous section. 11 | To get the frontend, run this command in the terminal **at the root of the project** to get the completed code directly, so you don't have to code it yourself: 12 | 13 | ```bash 14 | curl -fsSL https://github.com/Azure-Samples/azure-openai-rag-workshop-java/releases/download/latest/frontend.tar.gz | tar -xvz 15 | ``` 16 | 17 | As you can see, the project is available in the `src/frontend` folder. From the project directory, you can run this command to start the development server: 18 | 19 | ```bash 20 | cd src/frontend 21 | npm install 22 | npm run dev 23 | ``` 24 | 25 | This will start the application in development mode. Open [http://localhost:8000](http://localhost:8000) to view it in the browser. 26 | 27 |
28 | 29 | > In Codespaces, once the servce is running, you can click on the **Open in browser** button when prompted. 30 | > You can also select the **Ports** tab in the bottom panel, right click on the URL in the **Forwarded Address** column next to the `8000` port, and select **Open in browser**. 31 | 32 |
33 | 34 |
35 | 36 | > In development mode, the Web page will automatically reload when you make any change to the code. We recommend you to keep this command running in the background, and then have two windows side-by-side: one with your IDE where you will edit the code, and one with your Web browser where you can see the final result. 37 | 38 |
39 | 40 | ### Testing the completed website 41 | 42 | Now that you've downloaded the code and built the frontend, let's test the entire application. For that, you need to make sure that your Qdrant database and chat backend are running, as well as the chat website: 43 | 44 | Run these commands from the project root if you need to restart the backend services: 45 | 46 | ```bash 47 | docker compose up qdrant 48 | 49 | cd src/backend 50 | ./mvnw quarkus:dev 51 | 52 | cd src/frontend 53 | npm run dev 54 | ``` 55 | 56 | Now go back to your browser at http://localhost:8000, and send a question to the chatbot. You should see the answer appear in the chat window. 57 | 58 | ![Screenshot of the chatbot answer](./assets/chatbot-answer.png) 59 | -------------------------------------------------------------------------------- /docs/sections/10-deployment.md: -------------------------------------------------------------------------------- 1 | ## Deploying to Azure 2 | 3 | Our application is now ready to be deployed to Azure! But first of all, make sure you don't deploy the application with the llama3 model. For that, make sure to remove or comment the alternative from the `src/backend/src/main/resources/application.properties` file. 4 | 5 | ```properties 6 | quarkus.http.port=3000 7 | quarkus.log.level=INFO 8 | quarkus.log.category."ai.azure.openai.rag.workshop.backend".level=DEBUG 9 | #quarkus.arc.selected-alternatives=ai.azure.openai.rag.workshop.backend.configuration.ChatLanguageModelOllamaProducer 10 | ``` 11 | 12 | We'll use [Azure Static Web Apps](https://learn.microsoft.com/azure/static-web-apps/overview) to deploy the frontend, and [Azure Container Apps](https://learn.microsoft.com/azure/container-apps/overview) to deploy the backend and ingestion services. 13 | 14 | Run this command from the root of the project to build and deploy the application (this command deploys all services listed in the `azure.yaml` file located in the project root): 15 | 16 | ```bash 17 | azd deploy 18 | ``` 19 | 20 | Once everything is deployed, run the ingestion process against your deployed ingestion service, using `./scripts/ingest-data.sh` script on Linux or macOS, or `./scripts/ingest-data.ps1` on Windows: 21 | 22 | ```bash 23 | ./scripts/ingest-data.sh 24 | ``` 25 | 26 | This process should take a few minutes. Once it's done, you should see the URL of the deployed frontend application in the output of the command. 27 | 28 | ![Output of the azd command](./assets/azd-deploy-output.png) 29 | 30 | You can now open this URL in a browser and test the deployed application. 31 | 32 | ![Screenshot of the deployed application](./assets/deployed-app.png) 33 | 34 |
35 | 36 | > You can also build and deploy the services separately by running `azd deploy `. This allows you to deploy independently the backend, frontend and ingestion services if needed. 37 | > 38 | > Even better! If you're starting from scratch and have a completed code, you can use the `azd up` command. This command combines both `azd provision` and `azd deploy` to provision the Azure resources and deploy the application in one command. 39 | 40 |
41 | -------------------------------------------------------------------------------- /docs/sections/10.1-ci-cd.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | > This step is entirely optional, you can skip it if you want to jump directly to the next section. 4 | 5 |
6 | 7 | ## Configuring a CI/CD pipeline 8 | 9 | We now have a working deployed application, but deploying it manually every time we make a change is not very convenient. We'll automate this process by creating a CI/CD pipeline, using [GitHub Actions](https://github.com/features/actions). 10 | 11 | ### What's CI/CD? 12 | 13 | CI/CD stands for *Continuous Integration and Continuous Deployment*. 14 | 15 | Continuous Integration is a software development practice that requires developers to integrate their code into a shared repository several times a day. Each integration can then be verified by an automated build and automated tests. By doing so, you can detect errors quickly, and locate them more easily. 16 | 17 | Continuous Deployment pushes this practice further, by preparing for a release to production after each successful build. By doing so, you can get working software into the hands of users faster. 18 | 19 | ### What's GitHub Actions? 20 | 21 | GitHub Actions is a service that lets you automate your software development workflows. A workflow is a series of steps executed one after the other. You can use workflows to build, test and deploy your code, but you can also use them to automate other tasks, like sending a notification when an issue is created. 22 | 23 | It's a great way to automate your CI/CD pipelines, and it's free for public repositories. 24 | 25 | ### Adding the deployment workflow 26 | 27 | First we need to create the GitHub Actions workflow. Create the file `.github/workflows/deploy.yml` in your repository, with this content: 28 | 29 | ```bash 30 | name: Deploy to Azure 31 | on: 32 | push: 33 | # Run only when commits are pushed to main branch 34 | branches: 35 | - main 36 | 37 | # Set up permissions for deploying with secretless Azure federated credentials 38 | # https://learn.microsoft.com/en-us/azure/developer/github/connect-from-azure?tabs=azure-portal%2Clinux#set-up-azure-login-with-openid-connect-authentication 39 | permissions: 40 | id-token: write 41 | contents: read 42 | 43 | jobs: 44 | deploy: 45 | runs-on: ubuntu-latest 46 | env: 47 | AZURE_CLIENT_ID: ${{ vars.AZURE_CLIENT_ID }} 48 | AZURE_TENANT_ID: ${{ vars.AZURE_TENANT_ID }} 49 | AZURE_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID }} 50 | AZURE_OPENAI_URL: ${{ vars.AZURE_OPENAI_URL }} 51 | steps: 52 | - name: Checkout 53 | uses: actions/checkout@v3 54 | 55 | - name: Install azd 56 | uses: Azure/setup-azd@v0.1.0 57 | 58 | - name: Log in with Azure (Federated Credentials) 59 | if: ${{ env.AZURE_CLIENT_ID != '' }} 60 | run: | 61 | azd auth login ` 62 | --client-id "$Env:AZURE_CLIENT_ID" ` 63 | --federated-credential-provider "github" ` 64 | --tenant-id "$Env:AZURE_TENANT_ID" 65 | shell: pwsh 66 | 67 | - name: Build and deploy application 68 | run: | 69 | azd up --no-prompt 70 | env: 71 | AZURE_ENV_NAME: ${{ vars.AZURE_ENV_NAME }} 72 | AZURE_LOCATION: ${{ vars.AZURE_LOCATION }} 73 | AZURE_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID }} 74 | ``` 75 | 76 | This workflow will run when you push a commit change to the `main` branch of your repository. What it does is log in to Azure using the `azd auth login` command, and then run the `azd up` command to provision the infrastructure, build and deploy the application. 77 | 78 | Next we need to configure your GitHub repository environment variables. These will be used by the workflow to authenticate to Azure. To do so, run this command: 79 | 80 | ```bash 81 | azd pipeline config 82 | ``` 83 | 84 | You'll first be asked to log in to GitHub and then it will do the setup for you. These are the steps it will perform: 85 | - Creation of an [Azure Service Principal](https://learn.microsoft.com/entra/identity-platform/app-objects-and-service-principals?tabs=browser) to authenticate to Azure. 86 | - Set up OpenID Connect authentication between GitHub and Azure using [federated credentials](https://docs.github.com/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-azure). 87 | - Addition of [GitHub variables](https://docs.github.com/actions/learn-github-actions/variables) to your repository to store the IDs needed to authenticate to Azure. 88 | 89 |
90 | 91 | Since you're using the provided Open AI proxy service we have deployed, there's one extra variable that you need to set. 92 | 93 | First we need to log in to GitHub using the [GitHub CLI](https://cli.github.com/): 94 | 95 | ```bash 96 | # We need more permissions than provided by default with Codespaces 97 | unset GITHUB_TOKEN 98 | gh auth login -w 99 | ``` 100 | 101 | Once you're logged in, run this command to set the value of the `AZURE_OPENAI_URL` variable in your repository: 102 | 103 | ```bash 104 | gh variable set AZURE_OPENAI_URL \ 105 | --body "$$proxy$$" \ 106 | --repo / 107 | ``` 108 | 109 |
110 | 111 | ### Testing the deployment workflow 112 | 113 | Now that we have our workflow fully configured, we can test it by pushing a change to our repository. Commit your changes and push them to GitHub: 114 | 115 | ```bash 116 | git add . 117 | git commit -m "Setup CI/CD" 118 | git push 119 | ``` 120 | 121 | The workflow will run automatically, so we can look at its progress directly on GitHub. Open your repository in a browser, and select the **Actions** tab. You should see the workflow running. It will take a few minutes to complete, but you can follow the progress in the logs by clicking on the running workflow. 122 | 123 | ![Screenshot showing GitHub Actions workflow running](./assets/gh-actions.png) 124 | 125 | Then select the job named **deploy** on the left, and you should see the logs of the workflow. 126 | 127 | ![Screenshot showing GitHub Actions workflow logs](./assets/gh-workflow-details.png) 128 | 129 | When the workflow is complete, you should see a green checkmark. 130 | -------------------------------------------------------------------------------- /docs/sections/12-conclusion.md: -------------------------------------------------------------------------------- 1 | ## Conclusion 2 | 3 | This is the end of the workshop. We hope you enjoyed it, learned something new and more importantly, that you'll be able to take this knowledge back to your projects. 4 | 5 | If you missed any of the steps or would like to check your final code, you can run this command in the terminal at the root of the project to get the completed solution (be sure to commit your code first!): 6 | 7 | ```bash 8 | curl -fsSL https://github.com/Azure-Samples/azure-openai-rag-workshop-java/releases/download/latest/solution-java-quarkus.tar.gz | tar -xvz 9 | ``` 10 | 11 |
12 | 13 | > If you experienced any issues during the workshop, please let us know by [creating an issue](https://github.com/Azure-Samples/azure-openai-rag-workshop-java/issues) on the GitHub repository. 14 | 15 |
16 | 17 | ### Cleaning up Azure resources 18 | 19 |
20 | 21 | > Don't forget to delete the Azure resources once you are done running the workshop, to avoid incurring unnecessary costs! 22 | 23 |
24 | 25 | To delete the Azure resources, you can run this command: 26 | 27 | ```bash 28 | azd down --purge 29 | ``` 30 | 31 | ### Going further 32 | 33 | This workshop is based on the enterprise-ready sample **ChatGPT + Enterprise data with Azure OpenAI and AI Search**: 34 | 35 | - [JavaScript version](https://github.com/Azure-Samples/azure-search-openai-javascript) 36 | - [Python version](https://github.com/Azure-Samples/azure-search-openai-demo/) 37 | - [Java version](https://github.com/Azure-Samples/azure-search-openai-demo-java) 38 | - [C# version](https://github.com/Azure-Samples/azure-search-openai-demo-csharp) 39 | - [Serverless JavaScript version](https://github.com/Azure-Samples/serverless-chat-langchainjs) 40 | 41 | If you want to go further with more advanced use-cases, authentication, history and more, you should check it out! 42 | 43 | ### References 44 | 45 | - This workshop URL: [aka.ms/ws/openai-rag-quarkus](https://aka.ms/ws/openai-rag-quarkus) 46 | - The source repository for this workshop: [GitHub link](https://github.com/Azure-Samples/azure-openai-rag-workshop-java/) 47 | - If something does not work: [Report an issue](https://github.com/Azure-Samples/azure-openai-rag-workshop-java/issues) 48 | - Introduction presentation for this workshop: [Slides](https://azure-samples.github.io/azure-openai-rag-workshop-java/java-quarkus/) 49 | - Outperforming vector search performance with hybrid retrieval and semantic ranking: [Blog post](https://techcommunity.microsoft.com/t5/ai-azure-ai-services-blog/azure-ai-search-outperforming-vector-search-with-hybrid/ba-p/3929167) 50 | -------------------------------------------------------------------------------- /docs/sections/_old/09-azure-llm.md: -------------------------------------------------------------------------------- 1 | ## Deploy an LLM in Azure OpenAI 2 | 3 | [Azure](https://azure.microsoft.com) is Microsoft's comprehensive cloud platform, offering a vast array of services to build, deploy, and manage applications across a global network of Microsoft-managed data centers. In this workshop, we'll leverage several Azure services to run our chat application. 4 | 5 | ### Getting Started with Azure 6 | 7 |
8 | 9 | To complete this workshop, you'll need an Azure account. If you don't already have one, you can sign up for a free account, which includes Azure credits, on the [Azure website](https://azure.microsoft.com/free/). 10 | 11 |
12 | 13 | > If you already have an Azure account from your company, **DO NOT** use it for this workshop as it may have restrictions that will prevent you from completing the workshop. 14 | > You'll need to create a new account to redeem the Azure Pass. 15 | 16 |
17 | 18 |
19 | 20 |
21 | 22 | To complete this workshop, you'll need an Azure account. As you're attending this workshop in-person, you can create one and obtain a free Azure Pass credit by using this link: [redeem your Azure Pass](https://azcheck.in/$$azpass$$). 23 | 24 | > If you're **not** attending this workshop in-person, you can sign up for a free account, which includes Azure credits, on the [Azure website](https://azure.microsoft.com/free/). 25 | 26 |
27 | 28 | #### Log in to Azure 29 | 30 | Begin by logging into your Azure subscription with the following command: 31 | 32 | ```sh 33 | azd auth login --use-device-code 34 | ``` 35 | 36 | This command will provide you a *device code* to enter in a browser window. Follow the prompts until you're notified of a successful login. 37 | 38 | 39 | #### Setting up environment variables 40 | 41 | ```shell 42 | PROJECT="rag-workshop" 43 | RESOURCE_GROUP="rg-$PROJECT" 44 | LOCATION="swedencentral" 45 | TAG="$PROJECT" 46 | AI_SERVICE="ai-$PROJECT" 47 | AI_MODEL="gpt-4o-mini" 48 | ``` 49 | 50 | #### Creating the resource group 51 | 52 | ```shell 53 | az group create \ 54 | --name "$RESOURCE_GROUP" \ 55 | --location "$LOCATION" \ 56 | --tags system="$TAG" 57 | ``` 58 | 59 | #### Creating the Cognitive Service 60 | 61 | ```shell 62 | az cognitiveservices account create \ 63 | --name "$AI_SERVICE" \ 64 | --resource-group "$RESOURCE_GROUP" \ 65 | --location "$LOCATION" \ 66 | --custom-domain "$AI_SERVICE" \ 67 | --tags system="$TAG" \ 68 | --kind "OpenAI" \ 69 | --sku "S0" 70 | ```` 71 | 72 | #### Deploying a gpt-4o-mini model 73 | 74 | ```shell 75 | az cognitiveservices account deployment create \ 76 | --name "$AI_SERVICE" \ 77 | --resource-group "$RESOURCE_GROUP" \ 78 | --deployment-name "$AI_MODEL" \ 79 | --model-name "$AI_MODEL" \ 80 | --model-version "1106" \ 81 | --model-format "OpenAI" \ 82 | --sku-capacity 120 \ 83 | --sku-name "Standard" 84 | ``` 85 | 86 | #### Storing the key and endpoint in environment variables..." 87 | 88 | ```shell 89 | AZURE_OPENAI_KEY=$( 90 | az cognitiveservices account keys list \ 91 | --name "$AI_SERVICE" \ 92 | --resource-group "$RESOURCE_GROUP" \ 93 | | jq -r .key1 94 | ) 95 | AZURE_OPENAI_URL=$( 96 | az cognitiveservices account show \ 97 | --name "$AI_SERVICE" \ 98 | --resource-group "$RESOURCE_GROUP" \ 99 | | jq -r .properties.endpoint 100 | ) 101 | 102 | echo "AZURE_OPENAI_KEY=$AZURE_OPENAI_KEY" 103 | echo "AZURE_OPENAI_URL=$AZURE_OPENAI_URL" 104 | echo "AZURE_OPENAI_DEPLOYMENT_NAME=$AI_MODEL" 105 | ``` 106 | -------------------------------------------------------------------------------- /docs/slides/README.md: -------------------------------------------------------------------------------- 1 | # Slides 2 | 3 | To show the slides, you need to have Node.js installed with this tool: 4 | 5 | ```bash 6 | npm i -g backslide 7 | ``` 8 | 9 | Then, you can run the following command to show the slides: 10 | 11 | ```bash 12 | cd docs/slides 13 | bs serve 14 | ``` 15 | 16 | The slides will be available at http://localhost:4100. 17 | -------------------------------------------------------------------------------- /docs/slides/images/agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/slides/images/agent.png -------------------------------------------------------------------------------- /docs/slides/images/ai.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/slides/images/ai.jpg -------------------------------------------------------------------------------- /docs/slides/images/antonio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/slides/images/antonio.png -------------------------------------------------------------------------------- /docs/slides/images/book-langchain4j.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/slides/images/book-langchain4j.png -------------------------------------------------------------------------------- /docs/slides/images/chatgpt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/slides/images/chatgpt.png -------------------------------------------------------------------------------- /docs/slides/images/chris-dive.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/slides/images/chris-dive.jpg -------------------------------------------------------------------------------- /docs/slides/images/chris.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/slides/images/chris.jpg -------------------------------------------------------------------------------- /docs/slides/images/embedding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/slides/images/embedding.png -------------------------------------------------------------------------------- /docs/slides/images/julien.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/slides/images/julien.jpg -------------------------------------------------------------------------------- /docs/slides/images/llm-magazine.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/slides/images/llm-magazine.jpg -------------------------------------------------------------------------------- /docs/slides/images/llm-training.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/slides/images/llm-training.png -------------------------------------------------------------------------------- /docs/slides/images/microsoft-azure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/slides/images/microsoft-azure.png -------------------------------------------------------------------------------- /docs/slides/images/ms-full-logo.svg: -------------------------------------------------------------------------------- 1 | Microsoft logo -------------------------------------------------------------------------------- /docs/slides/images/olivier.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/slides/images/olivier.jpg -------------------------------------------------------------------------------- /docs/slides/images/openai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/slides/images/openai.png -------------------------------------------------------------------------------- /docs/slides/images/rag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/slides/images/rag.png -------------------------------------------------------------------------------- /docs/slides/images/sandra.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/slides/images/sandra.jpg -------------------------------------------------------------------------------- /docs/slides/images/tokens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/slides/images/tokens.png -------------------------------------------------------------------------------- /docs/slides/images/tokens2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/slides/images/tokens2.png -------------------------------------------------------------------------------- /docs/slides/images/yohan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/slides/images/yohan.jpg -------------------------------------------------------------------------------- /docs/slides/template/Archivo-Variable.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/slides/template/Archivo-Variable.ttf -------------------------------------------------------------------------------- /docs/slides/template/Inconsolata-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/slides/template/Inconsolata-Regular.ttf -------------------------------------------------------------------------------- /docs/slides/template/Saira-Variable.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/slides/template/Saira-Variable.ttf -------------------------------------------------------------------------------- /docs/slides/template/code.js: -------------------------------------------------------------------------------- 1 | window.setup = function() { 2 | window.annotations = []; 3 | window.oldannotations = []; 4 | window.slideshow.on('beforeShowSlide', function (slide) { 5 | window.oldannotations = window.annotations; 6 | window.annotations = []; 7 | }); 8 | window.slideshow.on('afterShowSlide', function (slide) { 9 | const element = document.querySelector('.remark-visible .remark-slide-content'); 10 | element.childNodes 11 | .filter(node => node.nodeName === "SCRIPT") 12 | .forEach(node => { 13 | eval(node.innerHTML); 14 | }); 15 | setTimeout(function() { 16 | window.oldannotations.forEach((annotation) => { 17 | annotation.remove(); 18 | }); 19 | window.oldannotations = []; 20 | }, 0); 21 | }); 22 | } 23 | 24 | function rough(selector, options, delay = 500) { 25 | setTimeout(function() { 26 | document 27 | .querySelectorAll(`.remark-visible ${selector}`) 28 | .forEach((e) => { 29 | const annotation = RoughNotation.annotate(e, options); 30 | 31 | // Keep track of annotation to remove it on slide change 32 | window.annotations.push(annotation); 33 | 34 | // Fix scale 35 | const scaler = document.querySelector('.remark-slide-scaler'); 36 | const scale = Number(/scale\((\d+\.?\d*)\)/.exec(scaler.style.transform)[1]); 37 | annotation._svg.style.transform = `scale(${1/scale})`; 38 | 39 | annotation.show(); 40 | }); 41 | }, delay); 42 | } 43 | -------------------------------------------------------------------------------- /docs/slides/template/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/slides/template/fa-brands-400.woff2 -------------------------------------------------------------------------------- /docs/slides/template/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/slides/template/fa-regular-400.woff2 -------------------------------------------------------------------------------- /docs/slides/template/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/docs/slides/template/fa-solid-900.woff2 -------------------------------------------------------------------------------- /docs/slides/template/fonts.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Saira'; 3 | font-style: normal; 4 | font-weight: 500; 5 | src: url(Saira-Variable.ttf) format('truetype'); 6 | } 7 | @font-face { 8 | font-family: 'Archivo'; 9 | font-style: normal; 10 | font-weight: 300 500; 11 | src: url(Archivo-Variable.ttf) format('truetype'); 12 | } 13 | @font-face { 14 | font-family: 'Inconsolata'; 15 | font-style: normal; 16 | font-weight: 400; 17 | src: url(Inconsolata-Regular.ttf) format('truetype'); 18 | } -------------------------------------------------------------------------------- /docs/slides/template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{title}} 6 | {{{style}}} 7 | 8 | 9 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /infra/abbreviations.json: -------------------------------------------------------------------------------- 1 | { 2 | "analysisServicesServers": "as", 3 | "apiManagementService": "apim-", 4 | "appConfigurationStores": "appcs-", 5 | "appManagedEnvironments": "cae-", 6 | "appContainerApps": "ca-", 7 | "authorizationPolicyDefinitions": "policy-", 8 | "automationAutomationAccounts": "aa-", 9 | "blueprintBlueprints": "bp-", 10 | "blueprintBlueprintsArtifacts": "bpa-", 11 | "cacheRedis": "redis-", 12 | "cdnProfiles": "cdnp-", 13 | "cdnProfilesEndpoints": "cdne-", 14 | "cognitiveServicesAccounts": "cog-", 15 | "cognitiveServicesFormRecognizer": "cog-fr-", 16 | "cognitiveServicesTextAnalytics": "cog-ta-", 17 | "cognitiveServicesSpeech": "cog-sp-", 18 | "computeAvailabilitySets": "avail-", 19 | "computeCloudServices": "cld-", 20 | "computeDiskEncryptionSets": "des", 21 | "computeDisks": "disk", 22 | "computeDisksOs": "osdisk", 23 | "computeGalleries": "gal", 24 | "computeSnapshots": "snap-", 25 | "computeVirtualMachines": "vm", 26 | "computeVirtualMachineScaleSets": "vmss-", 27 | "containerInstanceContainerGroups": "ci", 28 | "containerRegistryRegistries": "cr", 29 | "containerServiceManagedClusters": "aks-", 30 | "databricksWorkspaces": "dbw-", 31 | "dataFactoryFactories": "adf-", 32 | "dataLakeAnalyticsAccounts": "dla", 33 | "dataLakeStoreAccounts": "dls", 34 | "dataMigrationServices": "dms-", 35 | "dBforMySQLServers": "mysql-", 36 | "dBforPostgreSQLServers": "psql-", 37 | "devicesIotHubs": "iot-", 38 | "devicesProvisioningServices": "provs-", 39 | "devicesProvisioningServicesCertificates": "pcert-", 40 | "documentDBDatabaseAccounts": "cosmos-", 41 | "eventGridDomains": "evgd-", 42 | "eventGridDomainsTopics": "evgt-", 43 | "eventGridEventSubscriptions": "evgs-", 44 | "eventHubNamespaces": "evhns-", 45 | "eventHubNamespacesEventHubs": "evh-", 46 | "hdInsightClustersHadoop": "hadoop-", 47 | "hdInsightClustersHbase": "hbase-", 48 | "hdInsightClustersKafka": "kafka-", 49 | "hdInsightClustersMl": "mls-", 50 | "hdInsightClustersSpark": "spark-", 51 | "hdInsightClustersStorm": "storm-", 52 | "hybridComputeMachines": "arcs-", 53 | "insightsActionGroups": "ag-", 54 | "insightsComponents": "appi-", 55 | "keyVaultVaults": "kv-", 56 | "kubernetesConnectedClusters": "arck", 57 | "kustoClusters": "dec", 58 | "kustoClustersDatabases": "dedb", 59 | "loadTesting": "lt-", 60 | "logicIntegrationAccounts": "ia-", 61 | "logicWorkflows": "logic-", 62 | "machineLearningServicesWorkspaces": "mlw-", 63 | "managedIdentityUserAssignedIdentities": "id-", 64 | "managementManagementGroups": "mg-", 65 | "migrateAssessmentProjects": "migr-", 66 | "networkApplicationGateways": "agw-", 67 | "networkApplicationSecurityGroups": "asg-", 68 | "networkAzureFirewalls": "afw-", 69 | "networkBastionHosts": "bas-", 70 | "networkConnections": "con-", 71 | "networkDnsZones": "dnsz-", 72 | "networkExpressRouteCircuits": "erc-", 73 | "networkFirewallPolicies": "afwp-", 74 | "networkFirewallPoliciesWebApplication": "waf", 75 | "networkFirewallPoliciesRuleGroups": "wafrg", 76 | "networkFrontDoors": "fd-", 77 | "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", 78 | "networkLoadBalancersExternal": "lbe-", 79 | "networkLoadBalancersInternal": "lbi-", 80 | "networkLoadBalancersInboundNatRules": "rule-", 81 | "networkLocalNetworkGateways": "lgw-", 82 | "networkNatGateways": "ng-", 83 | "networkNetworkInterfaces": "nic-", 84 | "networkNetworkSecurityGroups": "nsg-", 85 | "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", 86 | "networkNetworkWatchers": "nw-", 87 | "networkPrivateDnsZones": "pdnsz-", 88 | "networkPrivateLinkServices": "pl-", 89 | "networkPublicIPAddresses": "pip-", 90 | "networkPublicIPPrefixes": "ippre-", 91 | "networkRouteFilters": "rf-", 92 | "networkRouteTables": "rt-", 93 | "networkRouteTablesRoutes": "udr-", 94 | "networkTrafficManagerProfiles": "traf-", 95 | "networkVirtualNetworkGateways": "vgw-", 96 | "networkVirtualNetworks": "vnet-", 97 | "networkVirtualNetworksSubnets": "snet-", 98 | "networkVirtualNetworksVirtualNetworkPeerings": "peer-", 99 | "networkVirtualWans": "vwan-", 100 | "networkVpnGateways": "vpng-", 101 | "networkVpnGatewaysVpnConnections": "vcn-", 102 | "networkVpnGatewaysVpnSites": "vst-", 103 | "notificationHubsNamespaces": "ntfns-", 104 | "notificationHubsNamespacesNotificationHubs": "ntf-", 105 | "operationalInsightsWorkspaces": "log-", 106 | "portalDashboards": "dash-", 107 | "powerBIDedicatedCapacities": "pbi-", 108 | "purviewAccounts": "pview-", 109 | "recoveryServicesVaults": "rsv-", 110 | "resourcesResourceGroups": "rg-", 111 | "searchSearchServices": "srch-", 112 | "serviceBusNamespaces": "sb-", 113 | "serviceBusNamespacesQueues": "sbq-", 114 | "serviceBusNamespacesTopics": "sbt-", 115 | "serviceEndPointPolicies": "se-", 116 | "serviceFabricClusters": "sf-", 117 | "signalRServiceSignalR": "sigr", 118 | "sqlManagedInstances": "sqlmi-", 119 | "sqlServers": "sql-", 120 | "sqlServersDataWarehouse": "sqldw-", 121 | "sqlServersDatabases": "sqldb-", 122 | "sqlServersDatabasesStretch": "sqlstrdb-", 123 | "storageStorageAccounts": "st", 124 | "storageStorageAccountsVm": "stvm", 125 | "storSimpleManagers": "ssimp", 126 | "streamAnalyticsCluster": "asa-", 127 | "synapseWorkspaces": "syn", 128 | "synapseWorkspacesAnalyticsWorkspaces": "synw", 129 | "synapseWorkspacesSqlPoolsDedicated": "syndp", 130 | "synapseWorkspacesSqlPoolsSpark": "synsp", 131 | "timeSeriesInsightsEnvironments": "tsi-", 132 | "webServerFarms": "plan-", 133 | "webSitesAppService": "app-", 134 | "webSitesAppServiceEnvironment": "ase-", 135 | "webSitesFunctions": "func-", 136 | "webStaticSites": "stapp-" 137 | } 138 | -------------------------------------------------------------------------------- /infra/azure/deploy-azure-openai-models.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Execute this script to deploy the needed Azure OpenAI models to execute the code. 4 | # For this, you need Azure CLI installed: https://learn.microsoft.com/cli/azure/install-azure-cli 5 | 6 | echo "Setting up environment variables..." 7 | echo "----------------------------------" 8 | PROJECT="rag-workshop" 9 | RESOURCE_GROUP="rg-$PROJECT" 10 | LOCATION="swedencentral" 11 | TAG="$PROJECT" 12 | AI_SERVICE="ai-$PROJECT" 13 | AI_MODEL="gpt-4o-mini" 14 | 15 | echo "Creating the resource group..." 16 | echo "------------------------------" 17 | az group create \ 18 | --name "$RESOURCE_GROUP" \ 19 | --location "$LOCATION" \ 20 | --tags system="$TAG" 21 | 22 | echo "Creating the Cognitive Service..." 23 | echo "---------------------------------" 24 | az cognitiveservices account create \ 25 | --name "$AI_SERVICE" \ 26 | --resource-group "$RESOURCE_GROUP" \ 27 | --location "$LOCATION" \ 28 | --custom-domain "$AI_SERVICE" \ 29 | --tags system="$TAG" \ 30 | --kind "OpenAI" \ 31 | --sku "S0" 32 | 33 | # If you want to know the available models, run the following Azure CLI command: 34 | # az cognitiveservices account list-models --resource-group "$RESOURCE_GROUP" --name "$AI_SERVICE" -o table 35 | 36 | echo "Deploying a gpt-4o-mini model..." 37 | echo "----------------------" 38 | az cognitiveservices account deployment create \ 39 | --name "$AI_SERVICE" \ 40 | --resource-group "$RESOURCE_GROUP" \ 41 | --deployment-name "$AI_MODEL" \ 42 | --model-name "$AI_MODEL" \ 43 | --model-version "1106" \ 44 | --model-format "OpenAI" \ 45 | --sku-capacity 120 \ 46 | --sku-name "Standard" 47 | 48 | echo "Storing the key and endpoint in environment variables..." 49 | echo "--------------------------------------------------------" 50 | AZURE_OPENAI_KEY=$( 51 | az cognitiveservices account keys list \ 52 | --name "$AI_SERVICE" \ 53 | --resource-group "$RESOURCE_GROUP" \ 54 | | jq -r .key1 55 | ) 56 | AZURE_OPENAI_URL=$( 57 | az cognitiveservices account show \ 58 | --name "$AI_SERVICE" \ 59 | --resource-group "$RESOURCE_GROUP" \ 60 | | jq -r .properties.endpoint 61 | ) 62 | 63 | echo "AZURE_OPENAI_KEY=$AZURE_OPENAI_KEY" 64 | echo "AZURE_OPENAI_URL=$AZURE_OPENAI_URL" 65 | echo "AZURE_OPENAI_DEPLOYMENT_NAME=$AI_MODEL" 66 | -------------------------------------------------------------------------------- /infra/core/ai/cognitiveservices.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates an Azure Cognitive Services instance.' 2 | param name string 3 | param location string = resourceGroup().location 4 | param tags object = {} 5 | @description('The custom subdomain name used to access the API. Defaults to the value of the name parameter.') 6 | param customSubDomainName string = name 7 | param disableLocalAuth bool = false 8 | param deployments array = [] 9 | param kind string = 'OpenAI' 10 | 11 | @allowed([ 'Enabled', 'Disabled' ]) 12 | param publicNetworkAccess string = 'Enabled' 13 | param sku object = { 14 | name: 'S0' 15 | } 16 | 17 | param allowedIpRules array = [] 18 | param networkAcls object = empty(allowedIpRules) ? { 19 | defaultAction: 'Allow' 20 | } : { 21 | ipRules: allowedIpRules 22 | defaultAction: 'Deny' 23 | } 24 | 25 | resource account 'Microsoft.CognitiveServices/accounts@2023-05-01' = { 26 | name: name 27 | location: location 28 | tags: tags 29 | kind: kind 30 | properties: { 31 | customSubDomainName: customSubDomainName 32 | publicNetworkAccess: publicNetworkAccess 33 | networkAcls: networkAcls 34 | disableLocalAuth: disableLocalAuth 35 | } 36 | sku: sku 37 | } 38 | 39 | @batchSize(1) 40 | resource deployment 'Microsoft.CognitiveServices/accounts/deployments@2023-05-01' = [for deployment in deployments: { 41 | parent: account 42 | name: deployment.name 43 | properties: { 44 | model: deployment.model 45 | raiPolicyName: contains(deployment, 'raiPolicyName') ? deployment.raiPolicyName : null 46 | } 47 | sku: contains(deployment, 'sku') ? deployment.sku : { 48 | name: 'Standard' 49 | capacity: 20 50 | } 51 | }] 52 | 53 | output endpoint string = account.properties.endpoint 54 | output endpoints object = account.properties.endpoints 55 | output id string = account.id 56 | output name string = account.name 57 | -------------------------------------------------------------------------------- /infra/core/host/container-apps-environment.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates an Azure Container Apps environment.' 2 | param name string 3 | param location string = resourceGroup().location 4 | param tags object = {} 5 | 6 | @description('Name of the Application Insights resource') 7 | param applicationInsightsName string = '' 8 | 9 | @description('Specifies if Dapr is enabled') 10 | param daprEnabled bool = false 11 | 12 | @description('Name of the Log Analytics workspace') 13 | param logAnalyticsWorkspaceName string 14 | 15 | resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2023-05-01' = { 16 | name: name 17 | location: location 18 | tags: tags 19 | properties: { 20 | appLogsConfiguration: { 21 | destination: 'log-analytics' 22 | logAnalyticsConfiguration: { 23 | customerId: logAnalyticsWorkspace.properties.customerId 24 | sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey 25 | } 26 | } 27 | daprAIInstrumentationKey: daprEnabled && !empty(applicationInsightsName) ? applicationInsights.properties.InstrumentationKey : '' 28 | } 29 | } 30 | 31 | resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' existing = { 32 | name: logAnalyticsWorkspaceName 33 | } 34 | 35 | resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (daprEnabled && !empty(applicationInsightsName)) { 36 | name: applicationInsightsName 37 | } 38 | 39 | output defaultDomain string = containerAppsEnvironment.properties.defaultDomain 40 | output id string = containerAppsEnvironment.id 41 | output name string = containerAppsEnvironment.name 42 | -------------------------------------------------------------------------------- /infra/core/host/container-apps.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates an Azure Container Registry and an Azure Container Apps environment.' 2 | param name string 3 | param location string = resourceGroup().location 4 | param tags object = {} 5 | 6 | param containerAppsEnvironmentName string 7 | param containerRegistryName string 8 | param containerRegistryResourceGroupName string = '' 9 | param containerRegistryAdminUserEnabled bool = false 10 | param logAnalyticsWorkspaceName string 11 | param applicationInsightsName string = '' 12 | param daprEnabled bool = false 13 | 14 | module containerAppsEnvironment 'container-apps-environment.bicep' = { 15 | name: '${name}-container-apps-environment' 16 | params: { 17 | name: containerAppsEnvironmentName 18 | location: location 19 | tags: tags 20 | logAnalyticsWorkspaceName: logAnalyticsWorkspaceName 21 | applicationInsightsName: applicationInsightsName 22 | daprEnabled: daprEnabled 23 | } 24 | } 25 | 26 | module containerRegistry 'container-registry.bicep' = { 27 | name: '${name}-container-registry' 28 | scope: !empty(containerRegistryResourceGroupName) ? resourceGroup(containerRegistryResourceGroupName) : resourceGroup() 29 | params: { 30 | name: containerRegistryName 31 | location: location 32 | adminUserEnabled: containerRegistryAdminUserEnabled 33 | tags: tags 34 | } 35 | } 36 | 37 | output defaultDomain string = containerAppsEnvironment.outputs.defaultDomain 38 | output environmentName string = containerAppsEnvironment.outputs.name 39 | output environmentId string = containerAppsEnvironment.outputs.id 40 | 41 | output registryLoginServer string = containerRegistry.outputs.loginServer 42 | output registryName string = containerRegistry.outputs.name 43 | -------------------------------------------------------------------------------- /infra/core/host/container-registry.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates an Azure Container Registry.' 2 | param name string 3 | param location string = resourceGroup().location 4 | param tags object = {} 5 | 6 | @description('Indicates whether admin user is enabled') 7 | param adminUserEnabled bool = false 8 | 9 | @description('Indicates whether anonymous pull is enabled') 10 | param anonymousPullEnabled bool = false 11 | 12 | @description('Azure ad authentication as arm policy settings') 13 | param azureADAuthenticationAsArmPolicy object = { 14 | status: 'enabled' 15 | } 16 | 17 | @description('Indicates whether data endpoint is enabled') 18 | param dataEndpointEnabled bool = false 19 | 20 | @description('Encryption settings') 21 | param encryption object = { 22 | status: 'disabled' 23 | } 24 | 25 | @description('Export policy settings') 26 | param exportPolicy object = { 27 | status: 'enabled' 28 | } 29 | 30 | @description('Metadata search settings') 31 | param metadataSearch string = 'Disabled' 32 | 33 | @description('Options for bypassing network rules') 34 | param networkRuleBypassOptions string = 'AzureServices' 35 | 36 | @description('Public network access setting') 37 | param publicNetworkAccess string = 'Enabled' 38 | 39 | @description('Quarantine policy settings') 40 | param quarantinePolicy object = { 41 | status: 'disabled' 42 | } 43 | 44 | @description('Retention policy settings') 45 | param retentionPolicy object = { 46 | days: 7 47 | status: 'disabled' 48 | } 49 | 50 | @description('Scope maps setting') 51 | param scopeMaps array = [] 52 | 53 | @description('SKU settings') 54 | param sku object = { 55 | name: 'Basic' 56 | } 57 | 58 | @description('Soft delete policy settings') 59 | param softDeletePolicy object = { 60 | retentionDays: 7 61 | status: 'disabled' 62 | } 63 | 64 | @description('Trust policy settings') 65 | param trustPolicy object = { 66 | type: 'Notary' 67 | status: 'disabled' 68 | } 69 | 70 | @description('Zone redundancy setting') 71 | param zoneRedundancy string = 'Disabled' 72 | 73 | @description('The log analytics workspace ID used for logging and monitoring') 74 | param workspaceId string = '' 75 | 76 | // 2023-11-01-preview needed for metadataSearch 77 | resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-11-01-preview' = { 78 | name: name 79 | location: location 80 | tags: tags 81 | sku: sku 82 | properties: { 83 | adminUserEnabled: adminUserEnabled 84 | anonymousPullEnabled: anonymousPullEnabled 85 | dataEndpointEnabled: dataEndpointEnabled 86 | encryption: encryption 87 | metadataSearch: metadataSearch 88 | networkRuleBypassOptions: networkRuleBypassOptions 89 | policies:{ 90 | quarantinePolicy: quarantinePolicy 91 | trustPolicy: trustPolicy 92 | retentionPolicy: retentionPolicy 93 | exportPolicy: exportPolicy 94 | azureADAuthenticationAsArmPolicy: azureADAuthenticationAsArmPolicy 95 | softDeletePolicy: softDeletePolicy 96 | } 97 | publicNetworkAccess: publicNetworkAccess 98 | zoneRedundancy: zoneRedundancy 99 | } 100 | 101 | resource scopeMap 'scopeMaps' = [for scopeMap in scopeMaps: { 102 | name: scopeMap.name 103 | properties: scopeMap.properties 104 | }] 105 | } 106 | 107 | // TODO: Update diagnostics to be its own module 108 | // Blocking issue: https://github.com/Azure/bicep/issues/622 109 | // Unable to pass in a `resource` scope or unable to use string interpolation in resource types 110 | resource diagnostics 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (!empty(workspaceId)) { 111 | name: 'registry-diagnostics' 112 | scope: containerRegistry 113 | properties: { 114 | workspaceId: workspaceId 115 | logs: [ 116 | { 117 | category: 'ContainerRegistryRepositoryEvents' 118 | enabled: true 119 | } 120 | { 121 | category: 'ContainerRegistryLoginEvents' 122 | enabled: true 123 | } 124 | ] 125 | metrics: [ 126 | { 127 | category: 'AllMetrics' 128 | enabled: true 129 | timeGrain: 'PT1M' 130 | } 131 | ] 132 | } 133 | } 134 | 135 | output id string = containerRegistry.id 136 | output loginServer string = containerRegistry.properties.loginServer 137 | output name string = containerRegistry.name 138 | -------------------------------------------------------------------------------- /infra/core/host/staticwebapp.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates an Azure Static Web Apps instance.' 2 | param name string 3 | param location string = resourceGroup().location 4 | param tags object = {} 5 | 6 | param sku object = { 7 | name: 'Free' 8 | tier: 'Free' 9 | } 10 | 11 | resource web 'Microsoft.Web/staticSites@2022-03-01' = { 12 | name: name 13 | location: location 14 | tags: tags 15 | sku: sku 16 | properties: { 17 | provider: 'Custom' 18 | } 19 | } 20 | 21 | output name string = web.name 22 | output uri string = 'https://${web.properties.defaultHostname}' 23 | -------------------------------------------------------------------------------- /infra/core/monitor/applicationinsights.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates an Application Insights instance based on an existing Log Analytics workspace.' 2 | param name string 3 | param dashboardName string = '' 4 | param location string = resourceGroup().location 5 | param tags object = {} 6 | param logAnalyticsWorkspaceId string 7 | 8 | resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { 9 | name: name 10 | location: location 11 | tags: tags 12 | kind: 'web' 13 | properties: { 14 | Application_Type: 'web' 15 | WorkspaceResourceId: logAnalyticsWorkspaceId 16 | } 17 | } 18 | 19 | module applicationInsightsDashboard 'applicationinsights-dashboard.bicep' = if (!empty(dashboardName)) { 20 | name: 'application-insights-dashboard' 21 | params: { 22 | name: dashboardName 23 | location: location 24 | applicationInsightsName: applicationInsights.name 25 | } 26 | } 27 | 28 | output connectionString string = applicationInsights.properties.ConnectionString 29 | output id string = applicationInsights.id 30 | output instrumentationKey string = applicationInsights.properties.InstrumentationKey 31 | output name string = applicationInsights.name 32 | -------------------------------------------------------------------------------- /infra/core/monitor/loganalytics.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates a Log Analytics workspace.' 2 | param name string 3 | param location string = resourceGroup().location 4 | param tags object = {} 5 | 6 | resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = { 7 | name: name 8 | location: location 9 | tags: tags 10 | properties: any({ 11 | retentionInDays: 30 12 | features: { 13 | searchVersion: 1 14 | } 15 | sku: { 16 | name: 'PerGB2018' 17 | } 18 | }) 19 | } 20 | 21 | output id string = logAnalytics.id 22 | output name string = logAnalytics.name 23 | -------------------------------------------------------------------------------- /infra/core/monitor/monitoring.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates an Application Insights instance and a Log Analytics workspace.' 2 | param logAnalyticsName string 3 | param applicationInsightsName string 4 | param applicationInsightsDashboardName string = '' 5 | param location string = resourceGroup().location 6 | param tags object = {} 7 | 8 | module logAnalytics 'loganalytics.bicep' = { 9 | name: 'loganalytics' 10 | params: { 11 | name: logAnalyticsName 12 | location: location 13 | tags: tags 14 | } 15 | } 16 | 17 | module applicationInsights 'applicationinsights.bicep' = { 18 | name: 'applicationinsights' 19 | params: { 20 | name: applicationInsightsName 21 | location: location 22 | tags: tags 23 | dashboardName: applicationInsightsDashboardName 24 | logAnalyticsWorkspaceId: logAnalytics.outputs.id 25 | } 26 | } 27 | 28 | output applicationInsightsConnectionString string = applicationInsights.outputs.connectionString 29 | output applicationInsightsId string = applicationInsights.outputs.id 30 | output applicationInsightsInstrumentationKey string = applicationInsights.outputs.instrumentationKey 31 | output applicationInsightsName string = applicationInsights.outputs.name 32 | output logAnalyticsWorkspaceId string = logAnalytics.outputs.id 33 | output logAnalyticsWorkspaceName string = logAnalytics.outputs.name 34 | -------------------------------------------------------------------------------- /infra/core/search/search-services.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates an Azure AI Search instance.' 2 | param name string 3 | param location string = resourceGroup().location 4 | param tags object = {} 5 | 6 | param sku object = { 7 | name: 'standard' 8 | } 9 | 10 | param authOptions object = {} 11 | param disableLocalAuth bool = false 12 | param disabledDataExfiltrationOptions array = [] 13 | param encryptionWithCmk object = { 14 | enforcement: 'Unspecified' 15 | } 16 | @allowed([ 17 | 'default' 18 | 'highDensity' 19 | ]) 20 | param hostingMode string = 'default' 21 | param networkRuleSet object = { 22 | bypass: 'None' 23 | ipRules: [] 24 | } 25 | param partitionCount int = 1 26 | @allowed([ 27 | 'enabled' 28 | 'disabled' 29 | ]) 30 | param publicNetworkAccess string = 'enabled' 31 | param replicaCount int = 1 32 | @allowed([ 33 | 'disabled' 34 | 'free' 35 | 'standard' 36 | ]) 37 | param semanticSearch string = 'disabled' 38 | 39 | var searchIdentityProvider = (sku.name == 'free') ? null : { 40 | type: 'SystemAssigned' 41 | } 42 | 43 | resource search 'Microsoft.Search/searchServices@2021-04-01-preview' = { 44 | name: name 45 | location: location 46 | tags: tags 47 | // The free tier does not support managed identity 48 | identity: searchIdentityProvider 49 | properties: { 50 | authOptions: disableLocalAuth ? null : authOptions 51 | disableLocalAuth: disableLocalAuth 52 | disabledDataExfiltrationOptions: disabledDataExfiltrationOptions 53 | encryptionWithCmk: encryptionWithCmk 54 | hostingMode: hostingMode 55 | networkRuleSet: networkRuleSet 56 | partitionCount: partitionCount 57 | publicNetworkAccess: publicNetworkAccess 58 | replicaCount: replicaCount 59 | semanticSearch: semanticSearch 60 | } 61 | sku: sku 62 | } 63 | 64 | output id string = search.id 65 | output endpoint string = 'https://${name}.search.windows.net/' 66 | output name string = search.name 67 | output principalId string = !empty(searchIdentityProvider) ? search.identity.principalId : '' 68 | 69 | -------------------------------------------------------------------------------- /infra/core/security/managed-identity.bicep: -------------------------------------------------------------------------------- 1 | param name string 2 | param location string = resourceGroup().location 3 | 4 | resource apiIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { 5 | name: name 6 | location: location 7 | } 8 | 9 | output tenantId string = apiIdentity.properties.tenantId 10 | output principalId string = apiIdentity.properties.principalId 11 | output clientId string = apiIdentity.properties.clientId 12 | -------------------------------------------------------------------------------- /infra/core/security/registry-access.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Assigns ACR Pull permissions to access an Azure Container Registry.' 2 | param containerRegistryName string 3 | param principalId string 4 | 5 | var acrPullRole = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d') 6 | 7 | resource aksAcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 8 | scope: containerRegistry // Use when specifying a scope that is different than the deployment scope 9 | name: guid(subscription().id, resourceGroup().id, principalId, acrPullRole) 10 | properties: { 11 | roleDefinitionId: acrPullRole 12 | principalType: 'ServicePrincipal' 13 | principalId: principalId 14 | } 15 | } 16 | 17 | resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-01-01-preview' existing = { 18 | name: containerRegistryName 19 | } 20 | -------------------------------------------------------------------------------- /infra/core/security/role.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates a role assignment for a service principal.' 2 | param principalId string 3 | 4 | @allowed([ 5 | 'Device' 6 | 'ForeignGroup' 7 | 'Group' 8 | 'ServicePrincipal' 9 | 'User' 10 | ]) 11 | param principalType string = 'ServicePrincipal' 12 | param roleDefinitionId string 13 | 14 | resource role 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 15 | name: guid(subscription().id, resourceGroup().id, principalId, roleDefinitionId) 16 | properties: { 17 | principalId: principalId 18 | principalType: principalType 19 | roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /infra/main.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "environmentName": { 6 | "value": "${AZURE_ENV_NAME}" 7 | }, 8 | "resourceGroupName": { 9 | "value": "${AZURE_RESOURCE_GROUP}" 10 | }, 11 | "location": { 12 | "value": "${AZURE_LOCATION}" 13 | }, 14 | "principalId": { 15 | "value": "${AZURE_PRINCIPAL_ID}" 16 | }, 17 | "openAiLocation": { 18 | "value": "${AZURE_OPENAI_LOCATION=eastus2}" 19 | }, 20 | "openAiUrl": { 21 | "value": "${AZURE_OPENAI_URL}" 22 | }, 23 | "searchServiceSkuName": { 24 | "value": "${AZURE_SEARCH_SERVICE_SKU=standard}" 25 | }, 26 | "chatGptDeploymentName": { 27 | "value": "${AZURE_OPENAI_CHATGPT_DEPLOYMENT=gpt-4o-mini}" 28 | }, 29 | "indexName": { 30 | "value": "${INDEX_NAME=kbindex}" 31 | }, 32 | "useQdrant": { 33 | "value": "${USE_QDRANT=true}" 34 | }, 35 | "qdrantPort": { 36 | "value": "${QDRANT_PORT=6334}" 37 | }, 38 | "isContinuousDeployment": { 39 | "value": "${CI=false}" 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "azure-openai-rag-workshop-java", 3 | "version": "1.0.0", 4 | "description": "Create your own ChatGPT with Retrieval-Augmented-Generation", 5 | "private": true, 6 | "type": "module", 7 | "directories": { 8 | "doc": "docs" 9 | }, 10 | "scripts": { 11 | "start": "npm run dev --workspace=frontend", 12 | "build": "npm run build -ws --if-present", 13 | "clean": "npm run clean -ws --if-present", 14 | "format": "prettier --list-different --write ." 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/Azure-Samples/azure-openai-rag-workshop-java.git" 19 | }, 20 | "homepage": "https://github.com/Azure-Samples/azure-openai-rag-workshop-java", 21 | "bugs": { 22 | "url": "https://github.com/Azure-Samples/azure-openai-rag-workshop-java/issues" 23 | }, 24 | "keywords": [], 25 | "author": "Microsoft", 26 | "license": "MIT", 27 | "workspaces": [ 28 | "src/frontend" 29 | ], 30 | "devDependencies": { 31 | "prettier": "^3.0.3", 32 | "rimraf": "^5.0.5", 33 | "typescript": "*" 34 | }, 35 | "engines": { 36 | "node": ">=20", 37 | "npm": ">=9" 38 | }, 39 | "prettier": { 40 | "tabWidth": 2, 41 | "semi": true, 42 | "singleQuote": true, 43 | "printWidth": 120, 44 | "bracketSpacing": true, 45 | "overrides": [ 46 | { 47 | "files": [ 48 | "*.json" 49 | ], 50 | "options": { 51 | "parser": "json" 52 | } 53 | } 54 | ] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | ai.azure.openai.rag.workshop 7 | java 8 | 1.0.0-SNAPSHOT 9 | pom 10 | Azure OpenAI RAG Workshop 11 | 12 | 13 | src/backend 14 | src/ingestion 15 | 16 | 17 | -------------------------------------------------------------------------------- /scripts/ingest-data.ps1: -------------------------------------------------------------------------------- 1 | $scriptPath = $MyInvocation.MyCommand.Path 2 | cd $scriptPath/../.. 3 | 4 | Write-Host "Loading azd .env file from current environment" 5 | $output = azd env get-values 6 | 7 | foreach ($line in $output) { 8 | if (!$line.Contains('=')) { 9 | continue 10 | } 11 | 12 | $name, $value = $line.Split("=") 13 | $value = $value -replace '^\"|\"$' 14 | [Environment]::SetEnvironmentVariable($name, $value) 15 | } 16 | 17 | if ([string]::IsNullOrEmpty($env:INGESTION_API_URI)) { 18 | [Environment]::SetEnvironmentVariable('INGESTION_API_URI', 'http://localhost:3001') 19 | } 20 | 21 | if ([string]::IsNullOrEmpty($env:INDEX_NAME)) { 22 | [Environment]::SetEnvironmentVariable('INDEX_NAME', 'kbindex') 23 | } 24 | 25 | Write-Host 'Uploading PDF files to the ingestion API' 26 | Invoke-RestMethod -Uri "$env:INGESTION_API_URI/ingest" -Method Post -InFile "./data/privacy-policy.pdf" 27 | Invoke-RestMethod -Uri "$env:INGESTION_API_URI/ingest" -Method Post -InFile "./data/support.pdf" 28 | Invoke-RestMethod -Uri "$env:INGESTION_API_URI/ingest" -Method Post -InFile "./data/terms-of-service.pdf" 29 | -------------------------------------------------------------------------------- /scripts/ingest-data.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd "$(dirname "${BASH_SOURCE[0]}")/.." 3 | 4 | if azd_env=$(azd env get-values); then 5 | echo "Loading azd .env file from current environment" 6 | export $(echo "$azd_env" | xargs) 7 | fi 8 | 9 | echo 'Uploading PDF files to the ingestion API' 10 | curl -F "file=@./data/privacy-policy.pdf" \ 11 | -F "file=@./data/support.pdf" \ 12 | -F "file=@./data/terms-of-service.pdf" \ 13 | "${INGESTION_API_URI:-http://localhost:3001}/ingest" 14 | -------------------------------------------------------------------------------- /scripts/repo/build-docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ############################################################################## 3 | # Usage: ./build-docs.sh [--local] 4 | # Build the docs and push them to the "docs" branch on GitHub. 5 | ############################################################################## 6 | 7 | set -euo pipefail 8 | cd "$(dirname "${BASH_SOURCE[0]}")/../.." 9 | 10 | DOCS_HOME=/tmp/azure-openai-rag-workshop-java-docs 11 | GH_USER=$(git config user.name) 12 | REPO=https://$GH_USER:$GH_TOKEN@github.com/Azure-Samples/azure-openai-rag-workshop-java.git 13 | 14 | echo "Preparing all workshop docs..." 15 | echo "(temp folder: $DOCS_HOME)" 16 | rm -rf "$DOCS_HOME" 17 | mkdir -p "$DOCS_HOME" 18 | 19 | cp -R docs "$DOCS_HOME" 20 | cd "$DOCS_HOME" 21 | 22 | # Build docs 23 | cd docs 24 | moaw build _workshop-java-quarkus.md -d workshop-java-quarkus.md 25 | moaw build _workshop-java-quarkus.md -d workshop.md 26 | 27 | if [[ ${1-} == "--local" ]]; then 28 | echo "Local mode: skipping GitHub push." 29 | open "$DOCS_HOME" 30 | else 31 | # Update git repo 32 | git init 33 | git checkout -b docs 34 | git remote add origin "$REPO" 35 | git add . 36 | git commit -m "docs: prepare workshop docs" 37 | git push -u origin docs --force 38 | 39 | rm -rf "$DOCS_HOME" 40 | fi 41 | 42 | echo "Successfully updated workshop docs." 43 | -------------------------------------------------------------------------------- /scripts/repo/create-packages.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ############################################################################## 3 | # Usage: ./create-packages.sh 4 | # Creates packages for skippable sections of the workshop 5 | ############################################################################## 6 | 7 | set -euo pipefail 8 | cd "$(dirname "${BASH_SOURCE[0]}")/../.." 9 | 10 | target_folder=dist 11 | 12 | rm -rf "$target_folder" 13 | mkdir -p "$target_folder" 14 | 15 | copyFolder() { 16 | local src="$1" 17 | local dest="$target_folder/${2:-}" 18 | find "$src" -type d -not -path '*node_modules*' -not -path '*/.git' -not -path '*.git/*' -not -path '*/dist' -not -path '*dist/*' -exec mkdir -p '{}' "$dest/{}" ';' 19 | find "$src" -type f -not -path '*node_modules*' -not -path '*.git/*' -not -path '*dist/*' -not -path '*/.DS_Store' -exec cp -r '{}' "$dest/{}" ';' 20 | } 21 | 22 | makeArchive() { 23 | local src="$1" 24 | local name="${2:-$src}" 25 | local archive="$name.tar.gz" 26 | local cwd="${3:-}" 27 | echo "Creating $archive..." 28 | if [[ -n "$cwd" ]]; then 29 | pushd "$target_folder/$cwd" >/dev/null 30 | tar -czvf "../$archive" "$src" 31 | popd 32 | rm -rf "$target_folder/${cwd:?}" 33 | else 34 | pushd "$target_folder/$cwd" >/dev/null 35 | tar -czvf "$archive" "$src" 36 | popd 37 | rm -rf "$target_folder/${src:?}" 38 | fi 39 | } 40 | 41 | ############################################################################## 42 | # Complete solution 43 | ############################################################################## 44 | echo "Creating solution package (for Java + Quarkus)..." 45 | copyFolder . solution-java-quarkus 46 | rm -rf "$target_folder/solution-java-quarkus/.azure" 47 | rm -rf "$target_folder/solution-java-quarkus/.qdrant" 48 | rm -rf "$target_folder/solution-java-quarkus/.env" 49 | rm -rf "$target_folder/solution-java-quarkus/.env*" 50 | rm -rf "$target_folder/solution-java-quarkus/docs" 51 | rm -rf "$target_folder/solution-java-quarkus/trainer" 52 | rm -rf "$target_folder/solution-java-quarkus/scripts/repo" 53 | rm -rf "$target_folder/solution-java-quarkus/.github" 54 | rm -rf "$target_folder/solution-java-quarkus/TODO" 55 | rm -rf "$target_folder/solution-java-quarkus/SUPPORT.md" 56 | rm -rf "$target_folder/solution-java-quarkus/CODE_OF_CONDUCT.md" 57 | rm -rf "$target_folder/solution-java-quarkus/SECURITY.md" 58 | rm -rf "$target_folder/solution-java-quarkus/scripts/setup-template.sh" 59 | perl -pi -e 's/stream: false/stream: true/g' "$target_folder/solution-java-quarkus/src/frontend/src/components/chat.ts" 60 | perl -pi -e 's/qdrant:6333/qdrant:6334/g' "$target_folder/solution-java-quarkus/docker-compose.yml" 61 | makeArchive . solution-java-quarkus solution-java-quarkus 62 | 63 | ############################################################################## 64 | # Frontend 65 | ############################################################################## 66 | 67 | echo "Creating frontend package..." 68 | copyFolder src/frontend 69 | makeArchive src frontend 70 | 71 | ############################################################################## 72 | # Deployment (CI/CD) 73 | ############################################################################## 74 | 75 | echo "Creating CI/CD package..." 76 | mkdir -p "$target_folder/ci-cd/.github/workflows" 77 | cp .github/workflows/deploy.yml "$target_folder/ci-cd/.github/workflows/deploy.yml" 78 | makeArchive . ci-cd ci-cd 79 | -------------------------------------------------------------------------------- /src/backend/.dockerignore: -------------------------------------------------------------------------------- 1 | !target/*-runner 2 | !target/*-runner.jar 3 | !target/lib/* 4 | !target/quarkus-app/* -------------------------------------------------------------------------------- /src/backend/.gitignore: -------------------------------------------------------------------------------- 1 | #Maven 2 | target/ 3 | pom.xml.tag 4 | pom.xml.releaseBackup 5 | pom.xml.versionsBackup 6 | release.properties 7 | .flattened-pom.xml 8 | 9 | # Eclipse 10 | .project 11 | .classpath 12 | .settings/ 13 | bin/ 14 | 15 | # IntelliJ 16 | .idea 17 | *.ipr 18 | *.iml 19 | *.iws 20 | 21 | # NetBeans 22 | nb-configuration.xml 23 | 24 | # Visual Studio Code 25 | .vscode 26 | .factorypath 27 | 28 | # OSX 29 | .DS_Store 30 | 31 | # Vim 32 | *.swp 33 | *.swo 34 | 35 | # patch 36 | *.orig 37 | *.rej 38 | 39 | # Local environment 40 | .env 41 | 42 | # Plugin directory 43 | /.quarkus/cli/plugins/ 44 | 45 | 46 | # Quinoa 47 | node_modules/ 48 | build/ 49 | dist/ 50 | .quinoa/ 51 | dist-ssr 52 | *.local 53 | -------------------------------------------------------------------------------- /src/backend/.mvn/wrapper/.gitignore: -------------------------------------------------------------------------------- 1 | maven-wrapper.jar 2 | -------------------------------------------------------------------------------- /src/backend/.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | import java.net.Authenticator; 23 | import java.net.PasswordAuthentication; 24 | import java.net.URL; 25 | import java.nio.file.Files; 26 | import java.nio.file.Path; 27 | import java.nio.file.Paths; 28 | import java.nio.file.StandardCopyOption; 29 | 30 | public final class MavenWrapperDownloader 31 | { 32 | private static final String WRAPPER_VERSION = "3.2.0"; 33 | 34 | private static final boolean VERBOSE = Boolean.parseBoolean( System.getenv( "MVNW_VERBOSE" ) ); 35 | 36 | public static void main( String[] args ) 37 | { 38 | log( "Apache Maven Wrapper Downloader " + WRAPPER_VERSION ); 39 | 40 | if ( args.length != 2 ) 41 | { 42 | System.err.println( " - ERROR wrapperUrl or wrapperJarPath parameter missing" ); 43 | System.exit( 1 ); 44 | } 45 | 46 | try 47 | { 48 | log( " - Downloader started" ); 49 | final URL wrapperUrl = new URL( args[0] ); 50 | final String jarPath = args[1].replace( "..", "" ); // Sanitize path 51 | final Path wrapperJarPath = Paths.get( jarPath ).toAbsolutePath().normalize(); 52 | downloadFileFromURL( wrapperUrl, wrapperJarPath ); 53 | log( "Done" ); 54 | } 55 | catch ( IOException e ) 56 | { 57 | System.err.println( "- Error downloading: " + e.getMessage() ); 58 | if ( VERBOSE ) 59 | { 60 | e.printStackTrace(); 61 | } 62 | System.exit( 1 ); 63 | } 64 | } 65 | 66 | private static void downloadFileFromURL( URL wrapperUrl, Path wrapperJarPath ) 67 | throws IOException 68 | { 69 | log( " - Downloading to: " + wrapperJarPath ); 70 | if ( System.getenv( "MVNW_USERNAME" ) != null && System.getenv( "MVNW_PASSWORD" ) != null ) 71 | { 72 | final String username = System.getenv( "MVNW_USERNAME" ); 73 | final char[] password = System.getenv( "MVNW_PASSWORD" ).toCharArray(); 74 | Authenticator.setDefault( new Authenticator() 75 | { 76 | @Override 77 | protected PasswordAuthentication getPasswordAuthentication() 78 | { 79 | return new PasswordAuthentication( username, password ); 80 | } 81 | } ); 82 | } 83 | try ( InputStream inStream = wrapperUrl.openStream() ) 84 | { 85 | Files.copy( inStream, wrapperJarPath, StandardCopyOption.REPLACE_EXISTING ); 86 | } 87 | log( " - Downloader complete" ); 88 | } 89 | 90 | private static void log( String msg ) 91 | { 92 | if ( VERBOSE ) 93 | { 94 | System.out.println( msg ); 95 | } 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/backend/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 19 | -------------------------------------------------------------------------------- /src/backend/src/main/docker/Dockerfile.jvm: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode 3 | # 4 | # Before building the container image run: 5 | # 6 | # ./mvnw package 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.jvm -t quarkus/java-quarkus-jvm . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/java-quarkus-jvm 15 | # 16 | # If you want to include the debug port into your docker image 17 | # you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005. 18 | # Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005 19 | # when running the container 20 | # 21 | # Then run the container using : 22 | # 23 | # docker run -i --rm -p 8080:8080 quarkus/java-quarkus-jvm 24 | # 25 | # This image uses the `run-java.sh` script to run the application. 26 | # This scripts computes the command line to execute your Java application, and 27 | # includes memory/GC tuning. 28 | # You can configure the behavior using the following environment properties: 29 | # - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") 30 | # - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options 31 | # in JAVA_OPTS (example: "-Dsome.property=foo") 32 | # - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is 33 | # used to calculate a default maximal heap memory based on a containers restriction. 34 | # If used in a container without any memory constraints for the container then this 35 | # option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio 36 | # of the container available memory as set here. The default is `50` which means 50% 37 | # of the available memory is used as an upper boundary. You can skip this mechanism by 38 | # setting this value to `0` in which case no `-Xmx` option is added. 39 | # - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This 40 | # is used to calculate a default initial heap memory based on the maximum heap memory. 41 | # If used in a container without any memory constraints for the container then this 42 | # option has no effect. If there is a memory constraint then `-Xms` is set to a ratio 43 | # of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx` 44 | # is used as the initial heap size. You can skip this mechanism by setting this value 45 | # to `0` in which case no `-Xms` option is added (example: "25") 46 | # - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS. 47 | # This is used to calculate the maximum value of the initial heap memory. If used in 48 | # a container without any memory constraints for the container then this option has 49 | # no effect. If there is a memory constraint then `-Xms` is limited to the value set 50 | # here. The default is 4096MB which means the calculated value of `-Xms` never will 51 | # be greater than 4096MB. The value of this variable is expressed in MB (example: "4096") 52 | # - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output 53 | # when things are happening. This option, if set to true, will set 54 | # `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true"). 55 | # - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example: 56 | # true"). 57 | # - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787"). 58 | # - CONTAINER_CORE_LIMIT: A calculated core limit as described in 59 | # https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2") 60 | # - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024"). 61 | # - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion. 62 | # (example: "20") 63 | # - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking. 64 | # (example: "40") 65 | # - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection. 66 | # (example: "4") 67 | # - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus 68 | # previous GC times. (example: "90") 69 | # - GC_METASPACE_SIZE: The initial metaspace size. (example: "20") 70 | # - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100") 71 | # - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should 72 | # contain the necessary JRE command-line options to specify the required GC, which 73 | # will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC). 74 | # - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080") 75 | # - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080") 76 | # - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be 77 | # accessed directly. (example: "foo.example.com,bar.example.com") 78 | # 79 | ### 80 | FROM registry.access.redhat.com/ubi8/openjdk-17:1.18 81 | 82 | ENV LANGUAGE='en_US:en' 83 | 84 | 85 | # We make four distinct layers so if there are application changes the library layers can be re-used 86 | COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/ 87 | COPY --chown=185 target/quarkus-app/*.jar /deployments/ 88 | COPY --chown=185 target/quarkus-app/app/ /deployments/app/ 89 | COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/ 90 | 91 | EXPOSE 8080 92 | USER 185 93 | ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" 94 | ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" 95 | 96 | ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ] 97 | 98 | -------------------------------------------------------------------------------- /src/backend/src/main/docker/Dockerfile.legacy-jar: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode 3 | # 4 | # Before building the container image run: 5 | # 6 | # ./mvnw package -Dquarkus.package.type=legacy-jar 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/java-quarkus-legacy-jar . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/java-quarkus-legacy-jar 15 | # 16 | # If you want to include the debug port into your docker image 17 | # you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005. 18 | # Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005 19 | # when running the container 20 | # 21 | # Then run the container using : 22 | # 23 | # docker run -i --rm -p 8080:8080 quarkus/java-quarkus-legacy-jar 24 | # 25 | # This image uses the `run-java.sh` script to run the application. 26 | # This scripts computes the command line to execute your Java application, and 27 | # includes memory/GC tuning. 28 | # You can configure the behavior using the following environment properties: 29 | # - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") 30 | # - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options 31 | # in JAVA_OPTS (example: "-Dsome.property=foo") 32 | # - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is 33 | # used to calculate a default maximal heap memory based on a containers restriction. 34 | # If used in a container without any memory constraints for the container then this 35 | # option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio 36 | # of the container available memory as set here. The default is `50` which means 50% 37 | # of the available memory is used as an upper boundary. You can skip this mechanism by 38 | # setting this value to `0` in which case no `-Xmx` option is added. 39 | # - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This 40 | # is used to calculate a default initial heap memory based on the maximum heap memory. 41 | # If used in a container without any memory constraints for the container then this 42 | # option has no effect. If there is a memory constraint then `-Xms` is set to a ratio 43 | # of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx` 44 | # is used as the initial heap size. You can skip this mechanism by setting this value 45 | # to `0` in which case no `-Xms` option is added (example: "25") 46 | # - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS. 47 | # This is used to calculate the maximum value of the initial heap memory. If used in 48 | # a container without any memory constraints for the container then this option has 49 | # no effect. If there is a memory constraint then `-Xms` is limited to the value set 50 | # here. The default is 4096MB which means the calculated value of `-Xms` never will 51 | # be greater than 4096MB. The value of this variable is expressed in MB (example: "4096") 52 | # - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output 53 | # when things are happening. This option, if set to true, will set 54 | # `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true"). 55 | # - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example: 56 | # true"). 57 | # - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787"). 58 | # - CONTAINER_CORE_LIMIT: A calculated core limit as described in 59 | # https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2") 60 | # - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024"). 61 | # - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion. 62 | # (example: "20") 63 | # - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking. 64 | # (example: "40") 65 | # - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection. 66 | # (example: "4") 67 | # - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus 68 | # previous GC times. (example: "90") 69 | # - GC_METASPACE_SIZE: The initial metaspace size. (example: "20") 70 | # - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100") 71 | # - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should 72 | # contain the necessary JRE command-line options to specify the required GC, which 73 | # will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC). 74 | # - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080") 75 | # - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080") 76 | # - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be 77 | # accessed directly. (example: "foo.example.com,bar.example.com") 78 | # 79 | ### 80 | FROM registry.access.redhat.com/ubi8/openjdk-17:1.18 81 | 82 | ENV LANGUAGE='en_US:en' 83 | 84 | 85 | COPY target/lib/* /deployments/lib/ 86 | COPY target/*-runner.jar /deployments/quarkus-run.jar 87 | 88 | EXPOSE 8080 89 | USER 185 90 | ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" 91 | ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" 92 | 93 | ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ] 94 | -------------------------------------------------------------------------------- /src/backend/src/main/docker/Dockerfile.native: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. 3 | # 4 | # Before building the container image run: 5 | # 6 | # ./mvnw package -Dnative 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.native -t quarkus/java-quarkus . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/java-quarkus 15 | # 16 | ### 17 | FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 18 | WORKDIR /work/ 19 | RUN chown 1001 /work \ 20 | && chmod "g+rwX" /work \ 21 | && chown 1001:root /work 22 | COPY --chown=1001:root target/*-runner /work/application 23 | 24 | EXPOSE 8080 25 | USER 1001 26 | 27 | ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] 28 | -------------------------------------------------------------------------------- /src/backend/src/main/docker/Dockerfile.native-micro: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. 3 | # It uses a micro base image, tuned for Quarkus native executables. 4 | # It reduces the size of the resulting container image. 5 | # Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image. 6 | # 7 | # Before building the container image run: 8 | # 9 | # ./mvnw package -Dnative 10 | # 11 | # Then, build the image with: 12 | # 13 | # docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/java-quarkus . 14 | # 15 | # Then run the container using: 16 | # 17 | # docker run -i --rm -p 8080:8080 quarkus/java-quarkus 18 | # 19 | ### 20 | FROM quay.io/quarkus/quarkus-micro-image:2.0 21 | WORKDIR /work/ 22 | RUN chown 1001 /work \ 23 | && chmod "g+rwX" /work \ 24 | && chown 1001:root /work 25 | COPY --chown=1001:root target/*-runner /work/application 26 | 27 | EXPOSE 8080 28 | USER 1001 29 | 30 | ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] 31 | -------------------------------------------------------------------------------- /src/backend/src/main/java/ai/azure/openai/rag/workshop/backend/configuration/ChatLanguageModelAzureOpenAiProducer.java: -------------------------------------------------------------------------------- 1 | package ai.azure.openai.rag.workshop.backend.configuration; 2 | 3 | import dev.langchain4j.model.azure.AzureOpenAiChatModel; 4 | import dev.langchain4j.model.chat.ChatLanguageModel; 5 | import jakarta.enterprise.inject.Produces; 6 | import org.eclipse.microprofile.config.inject.ConfigProperty; 7 | 8 | import com.azure.core.credential.TokenRequestContext; 9 | import com.azure.identity.DefaultAzureCredential; 10 | import com.azure.identity.DefaultAzureCredentialBuilder; 11 | 12 | import static java.time.Duration.ofSeconds; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | public class ChatLanguageModelAzureOpenAiProducer { 17 | 18 | private static final Logger log = LoggerFactory.getLogger(ChatLanguageModelAzureOpenAiProducer.class); 19 | 20 | @ConfigProperty(name = "AZURE_OPENAI_URL") 21 | String azureOpenAiEndpoint; 22 | 23 | @ConfigProperty(name = "AZURE_OPENAI_DEPLOYMENT_NAME", defaultValue = "gpt-4o-mini") 24 | String azureOpenAiDeploymentName; 25 | 26 | @Produces 27 | public ChatLanguageModel chatLanguageModel() { 28 | // initialize chat model here 29 | AzureOpenAiChatModel model; 30 | 31 | try { 32 | // Use the current user identity to authenticate with Azure OpenAI. 33 | // (no secrets needed, just use `az login` or `azd auth login` locally, and managed identity when deployed on Azure). 34 | DefaultAzureCredential credentials = new DefaultAzureCredentialBuilder().build(); 35 | 36 | // Try requesting a token, so we can fallback to the other builder if it doesn't work 37 | TokenRequestContext request = new TokenRequestContext(); 38 | request.addScopes("https://cognitiveservices.azure.com/.default"); 39 | credentials.getTokenSync(request); 40 | 41 | model = AzureOpenAiChatModel.builder() 42 | .tokenCredential(credentials) 43 | .endpoint(azureOpenAiEndpoint) 44 | .deploymentName(azureOpenAiDeploymentName) 45 | .timeout(ofSeconds(60)) 46 | .logRequestsAndResponses(true) 47 | .build(); 48 | } catch (Exception e) { 49 | // Default value for local execution 50 | log.info("### Using fallback configuration for OpenAI"); 51 | model = AzureOpenAiChatModel.builder() 52 | .apiKey("__dummy") 53 | .endpoint(azureOpenAiEndpoint) 54 | .deploymentName(azureOpenAiDeploymentName) 55 | .timeout(ofSeconds(60)) 56 | .logRequestsAndResponses(true) 57 | .build(); 58 | } 59 | 60 | log.info("### Producing ChatLanguageModel with AzureOpenAiChatModel"); 61 | 62 | return model; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/backend/src/main/java/ai/azure/openai/rag/workshop/backend/configuration/ChatLanguageModelOllamaProducer.java: -------------------------------------------------------------------------------- 1 | package ai.azure.openai.rag.workshop.backend.configuration; 2 | 3 | import dev.langchain4j.model.chat.ChatLanguageModel; 4 | import dev.langchain4j.model.ollama.OllamaChatModel; 5 | import jakarta.enterprise.inject.Alternative; 6 | import jakarta.enterprise.inject.Produces; 7 | import static java.time.Duration.ofSeconds; 8 | import org.eclipse.microprofile.config.inject.ConfigProperty; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | @Alternative 13 | public class ChatLanguageModelOllamaProducer { 14 | 15 | private static final Logger log = LoggerFactory.getLogger(ChatLanguageModelOllamaProducer.class); 16 | 17 | @ConfigProperty(name = "OLLAMA_BASE_URL", defaultValue = "http://localhost:11434") 18 | String ollamaBaseUrl; 19 | 20 | @ConfigProperty(name = "OLLAMA_MODEL_NAME", defaultValue = "mistral") 21 | String ollamaModelName; 22 | 23 | @Produces 24 | public ChatLanguageModel chatLanguageModel() { 25 | 26 | log.info("### Producing ChatLanguageModel with OllamaChatModel"); 27 | 28 | return OllamaChatModel.builder() 29 | .baseUrl(ollamaBaseUrl) 30 | .modelName(ollamaModelName) 31 | .timeout(ofSeconds(60)) 32 | .build(); 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/backend/src/main/java/ai/azure/openai/rag/workshop/backend/configuration/EmbeddingModelProducer.java: -------------------------------------------------------------------------------- 1 | package ai.azure.openai.rag.workshop.backend.configuration; 2 | 3 | import dev.langchain4j.model.embedding.onnx.allminilml6v2.AllMiniLmL6V2EmbeddingModel; 4 | import dev.langchain4j.model.embedding.EmbeddingModel; 5 | import jakarta.enterprise.inject.Produces; 6 | 7 | public class EmbeddingModelProducer { 8 | 9 | @Produces 10 | public EmbeddingModel embeddingModel() { 11 | return new AllMiniLmL6V2EmbeddingModel(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/backend/src/main/java/ai/azure/openai/rag/workshop/backend/configuration/EmbeddingStoreProducer.java: -------------------------------------------------------------------------------- 1 | package ai.azure.openai.rag.workshop.backend.configuration; 2 | 3 | import dev.langchain4j.data.segment.TextSegment; 4 | import dev.langchain4j.store.embedding.EmbeddingStore; 5 | import dev.langchain4j.store.embedding.qdrant.QdrantEmbeddingStore; 6 | import jakarta.enterprise.inject.Produces; 7 | import org.eclipse.microprofile.config.inject.ConfigProperty; 8 | 9 | import java.net.URI; 10 | import java.net.URISyntaxException; 11 | 12 | public class EmbeddingStoreProducer { 13 | 14 | @ConfigProperty(name = "AZURE_SEARCH_INDEX", defaultValue = "kbindex") 15 | String azureSearchIndexName; 16 | 17 | @ConfigProperty(name = "QDRANT_URL", defaultValue = "http://localhost:6334") 18 | String qdrantUrl; 19 | 20 | @Produces 21 | public EmbeddingStore embeddingStore() throws URISyntaxException { 22 | String qdrantHostname = new URI(qdrantUrl).getHost(); 23 | int qdrantPort = new URI(qdrantUrl).getPort(); 24 | return QdrantEmbeddingStore.builder() 25 | .collectionName(azureSearchIndexName) 26 | .host(qdrantHostname) 27 | .port(qdrantPort) 28 | .build(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/backend/src/main/java/ai/azure/openai/rag/workshop/backend/rest/ChatMessage.java: -------------------------------------------------------------------------------- 1 | package ai.azure.openai.rag.workshop.backend.rest; 2 | 3 | public class ChatMessage { 4 | 5 | public String content; 6 | public String role; 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/backend/src/main/java/ai/azure/openai/rag/workshop/backend/rest/ChatRequest.java: -------------------------------------------------------------------------------- 1 | package ai.azure.openai.rag.workshop.backend.rest; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class ChatRequest { 7 | 8 | public List messages = new ArrayList<>(); 9 | public double temperature = 1f; 10 | public double topP = 1f; 11 | public String user; 12 | } 13 | -------------------------------------------------------------------------------- /src/backend/src/main/java/ai/azure/openai/rag/workshop/backend/rest/ChatResource.java: -------------------------------------------------------------------------------- 1 | package ai.azure.openai.rag.workshop.backend.rest; 2 | 3 | import dev.langchain4j.data.embedding.Embedding; 4 | import dev.langchain4j.data.message.AiMessage; 5 | import dev.langchain4j.data.message.ChatMessage; 6 | import dev.langchain4j.data.message.SystemMessage; 7 | import dev.langchain4j.data.message.UserMessage; 8 | import dev.langchain4j.data.segment.TextSegment; 9 | import dev.langchain4j.model.chat.ChatLanguageModel; 10 | import dev.langchain4j.model.embedding.EmbeddingModel; 11 | import dev.langchain4j.model.output.Response; 12 | import dev.langchain4j.store.embedding.EmbeddingMatch; 13 | import dev.langchain4j.store.embedding.EmbeddingSearchRequest; 14 | import dev.langchain4j.store.embedding.EmbeddingSearchResult; 15 | import dev.langchain4j.store.embedding.EmbeddingStore; 16 | import jakarta.inject.Inject; 17 | import jakarta.ws.rs.*; 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | @Path("/chat") 25 | public class ChatResource { 26 | 27 | private static final Logger log = LoggerFactory.getLogger(ChatResource.class); 28 | 29 | private static final String SYSTEM_MESSAGE_PROMPT = """ 30 | Assistant helps the Consto Real Estate company customers with support questions regarding terms of service, privacy policy, and questions about support requests. 31 | Be brief in your answers. 32 | Answer ONLY with the facts listed in the list of sources below. 33 | If there isn't enough information below, say you don't know. 34 | Do not generate answers that don't use the sources below. 35 | If asking a clarifying question to the user would help, ask the question. 36 | For tabular information return it as an html table. 37 | Do not return markdown format. 38 | If the question is not in English, answer in the language used in the question. 39 | Each source has a name followed by colon and the actual information, always include the source name for each fact you use in the response. 40 | Use square brackets to reference the source, for example: [info1.txt]. 41 | Don't combine sources, list each source separately, for example: [info1.txt][info2.pdf]. 42 | """; 43 | 44 | @Inject 45 | EmbeddingModel embeddingModel; 46 | 47 | @Inject 48 | EmbeddingStore embeddingStore; 49 | 50 | @Inject 51 | ChatLanguageModel chatLanguageModel; 52 | 53 | @POST 54 | @Consumes({"application/json"}) 55 | @Produces({"application/json"}) 56 | public ChatResponse chat(ChatRequest chatRequest) { 57 | 58 | String question = chatRequest.messages.get(chatRequest.messages.size() - 1).content; 59 | 60 | // Embed the question (convert the user's question into vectors that represent the meaning) 61 | log.info("### Embed the question (convert the question into vectors that represent the meaning) using embeddedQuestion model"); 62 | Embedding embeddedQuestion = embeddingModel.embed(question).content(); 63 | log.debug("# Vector length: {}", embeddedQuestion.vector().length); 64 | 65 | // Find relevant embeddings from Qdrant based on the user's question 66 | log.info("### Find relevant embeddings from Qdrant based on the question"); 67 | EmbeddingSearchResult relevant = embeddingStore.search(EmbeddingSearchRequest.builder() 68 | .queryEmbedding(embeddedQuestion) 69 | .build()); 70 | 71 | // Builds chat history using the relevant embeddings 72 | log.info("### Builds chat history using the relevant embeddings"); 73 | List chatMessages = new ArrayList<>(); 74 | chatMessages.add(SystemMessage.from(SYSTEM_MESSAGE_PROMPT)); 75 | String userMessage = question + "\n\nSources:\n"; 76 | for (EmbeddingMatch textSegmentEmbeddingMatch : relevant.matches()) { 77 | userMessage += textSegmentEmbeddingMatch.embedded().metadata().getString("filename") + ": " + textSegmentEmbeddingMatch.embedded().text() + "\n"; 78 | } 79 | chatMessages.add(UserMessage.from(userMessage)); 80 | 81 | // Invoke the LLM 82 | log.info("### Invoke the LLM"); 83 | Response response = chatLanguageModel.generate(chatMessages); 84 | 85 | // Return the response 86 | return ChatResponse.fromMessage(response.content().text()); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/backend/src/main/java/ai/azure/openai/rag/workshop/backend/rest/ChatResponse.java: -------------------------------------------------------------------------------- 1 | package ai.azure.openai.rag.workshop.backend.rest; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class ChatResponse { 7 | 8 | /** 9 | * Create a ChatResponse when there is only one message to return. 10 | */ 11 | public static ChatResponse fromMessage(String message) { 12 | ChatResponse chatResponse = new ChatResponse(); 13 | ChatResponse.Choice choice = new ChatResponse.Choice(); 14 | choice.index = 0; 15 | choice.message = new ai.azure.openai.rag.workshop.backend.rest.ChatMessage(); 16 | choice.message.content = message; 17 | choice.message.role = "assistant"; 18 | chatResponse.choices.add(choice); 19 | return chatResponse; 20 | } 21 | 22 | public List choices = new ArrayList<>(); 23 | 24 | public static class Choice { 25 | public int index; 26 | public ChatMessage message; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/backend/src/main/plantuml/class-diagram-model.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | skinparam dpi 600 3 | 4 | allow_mixing 5 | hide empty members 6 | 7 | 'Image 8 | class ChatMessage { 9 | +String message 10 | } 11 | class ChatRequest 12 | class ChatResponse 13 | class Choice 14 | ChatRequest --> "*" ChatMessage 15 | ChatResponse --> "*" Choice 16 | Choice --> ChatMessage 17 | @enduml 18 | -------------------------------------------------------------------------------- /src/backend/src/main/plantuml/class-diagram-rest.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | skinparam dpi 600 3 | 4 | allow_mixing 5 | hide empty members 6 | 'left to right direction 7 | 8 | 'Image 9 | class ChatResource { 10 | +ChatResponse chat(ChatRequest) 11 | } 12 | class ChatLanguageModelProducer 13 | class EmbeddingModelProducer 14 | class EmbeddingStoreProducer 15 | ChatResource <.. ChatLanguageModelProducer: @Inject 16 | ChatResource <.. EmbeddingModelProducer: @Inject 17 | ChatResource <.. EmbeddingStoreProducer: @Inject 18 | @enduml 19 | -------------------------------------------------------------------------------- /src/backend/src/main/resources/META-INF/resources/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/src/backend/src/main/resources/META-INF/resources/favicon.ico -------------------------------------------------------------------------------- /src/backend/src/main/resources/META-INF/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ChatGPT with Enterprise Data 9 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/backend/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | quarkus.http.port=3000 2 | quarkus.log.level=INFO 3 | quarkus.log.category."ai.azure.openai.rag.workshop.backend".level=DEBUG 4 | #quarkus.arc.selected-alternatives=ai.azure.openai.rag.workshop.backend.configuration.ChatLanguageModelOllamaProducer 5 | -------------------------------------------------------------------------------- /src/backend/src/main/resources/default_banner.txt: -------------------------------------------------------------------------------- 1 | ______ _ _ 2 | | ___ \ | | | | 3 | | |_/ / __ _ ___| | _____ _ __ __| | 4 | | ___ \/ _` |/ __| |/ / _ \ '_ \ / _` | 5 | | |_/ / (_| | (__| < __/ | | | (_| | 6 | \____/ \__,_|\___|_|\_\___|_| |_|\__,_| 7 | 8 | -------------------------------------------------------------------------------- /src/backend/src/main/script/curl-chat-endpoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ############################################################################## 3 | # Usage: ./curl-chat-endpoint.sh 4 | # Curls the Chat Endpoint with a POST request 5 | ############################################################################## 6 | 7 | curl -X 'POST' \ 8 | 'http://localhost:3000/chat' \ 9 | -H 'accept: */*' \ 10 | -H 'Content-Type: application/json' \ 11 | -d '{ 12 | "messages": [ 13 | { 14 | "content": "What is the information that is collected automatically?", 15 | "role": "user" 16 | } 17 | ], 18 | "model": "gpt-4o-mini", 19 | "temperature": 0.7, 20 | "topP": 0.5, 21 | "user": "joedoe" 22 | }' 23 | -------------------------------------------------------------------------------- /src/frontend/.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,jsx,ts,tsx}": ["eslint --fix", "lit-analyzer"], 3 | "*": ["prettier --ignore-unknown --write"] 4 | } 5 | -------------------------------------------------------------------------------- /src/frontend/README.md: -------------------------------------------------------------------------------- 1 | # Chat frontend 2 | 3 | This project uses [Vite](https://vitejs.dev/) as a frontend build tool, and [Lit](https://lit.dev/) as a web components library. 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm run dev` 10 | 11 | To start the app in dev mode.\ 12 | Open [http://localhost:8000](http://localhost:8000) to view it in the browser. 13 | 14 | ### `npm run build` 15 | 16 | To build the app for production to the `dist` folder. 17 | -------------------------------------------------------------------------------- /src/frontend/assets/lightbulb.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/frontend/assets/new-chat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/frontend/assets/question.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/frontend/assets/send.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ChatGPT with Enterprise Data 9 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "1.0.0", 4 | "description": "Frontend for the ChatGPT RAG workshop", 5 | "private": true, 6 | "type": "module", 7 | "scripts": { 8 | "dev": "vite --port 8000 --host", 9 | "build": "vite build", 10 | "watch": "vite build --watch --minify false", 11 | "lint": "lit-analyzer", 12 | "clean": "npx rimraf dist" 13 | }, 14 | "author": "Microsoft", 15 | "license": "MIT", 16 | "dependencies": { 17 | "lit": "^3.0.0" 18 | }, 19 | "devDependencies": { 20 | "lit-analyzer": "^2.0.1", 21 | "typescript": "^5.2.2", 22 | "vite": "^5.0.12" 23 | }, 24 | "files": ["dist"] 25 | } 26 | -------------------------------------------------------------------------------- /src/frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/src/frontend/public/favicon.ico -------------------------------------------------------------------------------- /src/frontend/src/api.ts: -------------------------------------------------------------------------------- 1 | import { type ChatResponse, type ChatRequestOptions, type ChatResponseChunk } from './models.js'; 2 | 3 | export const apiBaseUrl = import.meta.env.VITE_BACKEND_API_URI || ''; 4 | 5 | export async function getCompletion(options: ChatRequestOptions) { 6 | const apiUrl = options.apiUrl || apiBaseUrl; 7 | const response = await fetch(`${apiUrl}/chat`, { 8 | method: 'POST', 9 | headers: { 'Content-Type': 'application/json' }, 10 | body: JSON.stringify({ 11 | messages: options.messages, 12 | stream: options.stream, 13 | context: { 14 | top: options.top, 15 | temperature: options.temperature, 16 | }, 17 | }), 18 | }); 19 | 20 | if (options.stream) { 21 | return getChunksFromResponse(response as Response, options.chunkIntervalMs); 22 | } 23 | 24 | const json: ChatResponse = await response.json(); 25 | if (response.status > 299 || !response.ok) { 26 | throw new Error(json.error || 'Unknown error'); 27 | } 28 | 29 | return json; 30 | } 31 | 32 | export function getCitationUrl(citation: string): string { 33 | return `${apiBaseUrl}/content/${citation}`; 34 | } 35 | 36 | export class NdJsonParserStream extends TransformStream { 37 | private buffer: string = ''; 38 | constructor() { 39 | let controller: TransformStreamDefaultController; 40 | super({ 41 | start: (_controller) => { 42 | controller = _controller; 43 | }, 44 | transform: (chunk) => { 45 | const jsonChunks = chunk.split('\n').filter(Boolean); 46 | for (const jsonChunk of jsonChunks) { 47 | try { 48 | this.buffer += jsonChunk; 49 | controller.enqueue(JSON.parse(this.buffer)); 50 | this.buffer = ''; 51 | } catch { 52 | // Invalid JSON, wait for next chunk 53 | } 54 | } 55 | }, 56 | }); 57 | } 58 | } 59 | 60 | export async function* getChunksFromResponse(response: Response, intervalMs: number): AsyncGenerator { 61 | const reader = response.body?.pipeThrough(new TextDecoderStream()).pipeThrough(new NdJsonParserStream()).getReader(); 62 | if (!reader) { 63 | throw new Error('No response body or body is not readable'); 64 | } 65 | 66 | let value: JSON | undefined; 67 | let done: boolean; 68 | while ((({ value, done } = await reader.read()), !done)) { 69 | yield new Promise((resolve) => { 70 | setTimeout(() => { 71 | resolve(value as T); 72 | }, intervalMs); 73 | }); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/frontend/src/components/debug.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable unicorn/template-indent */ 2 | import { LitElement, css, html } from 'lit'; 3 | import { map } from 'lit/directives/map.js'; 4 | import { customElement, property } from 'lit/decorators.js'; 5 | import { unsafeHTML } from 'lit/directives/unsafe-html.js'; 6 | import { type ChatDebugDetails } from '../models.js'; 7 | 8 | export type DebugComponentOptions = { 9 | strings: { 10 | thoughtsTitle: string; 11 | supportingContentTitle: string; 12 | }; 13 | }; 14 | 15 | @customElement('azc-debug') 16 | export class DebugComponent extends LitElement { 17 | @property({ type: Object }) details: ChatDebugDetails = { thoughts: '', dataPoints: [] }; 18 | @property({ type: Object }) options!: DebugComponentOptions; 19 | @property({ type: Boolean }) showThoughtProcess = true; 20 | 21 | protected renderThoughtProcess = (thoughtProcess: string) => { 22 | return html`${unsafeHTML(thoughtProcess)}`; 23 | }; 24 | 25 | protected renderDataPoints = (dataPoints: string[]) => { 26 | const infos = dataPoints.map((dataPoint) => { 27 | const [title, ...extract] = dataPoint.split(':'); 28 | return { title, extract: extract.join(':') }; 29 | }); 30 | return html`
31 | ${map( 32 | infos, 33 | (info) => 34 | html`
35 |
${info.title}
36 |
${info.extract}
37 |
`, 38 | )} 39 |
`; 40 | }; 41 | 42 | protected override render() { 43 | return html``; 59 | } 60 | 61 | static override styles = css` 62 | *:focus-visible { 63 | outline: var(--focus-outline) var(--primary); 64 | } 65 | button { 66 | padding: var(--space-md); 67 | font-size: 1rem; 68 | outline: var(--focus-outline) transparent; 69 | transition: outline 0.3s ease; 70 | border: none; 71 | 72 | &:not(:disabled) { 73 | cursor: pointer; 74 | } 75 | &:hover:not(:disabled) { 76 | // TODO: separate out hover style 77 | background: var(--submit-button-bg-hover); 78 | } 79 | } 80 | .active { 81 | border-bottom: 3px solid var(--primary); 82 | } 83 | .nav { 84 | padding-bottom: var(--space-md); 85 | } 86 | .debug-container { 87 | position: absolute; 88 | inset: var(--space-xl); 89 | display: flex; 90 | flex-direction: column; 91 | border-radius: var(--border-radius); 92 | background: var(--bg); 93 | overflow: hidden; 94 | padding: var(--space-xl); 95 | margin: 0px auto; 96 | max-width: 1024px; 97 | } 98 | .content { 99 | flex: 1; 100 | display: flex; 101 | flex-direction: column; 102 | overflow: auto; 103 | } 104 | .title { 105 | font-weight: bold; 106 | margin-bottom: var(--space-md); 107 | } 108 | .card { 109 | padding: var(--space-md); 110 | margin-bottom: var(--space-md); 111 | border-radius: var(--border-radius); 112 | // TODO: separate out card styles 113 | color: var(--bot-message-color); 114 | background: var(--bot-message-bg); 115 | border: var(--bot-message-border); 116 | box-shadow: var(--card-shadow); 117 | } 118 | `; 119 | } 120 | 121 | declare global { 122 | interface HTMLElementTagNameMap { 123 | 'azc-debug': DebugComponent; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/frontend/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './api.js'; 2 | export * from './components/chat.js'; 3 | export * from './components/debug.js'; 4 | export * from './message-parser.js'; 5 | export * from './models.js'; 6 | -------------------------------------------------------------------------------- /src/frontend/src/message-parser.ts: -------------------------------------------------------------------------------- 1 | import { type HTMLTemplateResult, html, nothing } from 'lit'; 2 | import { type ChatMessage, type ChatMessageContext } from './models.js'; 3 | 4 | export type ParsedMessage = { 5 | html: HTMLTemplateResult; 6 | citations: string[]; 7 | followupQuestions: string[]; 8 | role: string; 9 | context?: ChatMessageContext; 10 | }; 11 | 12 | export function parseMessageIntoHtml( 13 | message: ChatMessage, 14 | renderCitationReference: (citation: string, index: number) => HTMLTemplateResult, 15 | ): ParsedMessage { 16 | if (message.role === 'user') { 17 | return { 18 | html: html`${message.content}`, 19 | citations: [], 20 | followupQuestions: [], 21 | role: message.role, 22 | context: message.context, 23 | }; 24 | } 25 | 26 | const citations: string[] = []; 27 | const followupQuestions: string[] = []; 28 | 29 | // Extract any follow-up questions that might be in the message 30 | const text = message.content 31 | .replaceAll(/<<([^>]+)>>/g, (_match, content) => { 32 | followupQuestions.push(content); 33 | return ''; 34 | }) 35 | .split('<<')[0] // Truncate incomplete questions 36 | .trim(); 37 | 38 | // Extract any citations that might be in the message 39 | const parts = text.split(/\[([^\]]+)]/g); 40 | const result = html`${parts.map((part, index) => { 41 | if (index % 2 === 0) { 42 | return html`${part}`; 43 | } else if (index + 1 < parts.length) { 44 | // Handle only completed citations 45 | let citationIndex = citations.indexOf(part); 46 | if (citationIndex === -1) { 47 | citations.push(part); 48 | citationIndex = citations.length; 49 | } else { 50 | citationIndex++; 51 | } 52 | return renderCitationReference(part, citationIndex); 53 | } else { 54 | return nothing; 55 | } 56 | })}`; 57 | 58 | return { 59 | html: result, 60 | citations, 61 | followupQuestions, 62 | role: message.role, 63 | context: message.context, 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /src/frontend/src/models.ts: -------------------------------------------------------------------------------- 1 | export type Message = { 2 | content: string; 3 | role: string; 4 | }; 5 | 6 | export type ChatDebugDetails = { 7 | thoughts: string; 8 | dataPoints: string[]; 9 | }; 10 | 11 | export type ChatMessageContext = Record & { 12 | thoughts?: string; 13 | data_points?: string[]; 14 | }; 15 | 16 | export type ChatMessage = Message & { 17 | context?: ChatMessageContext; 18 | }; 19 | 20 | export type ChatResponse = { 21 | choices: Array<{ 22 | index: number; 23 | message: ChatMessage; 24 | }>; 25 | error?: string; 26 | }; 27 | 28 | export type ChatResponseChunk = { 29 | choices: Array<{ 30 | index: number; 31 | delta: Partial; 32 | }>; 33 | error?: string; 34 | }; 35 | 36 | export type ChatRequestOptions = { 37 | messages: Message[]; 38 | stream: boolean; 39 | chunkIntervalMs: number; 40 | apiUrl: string; 41 | } & ChatRequestOverrides; 42 | 43 | export type ChatRequestOverrides = { 44 | top?: number; 45 | temperature?: number; 46 | }; 47 | -------------------------------------------------------------------------------- /src/frontend/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "lib": ["esnext", "DOM", "DOM.Iterable"], 6 | "strict": true, 7 | "outDir": "./dist", 8 | "rootDir": "./src", 9 | "declaration": true, 10 | "declarationMap": true, 11 | "sourceMap": true, 12 | "inlineSources": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "noImplicitAny": false, 18 | "noImplicitThis": true, 19 | "moduleResolution": "node", 20 | "allowSyntheticDefaultImports": true, 21 | "experimentalDecorators": true, 22 | "forceConsistentCasingInFileNames": true, 23 | "noImplicitOverride": true, 24 | "emitDeclarationOnly": true, 25 | "useDefineForClassFields": false, 26 | "plugins": [ 27 | { 28 | "name": "ts-lit-plugin", 29 | "strict": true 30 | } 31 | ] 32 | }, 33 | "include": ["src/**/*.ts"] 34 | } 35 | -------------------------------------------------------------------------------- /src/frontend/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | // Expose environment variables to the client 4 | process.env.VITE_BACKEND_API_URI = process.env.BACKEND_API_URI ?? ''; 5 | console.log(`Using chat API base URL: "${process.env.VITE_BACKEND_API_URI}"`); 6 | 7 | export default defineConfig({ 8 | build: { 9 | outDir: './dist', 10 | emptyOutDir: true, 11 | sourcemap: true, 12 | rollupOptions: { 13 | output: { 14 | manualChunks: (id) => { 15 | if (id.includes('node_modules')) { 16 | return 'vendor'; 17 | } 18 | }, 19 | }, 20 | }, 21 | }, 22 | server: { 23 | proxy: { 24 | '/chat': 'http://127.0.0.1:3000', 25 | }, 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /src/ingestion/.dockerignore: -------------------------------------------------------------------------------- 1 | !target/*-runner 2 | !target/*-runner.jar 3 | !target/lib/* 4 | !target/quarkus-app/* -------------------------------------------------------------------------------- /src/ingestion/.gitignore: -------------------------------------------------------------------------------- 1 | #Maven 2 | target/ 3 | pom.xml.tag 4 | pom.xml.releaseBackup 5 | pom.xml.versionsBackup 6 | release.properties 7 | .flattened-pom.xml 8 | 9 | # Eclipse 10 | .project 11 | .classpath 12 | .settings/ 13 | bin/ 14 | 15 | # IntelliJ 16 | .idea 17 | *.ipr 18 | *.iml 19 | *.iws 20 | 21 | # NetBeans 22 | nb-configuration.xml 23 | 24 | # Visual Studio Code 25 | .vscode 26 | .factorypath 27 | 28 | # OSX 29 | .DS_Store 30 | 31 | # Vim 32 | *.swp 33 | *.swo 34 | 35 | # patch 36 | *.orig 37 | *.rej 38 | 39 | # Local environment 40 | .env 41 | 42 | # Plugin directory 43 | /.quarkus/cli/plugins/ 44 | 45 | # Data Ingestion 46 | *.pdf 47 | -------------------------------------------------------------------------------- /src/ingestion/.mvn/wrapper/.gitignore: -------------------------------------------------------------------------------- 1 | maven-wrapper.jar 2 | -------------------------------------------------------------------------------- /src/ingestion/.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | import java.net.Authenticator; 23 | import java.net.PasswordAuthentication; 24 | import java.net.URL; 25 | import java.nio.file.Files; 26 | import java.nio.file.Path; 27 | import java.nio.file.Paths; 28 | import java.nio.file.StandardCopyOption; 29 | 30 | public final class MavenWrapperDownloader 31 | { 32 | private static final String WRAPPER_VERSION = "3.2.0"; 33 | 34 | private static final boolean VERBOSE = Boolean.parseBoolean( System.getenv( "MVNW_VERBOSE" ) ); 35 | 36 | public static void main( String[] args ) 37 | { 38 | log( "Apache Maven Wrapper Downloader " + WRAPPER_VERSION ); 39 | 40 | if ( args.length != 2 ) 41 | { 42 | System.err.println( " - ERROR wrapperUrl or wrapperJarPath parameter missing" ); 43 | System.exit( 1 ); 44 | } 45 | 46 | try 47 | { 48 | log( " - Downloader started" ); 49 | final URL wrapperUrl = new URL( args[0] ); 50 | final String jarPath = args[1].replace( "..", "" ); // Sanitize path 51 | final Path wrapperJarPath = Paths.get( jarPath ).toAbsolutePath().normalize(); 52 | downloadFileFromURL( wrapperUrl, wrapperJarPath ); 53 | log( "Done" ); 54 | } 55 | catch ( IOException e ) 56 | { 57 | System.err.println( "- Error downloading: " + e.getMessage() ); 58 | if ( VERBOSE ) 59 | { 60 | e.printStackTrace(); 61 | } 62 | System.exit( 1 ); 63 | } 64 | } 65 | 66 | private static void downloadFileFromURL( URL wrapperUrl, Path wrapperJarPath ) 67 | throws IOException 68 | { 69 | log( " - Downloading to: " + wrapperJarPath ); 70 | if ( System.getenv( "MVNW_USERNAME" ) != null && System.getenv( "MVNW_PASSWORD" ) != null ) 71 | { 72 | final String username = System.getenv( "MVNW_USERNAME" ); 73 | final char[] password = System.getenv( "MVNW_PASSWORD" ).toCharArray(); 74 | Authenticator.setDefault( new Authenticator() 75 | { 76 | @Override 77 | protected PasswordAuthentication getPasswordAuthentication() 78 | { 79 | return new PasswordAuthentication( username, password ); 80 | } 81 | } ); 82 | } 83 | try ( InputStream inStream = wrapperUrl.openStream() ) 84 | { 85 | Files.copy( inStream, wrapperJarPath, StandardCopyOption.REPLACE_EXISTING ); 86 | } 87 | log( " - Downloader complete" ); 88 | } 89 | 90 | private static void log( String msg ) 91 | { 92 | if ( VERBOSE ) 93 | { 94 | System.out.println( msg ); 95 | } 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/ingestion/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 19 | -------------------------------------------------------------------------------- /src/ingestion/src/main/java/ai/azure/openai/rag/workshop/ingestion/configuration/EmbeddingModelProducer.java: -------------------------------------------------------------------------------- 1 | package ai.azure.openai.rag.workshop.ingestion.configuration; 2 | 3 | import dev.langchain4j.model.embedding.EmbeddingModel; 4 | import dev.langchain4j.model.embedding.onnx.allminilml6v2.AllMiniLmL6V2EmbeddingModel; 5 | import jakarta.enterprise.inject.Produces; 6 | 7 | public class EmbeddingModelProducer { 8 | 9 | @Produces 10 | public EmbeddingModel embeddingModel() { 11 | return new AllMiniLmL6V2EmbeddingModel(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/ingestion/src/main/java/ai/azure/openai/rag/workshop/ingestion/configuration/EmbeddingStoreProducer.java: -------------------------------------------------------------------------------- 1 | package ai.azure.openai.rag.workshop.ingestion.configuration; 2 | 3 | import dev.langchain4j.data.segment.TextSegment; 4 | import dev.langchain4j.store.embedding.EmbeddingStore; 5 | import dev.langchain4j.store.embedding.qdrant.QdrantEmbeddingStore; 6 | import io.qdrant.client.QdrantClient; 7 | import io.qdrant.client.QdrantGrpcClient; 8 | import io.qdrant.client.grpc.Collections.CollectionOperationResponse; 9 | import io.qdrant.client.grpc.Collections.Distance; 10 | import io.qdrant.client.grpc.Collections.VectorParams; 11 | import jakarta.enterprise.inject.Produces; 12 | import org.eclipse.microprofile.config.inject.ConfigProperty; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import com.google.common.util.concurrent.ListenableFuture; 17 | 18 | import java.net.URI; 19 | 20 | public class EmbeddingStoreProducer { 21 | 22 | private static final Logger log = LoggerFactory.getLogger(EmbeddingStoreProducer.class); 23 | 24 | @ConfigProperty(name = "AZURE_SEARCH_INDEX", defaultValue = "kbindex") 25 | String azureSearchIndexName; 26 | 27 | @ConfigProperty(name = "QDRANT_URL", defaultValue = "http://localhost:6334") 28 | String qdrantUrl; 29 | 30 | @Produces 31 | public EmbeddingStore embeddingStore() throws Exception { 32 | String qdrantHostname = new URI(qdrantUrl).getHost(); 33 | int qdrantPort = new URI(qdrantUrl).getPort(); 34 | 35 | QdrantGrpcClient.Builder grpcClientBuilder = QdrantGrpcClient.newBuilder(qdrantHostname, qdrantPort, false); 36 | QdrantClient qdrantClient = new QdrantClient(grpcClientBuilder.build()); 37 | try { 38 | qdrantClient.createCollectionAsync( 39 | azureSearchIndexName, 40 | VectorParams.newBuilder() 41 | .setSize(384) 42 | .setDistance(Distance.Cosine) 43 | .build() 44 | ).get(); 45 | } catch (Exception e) { 46 | log.info("Collection already exists, skipping creation. Error: {}", e.getMessage()); 47 | } 48 | 49 | return QdrantEmbeddingStore.builder() 50 | .client(qdrantClient) 51 | .collectionName(azureSearchIndexName) 52 | .build(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/ingestion/src/main/java/ai/azure/openai/rag/workshop/ingestion/rest/DocumentIngestor.java: -------------------------------------------------------------------------------- 1 | package ai.azure.openai.rag.workshop.ingestion.rest; 2 | 3 | import dev.langchain4j.data.document.Document; 4 | import dev.langchain4j.data.document.DocumentSplitter; 5 | import dev.langchain4j.data.document.parser.apache.pdfbox.ApachePdfBoxDocumentParser; 6 | import dev.langchain4j.data.document.splitter.DocumentSplitters; 7 | import dev.langchain4j.data.embedding.Embedding; 8 | import dev.langchain4j.data.segment.TextSegment; 9 | import dev.langchain4j.model.embedding.EmbeddingModel; 10 | import dev.langchain4j.store.embedding.EmbeddingStore; 11 | import jakarta.inject.Inject; 12 | import jakarta.ws.rs.Consumes; 13 | import jakarta.ws.rs.POST; 14 | import jakarta.ws.rs.Path; 15 | import org.jboss.resteasy.reactive.server.multipart.FormValue; 16 | import org.jboss.resteasy.reactive.server.multipart.MultipartFormDataInput; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | import java.io.IOException; 21 | import java.util.Collection; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | @Path("/ingest") 26 | public class DocumentIngestor { 27 | 28 | private static final Logger log = LoggerFactory.getLogger(DocumentIngestor.class); 29 | 30 | @Inject 31 | EmbeddingModel embeddingModel; 32 | 33 | @Inject 34 | EmbeddingStore embeddingStore; 35 | 36 | @POST 37 | @Consumes("multipart/form-data") 38 | public void ingest(MultipartFormDataInput input) throws IOException { 39 | for (Map.Entry> attribute : input.getValues().entrySet()) { 40 | for (FormValue fv : attribute.getValue()) { 41 | if (fv.isFileItem()) { 42 | log.info("### Load file, size {}", fv.getFileItem().getFileSize()); 43 | ApachePdfBoxDocumentParser pdfParser = new ApachePdfBoxDocumentParser(); 44 | Document document = pdfParser.parse(fv.getFileItem().getInputStream()); 45 | log.debug("# PDF size: {}", document.text().length()); 46 | 47 | log.info("### Split document into segments 100 tokens each"); 48 | DocumentSplitter splitter = DocumentSplitters.recursive(2000, 200); 49 | List segments = splitter.split(document); 50 | for (TextSegment segment : segments) { 51 | log.debug("# Segment size: {}", segment.text().length()); 52 | segment.metadata().put("filename", fv.getFileName()); 53 | } 54 | log.debug("# Number of segments: {}", segments.size()); 55 | 56 | log.info("### Embed segments (convert them into vectors that represent the meaning) using embedding model"); 57 | List embeddings = embeddingModel.embedAll(segments).content(); 58 | log.debug("# Number of embeddings: {}", embeddings.size()); 59 | log.debug("# Vector length: {}", embeddings.get(0).vector().length); 60 | 61 | log.info("### Store embeddings into Qdrant store for further search / retrieval"); 62 | embeddingStore.addAll(embeddings, segments); 63 | } 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/ingestion/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | quarkus.http.port=3001 2 | quarkus.log.level=INFO 3 | quarkus.log.category."ai.azure.openai.rag.workshop.ingestion".level=DEBUG 4 | -------------------------------------------------------------------------------- /src/ingestion/src/main/resources/default_banner.txt: -------------------------------------------------------------------------------- 1 | 2 | _____ _ _ 3 | |_ _| | | (_) 4 | | | _ __ __ _ ___ ___| |_ _ ___ _ __ 5 | | || '_ \ / _` |/ _ \/ __| __| |/ _ \| '_ \ 6 | _| || | | | (_| | __/\__ \ |_| | (_) | | | | 7 | \___/_| |_|\__, |\___||___/\__|_|\___/|_| |_| 8 | __/ | 9 | |___/ 10 | -------------------------------------------------------------------------------- /src/ingestion/src/main/resources/tinylog.properties: -------------------------------------------------------------------------------- 1 | writer.level = debug 2 | -------------------------------------------------------------------------------- /src/ingestion/src/test/java/ai/azure/openai/rag/workshop/ingestion/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/azure-openai-rag-workshop-java/eaea0c88e138fdc2abea473b19d624f47e905303/src/ingestion/src/test/java/ai/azure/openai/rag/workshop/ingestion/.gitkeep -------------------------------------------------------------------------------- /trainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | # Build Node.js app 4 | # ------------------------------------ 5 | FROM node:20-alpine as build 6 | WORKDIR /app 7 | COPY ./package*.json ./ 8 | COPY ./tsconfig.json ./ 9 | COPY ./src ./src 10 | RUN npm ci --cache /tmp/empty-cache 11 | RUN npm run build 12 | 13 | # Run Node.js app 14 | # ------------------------------------ 15 | FROM node:20-alpine 16 | ENV NODE_ENV=production 17 | 18 | WORKDIR /app 19 | COPY ./package*.json ./ 20 | RUN npm ci --omit=dev --cache /tmp/empty-cache 21 | COPY --from=build app/dist ./dist 22 | EXPOSE 3000 23 | CMD [ "npm", "start" ] 24 | -------------------------------------------------------------------------------- /trainer/README.md: -------------------------------------------------------------------------------- 1 | # Trainer material 2 | 3 | This directory contains the material for the trainer, when this workshop is given in a classroom setting. 4 | 5 | It also contains a proxy that you can use to share your Azure OpenAI instance with your attendees. 6 | 7 | ## Content 8 | 9 | - The introduction slides are in the `docs/slides` folder, and available at https://azure-samples.github.io/azure-openai-rag-workshop-java/ 10 | - The workshop is in the `docs/workshop` folder, and available at https://aka.ms/ws/openai-rag 11 | 12 | ## Preparation 13 | 14 | Since Azure OpenAI is not enabled by default on all subscriptions, it's difficult for attendees to follow the workshop with their own instance. 15 | 16 | To solve that issue, this folder containers an OpenAI proxy that you can share with your attendees. 17 | 18 | To deploy it, run: 19 | 20 | ```bash 21 | azd auth login # if needed 22 | azd env new openai-trainer 23 | azd env set AZURE_OPENAI_LOCATION # optional, default is swedencentral 24 | azd env set AZURE_OPENAI_CAPACITY # optional, default is 200 25 | azd up 26 | ``` 27 | 28 | You'll get a container app instance URL of the proxy when the deployment is complete. 29 | 30 | You can share it with your attendees so they can use it during the workshop with a link like this: 31 | 32 | ``` 33 | https://aka.ms/ws/openai-rag-quarkus?vars=proxy: 34 | ``` 35 | 36 | 37 | ## During the workshop 38 | 39 | Share this URL with your attendees, and make them run this command **before** their provision their own infrastructure (if they already did it, they'll just have to provision again): 40 | 41 | ```bash 42 | azd env set AZURE_OPENAI_URL 43 | ``` 44 | -------------------------------------------------------------------------------- /trainer/azure.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json 2 | 3 | name: azure-openai-proxy 4 | services: 5 | openai-proxy: 6 | project: . 7 | language: ts 8 | host: containerapp 9 | -------------------------------------------------------------------------------- /trainer/infra/abbreviations.json: -------------------------------------------------------------------------------- 1 | { 2 | "analysisServicesServers": "as", 3 | "apiManagementService": "apim-", 4 | "appConfigurationStores": "appcs-", 5 | "appManagedEnvironments": "cae-", 6 | "appContainerApps": "ca-", 7 | "authorizationPolicyDefinitions": "policy-", 8 | "automationAutomationAccounts": "aa-", 9 | "blueprintBlueprints": "bp-", 10 | "blueprintBlueprintsArtifacts": "bpa-", 11 | "cacheRedis": "redis-", 12 | "cdnProfiles": "cdnp-", 13 | "cdnProfilesEndpoints": "cdne-", 14 | "cognitiveServicesAccounts": "cog-", 15 | "cognitiveServicesFormRecognizer": "cog-fr-", 16 | "cognitiveServicesTextAnalytics": "cog-ta-", 17 | "cognitiveServicesSpeech": "cog-sp-", 18 | "computeAvailabilitySets": "avail-", 19 | "computeCloudServices": "cld-", 20 | "computeDiskEncryptionSets": "des", 21 | "computeDisks": "disk", 22 | "computeDisksOs": "osdisk", 23 | "computeGalleries": "gal", 24 | "computeSnapshots": "snap-", 25 | "computeVirtualMachines": "vm", 26 | "computeVirtualMachineScaleSets": "vmss-", 27 | "containerInstanceContainerGroups": "ci", 28 | "containerRegistryRegistries": "cr", 29 | "containerServiceManagedClusters": "aks-", 30 | "databricksWorkspaces": "dbw-", 31 | "dataFactoryFactories": "adf-", 32 | "dataLakeAnalyticsAccounts": "dla", 33 | "dataLakeStoreAccounts": "dls", 34 | "dataMigrationServices": "dms-", 35 | "dBforMySQLServers": "mysql-", 36 | "dBforPostgreSQLServers": "psql-", 37 | "devicesIotHubs": "iot-", 38 | "devicesProvisioningServices": "provs-", 39 | "devicesProvisioningServicesCertificates": "pcert-", 40 | "documentDBDatabaseAccounts": "cosmos-", 41 | "eventGridDomains": "evgd-", 42 | "eventGridDomainsTopics": "evgt-", 43 | "eventGridEventSubscriptions": "evgs-", 44 | "eventHubNamespaces": "evhns-", 45 | "eventHubNamespacesEventHubs": "evh-", 46 | "hdInsightClustersHadoop": "hadoop-", 47 | "hdInsightClustersHbase": "hbase-", 48 | "hdInsightClustersKafka": "kafka-", 49 | "hdInsightClustersMl": "mls-", 50 | "hdInsightClustersSpark": "spark-", 51 | "hdInsightClustersStorm": "storm-", 52 | "hybridComputeMachines": "arcs-", 53 | "insightsActionGroups": "ag-", 54 | "insightsComponents": "appi-", 55 | "keyVaultVaults": "kv-", 56 | "kubernetesConnectedClusters": "arck", 57 | "kustoClusters": "dec", 58 | "kustoClustersDatabases": "dedb", 59 | "loadTesting": "lt-", 60 | "logicIntegrationAccounts": "ia-", 61 | "logicWorkflows": "logic-", 62 | "machineLearningServicesWorkspaces": "mlw-", 63 | "managedIdentityUserAssignedIdentities": "id-", 64 | "managementManagementGroups": "mg-", 65 | "migrateAssessmentProjects": "migr-", 66 | "networkApplicationGateways": "agw-", 67 | "networkApplicationSecurityGroups": "asg-", 68 | "networkAzureFirewalls": "afw-", 69 | "networkBastionHosts": "bas-", 70 | "networkConnections": "con-", 71 | "networkDnsZones": "dnsz-", 72 | "networkExpressRouteCircuits": "erc-", 73 | "networkFirewallPolicies": "afwp-", 74 | "networkFirewallPoliciesWebApplication": "waf", 75 | "networkFirewallPoliciesRuleGroups": "wafrg", 76 | "networkFrontDoors": "fd-", 77 | "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", 78 | "networkLoadBalancersExternal": "lbe-", 79 | "networkLoadBalancersInternal": "lbi-", 80 | "networkLoadBalancersInboundNatRules": "rule-", 81 | "networkLocalNetworkGateways": "lgw-", 82 | "networkNatGateways": "ng-", 83 | "networkNetworkInterfaces": "nic-", 84 | "networkNetworkSecurityGroups": "nsg-", 85 | "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", 86 | "networkNetworkWatchers": "nw-", 87 | "networkPrivateDnsZones": "pdnsz-", 88 | "networkPrivateLinkServices": "pl-", 89 | "networkPublicIPAddresses": "pip-", 90 | "networkPublicIPPrefixes": "ippre-", 91 | "networkRouteFilters": "rf-", 92 | "networkRouteTables": "rt-", 93 | "networkRouteTablesRoutes": "udr-", 94 | "networkTrafficManagerProfiles": "traf-", 95 | "networkVirtualNetworkGateways": "vgw-", 96 | "networkVirtualNetworks": "vnet-", 97 | "networkVirtualNetworksSubnets": "snet-", 98 | "networkVirtualNetworksVirtualNetworkPeerings": "peer-", 99 | "networkVirtualWans": "vwan-", 100 | "networkVpnGateways": "vpng-", 101 | "networkVpnGatewaysVpnConnections": "vcn-", 102 | "networkVpnGatewaysVpnSites": "vst-", 103 | "notificationHubsNamespaces": "ntfns-", 104 | "notificationHubsNamespacesNotificationHubs": "ntf-", 105 | "operationalInsightsWorkspaces": "log-", 106 | "portalDashboards": "dash-", 107 | "powerBIDedicatedCapacities": "pbi-", 108 | "purviewAccounts": "pview-", 109 | "recoveryServicesVaults": "rsv-", 110 | "resourcesResourceGroups": "rg-", 111 | "searchSearchServices": "srch-", 112 | "serviceBusNamespaces": "sb-", 113 | "serviceBusNamespacesQueues": "sbq-", 114 | "serviceBusNamespacesTopics": "sbt-", 115 | "serviceEndPointPolicies": "se-", 116 | "serviceFabricClusters": "sf-", 117 | "signalRServiceSignalR": "sigr", 118 | "sqlManagedInstances": "sqlmi-", 119 | "sqlServers": "sql-", 120 | "sqlServersDataWarehouse": "sqldw-", 121 | "sqlServersDatabases": "sqldb-", 122 | "sqlServersDatabasesStretch": "sqlstrdb-", 123 | "storageStorageAccounts": "st", 124 | "storageStorageAccountsVm": "stvm", 125 | "storSimpleManagers": "ssimp", 126 | "streamAnalyticsCluster": "asa-", 127 | "synapseWorkspaces": "syn", 128 | "synapseWorkspacesAnalyticsWorkspaces": "synw", 129 | "synapseWorkspacesSqlPoolsDedicated": "syndp", 130 | "synapseWorkspacesSqlPoolsSpark": "synsp", 131 | "timeSeriesInsightsEnvironments": "tsi-", 132 | "webServerFarms": "plan-", 133 | "webSitesAppService": "app-", 134 | "webSitesAppServiceEnvironment": "ase-", 135 | "webSitesFunctions": "func-", 136 | "webStaticSites": "stapp-" 137 | } 138 | -------------------------------------------------------------------------------- /trainer/infra/core/ai/cognitiveservices.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates an Azure Cognitive Services instance.' 2 | param name string 3 | param location string = resourceGroup().location 4 | param tags object = {} 5 | @description('The custom subdomain name used to access the API. Defaults to the value of the name parameter.') 6 | param customSubDomainName string = name 7 | param disableLocalAuth bool = false 8 | param deployments array = [] 9 | param kind string = 'OpenAI' 10 | 11 | @allowed([ 'Enabled', 'Disabled' ]) 12 | param publicNetworkAccess string = 'Enabled' 13 | param sku object = { 14 | name: 'S0' 15 | } 16 | 17 | param allowedIpRules array = [] 18 | param networkAcls object = empty(allowedIpRules) ? { 19 | defaultAction: 'Allow' 20 | } : { 21 | ipRules: allowedIpRules 22 | defaultAction: 'Deny' 23 | } 24 | 25 | resource account 'Microsoft.CognitiveServices/accounts@2023-05-01' = { 26 | name: name 27 | location: location 28 | tags: tags 29 | kind: kind 30 | properties: { 31 | customSubDomainName: customSubDomainName 32 | publicNetworkAccess: publicNetworkAccess 33 | networkAcls: networkAcls 34 | disableLocalAuth: disableLocalAuth 35 | } 36 | sku: sku 37 | } 38 | 39 | @batchSize(1) 40 | resource deployment 'Microsoft.CognitiveServices/accounts/deployments@2023-05-01' = [for deployment in deployments: { 41 | parent: account 42 | name: deployment.name 43 | properties: { 44 | model: deployment.model 45 | raiPolicyName: contains(deployment, 'raiPolicyName') ? deployment.raiPolicyName : null 46 | } 47 | sku: contains(deployment, 'sku') ? deployment.sku : { 48 | name: 'Standard' 49 | capacity: 20 50 | } 51 | }] 52 | 53 | output endpoint string = account.properties.endpoint 54 | output endpoints object = account.properties.endpoints 55 | output id string = account.id 56 | output name string = account.name 57 | -------------------------------------------------------------------------------- /trainer/infra/core/host/container-apps-environment.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates an Azure Container Apps environment.' 2 | param name string 3 | param location string = resourceGroup().location 4 | param tags object = {} 5 | 6 | @description('Name of the Application Insights resource') 7 | param applicationInsightsName string = '' 8 | 9 | @description('Specifies if Dapr is enabled') 10 | param daprEnabled bool = false 11 | 12 | @description('Name of the Log Analytics workspace') 13 | param logAnalyticsWorkspaceName string 14 | 15 | resource containerAppsEnvironment 'Microsoft.App/managedEnvironments@2023-05-01' = { 16 | name: name 17 | location: location 18 | tags: tags 19 | properties: { 20 | appLogsConfiguration: { 21 | destination: 'log-analytics' 22 | logAnalyticsConfiguration: { 23 | customerId: logAnalyticsWorkspace.properties.customerId 24 | sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey 25 | } 26 | } 27 | daprAIInstrumentationKey: daprEnabled && !empty(applicationInsightsName) ? applicationInsights.properties.InstrumentationKey : '' 28 | } 29 | } 30 | 31 | resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' existing = { 32 | name: logAnalyticsWorkspaceName 33 | } 34 | 35 | resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = if (daprEnabled && !empty(applicationInsightsName)) { 36 | name: applicationInsightsName 37 | } 38 | 39 | output defaultDomain string = containerAppsEnvironment.properties.defaultDomain 40 | output id string = containerAppsEnvironment.id 41 | output name string = containerAppsEnvironment.name 42 | -------------------------------------------------------------------------------- /trainer/infra/core/host/container-apps.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates an Azure Container Registry and an Azure Container Apps environment.' 2 | param name string 3 | param location string = resourceGroup().location 4 | param tags object = {} 5 | 6 | param containerAppsEnvironmentName string 7 | param containerRegistryName string 8 | param containerRegistryResourceGroupName string = '' 9 | param containerRegistryAdminUserEnabled bool = false 10 | param logAnalyticsWorkspaceName string 11 | param applicationInsightsName string = '' 12 | param daprEnabled bool = false 13 | 14 | module containerAppsEnvironment 'container-apps-environment.bicep' = { 15 | name: '${name}-container-apps-environment' 16 | params: { 17 | name: containerAppsEnvironmentName 18 | location: location 19 | tags: tags 20 | logAnalyticsWorkspaceName: logAnalyticsWorkspaceName 21 | applicationInsightsName: applicationInsightsName 22 | daprEnabled: daprEnabled 23 | } 24 | } 25 | 26 | module containerRegistry 'container-registry.bicep' = { 27 | name: '${name}-container-registry' 28 | scope: !empty(containerRegistryResourceGroupName) ? resourceGroup(containerRegistryResourceGroupName) : resourceGroup() 29 | params: { 30 | name: containerRegistryName 31 | location: location 32 | adminUserEnabled: containerRegistryAdminUserEnabled 33 | tags: tags 34 | } 35 | } 36 | 37 | output defaultDomain string = containerAppsEnvironment.outputs.defaultDomain 38 | output environmentName string = containerAppsEnvironment.outputs.name 39 | output environmentId string = containerAppsEnvironment.outputs.id 40 | 41 | output registryLoginServer string = containerRegistry.outputs.loginServer 42 | output registryName string = containerRegistry.outputs.name 43 | -------------------------------------------------------------------------------- /trainer/infra/core/host/container-registry.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates an Azure Container Registry.' 2 | param name string 3 | param location string = resourceGroup().location 4 | param tags object = {} 5 | 6 | @description('Indicates whether admin user is enabled') 7 | param adminUserEnabled bool = false 8 | 9 | @description('Indicates whether anonymous pull is enabled') 10 | param anonymousPullEnabled bool = false 11 | 12 | @description('Azure ad authentication as arm policy settings') 13 | param azureADAuthenticationAsArmPolicy object = { 14 | status: 'enabled' 15 | } 16 | 17 | @description('Indicates whether data endpoint is enabled') 18 | param dataEndpointEnabled bool = false 19 | 20 | @description('Encryption settings') 21 | param encryption object = { 22 | status: 'disabled' 23 | } 24 | 25 | @description('Export policy settings') 26 | param exportPolicy object = { 27 | status: 'enabled' 28 | } 29 | 30 | @description('Metadata search settings') 31 | param metadataSearch string = 'Disabled' 32 | 33 | @description('Options for bypassing network rules') 34 | param networkRuleBypassOptions string = 'AzureServices' 35 | 36 | @description('Public network access setting') 37 | param publicNetworkAccess string = 'Enabled' 38 | 39 | @description('Quarantine policy settings') 40 | param quarantinePolicy object = { 41 | status: 'disabled' 42 | } 43 | 44 | @description('Retention policy settings') 45 | param retentionPolicy object = { 46 | days: 7 47 | status: 'disabled' 48 | } 49 | 50 | @description('Scope maps setting') 51 | param scopeMaps array = [] 52 | 53 | @description('SKU settings') 54 | param sku object = { 55 | name: 'Basic' 56 | } 57 | 58 | @description('Soft delete policy settings') 59 | param softDeletePolicy object = { 60 | retentionDays: 7 61 | status: 'disabled' 62 | } 63 | 64 | @description('Trust policy settings') 65 | param trustPolicy object = { 66 | type: 'Notary' 67 | status: 'disabled' 68 | } 69 | 70 | @description('Zone redundancy setting') 71 | param zoneRedundancy string = 'Disabled' 72 | 73 | @description('The log analytics workspace ID used for logging and monitoring') 74 | param workspaceId string = '' 75 | 76 | // 2023-11-01-preview needed for metadataSearch 77 | resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-11-01-preview' = { 78 | name: name 79 | location: location 80 | tags: tags 81 | sku: sku 82 | properties: { 83 | adminUserEnabled: adminUserEnabled 84 | anonymousPullEnabled: anonymousPullEnabled 85 | dataEndpointEnabled: dataEndpointEnabled 86 | encryption: encryption 87 | metadataSearch: metadataSearch 88 | networkRuleBypassOptions: networkRuleBypassOptions 89 | policies:{ 90 | quarantinePolicy: quarantinePolicy 91 | trustPolicy: trustPolicy 92 | retentionPolicy: retentionPolicy 93 | exportPolicy: exportPolicy 94 | azureADAuthenticationAsArmPolicy: azureADAuthenticationAsArmPolicy 95 | softDeletePolicy: softDeletePolicy 96 | } 97 | publicNetworkAccess: publicNetworkAccess 98 | zoneRedundancy: zoneRedundancy 99 | } 100 | 101 | resource scopeMap 'scopeMaps' = [for scopeMap in scopeMaps: { 102 | name: scopeMap.name 103 | properties: scopeMap.properties 104 | }] 105 | } 106 | 107 | // TODO: Update diagnostics to be its own module 108 | // Blocking issue: https://github.com/Azure/bicep/issues/622 109 | // Unable to pass in a `resource` scope or unable to use string interpolation in resource types 110 | resource diagnostics 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = if (!empty(workspaceId)) { 111 | name: 'registry-diagnostics' 112 | scope: containerRegistry 113 | properties: { 114 | workspaceId: workspaceId 115 | logs: [ 116 | { 117 | category: 'ContainerRegistryRepositoryEvents' 118 | enabled: true 119 | } 120 | { 121 | category: 'ContainerRegistryLoginEvents' 122 | enabled: true 123 | } 124 | ] 125 | metrics: [ 126 | { 127 | category: 'AllMetrics' 128 | enabled: true 129 | timeGrain: 'PT1M' 130 | } 131 | ] 132 | } 133 | } 134 | 135 | output id string = containerRegistry.id 136 | output loginServer string = containerRegistry.properties.loginServer 137 | output name string = containerRegistry.name 138 | -------------------------------------------------------------------------------- /trainer/infra/core/monitor/loganalytics.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates a Log Analytics workspace.' 2 | param name string 3 | param location string = resourceGroup().location 4 | param tags object = {} 5 | 6 | resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = { 7 | name: name 8 | location: location 9 | tags: tags 10 | properties: any({ 11 | retentionInDays: 30 12 | features: { 13 | searchVersion: 1 14 | } 15 | sku: { 16 | name: 'PerGB2018' 17 | } 18 | }) 19 | } 20 | 21 | output id string = logAnalytics.id 22 | output name string = logAnalytics.name 23 | -------------------------------------------------------------------------------- /trainer/infra/core/security/managed-identity.bicep: -------------------------------------------------------------------------------- 1 | param name string 2 | param location string = resourceGroup().location 3 | 4 | resource apiIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { 5 | name: name 6 | location: location 7 | } 8 | 9 | output tenantId string = apiIdentity.properties.tenantId 10 | output principalId string = apiIdentity.properties.principalId 11 | output clientId string = apiIdentity.properties.clientId 12 | -------------------------------------------------------------------------------- /trainer/infra/core/security/registry-access.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Assigns ACR Pull permissions to access an Azure Container Registry.' 2 | param containerRegistryName string 3 | param principalId string 4 | 5 | var acrPullRole = subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d') 6 | 7 | resource aksAcrPull 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 8 | scope: containerRegistry // Use when specifying a scope that is different than the deployment scope 9 | name: guid(subscription().id, resourceGroup().id, principalId, acrPullRole) 10 | properties: { 11 | roleDefinitionId: acrPullRole 12 | principalType: 'ServicePrincipal' 13 | principalId: principalId 14 | } 15 | } 16 | 17 | resource containerRegistry 'Microsoft.ContainerRegistry/registries@2023-01-01-preview' existing = { 18 | name: containerRegistryName 19 | } 20 | -------------------------------------------------------------------------------- /trainer/infra/core/security/role.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates a role assignment for a service principal.' 2 | param principalId string 3 | 4 | @allowed([ 5 | 'Device' 6 | 'ForeignGroup' 7 | 'Group' 8 | 'ServicePrincipal' 9 | 'User' 10 | ]) 11 | param principalType string = 'ServicePrincipal' 12 | param roleDefinitionId string 13 | 14 | resource role 'Microsoft.Authorization/roleAssignments@2022-04-01' = { 15 | name: guid(subscription().id, resourceGroup().id, principalId, roleDefinitionId) 16 | properties: { 17 | principalId: principalId 18 | principalType: principalType 19 | roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /trainer/infra/main.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "environmentName": { 6 | "value": "${AZURE_ENV_NAME}" 7 | }, 8 | "resourceGroupName": { 9 | "value": "${AZURE_RESOURCE_GROUP}" 10 | }, 11 | "location": { 12 | "value": "${AZURE_LOCATION}" 13 | }, 14 | "principalId": { 15 | "value": "${AZURE_PRINCIPAL_ID}" 16 | }, 17 | "openAiLocation": { 18 | "value": "${AZURE_OPENAI_LOCATION=swedencentral}" 19 | }, 20 | "openAiCapacity": { 21 | "value": "${AZURE_OPENAI_CAPACITY=200}" 22 | }, 23 | "chatGptDeploymentName": { 24 | "value": "${AZURE_OPENAI_CHATGPT_DEPLOYMENT=gpt-4o-mini}" 25 | }, 26 | "embeddingDeploymentName": { 27 | "value": "${AZURE_OPENAI_EMBEDDING_DEPLOYMENT=text-embedding-ada-002}" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /trainer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proxy", 3 | "version": "1.0.0", 4 | "description": "Proxy API for Azure OpenAI", 5 | "private": true, 6 | "type": "module", 7 | "exports": "./src/app.ts", 8 | "scripts": { 9 | "start": "fastify start -l info dist/app.js", 10 | "build": "tsc", 11 | "watch": "tsc -w", 12 | "dev": "npm run build && concurrently -k -p \"[{name}]\" -n \"TypeScript,App\" -c \"yellow.bold,cyan.bold\" \"npm:watch\" \"npm:dev:start\"", 13 | "dev:start": "fastify start --pretty-logs --ignore-watch=.ts$ -w -l debug dist/app.js", 14 | "docker:build": "docker build --tag proxy .", 15 | "docker:run": "docker run --rm --publish 3000:3000 --env-file .env proxy" 16 | }, 17 | "dependencies": { 18 | "@azure/identity": "^4.0.1", 19 | "@fastify/autoload": "^5.0.0", 20 | "@fastify/cors": "^9.0.1", 21 | "@fastify/http-proxy": "^9.3.0", 22 | "@fastify/sensible": "^5.0.0", 23 | "dotenv": "^16.3.1", 24 | "fastify": "^4.24.3", 25 | "fastify-cli": "^6.0.1", 26 | "fastify-plugin": "^4.0.0" 27 | }, 28 | "devDependencies": { 29 | "@types/node": "^20.11.7", 30 | "concurrently": "^8.2.0", 31 | "fastify-tsconfig": "^2.0.0", 32 | "ts-node": "^10.9.1", 33 | "typescript": "^5.1.6" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /trainer/src/app.ts: -------------------------------------------------------------------------------- 1 | import path, { join } from 'node:path'; 2 | import { fileURLToPath } from 'node:url'; 3 | import { type FastifyPluginAsync } from 'fastify'; 4 | import AutoLoad, { type AutoloadPluginOptions } from '@fastify/autoload'; 5 | // import cors from '@fastify/cors'; 6 | 7 | export type AppOptions = { 8 | // Place your custom options for app below here. 9 | } & Partial; 10 | 11 | // Pass --options via CLI arguments in command to enable these options. 12 | const options: AppOptions = {}; 13 | 14 | const __filename = fileURLToPath(import.meta.url); 15 | const __dirname = path.dirname(__filename); 16 | 17 | const app: FastifyPluginAsync = async (fastify, options_): Promise => { 18 | // Place here your custom code! 19 | 20 | // fastify.register(cors); 21 | 22 | // Do not touch the following lines 23 | 24 | // This loads all plugins defined in plugins 25 | // those should be support plugins that are reused 26 | // through your application 27 | fastify.register(AutoLoad, { 28 | dir: join(__dirname, 'plugins'), 29 | options: options_, 30 | }); 31 | 32 | // This loads all plugins defined in routes 33 | // define your routes in one of these 34 | fastify.register(AutoLoad, { 35 | dir: join(__dirname, 'routes'), 36 | options: options_, 37 | }); 38 | }; 39 | 40 | export default app; 41 | export { app, options }; 42 | -------------------------------------------------------------------------------- /trainer/src/plugins/config.ts: -------------------------------------------------------------------------------- 1 | import process from 'node:process'; 2 | import path from 'node:path'; 3 | import * as dotenv from 'dotenv'; 4 | import fp from 'fastify-plugin'; 5 | 6 | export interface AppConfig { 7 | azureOpenAiService: string; 8 | azureOpenAiChatGptDeployment: string; 9 | azureOpenAiChatGptModel: string; 10 | azureOpenAiEmbeddingDeployment: string; 11 | azureOpenAiEmbeddingModel: string; 12 | } 13 | 14 | const camelCaseToUpperSnakeCase = (s: string) => s.replaceAll(/[A-Z]/g, (l) => `_${l}`).toUpperCase(); 15 | 16 | export default fp( 17 | async (fastify, options) => { 18 | const environmentPath = path.resolve(process.cwd(), '.env'); 19 | 20 | console.log(`Loading .env config from ${environmentPath}...`); 21 | dotenv.config({ path: environmentPath }); 22 | 23 | const config: AppConfig = { 24 | azureOpenAiService: process.env.AZURE_OPENAI_SERVICE || '', 25 | azureOpenAiChatGptDeployment: process.env.AZURE_OPENAI_CHATGPT_DEPLOYMENT || 'chat', 26 | azureOpenAiChatGptModel: process.env.AZURE_OPENAI_CHATGPT_MODEL || 'gpt-4o-mini', 27 | azureOpenAiEmbeddingDeployment: process.env.AZURE_OPENAI_EMBEDDING_DEPLOYMENT || 'embedding', 28 | azureOpenAiEmbeddingModel: process.env.AZURE_OPENAI_EMBEDDING_MODEL || 'text-embedding-ada-002', 29 | }; 30 | 31 | // Check that all config values are set 32 | for (const [key, value] of Object.entries(config)) { 33 | if (!value && key !== 'azureOpenAiApiKey') { 34 | const variableName = camelCaseToUpperSnakeCase(key).replace('OPEN_AI', 'OPENAI'); 35 | const message = `${variableName} environment variable must be set`; 36 | fastify.log.error(message); 37 | throw new Error(message); 38 | } 39 | } 40 | 41 | fastify.decorate('config', config); 42 | }, 43 | { 44 | name: 'config', 45 | }, 46 | ); 47 | 48 | // When using .decorate you have to specify added properties for Typescript 49 | declare module 'fastify' { 50 | export interface FastifyInstance { 51 | config: AppConfig; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /trainer/src/plugins/proxy.ts: -------------------------------------------------------------------------------- 1 | import fp from 'fastify-plugin'; 2 | import proxy from '@fastify/http-proxy'; 3 | import { DefaultAzureCredential, getBearerTokenProvider } from '@azure/identity'; 4 | 5 | const AZURE_COGNITIVE_SERVICES_AD_SCOPE = 'https://cognitiveservices.azure.com/.default'; 6 | 7 | export default fp( 8 | async (fastify, options) => { 9 | const config = fastify.config; 10 | 11 | // Use the current user identity to authenticate with Azure OpenAI, AI Search and Blob Storage 12 | // (no secrets needed, just use 'az login' locally, and managed identity when deployed on Azure). 13 | // If you need to use keys, use separate AzureKeyCredential instances with the keys for each service 14 | const credential = new DefaultAzureCredential(); 15 | const getToken = getBearerTokenProvider(credential, AZURE_COGNITIVE_SERVICES_AD_SCOPE); 16 | 17 | const openAiUrl = `https://${config.azureOpenAiService}.openai.azure.com`; 18 | fastify.log.info(`Using OpenAI at ${openAiUrl}`); 19 | 20 | let openAiToken: string; 21 | 22 | fastify.register(proxy, { 23 | upstream: openAiUrl, 24 | prefix: '/openai', 25 | rewritePrefix: '/openai', 26 | preHandler: async (request, reply) => { 27 | openAiToken = await getToken(); 28 | }, 29 | // TODO: add and check API key 30 | // preValidation: 31 | replyOptions: { 32 | getUpstream: (request, base) => { 33 | return openAiUrl; 34 | }, 35 | rewriteRequestHeaders: (request, headers) => { 36 | const apiKey = openAiToken; 37 | return { 38 | ...headers, 39 | authorization: `Bearer ${apiKey}`, 40 | 'api-key': apiKey, 41 | }; 42 | }, 43 | }, 44 | }); 45 | }, 46 | { 47 | name: 'proxy', 48 | dependencies: ['config'], 49 | }, 50 | ); 51 | -------------------------------------------------------------------------------- /trainer/src/plugins/sensible.ts: -------------------------------------------------------------------------------- 1 | import fp from 'fastify-plugin'; 2 | import sensible, { type SensibleOptions } from '@fastify/sensible'; 3 | 4 | /** 5 | * This plugins adds some utilities to handle http errors 6 | * @see https://github.com/fastify/fastify-sensible 7 | */ 8 | export default fp(async (fastify) => { 9 | fastify.register(sensible); 10 | }); 11 | -------------------------------------------------------------------------------- /trainer/src/routes/root.ts: -------------------------------------------------------------------------------- 1 | import { type FastifyPluginAsync } from 'fastify'; 2 | 3 | const root: FastifyPluginAsync = async (fastify, options): Promise => { 4 | fastify.get('/', async function (request, reply) { 5 | return { message: 'proxy server up' }; 6 | }); 7 | }; 8 | 9 | export default root; 10 | -------------------------------------------------------------------------------- /trainer/test.http: -------------------------------------------------------------------------------- 1 | ################################################################## 2 | # VS Code with REST Client extension is needed to use this file. 3 | # Download at: https://aka.ms/vscode/rest-client 4 | ################################################################## 5 | 6 | @api_host = http://localhost:3000 7 | 8 | # Chat with the bot 9 | POST {{api_host}}/openai/deployments/gpt-4o-mini/chat/completions?api-version=2024-02-01 10 | Content-Type: application/json 11 | api-key: toto 12 | 13 | { 14 | "messages": [{ 15 | "content": "Test", 16 | "role": "user" 17 | }], 18 | "max_tokens": 128, 19 | "temperature": 0.7, 20 | "frequency_penalty": 0, 21 | "presence_penalty": 0, 22 | "top_p": 0.95, 23 | "stop": null 24 | } 25 | 26 | ### 27 | 28 | # Chat with the bot using streaming 29 | POST {{api_host}}/openai/deployments/gpt-4o-mini/chat/completions?api-version=2024-02-01 30 | content-type: application/json 31 | api-key: toto 32 | 33 | { 34 | "messages": [{ 35 | "content": "Test", 36 | "role": "user" 37 | }], 38 | "max_tokens": 128, 39 | "temperature": 0.7, 40 | "frequency_penalty": 0, 41 | "presence_penalty": 0, 42 | "top_p": 0.95, 43 | "stop": null, 44 | "stream": true 45 | } 46 | -------------------------------------------------------------------------------- /trainer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "fastify-tsconfig", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "sourceMap": true, 8 | "esModuleInterop": true, 9 | "lib": ["esnext"] 10 | }, 11 | "include": ["./src/**/*.ts"] 12 | } 13 | --------------------------------------------------------------------------------