├── .devcontainer └── devcontainer.json ├── .gitattributes ├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── aca-deploy.yaml │ ├── acr-build-push.yaml │ ├── azure-dev.yaml │ └── infra-ci.yaml ├── .gitignore ├── CHANGELOG.md ├── CODEOWNERS ├── CONTRIBUTING.md ├── LICENSE ├── LICENSE.md ├── README.md ├── SECURITY.md ├── app ├── business-api │ ├── account │ │ ├── .mvn │ │ │ └── wrapper │ │ │ │ └── maven-wrapper.properties │ │ ├── Dockerfile │ │ ├── applicationinsights.json │ │ ├── mvnw │ │ ├── mvnw.cmd │ │ ├── pom.xml │ │ └── src │ │ │ ├── main │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── microsoft │ │ │ │ │ └── openai │ │ │ │ │ └── samples │ │ │ │ │ └── assistant │ │ │ │ │ └── business │ │ │ │ │ ├── AccountApplication.java │ │ │ │ │ ├── controller │ │ │ │ │ ├── AccountController.java │ │ │ │ │ └── UserController.java │ │ │ │ │ ├── mcp │ │ │ │ │ ├── config │ │ │ │ │ │ └── MCPServerConfiguration.java │ │ │ │ │ └── server │ │ │ │ │ │ ├── AccountMCPService.java │ │ │ │ │ │ └── UserMCPService.java │ │ │ │ │ ├── models │ │ │ │ │ ├── Account.java │ │ │ │ │ ├── Beneficiary.java │ │ │ │ │ ├── PaymentMethod.java │ │ │ │ │ └── PaymentMethodSummary.java │ │ │ │ │ └── service │ │ │ │ │ ├── AccountService.java │ │ │ │ │ └── UserService.java │ │ │ └── resources │ │ │ │ ├── account.yaml │ │ │ │ └── application-dev.properties │ │ │ └── test │ │ │ └── java │ │ │ └── org │ │ │ └── springframework │ │ │ └── ai │ │ │ └── mcp │ │ │ └── sample │ │ │ └── client │ │ │ └── AccountMCPClient.java │ ├── payment │ │ ├── .mvn │ │ │ └── wrapper │ │ │ │ └── maven-wrapper.properties │ │ ├── Dockerfile │ │ ├── applicationinsights.json │ │ ├── mvnw │ │ ├── mvnw.cmd │ │ ├── pom.xml │ │ └── src │ │ │ └── main │ │ │ ├── java │ │ │ └── com │ │ │ │ └── microsoft │ │ │ │ └── openai │ │ │ │ └── samples │ │ │ │ └── assistant │ │ │ │ └── business │ │ │ │ ├── PaymentApplication.java │ │ │ │ ├── controller │ │ │ │ └── PaymentsController.java │ │ │ │ ├── mcp │ │ │ │ ├── config │ │ │ │ │ └── MCPServerConfiguration.java │ │ │ │ └── server │ │ │ │ │ └── PaymentMCPService.java │ │ │ │ ├── models │ │ │ │ ├── Payment.java │ │ │ │ └── Transaction.java │ │ │ │ └── service │ │ │ │ └── PaymentService.java │ │ │ └── resources │ │ │ ├── application-dev.properties │ │ │ ├── application.properties │ │ │ ├── payments.yaml │ │ │ └── transaction-history.yaml │ └── transactions-history │ │ ├── .mvn │ │ └── wrapper │ │ │ └── maven-wrapper.properties │ │ ├── Dockerfile │ │ ├── applicationinsights.json │ │ ├── mvnw │ │ ├── mvnw.cmd │ │ ├── pom.xml │ │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── microsoft │ │ │ └── openai │ │ │ └── samples │ │ │ └── assistant │ │ │ └── business │ │ │ ├── Transaction.java │ │ │ ├── TransactionController.java │ │ │ ├── TransactionService.java │ │ │ ├── TransactionsHistoryApplication.java │ │ │ └── mcp │ │ │ ├── config │ │ │ └── MCPServerConfiguration.java │ │ │ └── server │ │ │ └── TransactionMCPService.java │ │ └── resources │ │ ├── application-dev.properties │ │ └── transaction-history.yaml ├── compose.yaml ├── copilot │ ├── .mvn │ │ ├── jvm.config │ │ └── wrapper │ │ │ └── maven-wrapper.properties │ ├── Dockerfile │ ├── applicationinsights.json │ ├── copilot-backend │ │ ├── manifests │ │ │ ├── azd-env-configmap.yml │ │ │ ├── backend-deployment.tmpl.yml │ │ │ ├── backend-service.yml │ │ │ └── ingress.yml │ │ ├── pom.xml │ │ └── src │ │ │ ├── main │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── microsoft │ │ │ │ │ └── openai │ │ │ │ │ └── samples │ │ │ │ │ └── assistant │ │ │ │ │ ├── CopilotApplication.java │ │ │ │ │ ├── common │ │ │ │ │ └── ChatGPTMessage.java │ │ │ │ │ ├── config │ │ │ │ │ ├── AzureAuthenticationConfiguration.java │ │ │ │ │ ├── AzureOpenAIConfiguration.java │ │ │ │ │ ├── BlobStorageProxyConfiguration.java │ │ │ │ │ ├── DocumentIntelligenceConfiguration.java │ │ │ │ │ ├── DocumentIntelligenceInvoiceScanConfiguration.java │ │ │ │ │ ├── Langchain4JConfiguration.java │ │ │ │ │ └── MCPAgentsConfiguration.java │ │ │ │ │ ├── controller │ │ │ │ │ ├── ChatAppRequest.java │ │ │ │ │ ├── ChatAppRequestContext.java │ │ │ │ │ ├── ChatAppRequestOverrides.java │ │ │ │ │ ├── ChatController.java │ │ │ │ │ ├── ChatResponse.java │ │ │ │ │ ├── ResponseChoice.java │ │ │ │ │ ├── ResponseContext.java │ │ │ │ │ ├── ResponseMessage.java │ │ │ │ │ ├── auth │ │ │ │ │ │ └── AuthSetup.java │ │ │ │ │ └── content │ │ │ │ │ │ └── ContentController.java │ │ │ │ │ ├── plugin │ │ │ │ │ ├── InvoiceScanPlugin.java.sample │ │ │ │ │ ├── LoggedUserPlugin.java.sample │ │ │ │ │ ├── PaymentMockPlugin.java.sample │ │ │ │ │ ├── TransactionHistoryMockPlugin.java.sample │ │ │ │ │ └── mock │ │ │ │ │ │ ├── PaymentTransaction.java │ │ │ │ │ │ └── TransactionServiceMock.java │ │ │ │ │ ├── proxy │ │ │ │ │ └── OpenAIProxy.java │ │ │ │ │ └── security │ │ │ │ │ ├── LoggedUser.java │ │ │ │ │ └── LoggedUserService.java │ │ │ └── resources │ │ │ │ ├── account.yaml │ │ │ │ ├── application.properties │ │ │ │ ├── payments.yaml │ │ │ │ └── transaction-history.yaml │ │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── microsoft │ │ │ └── openai │ │ │ └── samples │ │ │ └── assistant │ │ │ └── AccountAgentIntegrationTest.java │ ├── copilot-common │ │ ├── pom.xml │ │ └── src │ │ │ └── main │ │ │ └── java │ │ │ └── com │ │ │ └── microsoft │ │ │ └── openai │ │ │ └── samples │ │ │ └── assistant │ │ │ ├── invoice │ │ │ └── DocumentIntelligenceInvoiceScanHelper.java │ │ │ └── proxy │ │ │ └── BlobStorageProxy.java │ ├── langchain4j-agents │ │ ├── pom.xml │ │ └── src │ │ │ ├── main │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── microsoft │ │ │ │ ├── langchain4j │ │ │ │ └── agent │ │ │ │ │ ├── AbstractReActAgent.java │ │ │ │ │ ├── Agent.java │ │ │ │ │ ├── AgentExecutionException.java │ │ │ │ │ ├── AgentMetadata.java │ │ │ │ │ └── mcp │ │ │ │ │ ├── MCPProtocolType.java │ │ │ │ │ ├── MCPServerMetadata.java │ │ │ │ │ └── MCPToolAgent.java │ │ │ │ └── openai │ │ │ │ └── samples │ │ │ │ └── assistant │ │ │ │ └── langchain4j │ │ │ │ ├── agent │ │ │ │ ├── SupervisorAgent.java │ │ │ │ └── mcp │ │ │ │ │ ├── AccountMCPAgent.java │ │ │ │ │ ├── PaymentMCPAgent.java │ │ │ │ │ └── TransactionHistoryMCPAgent.java │ │ │ │ └── tools │ │ │ │ └── InvoiceScanTool.java │ │ │ └── test │ │ │ ├── java │ │ │ └── dev │ │ │ │ └── langchain4j │ │ │ │ └── openapi │ │ │ │ └── mcp │ │ │ │ ├── AccountMCPAgentIntegrationTest.java │ │ │ │ ├── PaymentMCPAgentIntegrationTest.java │ │ │ │ ├── PaymentMCPAgentIntegrationWithImageTest.java │ │ │ │ ├── SupervisorAgentLongConversationIntegrationTest.java │ │ │ │ ├── SupervisorAgentNoRoutingIntegrationTest.java │ │ │ │ ├── SupervisorAgentRoutingIntegrationTest.java │ │ │ │ └── TransactionHistoryMCPAgentIntegrationTest.java │ │ │ └── resources │ │ │ ├── account.yaml │ │ │ ├── logback.xml │ │ │ ├── payments.yaml │ │ │ └── transaction-history.yaml │ ├── mvnw │ ├── mvnw.cmd │ └── pom.xml ├── frontend │ ├── .dockerignore │ ├── .env.dev │ ├── .env.local │ ├── .env.production │ ├── .npmrc │ ├── .prettierignore │ ├── .prettierrc.json │ ├── Dockerfile │ ├── Dockerfile-aks │ ├── index.html │ ├── manifests │ │ ├── frontend-deployment.tmpl.yml │ │ └── frontend-service.yml │ ├── nginx │ │ └── nginx.conf.template │ ├── package-lock.json │ ├── package.json │ ├── public │ │ └── favicon.ico │ ├── src │ │ ├── api │ │ │ ├── api.ts │ │ │ ├── index.ts │ │ │ └── models.ts │ │ ├── assets │ │ │ ├── github.svg │ │ │ └── search.svg │ │ ├── authConfig.ts │ │ ├── components │ │ │ ├── AnalysisPanel │ │ │ │ ├── AnalysisPanel.module.css │ │ │ │ ├── AnalysisPanel.tsx │ │ │ │ ├── AnalysisPanelTabs.tsx │ │ │ │ └── index.tsx │ │ │ ├── Answer │ │ │ │ ├── Answer.module.css │ │ │ │ ├── Answer.tsx │ │ │ │ ├── AnswerError.tsx │ │ │ │ ├── AnswerIcon.tsx │ │ │ │ ├── AnswerLoading.tsx │ │ │ │ ├── AnswerParser.tsx │ │ │ │ └── index.ts │ │ │ ├── AttachmentType.ts │ │ │ ├── ClearChatButton │ │ │ │ ├── ClearChatButton.module.css │ │ │ │ ├── ClearChatButton.tsx │ │ │ │ └── index.tsx │ │ │ ├── Example │ │ │ │ ├── Example.module.css │ │ │ │ ├── Example.tsx │ │ │ │ ├── ExampleList.tsx │ │ │ │ └── index.tsx │ │ │ ├── LoginButton │ │ │ │ ├── LoginButton.module.css │ │ │ │ ├── LoginButton.tsx │ │ │ │ └── index.tsx │ │ │ ├── QuestionInput │ │ │ │ ├── QuestionContext.ts │ │ │ │ ├── QuestionInput.module.css │ │ │ │ ├── QuestionInput.tsx │ │ │ │ └── index.ts │ │ │ ├── SettingsButton │ │ │ │ ├── SettingsButton.module.css │ │ │ │ ├── SettingsButton.tsx │ │ │ │ └── index.tsx │ │ │ ├── SupportingContent │ │ │ │ ├── SupportingContent.module.css │ │ │ │ ├── SupportingContent.tsx │ │ │ │ ├── SupportingContentParser.ts │ │ │ │ └── index.ts │ │ │ ├── TokenClaimsDisplay │ │ │ │ ├── TokenClaimsDisplay.tsx │ │ │ │ └── index.tsx │ │ │ └── UserChatMessage │ │ │ │ ├── UserChatMessage.module.css │ │ │ │ ├── UserChatMessage.tsx │ │ │ │ └── index.ts │ │ ├── index.css │ │ ├── index.tsx │ │ ├── pages │ │ │ ├── NoPage.tsx │ │ │ ├── chat │ │ │ │ ├── Chat.module.css │ │ │ │ └── Chat.tsx │ │ │ └── layout │ │ │ │ ├── Layout.module.css │ │ │ │ └── Layout.tsx │ │ └── vite-env.d.ts │ ├── tsconfig.json │ └── vite.config.ts ├── package-lock.json ├── start-compose.ps1 └── start-compose.sh ├── azure.yaml ├── data ├── eventbrite.png └── gori.png ├── docs ├── assets │ ├── HLA-MCP.png │ ├── HLA.png │ ├── azd-success.png │ ├── java.png │ ├── langchain4j.png │ ├── multi-agents.png │ ├── robot-agents-small.png │ ├── robot-agents.png │ ├── transaction-tracing.png │ └── ui.gif ├── faq.md ├── kusto-queries.md ├── multi-agents │ └── introduction.md └── troubleshooting.md └── infra ├── app ├── account.bicep ├── copilot.bicep ├── payment.bicep ├── transaction.bicep └── web.bicep ├── main.bicep ├── main.parameters.json └── shared ├── abbreviations.json ├── ai └── cognitiveservices.bicep ├── backend-dashboard.bicep ├── host ├── container-app-upsert.bicep ├── container-app.bicep ├── container-apps-environment.bicep ├── container-apps.bicep └── container-registry.bicep ├── monitor ├── applicationinsights-dashboard.bicep ├── applicationinsights.bicep ├── loganalytics.bicep └── monitoring.bicep ├── security ├── keyvault-access.bicep ├── keyvault-secret.bicep ├── keyvault.bicep ├── registry-access.bicep └── role.bicep └── storage └── storage-account.bicep /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Java 17 and maven 3.8.8 DevContainer to build Java RAG example with Azure AI", 3 | "image": "mcr.microsoft.com/devcontainers/java:1-17-bullseye", 4 | "features": { 5 | "azure-cli": "latest", 6 | "ghcr.io/azure/azure-dev/azd:latest": { 7 | "version": "1.9.1" 8 | }, 9 | "ghcr.io/devcontainers/features/java:1": { 10 | "version": "none", 11 | "installMaven": true, 12 | "mavenVersion": "3.8.8" 13 | }, 14 | "ghcr.io/devcontainers/features/node:1": { 15 | "version": "20.5.0" 16 | }, 17 | 18 | "ghcr.io/devcontainers/features/git:1": { 19 | "version": "2.39.1" 20 | }, 21 | "ghcr.io/devcontainers-contrib/features/typescript:2": {}, 22 | "ghcr.io/devcontainers/features/kubectl-helm-minikube:1": {}, 23 | "docker-in-docker": { 24 | "version": "latest", 25 | "moby": true, 26 | "dockerDashComposeVersion": "v1" 27 | } 28 | }, 29 | "customizations": { 30 | "vscode": { 31 | "extensions": [ 32 | "GitHub.vscode-github-actions", 33 | "ms-azuretools.azure-dev", 34 | "ms-azuretools.vscode-bicep", 35 | "vscjava.vscode-java-pack", 36 | "amodio.tsl-problem-matcher" 37 | ] 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sh text eol=lf -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | > Please provide us with the following information: 5 | > --------------------------------------------------------------- 6 | 7 | ### This issue is for a: (mark with an `x`) 8 | ``` 9 | - [ ] bug report -> please search issues before submitting 10 | - [ ] feature request 11 | - [ ] documentation issue or request 12 | - [ ] regression (a behavior that used to work and stopped in a new release) 13 | ``` 14 | 15 | ### Minimal steps to reproduce 16 | > 17 | 18 | ### Any log messages given by the failure 19 | > 20 | 21 | ### Expected/desired behavior 22 | > 23 | 24 | ### OS and Version? 25 | > Windows 7, 8 or 10. Linux (which distribution). macOS (Yosemite? El Capitan? Sierra?) 26 | 27 | ### azd version? 28 | > run `azd version` and copy paste here. 29 | 30 | ### Versions 31 | > 32 | 33 | ### Mention any other details that might be useful 34 | 35 | > --------------------------------------------------------------- 36 | > Thanks! We'll be in touch soon. 37 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Purpose 2 | 3 | * ... 4 | 5 | ## Does this introduce a breaking change? 6 | 7 | ``` 8 | [ ] Yes 9 | [ ] No 10 | ``` 11 | 12 | ## Pull Request Type 13 | What kind of change does this Pull Request introduce? 14 | 15 | 16 | ``` 17 | [ ] Bugfix 18 | [ ] Feature 19 | [ ] Code style update (formatting, local variables) 20 | [ ] Refactoring (no functional changes, no api changes) 21 | [ ] Documentation content changes 22 | [ ] Other... Please describe: 23 | ``` 24 | 25 | ## How to Test 26 | * Get the code 27 | 28 | ``` 29 | git clone [repo-address] 30 | cd [repo-name] 31 | git checkout [branch-name] 32 | npm install 33 | ``` 34 | 35 | * Test the code 36 | 37 | ``` 38 | ``` 39 | 40 | ## What to Check 41 | Verify that the following are valid 42 | * ... 43 | 44 | ## Other Information 45 | -------------------------------------------------------------------------------- /.github/workflows/aca-deploy.yaml: -------------------------------------------------------------------------------- 1 | name: Reusable Azure Container Apps deploy 2 | on: 3 | workflow_call: 4 | inputs: 5 | env-name: 6 | required: true 7 | type: string 8 | image-name: 9 | required: true 10 | type: string 11 | container-app-name: 12 | required: true 13 | type: string 14 | container-app-env-name: 15 | required: true 16 | type: string 17 | 18 | # Set up permissions for deploying with secretless Azure federated credentials 19 | # https://learn.microsoft.com/en-us/azure/developer/github/connect-from-azure?tabs=azure-portal%2Clinux#set-up-azure-login-with-openid-connect-authentication 20 | permissions: 21 | id-token: write 22 | contents: read 23 | 24 | jobs: 25 | deploy: 26 | runs-on: ubuntu-latest 27 | environment: ${{inputs.env-name}} 28 | steps: 29 | - name: Log in to Azure with service principal 30 | uses: azure/login@v2 31 | if: ${{ vars.AZURE_CLIENT_ID == '' }} 32 | with: 33 | creds: ${{ secrets.AZURE_CREDENTIALS }} 34 | - name: Log in with Azure (Federated Credentials) 35 | if: ${{ vars.AZURE_CLIENT_ID != '' }} 36 | uses: azure/login@v2 37 | with: 38 | client-id: ${{ vars.AZURE_CLIENT_ID }} 39 | tenant-id: ${{ vars.AZURE_TENANT_ID }} 40 | subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }} 41 | - name: Build and deploy Container App 42 | uses: azure/container-apps-deploy-action@v1 43 | with: 44 | acrName: ${{vars.ACR_NAME}} 45 | containerAppName: ${{inputs.container-app-name}} 46 | containerAppEnvironment: ${{inputs.container-app-env-name}} 47 | resourceGroup: ${{vars.RESOURCE_GROUP}} 48 | imageToDeploy: ${{vars.ACR_NAME}}.azurecr.io/${{inputs.image-name}}:${{github.sha}} -------------------------------------------------------------------------------- /.github/workflows/acr-build-push.yaml: -------------------------------------------------------------------------------- 1 | name: Reusable ACR Build and Push workflow 2 | on: 3 | workflow_call: 4 | inputs: 5 | env-name: 6 | required: true 7 | type: string 8 | image-name: 9 | required: true 10 | type: string 11 | app-folder-path: 12 | required: true 13 | type: string 14 | 15 | # Set up permissions for deploying with secretless Azure federated credentials 16 | # https://learn.microsoft.com/en-us/azure/developer/github/connect-from-azure?tabs=azure-portal%2Clinux#set-up-azure-login-with-openid-connect-authentication 17 | permissions: 18 | id-token: write 19 | contents: read 20 | 21 | jobs: 22 | build: 23 | runs-on: ubuntu-latest 24 | environment: ${{inputs.env-name}} 25 | 26 | steps: 27 | - name: Log in to Azure with service principal 28 | if: ${{ vars.AZURE_CLIENT_ID == '' }} 29 | uses: azure/login@v2 30 | with: 31 | creds: ${{ secrets.AZURE_CREDENTIALS }} 32 | - name: Log in Azure Container Registry 33 | if: ${{ vars.AZURE_CLIENT_ID == '' }} 34 | uses: azure/docker-login@v2 35 | with: 36 | login-server: ${{vars.ACR_NAME}}.azurecr.io 37 | username: ${{ secrets.SPI_CLIENT_ID }} 38 | password: ${{ secrets.SPI_CLIENT_SECRET }} 39 | - name: Log in with Azure (Federated Credentials) 40 | if: ${{ vars.AZURE_CLIENT_ID != '' }} 41 | uses: azure/login@v2 42 | with: 43 | client-id: ${{ vars.AZURE_CLIENT_ID }} 44 | tenant-id: ${{ vars.AZURE_TENANT_ID }} 45 | subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }} 46 | - name: Login to Azure Container Registry (Federated Credentials) 47 | if: ${{ vars.AZURE_CLIENT_ID != '' }} 48 | run: az acr login --name ${{vars.ACR_NAME}} 49 | - uses: actions/checkout@v2 50 | - name: Build and Push to ACR 51 | run: | 52 | echo "Building image [${{ inputs.image-name }}] and environment [${{ inputs.env-name }}]" 53 | cd ${{ inputs.app-folder-path }} 54 | docker build . -t ${{vars.ACR_NAME}}.azurecr.io/${{inputs.image-name}}:${{github.sha}} 55 | docker push ${{vars.ACR_NAME}}.azurecr.io/${{inputs.image-name}}:${{github.sha}} 56 | echo "image successfully pushed: ${{vars.ACR_NAME}}.azurecr.io/${{inputs.image-name}}:${{github.sha}}" -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [project-title] Changelog 2 | 3 | 4 | # x.y.z (yyyy-mm-dd) 5 | 6 | *Features* 7 | * ... 8 | 9 | *Bug Fixes* 10 | * ... 11 | 12 | *Breaking Changes* 13 | * ... 14 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # These owners will be the default owners for everything in 2 | # this repo. Unless a later match takes precedence, 3 | # @global-owner1 and @global-owner2 will be requested for 4 | # review when someone opens a pull request. 5 | * @dantelmomsft 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Azure Samples 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE -------------------------------------------------------------------------------- /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](), 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://msrc.microsoft.com/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://www.microsoft.com/msrc/pgp-key-msrc). 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://www.microsoft.com/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://microsoft.com/msrc/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://www.microsoft.com/msrc/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/business-api/account/.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 | wrapperVersion=3.3.2 18 | distributionType=only-script 19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip 20 | -------------------------------------------------------------------------------- /app/business-api/account/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS build 2 | 3 | WORKDIR /workspace/app 4 | EXPOSE 3100 5 | 6 | COPY mvnw . 7 | COPY .mvn .mvn 8 | COPY pom.xml . 9 | COPY src src 10 | 11 | RUN chmod +x ./mvnw 12 | # Convert CRLF to LF 13 | RUN sed -i 's/\r$//' ./mvnw 14 | RUN ./mvnw package -DskipTests 15 | RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar) 16 | 17 | RUN apt-get update && apt-get install -y curl 18 | RUN curl -LJ -o applicationinsights-agent-3.5.4.jar https://github.com/microsoft/ApplicationInsights-Java/releases/download/3.5.4/applicationinsights-agent-3.5.4.jar 19 | COPY applicationinsights.json . 20 | 21 | #for production deployment use mcr.microsoft.com/openjdk/jdk:17-distroless 22 | FROM mcr.microsoft.com/openjdk/jdk:17-distroless 23 | 24 | ARG DEPENDENCY=/workspace/app/target/dependency 25 | COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib 26 | COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF 27 | COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app 28 | COPY --from=build /workspace/app/applicationinsights-agent-3.5.4.jar /app 29 | COPY --from=build /workspace/app/applicationinsights.json /app 30 | 31 | EXPOSE 8080 32 | 33 | ENTRYPOINT ["java","-javaagent:/app/applicationinsights-agent-3.5.4.jar","-noverify", "-XX:MaxRAMPercentage=70", "-XX:+UseParallelGC", "-XX:ActiveProcessorCount=2", "-cp","app:app/lib/*","com.microsoft.openai.samples.assistant.business.AccountApplication"] -------------------------------------------------------------------------------- /app/business-api/account/applicationinsights.json: -------------------------------------------------------------------------------- 1 | { 2 | "role": { 3 | "name": "accounts-api" 4 | } 5 | } -------------------------------------------------------------------------------- /app/business-api/account/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.3.6 9 | 10 | 11 | com.microsoft.openai.samples.assistant.business 12 | account 13 | 1.0.0-SNAPSHOT 14 | 15 | 16 | 17 17 | 17 18 | UTF-8 19 | 20 | 21 | 22 | 23 | 24 | 25 | org.springframework.ai 26 | spring-ai-bom 27 | 1.0.0-SNAPSHOT 28 | pom 29 | import 30 | 31 | 32 | 33 | 34 | 35 | 36 | org.springframework.ai 37 | spring-ai-starter-mcp-server-webmvc 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-web 42 | 43 | 44 | 45 | 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-maven-plugin 50 | 51 | 52 | 53 | 54 | 55 | 56 | Central Portal Snapshots 57 | central-portal-snapshots 58 | https://central.sonatype.com/repository/maven-snapshots/ 59 | 60 | false 61 | 62 | 63 | true 64 | 65 | 66 | 67 | spring-milestones 68 | Spring Milestones 69 | https://repo.spring.io/milestone 70 | 71 | false 72 | 73 | 74 | 75 | spring-snapshots 76 | Spring Snapshots 77 | https://repo.spring.io/snapshot 78 | 79 | false 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/AccountApplication.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | package com.microsoft.openai.samples.assistant.business; 3 | 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; 9 | 10 | @SpringBootApplication 11 | public class AccountApplication { 12 | 13 | private static final Logger LOG = LoggerFactory.getLogger(AccountApplication.class); 14 | 15 | public static void main(String[] args) { 16 | LOG.info( 17 | "Application profile from system property is [{}]", 18 | System.getProperty("spring.profiles.active")); 19 | new SpringApplication(AccountApplication.class).run(args); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/controller/AccountController.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.business.controller; 2 | 3 | import com.microsoft.openai.samples.assistant.business.models.Account; 4 | import com.microsoft.openai.samples.assistant.business.models.PaymentMethod; 5 | import com.microsoft.openai.samples.assistant.business.models.Beneficiary; 6 | import com.microsoft.openai.samples.assistant.business.service.AccountService; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PathVariable; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | import java.util.List; 15 | 16 | @RestController 17 | @RequestMapping("/accounts") 18 | public class AccountController { 19 | 20 | private final AccountService accountService; 21 | private static final Logger logger = LoggerFactory.getLogger(AccountController.class); 22 | 23 | public AccountController(AccountService accountService) { 24 | this.accountService = accountService; 25 | } 26 | 27 | @GetMapping("/{accountId}") 28 | public Account getAccountDetails(@PathVariable String accountId) { 29 | logger.info("Received request to get account details for account id: {}", accountId); 30 | return accountService.getAccountDetails(accountId); 31 | } 32 | 33 | @GetMapping("/{accountId}/paymentmethods/{methodId}") 34 | public PaymentMethod getPaymentMethodDetails(@PathVariable String accountId, @PathVariable String methodId) { 35 | logger.info("Received request to get payment method details for account id: {} and method id: {}", accountId, methodId); 36 | return accountService.getPaymentMethodDetails(methodId); 37 | } 38 | 39 | @GetMapping("/{accountId}/registeredBeneficiaries") 40 | public List getBeneficiaryDetails(@PathVariable String accountId) { 41 | logger.info("Received request to get beneficiary details for account id: {}", accountId); 42 | return accountService.getRegisteredBeneficiary(accountId); 43 | } 44 | } -------------------------------------------------------------------------------- /app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.business.controller; 2 | 3 | import com.microsoft.openai.samples.assistant.business.models.Account; 4 | import com.microsoft.openai.samples.assistant.business.service.UserService; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.PathVariable; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | import java.util.List; 14 | 15 | @RestController 16 | @RequestMapping("/users") 17 | public class UserController { 18 | 19 | private final UserService userService; 20 | private static final Logger logger = LoggerFactory.getLogger(UserController.class); 21 | 22 | 23 | @Autowired 24 | public UserController(UserService userService) { 25 | this.userService = userService; 26 | } 27 | @GetMapping("/{userName}/accounts") 28 | public List getAccountsByUserName(@PathVariable String userName) { 29 | // Implement the logic to get the list of all accounts for a specific user 30 | logger.info("Received request to get accounts for user: {}", userName); 31 | return userService.getAccountsByUserName(userName); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/mcp/config/MCPServerConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.business.mcp.config; 2 | 3 | 4 | import com.microsoft.openai.samples.assistant.business.mcp.server.AccountMCPService; 5 | import com.microsoft.openai.samples.assistant.business.mcp.server.UserMCPService; 6 | import org.springframework.ai.tool.ToolCallbackProvider; 7 | import org.springframework.ai.tool.method.MethodToolCallbackProvider; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | @Configuration 12 | public class MCPServerConfiguration { 13 | 14 | @Bean 15 | public ToolCallbackProvider accountTools(AccountMCPService accountMCPService) { 16 | return MethodToolCallbackProvider.builder().toolObjects(accountMCPService).build(); 17 | } 18 | 19 | @Bean 20 | public ToolCallbackProvider userTools(UserMCPService userMCPService) { 21 | return MethodToolCallbackProvider.builder().toolObjects(userMCPService).build(); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/mcp/server/AccountMCPService.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.business.mcp.server; 2 | 3 | import com.microsoft.openai.samples.assistant.business.models.Account; 4 | import com.microsoft.openai.samples.assistant.business.models.Beneficiary; 5 | import com.microsoft.openai.samples.assistant.business.models.PaymentMethod; 6 | import com.microsoft.openai.samples.assistant.business.service.AccountService; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.ai.tool.annotation.Tool; 9 | 10 | import java.util.List; 11 | 12 | @Service 13 | public class AccountMCPService { 14 | 15 | private final AccountService accountService; 16 | public AccountMCPService(AccountService accountService) { 17 | this.accountService = accountService; 18 | } 19 | 20 | @Tool(description = "Get account details and available payment methods") 21 | public Account getAccountDetails(String accountId) { 22 | return this.accountService.getAccountDetails(accountId); 23 | 24 | } 25 | 26 | @Tool(description = "Get payment method detail with available balance") 27 | public PaymentMethod getPaymentMethodDetails(String paymentMethodId) { 28 | return this.accountService.getPaymentMethodDetails(paymentMethodId); 29 | } 30 | 31 | @Tool(description = "Get list of registered beneficiaries for a specific account") 32 | public List getRegisteredBeneficiary(String accountId) { 33 | return this.accountService.getRegisteredBeneficiary(accountId); 34 | } 35 | } -------------------------------------------------------------------------------- /app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/mcp/server/UserMCPService.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.business.mcp.server; 2 | 3 | import com.microsoft.openai.samples.assistant.business.models.Account; 4 | import com.microsoft.openai.samples.assistant.business.service.UserService; 5 | import org.springframework.ai.tool.annotation.Tool; 6 | import org.springframework.ai.tool.annotation.ToolParam; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.Arrays; 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | 15 | @Service 16 | public class UserMCPService { 17 | private final UserService userService; 18 | 19 | public UserMCPService(UserService userService) { 20 | this.userService = userService; 21 | } 22 | 23 | @Tool(description = "Get the list of all accounts for a specific user") 24 | public List getAccountsByUserName(@ToolParam( description ="userName once the user has logged" ) String userName) { 25 | return userService.getAccountsByUserName(userName); 26 | } 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/models/Account.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.business.models; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 5 | import com.fasterxml.jackson.annotation.JsonInclude; 6 | import com.fasterxml.jackson.annotation.JsonProperty; 7 | 8 | import java.util.List; 9 | 10 | @JsonInclude(JsonInclude.Include.NON_NULL) 11 | @JsonIgnoreProperties(ignoreUnknown = true) 12 | public record Account( 13 | @JsonProperty("id") String id, 14 | @JsonProperty("userName") String userName, 15 | @JsonProperty("accountHolderFullName") String accountHolderFullName, 16 | @JsonProperty("currency") String currency, 17 | @JsonProperty("activationDate") String activationDate, 18 | @JsonProperty("balance") String balance, 19 | List paymentMethods 20 | ) {} 21 | 22 | -------------------------------------------------------------------------------- /app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/models/Beneficiary.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.business.models; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | 7 | 8 | @JsonIgnoreProperties(ignoreUnknown = true) 9 | public record Beneficiary( 10 | @JsonProperty("id") String id, 11 | @JsonProperty("fullName") String fullName, 12 | @JsonProperty("bankCode") String bankCode, 13 | @JsonProperty("bankName") String bankName 14 | ) {} 15 | 16 | -------------------------------------------------------------------------------- /app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/models/PaymentMethod.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.business.models; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 5 | import com.fasterxml.jackson.annotation.JsonInclude; 6 | import com.fasterxml.jackson.annotation.JsonProperty; 7 | 8 | @JsonInclude(JsonInclude.Include.NON_NULL) 9 | @JsonIgnoreProperties(ignoreUnknown = true) 10 | public record PaymentMethod( 11 | @JsonProperty("id") String id, 12 | @JsonProperty("type") String type, 13 | @JsonProperty("activationDate") String activationDate, 14 | @JsonProperty("expirationDate") String expirationDate, 15 | @JsonProperty("availableBalance") String availableBalance, 16 | // card number is valued only for credit card type 17 | @JsonProperty("cardNumber") String cardNumber 18 | ) {} 19 | 20 | -------------------------------------------------------------------------------- /app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/models/PaymentMethodSummary.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.business.models; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | @JsonIgnoreProperties(ignoreUnknown = true) 7 | public record PaymentMethodSummary( 8 | @JsonProperty("id") String id, 9 | @JsonProperty("type") String type, 10 | @JsonProperty("activationDate") String activationDate, 11 | @JsonProperty("expirationDate") String expirationDate 12 | ) {} 13 | 14 | -------------------------------------------------------------------------------- /app/business-api/account/src/main/java/com/microsoft/openai/samples/assistant/business/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.business.service; 2 | 3 | import com.microsoft.openai.samples.assistant.business.models.Account; 4 | import org.springframework.stereotype.Service; 5 | 6 | import java.util.*; 7 | 8 | 9 | @Service 10 | public class UserService { 11 | 12 | 13 | private Map accounts = new HashMap<>(); 14 | 15 | public UserService() { 16 | accounts.put( 17 | "alice.user@contoso.com", 18 | new Account( 19 | "1000", 20 | "alice.user@contoso.com", 21 | "Alice User", 22 | "USD", 23 | "2022-01-01", 24 | "5000", 25 | null 26 | ) 27 | ); 28 | accounts.put( 29 | "bob.user@contoso.com", 30 | new Account( 31 | "1010", 32 | "bob.user@contoso.com", 33 | "Bob User", 34 | "EUR", 35 | "2022-01-01", 36 | "10000", 37 | null 38 | ) 39 | ); 40 | accounts.put( 41 | "charlie.user@contoso.com", 42 | new Account( 43 | "1020", 44 | "charlie.user@contoso.com", 45 | "Charlie User", 46 | "EUR", 47 | "2022-01-01", 48 | "3000", 49 | null 50 | ) 51 | ); 52 | 53 | } 54 | public List getAccountsByUserName(String userName) { 55 | return Arrays.asList(accounts.get(userName)); 56 | } 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /app/business-api/account/src/main/resources/application-dev.properties: -------------------------------------------------------------------------------- 1 | server.port=8070 -------------------------------------------------------------------------------- /app/business-api/account/src/test/java/org/springframework/ai/mcp/sample/client/AccountMCPClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 - 2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.springframework.ai.mcp.sample.client; 17 | 18 | import java.util.Map; 19 | 20 | import io.modelcontextprotocol.client.McpClient; 21 | import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport; 22 | import io.modelcontextprotocol.spec.McpClientTransport; 23 | import io.modelcontextprotocol.spec.McpSchema.CallToolRequest; 24 | import io.modelcontextprotocol.spec.McpSchema.CallToolResult; 25 | import io.modelcontextprotocol.spec.McpSchema.ListToolsResult; 26 | 27 | /** 28 | * @author Christian Tzolov 29 | */ 30 | 31 | public class AccountMCPClient { 32 | 33 | 34 | 35 | public static void main(String[] args) { 36 | var transport = new HttpClientSseClientTransport("http://localhost:8070"); 37 | 38 | var client = McpClient.sync(transport).build(); 39 | 40 | client.initialize(); 41 | 42 | client.ping(); 43 | 44 | // List and demonstrate tools 45 | ListToolsResult toolsList = client.listTools(); 46 | System.out.println("Available Tools = " + toolsList); 47 | toolsList.tools().stream().forEach(tool -> { 48 | System.out.println("Tool: " + tool.name() + ", description: " + tool.description() + ", schema: " + tool.inputSchema()); 49 | }); 50 | 51 | CallToolResult accountResult = client.callTool(new CallToolRequest("getAccountDetails", 52 | Map.of("accountId", "1010"))); 53 | System.out.println("Account : " + accountResult); 54 | 55 | CallToolResult userResult = client.callTool(new CallToolRequest("getAccountsByUserName", 56 | Map.of("userName", "bob.user@contoso.com"))); 57 | System.out.println("Account for Bob : " + userResult); 58 | 59 | client.closeGracefully(); 60 | 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /app/business-api/payment/.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 | wrapperVersion=3.3.2 18 | distributionType=only-script 19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip 20 | -------------------------------------------------------------------------------- /app/business-api/payment/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS build 2 | 3 | WORKDIR /workspace/app 4 | EXPOSE 3100 5 | 6 | COPY mvnw . 7 | COPY .mvn .mvn 8 | COPY pom.xml . 9 | COPY src src 10 | 11 | RUN chmod +x ./mvnw 12 | # Convert CRLF to LF 13 | RUN sed -i 's/\r$//' ./mvnw 14 | RUN ./mvnw package -DskipTests 15 | RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar) 16 | 17 | RUN apt-get update && apt-get install -y curl 18 | RUN curl -LJ -o applicationinsights-agent-3.5.4.jar https://github.com/microsoft/ApplicationInsights-Java/releases/download/3.5.4/applicationinsights-agent-3.5.4.jar 19 | COPY applicationinsights.json . 20 | 21 | #for production deployment use mcr.microsoft.com/openjdk/jdk:17-distroless 22 | FROM mcr.microsoft.com/openjdk/jdk:17-distroless 23 | 24 | ARG DEPENDENCY=/workspace/app/target/dependency 25 | COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib 26 | COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF 27 | COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app 28 | COPY --from=build /workspace/app/applicationinsights-agent-3.5.4.jar /app 29 | COPY --from=build /workspace/app/applicationinsights.json /app 30 | 31 | EXPOSE 8080 32 | 33 | ENTRYPOINT ["java","-javaagent:/app/applicationinsights-agent-3.5.4.jar","-noverify", "-XX:MaxRAMPercentage=70", "-XX:+UseParallelGC", "-XX:ActiveProcessorCount=2", "-cp","app:app/lib/*","com.microsoft.openai.samples.assistant.business.PaymentApplication"] -------------------------------------------------------------------------------- /app/business-api/payment/applicationinsights.json: -------------------------------------------------------------------------------- 1 | { 2 | "role": { 3 | "name": "payments-api" 4 | } 5 | } -------------------------------------------------------------------------------- /app/business-api/payment/src/main/java/com/microsoft/openai/samples/assistant/business/PaymentApplication.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | package com.microsoft.openai.samples.assistant.business; 3 | 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | 9 | @SpringBootApplication 10 | public class PaymentApplication { 11 | 12 | private static final Logger LOG = LoggerFactory.getLogger(PaymentApplication.class); 13 | 14 | public static void main(String[] args) { 15 | LOG.info( 16 | "Application profile from system property is [{}]", 17 | System.getProperty("spring.profiles.active")); 18 | new SpringApplication(PaymentApplication.class).run(args); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/business-api/payment/src/main/java/com/microsoft/openai/samples/assistant/business/controller/PaymentsController.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.business.controller; 2 | 3 | import com.microsoft.openai.samples.assistant.business.models.Payment; 4 | import com.microsoft.openai.samples.assistant.business.service.PaymentService; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.web.bind.annotation.PostMapping; 9 | import org.springframework.web.bind.annotation.RequestBody; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | @RestController 13 | public class PaymentsController { 14 | 15 | private static final Logger logger = LoggerFactory.getLogger(PaymentsController.class); 16 | 17 | private final PaymentService paymentService; 18 | 19 | public PaymentsController(PaymentService paymentService) { 20 | this.paymentService = paymentService; 21 | } 22 | 23 | @PostMapping("/payments") 24 | public void submitPayment(@RequestBody Payment payment) { 25 | logger.info("Received payment request: {}", payment); 26 | paymentService.processPayment(payment); 27 | } 28 | } -------------------------------------------------------------------------------- /app/business-api/payment/src/main/java/com/microsoft/openai/samples/assistant/business/mcp/config/MCPServerConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.business.mcp.config; 2 | 3 | import com.microsoft.openai.samples.assistant.business.mcp.server.PaymentMCPService; 4 | import org.springframework.ai.tool.ToolCallbackProvider; 5 | import org.springframework.ai.tool.method.MethodToolCallbackProvider; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | @Configuration 10 | public class MCPServerConfiguration { 11 | 12 | @Bean 13 | public ToolCallbackProvider paymentTools(PaymentMCPService paymentMCPService) { 14 | return MethodToolCallbackProvider.builder().toolObjects(paymentMCPService).build(); 15 | } 16 | } -------------------------------------------------------------------------------- /app/business-api/payment/src/main/java/com/microsoft/openai/samples/assistant/business/mcp/server/PaymentMCPService.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.business.mcp.server; 2 | 3 | 4 | import com.microsoft.openai.samples.assistant.business.models.Payment; 5 | import com.microsoft.openai.samples.assistant.business.service.PaymentService; 6 | import org.springframework.ai.tool.annotation.Tool; 7 | 8 | import org.springframework.stereotype.Service; 9 | 10 | @Service 11 | public class PaymentMCPService { 12 | 13 | private final PaymentService paymentService; 14 | 15 | public PaymentMCPService(PaymentService paymentService) { 16 | this.paymentService = paymentService; 17 | } 18 | 19 | @Tool(description = "Submit a payment request") 20 | public void processPayment(Payment payment){ 21 | paymentService.processPayment(payment); 22 | } 23 | } -------------------------------------------------------------------------------- /app/business-api/payment/src/main/java/com/microsoft/openai/samples/assistant/business/models/Payment.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.business.models; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | 7 | @JsonIgnoreProperties(ignoreUnknown = true) 8 | public record Payment( 9 | @JsonProperty("description") String description, 10 | @JsonProperty("recipientName") String recipientName, 11 | @JsonProperty("recipientBankCode") String recipientBankCode, 12 | @JsonProperty("accountId") String accountId, 13 | @JsonProperty("paymentMethodId") String paymentMethodId, 14 | @JsonProperty("paymentType") String paymentType, 15 | @JsonProperty("amount") String amount, 16 | @JsonProperty("timestamp") String timestamp 17 | ) {} -------------------------------------------------------------------------------- /app/business-api/payment/src/main/java/com/microsoft/openai/samples/assistant/business/models/Transaction.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.business.models; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | 7 | @JsonIgnoreProperties(ignoreUnknown = true) 8 | public record Transaction( 9 | @JsonProperty("id") String id, 10 | @JsonProperty("description") String description, 11 | //income/outcome 12 | @JsonProperty("type") String type, 13 | @JsonProperty("recipientName") String recipientName, 14 | @JsonProperty("recipientBankReference") String recipientBankReference, 15 | @JsonProperty("accountId") String accountId, 16 | @JsonProperty("paymentType") String paymentType, 17 | @JsonProperty("amount") String amount, 18 | @JsonProperty("timestamp") String timestamp 19 | ) {} -------------------------------------------------------------------------------- /app/business-api/payment/src/main/resources/application-dev.properties: -------------------------------------------------------------------------------- 1 | server.port=8060 2 | 3 | logging.level.org.springframework.web=DEBUG -------------------------------------------------------------------------------- /app/business-api/payment/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | transactions.api.url=${TRANSACTIONS_API_SERVER_URL} -------------------------------------------------------------------------------- /app/business-api/payment/src/main/resources/payments.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | title: Payment API 4 | version: 1.0.0 5 | paths: 6 | /payments: 7 | post: 8 | operationId: submitPayment 9 | summary: Submit a payment request 10 | requestBody: 11 | required: true 12 | content: 13 | application/json: 14 | schema: 15 | $ref: '#/components/schemas/Payment' 16 | responses: 17 | '201': 18 | description: Payment request submitted successfully 19 | '400': 20 | description: Invalid request body 21 | '500': 22 | description: Internal server error 23 | components: 24 | schemas: 25 | Payment: 26 | type: object 27 | properties: 28 | description: 29 | type: string 30 | description: Description of the payment 31 | recipientName: 32 | type: string 33 | description: Name of the recipient 34 | recipientId: 35 | type: string 36 | description: ID of the recipient 37 | recipientBankCode: 38 | type: string 39 | description: Bank code of the recipient 40 | accountId: 41 | type: string 42 | description: ID of the account 43 | paymentMethodId: 44 | type: string 45 | description: ID of the payment method 46 | amount: 47 | type: string 48 | description: Amount of the payment 49 | timestamp: 50 | type: string 51 | description: Timestamp of the payment 52 | required: 53 | - description 54 | - recipientName 55 | - recipientBankCode 56 | - accountId 57 | - paymentMethodId 58 | - amount 59 | - timestamp -------------------------------------------------------------------------------- /app/business-api/transactions-history/.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 | wrapperVersion=3.3.2 18 | distributionType=only-script 19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip 20 | -------------------------------------------------------------------------------- /app/business-api/transactions-history/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS build 2 | 3 | WORKDIR /workspace/app 4 | EXPOSE 3100 5 | 6 | COPY mvnw . 7 | COPY .mvn .mvn 8 | COPY pom.xml . 9 | COPY src src 10 | 11 | RUN chmod +x ./mvnw 12 | # Convert CRLF to LF 13 | RUN sed -i 's/\r$//' ./mvnw 14 | RUN ./mvnw package -DskipTests 15 | RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar) 16 | 17 | #install curl 18 | RUN apt-get update && apt-get install -y curl 19 | RUN curl -LJ -o applicationinsights-agent-3.5.4.jar https://github.com/microsoft/ApplicationInsights-Java/releases/download/3.5.4/applicationinsights-agent-3.5.4.jar 20 | COPY applicationinsights.json . 21 | 22 | #for production deployment use mcr.microsoft.com/openjdk/jdk:17-distroless 23 | FROM mcr.microsoft.com/openjdk/jdk:17-distroless 24 | 25 | ARG DEPENDENCY=/workspace/app/target/dependency 26 | COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib 27 | COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF 28 | COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app 29 | COPY --from=build /workspace/app/applicationinsights-agent-3.5.4.jar /app 30 | COPY --from=build /workspace/app/applicationinsights.json /app 31 | 32 | EXPOSE 8080 33 | 34 | ENTRYPOINT ["java","-javaagent:/app/applicationinsights-agent-3.5.4.jar","-noverify", "-XX:MaxRAMPercentage=70", "-XX:+UseParallelGC", "-XX:ActiveProcessorCount=2", "-cp","app:app/lib/*","com.microsoft.openai.samples.assistant.business.TransactionsHistoryApplication"] -------------------------------------------------------------------------------- /app/business-api/transactions-history/applicationinsights.json: -------------------------------------------------------------------------------- 1 | { 2 | "role": { 3 | "name": "transactions-api" 4 | } 5 | } -------------------------------------------------------------------------------- /app/business-api/transactions-history/src/main/java/com/microsoft/openai/samples/assistant/business/Transaction.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.business; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | 7 | @JsonIgnoreProperties(ignoreUnknown = true) 8 | public record Transaction( 9 | @JsonProperty("id") String id, 10 | @JsonProperty("description") String description, 11 | //income/outcome 12 | @JsonProperty("type") String type, 13 | @JsonProperty("recipientName") String recipientName, 14 | @JsonProperty("recipientBankReference") String recipientBankReference, 15 | @JsonProperty("accountId") String accountId, 16 | @JsonProperty("paymentType") String paymentType, 17 | @JsonProperty("amount") String amount, 18 | @JsonProperty("timestamp") String timestamp 19 | ) {} -------------------------------------------------------------------------------- /app/business-api/transactions-history/src/main/java/com/microsoft/openai/samples/assistant/business/TransactionController.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.business; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.web.bind.annotation.*; 6 | 7 | import java.util.List; 8 | 9 | @RestController 10 | @RequestMapping("/transactions") 11 | public class TransactionController { 12 | 13 | private final TransactionService transactionService; 14 | private static final Logger logger = LoggerFactory.getLogger(TransactionController.class); 15 | public TransactionController(TransactionService transactionService) { 16 | this.transactionService = transactionService; 17 | } 18 | 19 | @GetMapping("/{accountId}") 20 | public List getTransactions(@PathVariable String accountId, @RequestParam(name = "recipient_name", required = false) String recipientName){ 21 | logger.info("Received request to get transactions for accountid[{}]. Recipient filter is[{}]",accountId,recipientName); 22 | if(recipientName != null && !recipientName.isEmpty()){ 23 | return transactionService.getTransactionsByRecipientName(accountId, recipientName); 24 | } 25 | else 26 | return transactionService.getlastTransactions(accountId); 27 | } 28 | 29 | @PostMapping("/{accountId}") 30 | public void notifyTransaction(@PathVariable String accountId, @RequestBody Transaction transaction){ 31 | logger.info("Received request to notify transaction for accountid[{}]. {}", accountId,transaction); 32 | transactionService.notifyTransaction(accountId, transaction); 33 | } 34 | 35 | 36 | } -------------------------------------------------------------------------------- /app/business-api/transactions-history/src/main/java/com/microsoft/openai/samples/assistant/business/TransactionsHistoryApplication.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | package com.microsoft.openai.samples.assistant.business; 3 | 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | 9 | @SpringBootApplication 10 | public class TransactionsHistoryApplication { 11 | 12 | private static final Logger LOG = LoggerFactory.getLogger(TransactionsHistoryApplication.class); 13 | 14 | public static void main(String[] args) { 15 | LOG.info( 16 | "Application profile from system property is [{}]", 17 | System.getProperty("spring.profiles.active")); 18 | new SpringApplication(TransactionsHistoryApplication.class).run(args); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/business-api/transactions-history/src/main/java/com/microsoft/openai/samples/assistant/business/mcp/config/MCPServerConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.business.mcp.config; 2 | 3 | import com.microsoft.openai.samples.assistant.business.mcp.server.TransactionMCPService; 4 | import org.springframework.ai.tool.ToolCallbackProvider; 5 | import org.springframework.ai.tool.method.MethodToolCallbackProvider; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | @Configuration 10 | public class MCPServerConfiguration { 11 | 12 | @Bean 13 | public ToolCallbackProvider transactionTools(TransactionMCPService transactionMCPService) { 14 | return MethodToolCallbackProvider.builder().toolObjects(transactionMCPService).build(); 15 | } 16 | } -------------------------------------------------------------------------------- /app/business-api/transactions-history/src/main/java/com/microsoft/openai/samples/assistant/business/mcp/server/TransactionMCPService.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.business.mcp.server; 2 | 3 | import com.microsoft.openai.samples.assistant.business.Transaction; 4 | import com.microsoft.openai.samples.assistant.business.TransactionService; 5 | import org.springframework.stereotype.Service; 6 | import org.springframework.ai.tool.annotation.Tool; 7 | import org.springframework.ai.tool.annotation.ToolParam; 8 | 9 | import java.util.List; 10 | 11 | @Service 12 | public class TransactionMCPService { 13 | 14 | private final TransactionService transactionService; 15 | 16 | public TransactionMCPService(TransactionService transactionService) { 17 | this.transactionService = transactionService; 18 | } 19 | 20 | @Tool(description = "Get transactions by recipient name") 21 | public List getTransactionsByRecipientName( 22 | @ToolParam(description = "The account ID") String accountId, 23 | @ToolParam(description = "The recipient's name") String recipientName) { 24 | return transactionService.getTransactionsByRecipientName(accountId, recipientName); 25 | } 26 | 27 | @Tool(description = "Get the last transactions for an account") 28 | public List getLastTransactions( 29 | @ToolParam(description = "The account ID") String accountId) { 30 | return transactionService.getlastTransactions(accountId); 31 | } 32 | } -------------------------------------------------------------------------------- /app/business-api/transactions-history/src/main/resources/application-dev.properties: -------------------------------------------------------------------------------- 1 | server.port=8090 2 | 3 | logging.level.org.springframework.web=DEBUG -------------------------------------------------------------------------------- /app/compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | frontend: 3 | image: agent-openai-java-banking-assistant/frontend 4 | build: ./frontend 5 | environment: 6 | REACT_APP_API_BASE_URL: "http://copilot:8080" 7 | ports: 8 | - "80:80" 9 | copilot: 10 | image: agent-openai-java-banking-assistant/copilot-backend 11 | build: ./copilot 12 | environment: 13 | - AZURE_STORAGE_ACCOUNT=${AZURE_STORAGE_ACCOUNT} 14 | - AZURE_STORAGE_CONTAINER=${AZURE_STORAGE_CONTAINER} 15 | - AZURE_OPENAI_CHATGPT_MODEL=${AZURE_OPENAI_CHATGPT_MODEL} 16 | - AZURE_OPENAI_SERVICE=${AZURE_OPENAI_SERVICE} 17 | - AZURE_OPENAI_CHATGPT_DEPLOYMENT=${AZURE_OPENAI_CHATGPT_DEPLOYMENT} 18 | - AZURE_DOCUMENT_INTELLIGENCE_SERVICE=${AZURE_DOCUMENT_INTELLIGENCE_SERVICE} 19 | - spring_profiles_active=docker 20 | - ACCOUNTS_API_SERVER_URL=http://account:8080 21 | - PAYMENTS_API_SERVER_URL=http://payment:8080 22 | - TRANSACTIONS_API_SERVER_URL=http://transaction:8080 23 | - AZURE_CLIENT_ID=${servicePrincipal} 24 | - AZURE_CLIENT_SECRET=${servicePrincipalPassword} 25 | - AZURE_TENANT_ID=${servicePrincipalTenant} 26 | account: 27 | image: agent-openai-java-banking-assistant/business-account 28 | build: 29 | context: ./business-api/account 30 | payment: 31 | image: agent-openai-java-banking-assistant/business-payment 32 | build: 33 | context: ./business-api/payment 34 | environment: 35 | - TRANSACTIONS_API_SERVER_URL=http://transaction:8080 36 | transaction: 37 | image: agent-openai-java-banking-assistant/business-transaction-history 38 | build: 39 | context: ./business-api/transactions-history 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/copilot/.mvn/jvm.config: -------------------------------------------------------------------------------- 1 | --add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED 2 | --add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED 3 | --add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED 4 | --add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED 5 | -------------------------------------------------------------------------------- /app/copilot/.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 | wrapperVersion=3.3.2 18 | distributionType=only-script 19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip 20 | -------------------------------------------------------------------------------- /app/copilot/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS build 2 | 3 | WORKDIR /workspace/app 4 | EXPOSE 3100 5 | 6 | COPY mvnw . 7 | COPY .mvn .mvn 8 | COPY pom.xml . 9 | COPY copilot-backend copilot-backend 10 | COPY copilot-common copilot-common 11 | COPY langchain4j-agents langchain4j-agents 12 | 13 | RUN chmod +x ./mvnw 14 | # Convert CRLF to LF 15 | RUN sed -i 's/\r$//' ./mvnw 16 | RUN ./mvnw package -DskipTests 17 | RUN mkdir -p copilot-backend/target/dependency && (cd copilot-backend/target/dependency; jar -xf ../*.jar) 18 | 19 | #install curl 20 | RUN apt-get update && apt-get install -y curl 21 | RUN curl -LJ -o applicationinsights-agent-3.5.4.jar https://github.com/microsoft/ApplicationInsights-Java/releases/download/3.5.4/applicationinsights-agent-3.5.4.jar 22 | COPY applicationinsights.json . 23 | 24 | #for production deployment use mcr.microsoft.com/openjdk/jdk:17-distroless 25 | FROM mcr.microsoft.com/openjdk/jdk:17-distroless 26 | 27 | ARG DEPENDENCY=/workspace/app/copilot-backend/target/dependency/ 28 | COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib 29 | COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF 30 | COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app 31 | COPY --from=build /workspace/app/applicationinsights-agent-3.5.4.jar /app 32 | COPY --from=build /workspace/app/applicationinsights.json /app 33 | EXPOSE 8080 34 | 35 | ENTRYPOINT ["java","-javaagent:/app/applicationinsights-agent-3.5.4.jar","-noverify", "-XX:MaxRAMPercentage=70", "-XX:+UseParallelGC", "-XX:ActiveProcessorCount=2", "-cp","app:app/lib/*","com.microsoft.openai.samples.assistant.CopilotApplication"] -------------------------------------------------------------------------------- /app/copilot/applicationinsights.json: -------------------------------------------------------------------------------- 1 | { 2 | "role": { 3 | "name": "copilot-api" 4 | } 5 | } -------------------------------------------------------------------------------- /app/copilot/copilot-backend/manifests/azd-env-configmap.yml: -------------------------------------------------------------------------------- 1 | # Updated 2024-04-18 16:01:23 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: azd-env-configmap 6 | data: 7 | API_ALLOW_ORIGINS: "" 8 | APPLICATIONINSIGHTS_CONNECTION_STRING: "InstrumentationKey=6fa478c2-3ea8-451e-8350-acafdaf6264a;IngestionEndpoint=https://francecentral-1.in.applicationinsights.azure.com/;LiveEndpoint=https://francecentral.livediagnostics.monitor.azure.com/;ApplicationId=cc2958e9-c1f3-4fc8-910f-49d81979c208" 9 | AZURE_AKS_CLUSTER_NAME: "aks-yvh3rjtnvwvvm" 10 | AZURE_CLIENT_ID: "19ccc9b6-37b0-47ce-b99e-efddf02bef26" 11 | AZURE_CONTAINER_REGISTRY_ENDPOINT: "cryvh3rjtnvwvvm.azurecr.io" 12 | AZURE_CONTAINER_REGISTRY_NAME: "cryvh3rjtnvwvvm" 13 | AZURE_ENV_NAME: "java-chat-12-aks-test" 14 | AZURE_FORMRECOGNIZER_RESOURCE_GROUP: "rg-java-chat-12-aks-test" 15 | AZURE_FORMRECOGNIZER_SERVICE: "cog-fr-yvh3rjtnvwvvm" 16 | AZURE_KEY_VAULT_ENDPOINT: "https://kv-yvh3rjtnvwvvm.vault.azure.net/" 17 | AZURE_LOCATION: "francecentral" 18 | AZURE_OPENAI_CHATGPT_DEPLOYMENT: "chat" 19 | AZURE_OPENAI_CHATGPT_MODEL: "gpt-35-turbo" 20 | AZURE_OPENAI_EMB_DEPLOYMENT: "embedding" 21 | AZURE_OPENAI_EMB_MODEL_NAME: "text-embedding-ada-002" 22 | AZURE_OPENAI_RESOURCE_GROUP: "rg-java-chat-12-aks-test" 23 | AZURE_OPENAI_SERVICE: "cog-yvh3rjtnvwvvm" 24 | AZURE_RESOURCE_GROUP: "rg-java-chat-12-aks-test" 25 | AZURE_SEARCH_INDEX: "gptkbindex" 26 | AZURE_SEARCH_SERVICE: "gptkb-yvh3rjtnvwvvm" 27 | AZURE_SEARCH_SERVICE_RESOURCE_GROUP: "rg-java-chat-12-aks-test" 28 | AZURE_SERVICEBUS_NAMESPACE: "sb-yvh3rjtnvwvvm" 29 | AZURE_SERVICEBUS_SKU_NAME: "Standard" 30 | AZURE_STORAGE_ACCOUNT: "styvh3rjtnvwvvm" 31 | AZURE_STORAGE_CONTAINER: "content" 32 | AZURE_STORAGE_RESOURCE_GROUP: "rg-java-chat-12-aks-test" 33 | AZURE_SUBSCRIPTION_ID: "8b82fc4d-aabe-4658-88f2-5674bf49eec0" 34 | AZURE_TENANT_ID: "fa3408a7-4997-49b5-bda2-62886815fa49" 35 | OPENAI_API_KEY: "" 36 | OPENAI_HOST: "azure" 37 | OPENAI_ORGANIZATION: "" 38 | REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING: "InstrumentationKey=6fa478c2-3ea8-451e-8350-acafdaf6264a;IngestionEndpoint=https://francecentral-1.in.applicationinsights.azure.com/;LiveEndpoint=https://francecentral.livediagnostics.monitor.azure.com/;ApplicationId=cc2958e9-c1f3-4fc8-910f-49d81979c208" 39 | SERVICE_API_ENDPOINT_URL: "http://68.220.199.26" 40 | SERVICE_API_IMAGE_NAME: "crnq6gqbtyxqno2.azurecr.io/azure-search-openai-demo/api-java-chat-12-aks-test:azd-deploy-1710941647" 41 | SERVICE_FRONTEND_ENDPOINT_URL: "http://10.0.118.173:80" 42 | SERVICE_FRONTEND_IMAGE_NAME: "crnq6gqbtyxqno2.azurecr.io/azure-search-openai-demo/frontend-java-chat-12-aks-test:azd-deploy-1710941650" 43 | SERVICE_INDEXER_ENDPOINT_URL: "http://10.0.165.214:80" 44 | SERVICE_INDEXER_IMAGE_NAME: "crnq6gqbtyxqno2.azurecr.io/azure-search-openai-demo/indexer-java-chat-12-aks-test:azd-deploy-1710941756" 45 | 46 | -------------------------------------------------------------------------------- /app/copilot/copilot-backend/manifests/backend-deployment.tmpl.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: backend-deployment 5 | namespace: azure-open-ai 6 | labels: 7 | app: backend 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: backend 13 | template: 14 | metadata: 15 | labels: 16 | app: backend 17 | spec: 18 | containers: 19 | - name: backend 20 | image: {{.Env.SERVICE_API_IMAGE_NAME}} 21 | imagePullPolicy: IfNotPresent 22 | ports: 23 | - containerPort: 8080 24 | envFrom: 25 | - configMapRef: 26 | name: azd-env-configmap 27 | resources: 28 | requests: 29 | memory: "2Gi" 30 | -------------------------------------------------------------------------------- /app/copilot/copilot-backend/manifests/backend-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: backend-service 5 | namespace: azure-open-ai 6 | spec: 7 | type: ClusterIP 8 | ports: 9 | - protocol: TCP 10 | port: 80 11 | targetPort: 8080 12 | selector: 13 | app: backend 14 | -------------------------------------------------------------------------------- /app/copilot/copilot-backend/manifests/ingress.yml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: ingress-api 5 | namespace: azure-open-ai 6 | # annotations: 7 | # nginx.ingress.kubernetes.io/use-regex: "true" 8 | # nginx.ingress.kubernetes.io/rewrite-target: /$2 9 | spec: 10 | ingressClassName: webapprouting.kubernetes.azure.com 11 | rules: 12 | - http: 13 | paths: 14 | - path: /api 15 | pathType: Prefix 16 | backend: 17 | service: 18 | name: backend-service 19 | port: 20 | number: 80 21 | - path: / 22 | pathType: Prefix 23 | backend: 24 | service: 25 | name: frontend-service 26 | port: 27 | number: 80 -------------------------------------------------------------------------------- /app/copilot/copilot-backend/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.3.6 9 | 10 | 11 | com.microsoft.openai.samples.assistant 12 | personal-finance-assistant-copilot 13 | 1.0.0-SNAPSHOT 14 | personal-finance-assistant-copilot-langchain4j 15 | This sample demonstrate how to create a generative ai multi-agent solution for a banking personal assistant 16 | 17 | 18 | 17 19 | 17 20 | 21 | 5.20.0 22 | 4.5.1 23 | 1.0.0-beta2 24 | 25 | 26 | 27 | 28 | 29 | com.azure.spring 30 | spring-cloud-azure-dependencies 31 | ${spring-cloud-azure.version} 32 | pom 33 | import 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | com.microsoft.openai.samples.assistant 42 | langchain4j-agents 43 | 1.0.0-SNAPSHOT 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-starter-web 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-starter-security 52 | 53 | 54 | dev.langchain4j 55 | langchain4j-azure-open-ai 56 | ${langchain4j.version} 57 | 58 | 59 | com.azure 60 | azure-identity 61 | 62 | 63 | 64 | 65 | 66 | 67 | org.springframework.boot 68 | spring-boot-maven-plugin 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/CopilotApplication.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | package com.microsoft.openai.samples.assistant; 3 | 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; 9 | 10 | @SpringBootApplication(exclude = { SecurityAutoConfiguration.class }) 11 | 12 | public class CopilotApplication { 13 | 14 | private static final Logger LOG = LoggerFactory.getLogger(CopilotApplication.class); 15 | 16 | public static void main(String[] args) { 17 | LOG.info( 18 | "Application profile from system property is [{}]", 19 | System.getProperty("spring.profiles.active")); 20 | new SpringApplication(CopilotApplication.class).run(args); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/common/ChatGPTMessage.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | package com.microsoft.openai.samples.assistant.common; 3 | 4 | import com.azure.core.util.ExpandableStringEnum; 5 | 6 | import java.util.Collection; 7 | 8 | public record ChatGPTMessage(ChatRole role, String content) { 9 | 10 | public static final class ChatRole extends ExpandableStringEnum { 11 | public static final ChatRole SYSTEM = fromString("system"); 12 | 13 | public static final ChatRole ASSISTANT = fromString("assistant"); 14 | 15 | public static final ChatRole USER = fromString("user"); 16 | 17 | public static ChatRole fromString(String name) { 18 | return fromString(name, ChatRole.class); 19 | } 20 | 21 | public static Collection values() { 22 | return values(ChatRole.class); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/config/AzureAuthenticationConfiguration.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | package com.microsoft.openai.samples.assistant.config; 3 | 4 | import com.azure.core.credential.TokenCredential; 5 | import com.azure.identity.AzureCliCredentialBuilder; 6 | import com.azure.identity.EnvironmentCredentialBuilder; 7 | import com.azure.identity.ManagedIdentityCredentialBuilder; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.context.annotation.Profile; 12 | 13 | @Configuration 14 | public class AzureAuthenticationConfiguration { 15 | 16 | @Value("${azure.identity.client-id}") 17 | String clientId; 18 | 19 | @Profile("dev") 20 | @Bean 21 | public TokenCredential localTokenCredential() { 22 | return new AzureCliCredentialBuilder().build(); 23 | } 24 | 25 | @Profile("docker") 26 | @Bean 27 | public TokenCredential servicePrincipalTokenCredential() { 28 | return new EnvironmentCredentialBuilder().build(); 29 | } 30 | 31 | @Bean 32 | @Profile("default") 33 | public TokenCredential managedIdentityTokenCredential() { 34 | if (this.clientId.equals("system-managed-identity")) 35 | return new ManagedIdentityCredentialBuilder().build(); 36 | else 37 | return new ManagedIdentityCredentialBuilder().clientId(this.clientId).build(); 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/config/BlobStorageProxyConfiguration.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | package com.microsoft.openai.samples.assistant.config; 3 | 4 | import com.azure.ai.documentintelligence.DocumentIntelligenceClient; 5 | import com.azure.core.credential.TokenCredential; 6 | import com.microsoft.openai.samples.assistant.invoice.DocumentIntelligenceInvoiceScanHelper; 7 | import com.microsoft.openai.samples.assistant.langchain4j.agent.mcp.AccountMCPAgent; 8 | import com.microsoft.openai.samples.assistant.langchain4j.agent.mcp.PaymentMCPAgent; 9 | import com.microsoft.openai.samples.assistant.langchain4j.agent.mcp.TransactionHistoryMCPAgent; 10 | import com.microsoft.openai.samples.assistant.proxy.BlobStorageProxy; 11 | import com.microsoft.openai.samples.assistant.security.LoggedUserService; 12 | import dev.langchain4j.model.chat.ChatLanguageModel; 13 | import org.springframework.beans.factory.annotation.Value; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | 17 | @Configuration 18 | public class BlobStorageProxyConfiguration { 19 | @Value("${storage-account.service}") 20 | String storageAccountServiceName; 21 | @Value("${blob.container.name}") 22 | String containerName; 23 | 24 | @Bean 25 | public BlobStorageProxy blobStorageProxy(TokenCredential tokenCredential) { 26 | return new BlobStorageProxy(storageAccountServiceName,containerName,tokenCredential); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/config/DocumentIntelligenceConfiguration.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | package com.microsoft.openai.samples.assistant.config; 3 | 4 | import com.azure.ai.documentintelligence.DocumentIntelligenceClient; 5 | import com.azure.ai.documentintelligence.DocumentIntelligenceClientBuilder; 6 | import com.azure.core.credential.TokenCredential; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | @Configuration 12 | public class DocumentIntelligenceConfiguration { 13 | 14 | @Value("${documentintelligence.service}") 15 | String documentIntelligenceServiceName; 16 | 17 | final TokenCredential tokenCredential; 18 | 19 | public DocumentIntelligenceConfiguration(TokenCredential tokenCredential) { 20 | this.tokenCredential = tokenCredential; 21 | } 22 | 23 | @Bean 24 | public DocumentIntelligenceClient documentIntelligenceClient() { 25 | String endpoint = "https://%s.cognitiveservices.azure.com".formatted(documentIntelligenceServiceName); 26 | 27 | return new DocumentIntelligenceClientBuilder() 28 | .credential(tokenCredential) 29 | .endpoint(endpoint) 30 | .buildClient(); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/config/DocumentIntelligenceInvoiceScanConfiguration.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | package com.microsoft.openai.samples.assistant.config; 3 | 4 | import com.azure.ai.documentintelligence.DocumentIntelligenceClient; 5 | import com.azure.ai.documentintelligence.DocumentIntelligenceClientBuilder; 6 | import com.azure.core.credential.TokenCredential; 7 | import com.microsoft.openai.samples.assistant.invoice.DocumentIntelligenceInvoiceScanHelper; 8 | import com.microsoft.openai.samples.assistant.proxy.BlobStorageProxy; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | 13 | @Configuration 14 | public class DocumentIntelligenceInvoiceScanConfiguration { 15 | 16 | @Value("${documentintelligence.service}") 17 | String documentIntelligenceServiceName; 18 | 19 | final TokenCredential tokenCredential; 20 | 21 | public DocumentIntelligenceInvoiceScanConfiguration(TokenCredential tokenCredential) { 22 | this.tokenCredential = tokenCredential; 23 | } 24 | 25 | @Bean 26 | public DocumentIntelligenceInvoiceScanHelper documentIntelligenceInvoiceScanHelper(BlobStorageProxy blobStorageProxy) { 27 | 28 | String endpoint = "https://%s.cognitiveservices.azure.com".formatted(documentIntelligenceServiceName); 29 | 30 | var documentIntelligenceClient = new DocumentIntelligenceClientBuilder() 31 | .credential(tokenCredential) 32 | .endpoint(endpoint) 33 | .buildClient(); 34 | 35 | return new DocumentIntelligenceInvoiceScanHelper(documentIntelligenceClient,blobStorageProxy); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/config/Langchain4JConfiguration.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | package com.microsoft.openai.samples.assistant.config; 3 | 4 | 5 | import com.azure.ai.openai.OpenAIClient; 6 | 7 | import dev.langchain4j.model.azure.AzureOpenAiChatModel; 8 | import dev.langchain4j.model.chat.ChatLanguageModel; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | 13 | @Configuration 14 | public class Langchain4JConfiguration { 15 | 16 | @Value("${openai.chatgpt.deployment}") 17 | private String gptChatDeploymentModelId; 18 | 19 | @Bean 20 | public ChatLanguageModel chatLanguageModel(OpenAIClient azureOpenAICLient) { 21 | 22 | return AzureOpenAiChatModel.builder() 23 | .openAIClient(azureOpenAICLient) 24 | .deploymentName(gptChatDeploymentModelId) 25 | .temperature(0.3) 26 | .logRequestsAndResponses(true) 27 | .build(); 28 | } 29 | 30 | 31 | } 32 | -------------------------------------------------------------------------------- /app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/config/MCPAgentsConfiguration.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | package com.microsoft.openai.samples.assistant.config; 3 | 4 | import com.microsoft.openai.samples.assistant.invoice.DocumentIntelligenceInvoiceScanHelper; 5 | import com.microsoft.openai.samples.assistant.langchain4j.agent.SupervisorAgent; 6 | import com.microsoft.openai.samples.assistant.langchain4j.agent.mcp.AccountMCPAgent; 7 | import com.microsoft.openai.samples.assistant.langchain4j.agent.mcp.PaymentMCPAgent; 8 | import com.microsoft.openai.samples.assistant.langchain4j.agent.mcp.TransactionHistoryMCPAgent; 9 | import com.microsoft.openai.samples.assistant.security.LoggedUserService; 10 | import dev.langchain4j.model.chat.ChatLanguageModel; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.context.annotation.Bean; 13 | import org.springframework.context.annotation.Configuration; 14 | 15 | import java.util.List; 16 | 17 | @Configuration 18 | public class MCPAgentsConfiguration { 19 | @Value("${transactions.api.url}") String transactionsMCPServerUrl; 20 | @Value("${accounts.api.url}") String accountsMCPServerUrl; 21 | @Value("${payments.api.url}") String paymentsMCPServerUrl; 22 | 23 | private final ChatLanguageModel chatLanguageModel; 24 | private final LoggedUserService loggedUserService; 25 | private final DocumentIntelligenceInvoiceScanHelper documentIntelligenceInvoiceScanHelper; 26 | 27 | public MCPAgentsConfiguration(ChatLanguageModel chatLanguageModel, LoggedUserService loggedUserService, DocumentIntelligenceInvoiceScanHelper documentIntelligenceInvoiceScanHelper) { 28 | this.chatLanguageModel = chatLanguageModel; 29 | this.loggedUserService = loggedUserService; 30 | this.documentIntelligenceInvoiceScanHelper = documentIntelligenceInvoiceScanHelper; 31 | } 32 | @Bean 33 | public AccountMCPAgent accountMCPAgent() { 34 | return new AccountMCPAgent(chatLanguageModel, loggedUserService.getLoggedUser().username(), accountsMCPServerUrl); 35 | } 36 | 37 | @Bean 38 | public TransactionHistoryMCPAgent transactionHistoryMCPAgent() { 39 | return new TransactionHistoryMCPAgent(chatLanguageModel, loggedUserService.getLoggedUser().username(), transactionsMCPServerUrl,accountsMCPServerUrl); 40 | } 41 | 42 | @Bean 43 | public PaymentMCPAgent paymentMCPAgent() { 44 | return new PaymentMCPAgent(chatLanguageModel,documentIntelligenceInvoiceScanHelper, loggedUserService.getLoggedUser().username(),transactionsMCPServerUrl,accountsMCPServerUrl, paymentsMCPServerUrl); 45 | } 46 | 47 | @Bean 48 | public SupervisorAgent supervisorAgent(ChatLanguageModel chatLanguageModel){ 49 | return new SupervisorAgent(chatLanguageModel, 50 | List.of(accountMCPAgent(), 51 | transactionHistoryMCPAgent(), 52 | paymentMCPAgent())); 53 | 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatAppRequest.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | package com.microsoft.openai.samples.assistant.controller; 3 | 4 | import java.util.List; 5 | 6 | public record ChatAppRequest( 7 | List messages, 8 | 9 | List attachments, 10 | ChatAppRequestContext context, 11 | boolean stream, 12 | String approach) {} 13 | -------------------------------------------------------------------------------- /app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatAppRequestContext.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | package com.microsoft.openai.samples.assistant.controller; 3 | 4 | public record ChatAppRequestContext(ChatAppRequestOverrides overrides) {} 5 | -------------------------------------------------------------------------------- /app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatAppRequestOverrides.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | package com.microsoft.openai.samples.assistant.controller; 3 | 4 | public record ChatAppRequestOverrides( 5 | boolean semantic_ranker, 6 | boolean semantic_captions, 7 | String exclude_category, 8 | int top, 9 | float temperature, 10 | String prompt_template, 11 | String prompt_template_prefix, 12 | String prompt_template_suffix, 13 | boolean suggest_followup_questions, 14 | boolean use_oid_security_filter, 15 | boolean use_groups_security_filter, 16 | String semantic_kernel_mode) {} 17 | -------------------------------------------------------------------------------- /app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ChatResponse.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | package com.microsoft.openai.samples.assistant.controller; 3 | 4 | 5 | 6 | import com.microsoft.openai.samples.assistant.common.ChatGPTMessage; 7 | import dev.langchain4j.data.message.AiMessage; 8 | 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | public record ChatResponse(List choices) { 13 | 14 | public static ChatResponse buildChatResponse(AiMessage aiMessage) { 15 | List dataPoints = Collections.emptyList(); 16 | String thoughts = ""; 17 | List attachments = Collections.emptyList(); 18 | 19 | return new ChatResponse( 20 | List.of( 21 | new ResponseChoice( 22 | 0, 23 | new ResponseMessage( 24 | aiMessage.text(), 25 | ChatGPTMessage.ChatRole.ASSISTANT.toString(), 26 | attachments 27 | ), 28 | new ResponseContext(thoughts, dataPoints), 29 | new ResponseMessage( 30 | aiMessage.text(), 31 | ChatGPTMessage.ChatRole.ASSISTANT.toString(), 32 | attachments)))); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ResponseChoice.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | package com.microsoft.openai.samples.assistant.controller; 3 | 4 | public record ResponseChoice( 5 | int index, ResponseMessage message, ResponseContext context, ResponseMessage delta) {} 6 | -------------------------------------------------------------------------------- /app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ResponseContext.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | package com.microsoft.openai.samples.assistant.controller; 3 | 4 | import java.util.List; 5 | 6 | public record ResponseContext(String thoughts, List data_points) {} 7 | -------------------------------------------------------------------------------- /app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/ResponseMessage.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | package com.microsoft.openai.samples.assistant.controller; 3 | 4 | import java.util.List; 5 | 6 | public record ResponseMessage(String content, String role, List attachments) {} 7 | -------------------------------------------------------------------------------- /app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/controller/auth/AuthSetup.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | package com.microsoft.openai.samples.assistant.controller.auth; 3 | 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | @RestController 8 | public class AuthSetup { 9 | 10 | @GetMapping("/api/auth_setup") 11 | public String authSetup() { 12 | return """ 13 | { 14 | "useLogin": false 15 | } 16 | """ 17 | .stripIndent(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/LoggedUserPlugin.java.sample: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.plugin; 2 | 3 | import com.microsoft.openai.samples.assistant.security.LoggedUser; 4 | import com.microsoft.openai.samples.assistant.security.LoggedUserService; 5 | import com.microsoft.semantickernel.semanticfunctions.annotations.DefineKernelFunction; 6 | 7 | public class LoggedUserPlugin { 8 | 9 | private final LoggedUserService loggedUserService; 10 | public LoggedUserPlugin(LoggedUserService loggedUserService) 11 | { 12 | this.loggedUserService = loggedUserService; 13 | } 14 | @DefineKernelFunction(name = "UserContext", description = "Gets the user details after login") 15 | public String getUserContext() { 16 | LoggedUser loggedUser = loggedUserService.getLoggedUser(); 17 | return loggedUser.toString(); 18 | 19 | 20 | } 21 | 22 | } 23 | 24 | -------------------------------------------------------------------------------- /app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/PaymentMockPlugin.java.sample: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.plugin; 2 | 3 | import com.microsoft.semantickernel.semanticfunctions.annotations.DefineKernelFunction; 4 | import com.microsoft.semantickernel.semanticfunctions.annotations.KernelFunctionParameter; 5 | 6 | public class PaymentMockPlugin { 7 | 8 | 9 | @DefineKernelFunction(name = "payBill", description = "Gets the last payment transactions based on the payee, recipient name") 10 | public String submitBillPayment( 11 | @KernelFunctionParameter(name = "recipientName", description = "Name of the payee, recipient") String recipientName, 12 | @KernelFunctionParameter(name = "documentId", description = " the bill id or invoice number") String documentID, 13 | @KernelFunctionParameter(name = "amount", description = "the total amount to pay") String amount) { 14 | 15 | System.out.println("Bill payment executed for recipient: " + recipientName + " with documentId: " + documentID + " and amount: " + amount); 16 | 17 | return "Payment Successful"; 18 | 19 | } 20 | 21 | } 22 | 23 | -------------------------------------------------------------------------------- /app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/TransactionHistoryMockPlugin.java.sample: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.plugin; 2 | 3 | import com.microsoft.openai.samples.assistant.plugin.mock.TransactionServiceMock; 4 | import com.microsoft.semantickernel.semanticfunctions.annotations.DefineKernelFunction; 5 | import com.microsoft.semantickernel.semanticfunctions.annotations.KernelFunctionParameter; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | public class TransactionHistoryMockPlugin { 10 | private static final Logger LOGGER = LoggerFactory.getLogger(TransactionHistoryMockPlugin.class); 11 | private final TransactionServiceMock transactionService; 12 | public TransactionHistoryMockPlugin(){ 13 | this.transactionService = new TransactionServiceMock(); 14 | } 15 | 16 | @DefineKernelFunction(name = "getTransactionsByRecepient", description = "Gets the last payment transactions based on the payee, recipient name") 17 | public String getTransactionsByRecepient( 18 | @KernelFunctionParameter(name = "accountId", description = "The banking account id of the user") String accountId, 19 | @KernelFunctionParameter(name = "recipientName", description = "Name of the payee, recipient") String recipientName) { 20 | String transactionsByRecipient = transactionService.getTransactionsByRecipientName(accountId,recipientName).toString(); 21 | LOGGER.info("Transactions for [{}]:{} ",recipientName,transactionsByRecipient); 22 | return transactionsByRecipient; 23 | 24 | 25 | } 26 | 27 | 28 | @DefineKernelFunction(name = "getTransactions", description = "Gets the last payment transactions") 29 | public String getTransactions( 30 | @KernelFunctionParameter(name = "accountId", description = "The banking account id of the user") String accountId 31 | ) { 32 | String lastTransactions = transactionService.getlastTransactions(accountId).toString(); 33 | LOGGER.info("Last transactions:{} ",lastTransactions); 34 | return lastTransactions; 35 | 36 | 37 | } 38 | 39 | 40 | } 41 | 42 | -------------------------------------------------------------------------------- /app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/plugin/mock/PaymentTransaction.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.plugin.mock; 2 | 3 | public record PaymentTransaction( 4 | String id, 5 | String description, 6 | String recipientName, 7 | String recipientBankReference, 8 | String accountId, 9 | String paymentType, 10 | String amount, 11 | String timestamp 12 | ) {} 13 | 14 | -------------------------------------------------------------------------------- /app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/security/LoggedUser.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.security; 2 | 3 | public record LoggedUser(String username, String mail, String role, String displayName) { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /app/copilot/copilot-backend/src/main/java/com/microsoft/openai/samples/assistant/security/LoggedUserService.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.security; 2 | 3 | import org.springframework.security.authentication.AnonymousAuthenticationToken; 4 | import org.springframework.security.core.Authentication; 5 | import org.springframework.security.core.context.SecurityContextHolder; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class LoggedUserService { 10 | 11 | public LoggedUser getLoggedUser(){ 12 | Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); 13 | 14 | //this is always true in the PoC code 15 | if(authentication == null) { 16 | return getDefaultUser(); 17 | } 18 | //this code is never executed in the PoC. It's a hook for future improvements requiring integration with authentication providers. 19 | if (!(authentication instanceof AnonymousAuthenticationToken)) { 20 | String currentUserName = authentication.getName(); 21 | 22 | Object details = authentication.getDetails(); 23 | //object should be cast to specific type based on the authentication provider 24 | return new LoggedUser(currentUserName, "changeme@contoso.com", "changeme", "changeme"); 25 | } 26 | return getDefaultUser(); 27 | } 28 | 29 | private LoggedUser getDefaultUser() { 30 | return new LoggedUser("bob.user@contoso.com", "bob.user@contoso.com", "generic", "Bob The User"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/copilot/copilot-backend/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | #Used to enable mocked class to take precedence over real class in unit tests 2 | spring.main.lazy-initialization=true 3 | 4 | openai.service=${AZURE_OPENAI_SERVICE} 5 | openai.chatgpt.deployment=${AZURE_OPENAI_CHATGPT_DEPLOYMENT:gpt-4o} 6 | openai.tracing.enabled=${AZURE_OPENAI_TRACING_ENABLED:false} 7 | 8 | documentintelligence.service=${AZURE_DOCUMENT_INTELLIGENCE_SERVICE:example} 9 | 10 | 11 | storage-account.service=${AZURE_STORAGE_ACCOUNT} 12 | blob.container.name=${AZURE_STORAGE_CONTAINER:content} 13 | 14 | logging.level.com.microsoft.openai.samples.rag.ask.approaches.semantickernel=DEBUG 15 | logging.level.com.microsoft.semantickernel.samples.openapi.OpenAPIHttpRequestPlugin=DEBUG 16 | 17 | server.error.include-message=always 18 | 19 | # Support for User Assigned Managed identity 20 | azure.identity.client-id=${AZURE_CLIENT_ID:system-managed-identity} 21 | 22 | # MCP endpoints 23 | transactions.api.url=${TRANSACTIONS_API_SERVER_URL}/sse 24 | accounts.api.url=${ACCOUNTS_API_SERVER_URL}/sse 25 | payments.api.url=${PAYMENTS_API_SERVER_URL}/sse -------------------------------------------------------------------------------- /app/copilot/copilot-backend/src/main/resources/payments.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | title: Payment API 4 | version: 1.0.0 5 | paths: 6 | /payments: 7 | post: 8 | operationId: submitPayment 9 | summary: Submit a payment request 10 | description: Submit a payment request 11 | requestBody: 12 | required: true 13 | description: Payment to submit 14 | content: 15 | application/json: 16 | schema: 17 | $ref: '#/components/schemas/Payment' 18 | responses: 19 | '200': 20 | description: Payment request submitted successfully 21 | '400': 22 | description: Invalid request body 23 | '500': 24 | description: Internal server error 25 | components: 26 | schemas: 27 | Payment: 28 | type: object 29 | properties: 30 | description: 31 | type: string 32 | description: Description of the payment 33 | recipientName: 34 | type: string 35 | description: Name of the recipient 36 | recipientBankCode: 37 | type: string 38 | description: Bank code of the recipient 39 | accountId: 40 | type: string 41 | description: ID of the account 42 | paymentMethodId: 43 | type: string 44 | description: ID of the payment method 45 | paymentType: 46 | type: string 47 | description: 'The type of payment: creditcard, banktransfer, directdebit, visa, mastercard, paypal, etc.' 48 | amount: 49 | type: string 50 | description: Amount of the payment 51 | timestamp: 52 | type: string 53 | description: Timestamp of the payment 54 | requestBodies: 55 | Payment: 56 | content: 57 | application/json: 58 | schema: 59 | $ref: '#/components/schemas/Payment' 60 | description: Payment object to submit -------------------------------------------------------------------------------- /app/copilot/copilot-backend/src/test/java/com/microsoft/openai/samples/assistant/AccountAgentIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant; 2 | 3 | public class AccountAgentIntegrationTest { 4 | 5 | public static void main(String[] args) { 6 | 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /app/copilot/copilot-common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.microsoft.openai.samples.assistant 8 | copilot-parent 9 | 1.0.0-SNAPSHOT 10 | 11 | 12 | copilot-common 13 | 14 | 15 | 16 | com.azure 17 | azure-ai-documentintelligence 18 | 19 | 20 | 21 | 22 | org.json 23 | json 24 | 20240303 25 | 26 | 27 | com.azure 28 | azure-storage-blob 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/copilot/copilot-common/src/main/java/com/microsoft/openai/samples/assistant/proxy/BlobStorageProxy.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft. All rights reserved. 2 | package com.microsoft.openai.samples.assistant.proxy; 3 | 4 | import com.azure.core.credential.TokenCredential; 5 | import com.azure.storage.blob.BlobClient; 6 | import com.azure.storage.blob.BlobContainerClient; 7 | import com.azure.storage.blob.BlobContainerClientBuilder; 8 | 9 | 10 | import java.io.ByteArrayInputStream; 11 | import java.io.ByteArrayOutputStream; 12 | import java.io.IOException; 13 | 14 | /** 15 | * This class is a proxy to the Blob storage API. It is responsible for: - calling the API - 16 | * handling errors and retry strategy - add monitoring points - add circuit breaker with exponential 17 | * backoff 18 | */ 19 | 20 | public class BlobStorageProxy { 21 | 22 | private final BlobContainerClient client; 23 | 24 | public BlobStorageProxy( 25 | String storageAccountServiceName, 26 | String containerName, 27 | TokenCredential tokenCredential) { 28 | 29 | String endpoint = "https://%s.blob.core.windows.net".formatted(storageAccountServiceName); 30 | this.client = 31 | new BlobContainerClientBuilder() 32 | .endpoint(endpoint) 33 | .credential(tokenCredential) 34 | .containerName(containerName) 35 | .buildClient(); 36 | } 37 | 38 | public byte[] getFileAsBytes(String fileName) throws IOException { 39 | var blobClient = client.getBlobClient(fileName); 40 | int dataSize = (int) blobClient.getProperties().getBlobSize(); 41 | 42 | // There is no need to close ByteArrayOutputStream. 43 | // https://docs.oracle.com/javase/8/docs/api/java/io/ByteArrayOutputStream.html 44 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(dataSize); 45 | blobClient.downloadStream(outputStream); 46 | 47 | return outputStream.toByteArray(); 48 | } 49 | 50 | public void storeFile(byte[] bytes, String originalFilename) { 51 | BlobClient blobClient = client.getBlobClient(originalFilename); 52 | blobClient.upload(new ByteArrayInputStream(bytes), bytes.length, true); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/copilot/langchain4j-agents/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.microsoft.openai.samples.assistant 6 | copilot-parent 7 | 1.0.0-SNAPSHOT 8 | ../pom.xml 9 | 10 | 11 | langchain4j-agents 12 | agents implementation based on LangChain4j 13 | 14 | 15 | 16 | 17 | com.microsoft.openai.samples.assistant 18 | copilot-common 19 | 1.0.0-SNAPSHOT 20 | 21 | 22 | dev.langchain4j 23 | langchain4j-mcp 24 | 25 | ${langchain4j.version} 26 | 27 | 28 | dev.langchain4j 29 | langchain4j-azure-open-ai 30 | ${langchain4j.version} 31 | test 32 | 33 | 34 | com.azure 35 | azure-identity 36 | 37 | test 38 | 39 | 40 | org.junit.jupiter 41 | junit-jupiter-api 42 | 5.11.3 43 | test 44 | 45 | 46 | ch.qos.logback 47 | logback-classic 48 | 1.5.8 49 | test 50 | 51 | 52 | org.assertj 53 | assertj-core 54 | 3.27.3 55 | test 56 | 57 | 58 | org.wiremock 59 | wiremock 60 | 3.12.1 61 | test 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /app/copilot/langchain4j-agents/src/main/java/com/microsoft/langchain4j/agent/Agent.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.langchain4j.agent; 2 | 3 | import dev.langchain4j.data.message.ChatMessage; 4 | 5 | import java.util.List; 6 | 7 | public interface Agent { 8 | 9 | String getName(); 10 | AgentMetadata getMetadata(); 11 | List invoke(List chatHistory) throws AgentExecutionException; 12 | } 13 | -------------------------------------------------------------------------------- /app/copilot/langchain4j-agents/src/main/java/com/microsoft/langchain4j/agent/AgentExecutionException.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.langchain4j.agent; 2 | 3 | public class AgentExecutionException extends RuntimeException { 4 | public AgentExecutionException(String message) { 5 | super(message); 6 | } 7 | 8 | public AgentExecutionException(String message, Throwable cause) { 9 | super(message, cause); 10 | } 11 | } -------------------------------------------------------------------------------- /app/copilot/langchain4j-agents/src/main/java/com/microsoft/langchain4j/agent/AgentMetadata.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.langchain4j.agent; 2 | 3 | import java.util.List; 4 | 5 | public record AgentMetadata(String description, List intents) { 6 | } 7 | 8 | -------------------------------------------------------------------------------- /app/copilot/langchain4j-agents/src/main/java/com/microsoft/langchain4j/agent/mcp/MCPProtocolType.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.langchain4j.agent.mcp; 2 | 3 | public enum MCPProtocolType { 4 | SSE, 5 | STDIO 6 | } 7 | -------------------------------------------------------------------------------- /app/copilot/langchain4j-agents/src/main/java/com/microsoft/langchain4j/agent/mcp/MCPServerMetadata.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.langchain4j.agent.mcp; 2 | 3 | public record MCPServerMetadata(String serverName, String url, MCPProtocolType protocolType) { 4 | } 5 | 6 | -------------------------------------------------------------------------------- /app/copilot/langchain4j-agents/src/main/java/com/microsoft/openai/samples/assistant/langchain4j/agent/mcp/AccountMCPAgent.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.langchain4j.agent.mcp; 2 | 3 | import com.microsoft.langchain4j.agent.AgentMetadata; 4 | import com.microsoft.langchain4j.agent.mcp.MCPProtocolType; 5 | import com.microsoft.langchain4j.agent.mcp.MCPServerMetadata; 6 | import com.microsoft.langchain4j.agent.mcp.MCPToolAgent; 7 | import dev.langchain4j.model.chat.ChatLanguageModel; 8 | import dev.langchain4j.model.input.Prompt; 9 | import dev.langchain4j.model.input.PromptTemplate; 10 | 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | public class AccountMCPAgent extends MCPToolAgent { 15 | 16 | private final Prompt agentPrompt; 17 | 18 | private static final String ACCOUNT_AGENT_SYSTEM_MESSAGE = """ 19 | you are a personal financial advisor who help the user to retrieve information about their bank accounts. 20 | Use html list or table to display the account information. 21 | Always use the below logged user details to retrieve account info: 22 | '{{loggedUserName}}' 23 | """; 24 | 25 | public AccountMCPAgent(ChatLanguageModel chatModel, String loggedUserName, String accountMCPServerUrl) { 26 | super(chatModel, List.of(new MCPServerMetadata("account", accountMCPServerUrl, MCPProtocolType.SSE))); 27 | 28 | if (loggedUserName == null || loggedUserName.isEmpty()) { 29 | throw new IllegalArgumentException("loggedUserName cannot be null or empty"); 30 | } 31 | 32 | PromptTemplate promptTemplate = PromptTemplate.from(ACCOUNT_AGENT_SYSTEM_MESSAGE); 33 | this.agentPrompt = promptTemplate.apply(Map.of("loggedUserName", loggedUserName)); 34 | } 35 | 36 | @Override 37 | public String getName() { 38 | return "AccountAgent"; 39 | } 40 | 41 | @Override 42 | public AgentMetadata getMetadata() { 43 | return new AgentMetadata( 44 | "Personal financial advisor for retrieving bank account information.", 45 | List.of("RetrieveAccountInfo", "DisplayAccountDetails") 46 | ); 47 | } 48 | 49 | @Override 50 | protected String getSystemMessage() { 51 | return agentPrompt.text(); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /app/copilot/langchain4j-agents/src/main/java/com/microsoft/openai/samples/assistant/langchain4j/agent/mcp/TransactionHistoryMCPAgent.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.langchain4j.agent.mcp; 2 | 3 | import com.microsoft.langchain4j.agent.AgentMetadata; 4 | import com.microsoft.langchain4j.agent.mcp.MCPProtocolType; 5 | import com.microsoft.langchain4j.agent.mcp.MCPServerMetadata; 6 | import com.microsoft.langchain4j.agent.mcp.MCPToolAgent; 7 | import dev.langchain4j.model.chat.ChatLanguageModel; 8 | import dev.langchain4j.model.input.Prompt; 9 | import dev.langchain4j.model.input.PromptTemplate; 10 | 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | public class TransactionHistoryMCPAgent extends MCPToolAgent { 15 | 16 | private final Prompt agentPrompt; 17 | 18 | private static final String TRANSACTION_HISTORY_AGENT_SYSTEM_MESSAGE = """ 19 | you are a personal financial advisor who help the user with their recurrent bill payments. To search about the payments history you need to know the payee name and the account id. 20 | If the user doesn't provide the payee name, search the last 10 transactions order by date. 21 | If the user want to search last transactions for a specific payee, ask to provide the payee name. 22 | Use html list or table to display the transaction information. 23 | Always use the below logged user details to retrieve account info: 24 | '{{loggedUserName}}' 25 | Current timestamp: 26 | '{{currentDateTime}}' 27 | """; 28 | 29 | public TransactionHistoryMCPAgent(ChatLanguageModel chatModel, String loggedUserName, String transactionMCPServerUrl, String accountMCPServerUrl) { 30 | super(chatModel, List.of(new MCPServerMetadata("transaction-history", transactionMCPServerUrl, MCPProtocolType.SSE), 31 | new MCPServerMetadata("account", accountMCPServerUrl, MCPProtocolType.SSE))); 32 | 33 | if (loggedUserName == null || loggedUserName.isEmpty()) { 34 | throw new IllegalArgumentException("loggedUserName cannot be null or empty"); 35 | } 36 | 37 | PromptTemplate promptTemplate = PromptTemplate.from(TRANSACTION_HISTORY_AGENT_SYSTEM_MESSAGE); 38 | var datetimeIso8601 = java.time.ZonedDateTime.now(java.time.ZoneId.of("UTC")).toInstant().toString(); 39 | 40 | this.agentPrompt = promptTemplate.apply(Map.of( 41 | "loggedUserName", loggedUserName, 42 | "currentDateTime", datetimeIso8601 43 | )); 44 | } 45 | 46 | @Override 47 | public String getName() { 48 | return "TransactionHistoryAgent"; 49 | } 50 | 51 | @Override 52 | public AgentMetadata getMetadata() { 53 | return new AgentMetadata( 54 | "Personal financial advisor for retrieving transaction history information.", 55 | List.of("RetrieveTransactionHistory", "DisplayTransactionDetails") 56 | ); 57 | } 58 | 59 | @Override 60 | protected String getSystemMessage() { 61 | return agentPrompt.text(); 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /app/copilot/langchain4j-agents/src/main/java/com/microsoft/openai/samples/assistant/langchain4j/tools/InvoiceScanTool.java: -------------------------------------------------------------------------------- 1 | package com.microsoft.openai.samples.assistant.langchain4j.tools; 2 | 3 | 4 | import com.microsoft.openai.samples.assistant.invoice.DocumentIntelligenceInvoiceScanHelper; 5 | import dev.langchain4j.agent.tool.P; 6 | import dev.langchain4j.agent.tool.Tool; 7 | import dev.langchain4j.agent.tool.ToolExecutionRequest; 8 | import dev.langchain4j.service.tool.ToolExecutor; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | 16 | public class InvoiceScanTool { 17 | 18 | private static final Logger LOGGER = LoggerFactory.getLogger(InvoiceScanTool.class); 19 | private final DocumentIntelligenceInvoiceScanHelper documentIntelligenceInvoiceScanHelper; 20 | public InvoiceScanTool(DocumentIntelligenceInvoiceScanHelper documentIntelligenceInvoiceScanHelper) { 21 | this.documentIntelligenceInvoiceScanHelper = documentIntelligenceInvoiceScanHelper; 22 | } 23 | @Tool( "Extract the invoice or bill data scanning a photo or image") 24 | public String scanInvoice( 25 | @P("the path to the file containing the image or photo") String filePath) { 26 | 27 | Map scanData = null; 28 | 29 | try{ 30 | scanData = documentIntelligenceInvoiceScanHelper.scan(filePath); 31 | } catch (Exception e) { 32 | LOGGER.warn("Error extracting data from invoice {}:", filePath,e); 33 | scanData = new HashMap<>(); 34 | } 35 | 36 | LOGGER.info("scanInvoice tool: Data extracted {}:{}", filePath,scanData); 37 | return scanData.toString(); 38 | 39 | } 40 | 41 | 42 | 43 | } 44 | 45 | -------------------------------------------------------------------------------- /app/copilot/langchain4j-agents/src/test/java/dev/langchain4j/openapi/mcp/AccountMCPAgentIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package dev.langchain4j.openapi.mcp; 2 | 3 | import com.microsoft.openai.samples.assistant.langchain4j.agent.mcp.AccountMCPAgent; 4 | import dev.langchain4j.data.message.ChatMessage; 5 | import dev.langchain4j.data.message.UserMessage; 6 | import dev.langchain4j.model.azure.AzureOpenAiChatModel; 7 | 8 | import java.util.ArrayList; 9 | 10 | public class AccountMCPAgentIntegrationTest { 11 | 12 | public static void main(String[] args) throws Exception { 13 | 14 | //Azure Open AI Chat Model 15 | var azureOpenAiChatModel = AzureOpenAiChatModel.builder() 16 | .apiKey(System.getenv("AZURE_OPENAI_KEY")) 17 | .endpoint(System.getenv("AZURE_OPENAI_ENDPOINT")) 18 | .deploymentName(System.getenv("AZURE_OPENAI_DEPLOYMENT_NAME")) 19 | .temperature(0.3) 20 | .logRequestsAndResponses(true) 21 | .build(); 22 | 23 | var accountAgent = new AccountMCPAgent(azureOpenAiChatModel,"bob.user@contoso.com","http://localhost:8070/sse"); 24 | 25 | var chatHistory = new ArrayList(); 26 | chatHistory.add(UserMessage.from("How much money do I have in my account?")); 27 | 28 | accountAgent.invoke(chatHistory); 29 | System.out.println(chatHistory.get(chatHistory.size()-1)); 30 | 31 | chatHistory.add(UserMessage.from("what about my visa")); 32 | accountAgent.invoke(chatHistory); 33 | System.out.println(chatHistory.get(chatHistory.size()-1)); 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/copilot/langchain4j-agents/src/test/java/dev/langchain4j/openapi/mcp/TransactionHistoryMCPAgentIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package dev.langchain4j.openapi.mcp; 2 | 3 | import com.microsoft.openai.samples.assistant.langchain4j.agent.mcp.TransactionHistoryMCPAgent; 4 | import dev.langchain4j.data.message.ChatMessage; 5 | import dev.langchain4j.data.message.UserMessage; 6 | import dev.langchain4j.model.azure.AzureOpenAiChatModel; 7 | 8 | import java.util.ArrayList; 9 | 10 | public class TransactionHistoryMCPAgentIntegrationTest { 11 | 12 | public static void main(String[] args) throws Exception { 13 | 14 | //Azure Open AI Chat Model 15 | var azureOpenAiChatModel = AzureOpenAiChatModel.builder() 16 | .apiKey(System.getenv("AZURE_OPENAI_KEY")) 17 | .endpoint(System.getenv("AZURE_OPENAI_ENDPOINT")) 18 | .deploymentName(System.getenv("AZURE_OPENAI_DEPLOYMENT_NAME")) 19 | .temperature(0.3) 20 | .logRequestsAndResponses(true) 21 | .build(); 22 | 23 | var transactionHistoryAgent = new TransactionHistoryMCPAgent(azureOpenAiChatModel, 24 | "bob.user@contoso.com", 25 | "http://localhost:8090/sse", 26 | "http://localhost:8070/sse"); 27 | 28 | var chatHistory = new ArrayList(); 29 | 30 | 31 | chatHistory.add(UserMessage.from("When was last time I've paid contoso?")); 32 | transactionHistoryAgent.invoke(chatHistory); 33 | System.out.println(chatHistory.get(chatHistory.size()-1)); 34 | 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/copilot/langchain4j-agents/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%kvp- %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/copilot/langchain4j-agents/src/test/resources/payments.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | title: Payment API 4 | version: 1.0.0 5 | paths: 6 | /payments: 7 | post: 8 | operationId: submitPayment 9 | summary: Submit a payment request 10 | description: Submit a payment request 11 | requestBody: 12 | required: true 13 | description: Payment to submit 14 | content: 15 | application/json: 16 | schema: 17 | $ref: '#/components/schemas/Payment' 18 | responses: 19 | '200': 20 | description: Payment request submitted successfully 21 | '400': 22 | description: Invalid request body 23 | '500': 24 | description: Internal server error 25 | components: 26 | schemas: 27 | Payment: 28 | type: object 29 | properties: 30 | description: 31 | type: string 32 | description: Description of the payment 33 | recipientName: 34 | type: string 35 | description: Name of the recipient 36 | recipientBankCode: 37 | type: string 38 | description: Bank code of the recipient 39 | accountId: 40 | type: string 41 | description: ID of the account 42 | paymentMethodId: 43 | type: string 44 | description: ID of the payment method 45 | paymentType: 46 | type: string 47 | description: 'The type of payment: creditcard, banktransfer, directdebit, visa, mastercard, paypal, etc.' 48 | amount: 49 | type: string 50 | description: Amount of the payment 51 | timestamp: 52 | type: string 53 | description: Timestamp of the payment 54 | requestBodies: 55 | Payment: 56 | content: 57 | application/json: 58 | schema: 59 | $ref: '#/components/schemas/Payment' 60 | description: Payment object to submit -------------------------------------------------------------------------------- /app/copilot/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.microsoft.openai.samples.assistant 8 | copilot-parent 9 | 1.0.0-SNAPSHOT 10 | pom 11 | 12 | 13 | 17 14 | 17 15 | UTF-8 16 | 1.0.0-beta2 17 | 1.2.33 18 | 19 | 20 | 21 | 22 | 23 | com.azure 24 | azure-sdk-bom 25 | ${azure.sdk.version} 26 | pom 27 | import 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | org.apache.maven.plugins 36 | maven-compiler-plugin 37 | 38 | ${maven.compiler.source} 39 | ${maven.compiler.target} 40 | 41 | 42 | 43 | 44 | 45 | 46 | copilot-backend 47 | langchain4j-agents 48 | copilot-common 49 | 50 | 51 | -------------------------------------------------------------------------------- /app/frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | manifests 2 | node_modules -------------------------------------------------------------------------------- /app/frontend/.env.dev: -------------------------------------------------------------------------------- 1 | VITE_BACKEND_URI=http://localhost:8081/api 2 | -------------------------------------------------------------------------------- /app/frontend/.env.local: -------------------------------------------------------------------------------- 1 | VITE_BACKEND_URI=http://localhost:8081/api 2 | -------------------------------------------------------------------------------- /app/frontend/.env.production: -------------------------------------------------------------------------------- 1 | VITE_BACKEND_URI=/api 2 | 3 | -------------------------------------------------------------------------------- /app/frontend/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /app/frontend/.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore JSON 2 | **/*.json 3 | -------------------------------------------------------------------------------- /app/frontend/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "printWidth": 160, 4 | "arrowParens": "avoid", 5 | "trailingComma": "none" 6 | } 7 | -------------------------------------------------------------------------------- /app/frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine AS build 2 | 3 | # make the 'app' folder the current working directory 4 | WORKDIR /app 5 | 6 | COPY . . 7 | 8 | # install project dependencies 9 | RUN npm install 10 | RUN npm run build 11 | 12 | FROM nginx:alpine 13 | 14 | WORKDIR /usr/share/nginx/html 15 | COPY --from=build /app/build . 16 | COPY --from=build /app/nginx/nginx.conf.template /etc/nginx/conf.d 17 | 18 | EXPOSE 80 19 | 20 | CMD ["/bin/sh", "-c", "envsubst < /etc/nginx/conf.d/nginx.conf.template > /etc/nginx/conf.d/default.conf && nginx -g \"daemon off;\""] 21 | -------------------------------------------------------------------------------- /app/frontend/Dockerfile-aks: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine AS build 2 | 3 | # make the 'app' folder the current working directory 4 | WORKDIR /app 5 | 6 | COPY . . 7 | 8 | 9 | # install project dependencies 10 | RUN npm install 11 | RUN npm run build 12 | 13 | FROM nginx:alpine 14 | 15 | WORKDIR /usr/share/nginx/html 16 | COPY --from=build /app/build . 17 | 18 | EXPOSE 80 19 | 20 | CMD ["/bin/sh", "-c", "nginx -g \"daemon off;\""] -------------------------------------------------------------------------------- /app/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | GPT + Enterprise data | Java Sample 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/frontend/manifests/frontend-deployment.tmpl.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: frontend-deployment 5 | namespace: azure-open-ai 6 | labels: 7 | app: frontend 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: frontend 13 | template: 14 | metadata: 15 | labels: 16 | app: frontend 17 | spec: 18 | containers: 19 | - name: frontend 20 | image: {{.Env.SERVICE_FRONTEND_IMAGE_NAME}} 21 | imagePullPolicy: IfNotPresent 22 | ports: 23 | - containerPort: 80 24 | envFrom: 25 | - configMapRef: 26 | name: azd-env-configmap 27 | -------------------------------------------------------------------------------- /app/frontend/manifests/frontend-service.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: frontend-service 5 | namespace: azure-open-ai 6 | spec: 7 | type: ClusterIP 8 | ports: 9 | - protocol: TCP 10 | port: 80 11 | targetPort: 80 12 | selector: 13 | app: frontend 14 | -------------------------------------------------------------------------------- /app/frontend/nginx/nginx.conf.template: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | location / { 4 | root /usr/share/nginx/html; 5 | index index.html index.htm; 6 | } 7 | 8 | location /api { 9 | proxy_ssl_server_name on; 10 | proxy_http_version 1.1; 11 | proxy_pass $REACT_APP_API_BASE_URL; 12 | } 13 | } -------------------------------------------------------------------------------- /app/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "private": true, 4 | "version": "1.0.0-alpha", 5 | "type": "module", 6 | "engines": { 7 | "node": ">=14.0.0" 8 | }, 9 | "scripts": { 10 | "dev": "vite --port=8081", 11 | "build": "tsc && vite build", 12 | "preview": "vite preview" 13 | }, 14 | "dependencies": { 15 | "@azure/msal-browser": "^3.1.0", 16 | "@azure/msal-react": "^2.0.4", 17 | "@fluentui/react": "^8.112.5", 18 | "@fluentui/react-components": "^9.37.3", 19 | "@fluentui/react-icons": "^2.0.221", 20 | "@react-spring/web": "^9.7.3", 21 | "dompurify": "^3.2.4", 22 | "frontend": "file:", 23 | "ndjson-readablestream": "^1.0.7", 24 | "react": "^18.2.0", 25 | "react-dom": "^18.2.0", 26 | "react-router-dom": "^6.18.0", 27 | "scheduler": "^0.20.2" 28 | }, 29 | "devDependencies": { 30 | "@types/dompurify": "^3.0.3", 31 | "@types/react": "^18.2.34", 32 | "@types/react-dom": "^18.2.14", 33 | "@vitejs/plugin-react": "^4.3.4", 34 | "prettier": "^3.0.3", 35 | "typescript": "^5.2.2", 36 | "vite": "^6.3.1" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/agent-openai-java-banking-assistant/0a7fee1d6958674e1103b5d1ff729e8b716e4321/app/frontend/public/favicon.ico -------------------------------------------------------------------------------- /app/frontend/src/api/api.ts: -------------------------------------------------------------------------------- 1 | import { ChatAppResponse, ChatAppResponseOrError, ChatAppRequest } from "./models"; 2 | import { useLogin } from "../authConfig"; 3 | 4 | const BACKEND_URI = import.meta.env.VITE_BACKEND_URI ? import.meta.env.VITE_BACKEND_URI : ""; 5 | 6 | function getHeaders(idToken: string | undefined, stream:boolean): Record { 7 | var headers: Record = { 8 | "Content-Type": "application/json" 9 | }; 10 | // If using login, add the id token of the logged in account as the authorization 11 | if (useLogin) { 12 | if (idToken) { 13 | headers["Authorization"] = `Bearer ${idToken}` 14 | } 15 | } 16 | 17 | if (stream) { 18 | headers["Accept"] = "application/x-ndjson"; 19 | } else { 20 | headers["Accept"] = "application/json"; 21 | } 22 | 23 | return headers; 24 | } 25 | 26 | export async function askApi(request: ChatAppRequest, idToken: string | undefined): Promise { 27 | const response = await fetch(`${BACKEND_URI}/ask`, { 28 | method: "POST", 29 | headers: getHeaders(idToken, request.stream || false), 30 | body: JSON.stringify(request) 31 | }); 32 | 33 | const parsedResponse: ChatAppResponseOrError = await response.json(); 34 | if (response.status > 299 || !response.ok) { 35 | throw Error(parsedResponse.error || "Unknown error"); 36 | } 37 | 38 | return parsedResponse as ChatAppResponse; 39 | } 40 | 41 | export async function chatApi(request: ChatAppRequest, idToken: string | undefined): Promise { 42 | return await fetch(`${BACKEND_URI}/chat`, { 43 | method: "POST", 44 | headers: getHeaders(idToken, request.stream || false), 45 | body: JSON.stringify(request) 46 | }); 47 | } 48 | 49 | export function getCitationFilePath(citation: string): string { 50 | return `${BACKEND_URI}/content/${citation}`; 51 | } 52 | 53 | export function uploadAttachment(file: File): Promise { 54 | const formData = new FormData(); 55 | formData.append("file", file); 56 | 57 | return fetch(`${BACKEND_URI}/content`, { 58 | method: "POST", 59 | body: formData 60 | }).then(response => { 61 | if (response.status > 299 || !response.ok) { 62 | throw Error("Failed to upload attachment"); 63 | } 64 | return response.text(); 65 | }); 66 | } 67 | 68 | export function getImage(name: string): string { 69 | return `${BACKEND_URI}/content/${name}`; 70 | } 71 | 72 | -------------------------------------------------------------------------------- /app/frontend/src/api/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./api"; 2 | export * from "./models"; 3 | -------------------------------------------------------------------------------- /app/frontend/src/api/models.ts: -------------------------------------------------------------------------------- 1 | export const enum Approaches { 2 | JAVA_OPENAI_SDK = "jos", 3 | JAVA_SEMANTIC_KERNEL = "jsk", 4 | JAVA_SEMANTIC_KERNEL_PLANNER = "jskp" 5 | } 6 | 7 | export const enum RetrievalMode { 8 | Hybrid = "hybrid", 9 | Vectors = "vectors", 10 | Text = "text" 11 | } 12 | 13 | export const enum SKMode { 14 | Chains = "chains", 15 | Planner = "planner" 16 | } 17 | 18 | export type ChatAppRequestOverrides = { 19 | retrieval_mode?: RetrievalMode; 20 | semantic_ranker?: boolean; 21 | semantic_captions?: boolean; 22 | exclude_category?: string; 23 | top?: number; 24 | temperature?: number; 25 | prompt_template?: string; 26 | prompt_template_prefix?: string; 27 | prompt_template_suffix?: string; 28 | suggest_followup_questions?: boolean; 29 | use_oid_security_filter?: boolean; 30 | use_groups_security_filter?: boolean; 31 | semantic_kernel_mode?: SKMode; 32 | }; 33 | 34 | export type ResponseMessage = { 35 | content: string; 36 | role: string; 37 | attachments?: string[]; 38 | }; 39 | 40 | export type ResponseContext = { 41 | thoughts: string | null; 42 | data_points: string[]; 43 | }; 44 | 45 | export type ResponseChoice = { 46 | index: number; 47 | message: ResponseMessage; 48 | context: ResponseContext; 49 | session_state: any; 50 | }; 51 | 52 | export type ChatAppResponseOrError = { 53 | choices?: ResponseChoice[]; 54 | error?: string; 55 | }; 56 | 57 | export type ChatAppResponse = { 58 | choices: ResponseChoice[]; 59 | }; 60 | 61 | export type ChatAppRequestContext = { 62 | overrides?: ChatAppRequestOverrides; 63 | }; 64 | 65 | export type ChatAppRequest = { 66 | messages: ResponseMessage[]; 67 | approach: Approaches; 68 | context?: ChatAppRequestContext; 69 | stream?: boolean; 70 | session_state: any; 71 | }; 72 | -------------------------------------------------------------------------------- /app/frontend/src/assets/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/frontend/src/assets/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/frontend/src/components/AnalysisPanel/AnalysisPanel.module.css: -------------------------------------------------------------------------------- 1 | .thoughtProcess { 2 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; 3 | word-wrap: break-word; 4 | padding-top: 12px; 5 | padding-bottom: 12px; 6 | } 7 | -------------------------------------------------------------------------------- /app/frontend/src/components/AnalysisPanel/AnalysisPanel.tsx: -------------------------------------------------------------------------------- 1 | import { Pivot, PivotItem } from "@fluentui/react"; 2 | import DOMPurify from "dompurify"; 3 | 4 | import styles from "./AnalysisPanel.module.css"; 5 | 6 | import { SupportingContent } from "../SupportingContent"; 7 | import { ChatAppResponse } from "../../api"; 8 | import { AnalysisPanelTabs } from "./AnalysisPanelTabs"; 9 | 10 | interface Props { 11 | className: string; 12 | activeTab: AnalysisPanelTabs; 13 | onActiveTabChanged: (tab: AnalysisPanelTabs) => void; 14 | activeCitation: string | undefined; 15 | citationHeight: string; 16 | answer: ChatAppResponse; 17 | } 18 | 19 | const pivotItemDisabledStyle = { disabled: true, style: { color: "grey" } }; 20 | 21 | export const AnalysisPanel = ({ answer, activeTab, activeCitation, citationHeight, className, onActiveTabChanged }: Props) => { 22 | const isDisabledThoughtProcessTab: boolean = !answer.choices[0].context.thoughts; 23 | const isDisabledSupportingContentTab: boolean = !answer.choices[0].context.data_points.length; 24 | const isDisabledCitationTab: boolean = !activeCitation; 25 | 26 | const sanitizedThoughts = DOMPurify.sanitize(answer.choices[0].context.thoughts!); 27 | 28 | return ( 29 | pivotItem && onActiveTabChanged(pivotItem.props.itemKey! as AnalysisPanelTabs)} 33 | > 34 | 39 | 40 | 41 | 46 | 47 | 48 | 53 | 54 | 55 | 56 | ); 57 | }; 58 | -------------------------------------------------------------------------------- /app/frontend/src/components/AnalysisPanel/AnalysisPanelTabs.tsx: -------------------------------------------------------------------------------- 1 | export enum AnalysisPanelTabs { 2 | ThoughtProcessTab = "thoughtProcess", 3 | SupportingContentTab = "supportingContent", 4 | CitationTab = "citation" 5 | } 6 | -------------------------------------------------------------------------------- /app/frontend/src/components/AnalysisPanel/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./AnalysisPanel"; 2 | export * from "./AnalysisPanelTabs"; 3 | -------------------------------------------------------------------------------- /app/frontend/src/components/Answer/Answer.module.css: -------------------------------------------------------------------------------- 1 | .answerContainer { 2 | padding: 20px; 3 | background: rgb(249, 249, 249); 4 | border-radius: 8px; 5 | box-shadow: 6 | 0px 2px 4px rgba(0, 0, 0, 0.14), 7 | 0px 0px 2px rgba(0, 0, 0, 0.12); 8 | outline: transparent solid 1px; 9 | } 10 | 11 | .answerLogo { 12 | font-size: 28px; 13 | } 14 | 15 | .answerText { 16 | font-size: 16px; 17 | font-weight: 400; 18 | line-height: 22px; 19 | padding-top: 16px; 20 | padding-bottom: 16px; 21 | white-space: pre-line; 22 | } 23 | 24 | .answerText table { 25 | border-collapse: collapse; 26 | } 27 | 28 | .answerText td, 29 | .answerText th { 30 | border: 1px solid; 31 | padding: 5px; 32 | } 33 | 34 | .selected { 35 | outline: 2px solid rgba(115, 118, 225, 1); 36 | } 37 | 38 | .citationLearnMore { 39 | margin-right: 5px; 40 | font-weight: 600; 41 | line-height: 24px; 42 | } 43 | 44 | .citation { 45 | font-weight: 500; 46 | line-height: 24px; 47 | text-align: center; 48 | border-radius: 4px; 49 | padding: 0px 8px; 50 | background: #d1dbfa; 51 | color: #123bb6; 52 | text-decoration: none; 53 | cursor: pointer; 54 | } 55 | 56 | .citation:hover { 57 | text-decoration: underline; 58 | } 59 | 60 | .followupQuestionsList { 61 | margin-top: 10px; 62 | } 63 | 64 | .followupQuestionLearnMore { 65 | margin-right: 5px; 66 | font-weight: 600; 67 | line-height: 24px; 68 | } 69 | 70 | .followupQuestion { 71 | font-weight: 600; 72 | line-height: 24px; 73 | text-align: center; 74 | border-radius: 4px; 75 | padding: 0px 8px; 76 | background: #e8ebfa; 77 | color: black; 78 | font-style: italic; 79 | text-decoration: none; 80 | cursor: pointer; 81 | } 82 | 83 | .supContainer { 84 | text-decoration: none; 85 | cursor: pointer; 86 | } 87 | 88 | .supContainer:hover { 89 | text-decoration: underline; 90 | } 91 | 92 | sup { 93 | position: relative; 94 | display: inline-flex; 95 | align-items: center; 96 | justify-content: center; 97 | font-size: 10px; 98 | font-weight: 600; 99 | vertical-align: top; 100 | top: -1; 101 | margin: 0px 2px; 102 | min-width: 14px; 103 | height: 14px; 104 | border-radius: 3px; 105 | background: #d1dbfa; 106 | color: #123bb6; 107 | text-decoration-color: transparent; 108 | outline: transparent solid 1px; 109 | cursor: pointer; 110 | } 111 | 112 | .retryButton { 113 | width: fit-content; 114 | } 115 | 116 | @keyframes loading { 117 | 0% { 118 | content: ""; 119 | } 120 | 25% { 121 | content: "."; 122 | } 123 | 50% { 124 | content: ".."; 125 | } 126 | 75% { 127 | content: "..."; 128 | } 129 | 100% { 130 | content: ""; 131 | } 132 | } 133 | 134 | .loadingdots::after { 135 | content: ""; 136 | animation: loading 1s infinite; 137 | } 138 | -------------------------------------------------------------------------------- /app/frontend/src/components/Answer/AnswerError.tsx: -------------------------------------------------------------------------------- 1 | import { Stack, PrimaryButton } from "@fluentui/react"; 2 | import { ErrorCircle24Regular } from "@fluentui/react-icons"; 3 | 4 | import styles from "./Answer.module.css"; 5 | 6 | interface Props { 7 | error: string; 8 | onRetry: () => void; 9 | } 10 | 11 | export const AnswerError = ({ error, onRetry }: Props) => { 12 | return ( 13 | 14 | 15 | 16 | 17 | {error} 18 | 19 | 20 | 21 | 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /app/frontend/src/components/Answer/AnswerIcon.tsx: -------------------------------------------------------------------------------- 1 | import { Sparkle28Filled } from "@fluentui/react-icons"; 2 | 3 | export const AnswerIcon = () => { 4 | return ; 5 | }; 6 | -------------------------------------------------------------------------------- /app/frontend/src/components/Answer/AnswerLoading.tsx: -------------------------------------------------------------------------------- 1 | import { Stack } from "@fluentui/react"; 2 | import { animated, useSpring } from "@react-spring/web"; 3 | 4 | import styles from "./Answer.module.css"; 5 | import { AnswerIcon } from "./AnswerIcon"; 6 | 7 | export const AnswerLoading = () => { 8 | const animatedStyles = useSpring({ 9 | from: { opacity: 0 }, 10 | to: { opacity: 1 } 11 | }); 12 | 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | Generating answer 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /app/frontend/src/components/Answer/AnswerParser.tsx: -------------------------------------------------------------------------------- 1 | import { renderToStaticMarkup } from "react-dom/server"; 2 | import { getCitationFilePath } from "../../api"; 3 | 4 | type HtmlParsedAnswer = { 5 | answerHtml: string; 6 | citations: string[]; 7 | followupQuestions: string[]; 8 | }; 9 | 10 | export function parseAnswerToHtml(answer: string, isStreaming: boolean, onCitationClicked: (citationFilePath: string) => void): HtmlParsedAnswer { 11 | const citations: string[] = []; 12 | const followupQuestions: string[] = []; 13 | 14 | // Extract any follow-up questions that might be in the answer 15 | let parsedAnswer = answer.replace(/<<([^>>]+)>>/g, (match, content) => { 16 | followupQuestions.push(content); 17 | return ""; 18 | }); 19 | 20 | // trim any whitespace from the end of the answer after removing follow-up questions 21 | parsedAnswer = parsedAnswer.trim(); 22 | 23 | // Omit a citation that is still being typed during streaming 24 | if (isStreaming) { 25 | let lastIndex = parsedAnswer.length; 26 | for (let i = parsedAnswer.length - 1; i >= 0; i--) { 27 | if (parsedAnswer[i] === "]") { 28 | break; 29 | } else if (parsedAnswer[i] === "[") { 30 | lastIndex = i; 31 | break; 32 | } 33 | } 34 | const truncatedAnswer = parsedAnswer.substring(0, lastIndex); 35 | parsedAnswer = truncatedAnswer; 36 | } 37 | 38 | const parts = parsedAnswer.split(/\[([^\]]+)\]/g); 39 | 40 | const fragments: string[] = parts.map((part, index) => { 41 | if (index % 2 === 0) { 42 | return part; 43 | } else { 44 | let citationIndex: number; 45 | if (citations.indexOf(part) !== -1) { 46 | citationIndex = citations.indexOf(part) + 1; 47 | } else { 48 | citations.push(part); 49 | citationIndex = citations.length; 50 | } 51 | 52 | const path = getCitationFilePath(part); 53 | 54 | return renderToStaticMarkup( 55 | onCitationClicked(path)}> 56 | {citationIndex} 57 | 58 | ); 59 | } 60 | }); 61 | 62 | return { 63 | answerHtml: fragments.join(""), 64 | citations, 65 | followupQuestions 66 | }; 67 | } 68 | -------------------------------------------------------------------------------- /app/frontend/src/components/Answer/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Answer"; 2 | export * from "./AnswerLoading"; 3 | export * from "./AnswerError"; 4 | -------------------------------------------------------------------------------- /app/frontend/src/components/AttachmentType.ts: -------------------------------------------------------------------------------- 1 | export type AttachmentType = { 2 | name: string; 3 | file: File; //Reference to the javascript File object. 4 | }; -------------------------------------------------------------------------------- /app/frontend/src/components/ClearChatButton/ClearChatButton.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | align-items: center; 4 | gap: 6px; 5 | cursor: pointer; 6 | } 7 | -------------------------------------------------------------------------------- /app/frontend/src/components/ClearChatButton/ClearChatButton.tsx: -------------------------------------------------------------------------------- 1 | import { Delete24Regular } from "@fluentui/react-icons"; 2 | import { Button } from "@fluentui/react-components"; 3 | 4 | import styles from "./ClearChatButton.module.css"; 5 | 6 | interface Props { 7 | className?: string; 8 | onClick: () => void; 9 | disabled?: boolean; 10 | } 11 | 12 | export const ClearChatButton = ({ className, disabled, onClick }: Props) => { 13 | return ( 14 | 15 | } disabled={disabled} onClick={onClick}> 16 | {"Clear chat"} 17 | 18 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /app/frontend/src/components/ClearChatButton/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./ClearChatButton"; 2 | -------------------------------------------------------------------------------- /app/frontend/src/components/Example/Example.module.css: -------------------------------------------------------------------------------- 1 | .examplesNavList { 2 | list-style: none; 3 | padding-left: 0; 4 | display: flex; 5 | flex-wrap: wrap; 6 | gap: 10px; 7 | flex: 1; 8 | justify-content: center; 9 | } 10 | 11 | .example { 12 | word-break: break-word; 13 | background: #dbdbdb; 14 | border-radius: 8px; 15 | display: flex; 16 | flex-direction: column; 17 | padding: 20px; 18 | margin-bottom: 5px; 19 | cursor: pointer; 20 | } 21 | 22 | .example:hover { 23 | box-shadow: 24 | 0px 8px 16px rgba(0, 0, 0, 0.14), 25 | 0px 0px 2px rgba(0, 0, 0, 0.12); 26 | outline: 2px solid rgba(115, 118, 225, 1); 27 | } 28 | 29 | .exampleText { 30 | margin: 0; 31 | font-size: 22px; 32 | width: 280px; 33 | height: 100px; 34 | } 35 | 36 | @media only screen and (max-height: 780px) { 37 | .exampleText { 38 | font-size: 20px; 39 | height: 80px; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/frontend/src/components/Example/Example.tsx: -------------------------------------------------------------------------------- 1 | import styles from "./Example.module.css"; 2 | 3 | interface Props { 4 | text: string; 5 | value: string; 6 | onClick: (value: string) => void; 7 | } 8 | 9 | export const Example = ({ text, value, onClick }: Props) => { 10 | return ( 11 | onClick(value)}> 12 | {text} 13 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /app/frontend/src/components/Example/ExampleList.tsx: -------------------------------------------------------------------------------- 1 | import { Example } from "./Example"; 2 | 3 | import styles from "./Example.module.css"; 4 | 5 | export type ExampleModel = { 6 | text: string; 7 | value: string; 8 | }; 9 | 10 | const EXAMPLES: ExampleModel[] = [ 11 | { text: "I want to pay a bill", value: "I want to pay a bill"}, 12 | { text: "what are this year payments ?", value: "what are this year payments ?" }, 13 | { text: "What is the limit on my visa ?", value: "What is the limit on my visa ?" } 14 | ]; 15 | 16 | interface Props { 17 | onExampleClicked: (value: string) => void; 18 | } 19 | 20 | export const ExampleList = ({ onExampleClicked }: Props) => { 21 | return ( 22 | 23 | {EXAMPLES.map((x, i) => ( 24 | 25 | 26 | 27 | ))} 28 | 29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /app/frontend/src/components/Example/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./Example"; 2 | export * from "./ExampleList"; 3 | -------------------------------------------------------------------------------- /app/frontend/src/components/LoginButton/LoginButton.module.css: -------------------------------------------------------------------------------- 1 | .loginButton { 2 | border-radius: 5px; 3 | padding: 30px 30px; 4 | font-weight: 100; 5 | } 6 | -------------------------------------------------------------------------------- /app/frontend/src/components/LoginButton/LoginButton.tsx: -------------------------------------------------------------------------------- 1 | import { DefaultButton } from "@fluentui/react"; 2 | import { useMsal } from "@azure/msal-react"; 3 | 4 | import styles from "./LoginButton.module.css"; 5 | import { getRedirectUri, loginRequest } from "../../authConfig"; 6 | 7 | export const LoginButton = () => { 8 | const { instance } = useMsal(); 9 | const activeAccount = instance.getActiveAccount(); 10 | const handleLoginPopup = () => { 11 | /** 12 | * When using popup and silent APIs, we recommend setting the redirectUri to a blank page or a page 13 | * that does not implement MSAL. Keep in mind that all redirect routes must be registered with the application 14 | * For more information, please follow this link: https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/login-user.md#redirecturi-considerations 15 | */ 16 | instance 17 | .loginPopup({ 18 | ...loginRequest, 19 | redirectUri: getRedirectUri() 20 | }) 21 | .catch(error => console.log(error)); 22 | }; 23 | const handleLogoutPopup = () => { 24 | instance 25 | .logoutPopup({ 26 | mainWindowRedirectUri: "/", // redirects the top level app after logout 27 | account: instance.getActiveAccount() 28 | }) 29 | .catch(error => console.log(error)); 30 | }; 31 | const logoutText = `Logout\n${activeAccount?.username}`; 32 | return ( 33 | 38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /app/frontend/src/components/LoginButton/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./LoginButton"; 2 | -------------------------------------------------------------------------------- /app/frontend/src/components/QuestionInput/QuestionContext.ts: -------------------------------------------------------------------------------- 1 | import { AttachmentType } from "../AttachmentType"; 2 | 3 | export type QuestionContextType = { 4 | question: string; 5 | attachments?: string[]; 6 | 7 | }; -------------------------------------------------------------------------------- /app/frontend/src/components/QuestionInput/QuestionInput.module.css: -------------------------------------------------------------------------------- 1 | .questionInputContainer { 2 | border-radius: 8px; 3 | box-shadow: 4 | 0px 8px 16px rgba(0, 0, 0, 0.14), 5 | 0px 0px 2px rgba(0, 0, 0, 0.12); 6 | height: 150px; 7 | width: 100%; 8 | padding: 15px; 9 | background: white; 10 | } 11 | 12 | .questionInputTextArea { 13 | width: 100%; 14 | line-height: 40px; 15 | } 16 | 17 | .questionInputButtonsContainer { 18 | display: flex; 19 | flex-direction: column; 20 | justify-content: flex-end; 21 | } 22 | 23 | .attachmentContainer { 24 | display: flex; 25 | justify-content: space-between; 26 | align-items: center; 27 | } 28 | 29 | .imagePreview { 30 | border: 1px solid #ddd; /* Gray border */ 31 | border-radius: 4px; /* Rounded border */ 32 | padding: 5px; /* Some padding */ 33 | height: 130px; /* Set a small width */ 34 | } -------------------------------------------------------------------------------- /app/frontend/src/components/QuestionInput/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./QuestionInput"; 2 | -------------------------------------------------------------------------------- /app/frontend/src/components/SettingsButton/SettingsButton.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | align-items: center; 4 | gap: 6px; 5 | cursor: pointer; 6 | } 7 | -------------------------------------------------------------------------------- /app/frontend/src/components/SettingsButton/SettingsButton.tsx: -------------------------------------------------------------------------------- 1 | import { Settings24Regular } from "@fluentui/react-icons"; 2 | import { Button } from "@fluentui/react-components"; 3 | 4 | import styles from "./SettingsButton.module.css"; 5 | 6 | interface Props { 7 | className?: string; 8 | onClick: () => void; 9 | } 10 | 11 | export const SettingsButton = ({ className, onClick }: Props) => { 12 | return ( 13 | 14 | } onClick={onClick}> 15 | {"Developer settings"} 16 | 17 | 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /app/frontend/src/components/SettingsButton/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./SettingsButton"; 2 | -------------------------------------------------------------------------------- /app/frontend/src/components/SupportingContent/SupportingContent.module.css: -------------------------------------------------------------------------------- 1 | .supportingContentNavList { 2 | list-style: none; 3 | padding-left: 5px; 4 | display: flex; 5 | flex-direction: column; 6 | gap: 10px; 7 | } 8 | 9 | .supportingContentItem { 10 | word-break: break-word; 11 | background: rgb(249, 249, 249); 12 | border-radius: 8px; 13 | box-shadow: 14 | rgb(0 0 0 / 5%) 0px 0px 0px 1px, 15 | rgb(0 0 0 / 10%) 0px 2px 3px 0px; 16 | outline: transparent solid 1px; 17 | 18 | display: flex; 19 | flex-direction: column; 20 | padding: 20px; 21 | } 22 | 23 | .supportingContentItemHeader { 24 | margin: 0; 25 | } 26 | 27 | .supportingContentItemText { 28 | margin-bottom: 0; 29 | font-weight: 300; 30 | } 31 | -------------------------------------------------------------------------------- /app/frontend/src/components/SupportingContent/SupportingContent.tsx: -------------------------------------------------------------------------------- 1 | import { parseSupportingContentItem } from "./SupportingContentParser"; 2 | 3 | import styles from "./SupportingContent.module.css"; 4 | 5 | interface Props { 6 | supportingContent: string[]; 7 | } 8 | 9 | export const SupportingContent = ({ supportingContent }: Props) => { 10 | return ( 11 | 12 | {supportingContent.map((x, i) => { 13 | const parsed = parseSupportingContentItem(x); 14 | 15 | return ( 16 | 17 | {parsed.title} 18 | {parsed.content} 19 | 20 | ); 21 | })} 22 | 23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /app/frontend/src/components/SupportingContent/SupportingContentParser.ts: -------------------------------------------------------------------------------- 1 | type ParsedSupportingContentItem = { 2 | title: string; 3 | content: string; 4 | }; 5 | 6 | export function parseSupportingContentItem(item: string): ParsedSupportingContentItem { 7 | // Assumes the item starts with the file name followed by : and the content. 8 | // Example: "sdp_corporate.pdf: this is the content that follows". 9 | const parts = item.split(": "); 10 | const title = parts[0]; 11 | const content = parts.slice(1).join(": "); 12 | 13 | return { 14 | title, 15 | content 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /app/frontend/src/components/SupportingContent/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./SupportingContent"; 2 | -------------------------------------------------------------------------------- /app/frontend/src/components/TokenClaimsDisplay/TokenClaimsDisplay.tsx: -------------------------------------------------------------------------------- 1 | import { Label } from "@fluentui/react"; 2 | import { useMsal } from "@azure/msal-react"; 3 | import { 4 | DataGridBody, 5 | DataGridRow, 6 | DataGrid, 7 | DataGridHeader, 8 | DataGridHeaderCell, 9 | DataGridCell, 10 | createTableColumn, 11 | TableColumnDefinition 12 | } from "@fluentui/react-table"; 13 | 14 | type Claim = { 15 | name: string; 16 | value: string; 17 | }; 18 | 19 | export const TokenClaimsDisplay = () => { 20 | const { instance } = useMsal(); 21 | const activeAccount = instance.getActiveAccount(); 22 | 23 | const ToString = (a: string | any) => { 24 | if (typeof a === "string") { 25 | return a; 26 | } else { 27 | return JSON.stringify(a); 28 | } 29 | }; 30 | 31 | const items: Claim[] = activeAccount?.idTokenClaims 32 | ? Object.keys(activeAccount.idTokenClaims).map((key: string) => { 33 | return { name: key, value: ToString((activeAccount.idTokenClaims ?? {})[key]) }; 34 | }) 35 | : []; 36 | 37 | const columns: TableColumnDefinition[] = [ 38 | createTableColumn({ 39 | columnId: "name", 40 | compare: (a: Claim, b: Claim) => { 41 | return a.name.localeCompare(b.name); 42 | }, 43 | renderHeaderCell: () => { 44 | return "Name"; 45 | }, 46 | renderCell: item => { 47 | return item.name; 48 | } 49 | }), 50 | createTableColumn({ 51 | columnId: "value", 52 | compare: (a: Claim, b: Claim) => { 53 | return a.value.localeCompare(b.value); 54 | }, 55 | renderHeaderCell: () => { 56 | return "Value"; 57 | }, 58 | renderCell: item => { 59 | return item.value; 60 | } 61 | }) 62 | ]; 63 | 64 | return ( 65 | 66 | ID Token Claims 67 | item.name}> 68 | 69 | {({ renderHeaderCell }) => {renderHeaderCell()}} 70 | 71 | > 72 | {({ item, rowId }) => key={rowId}>{({ renderCell }) => {renderCell(item)}}} 73 | 74 | 75 | 76 | ); 77 | }; 78 | -------------------------------------------------------------------------------- /app/frontend/src/components/TokenClaimsDisplay/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./TokenClaimsDisplay"; 2 | -------------------------------------------------------------------------------- /app/frontend/src/components/UserChatMessage/UserChatMessage.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | justify-content: flex-end; 4 | margin-bottom: 20px; 5 | max-width: 80%; 6 | margin-left: auto; 7 | } 8 | 9 | .message { 10 | padding: 20px; 11 | background: #e8ebfa; 12 | border-radius: 8px; 13 | box-shadow: 14 | 0px 2px 4px rgba(0, 0, 0, 0.14), 15 | 0px 0px 2px rgba(0, 0, 0, 0.12); 16 | outline: transparent solid 1px; 17 | } 18 | 19 | .attachementPreview { 20 | display: flex; 21 | justify-content: flex-end; 22 | margin-left: auto; 23 | border: 1px solid #ddd; /* Gray border */ 24 | border-radius: 4px; /* Rounded border */ 25 | padding: 5px; /* Some padding */ 26 | height: 500px; /* Set a small width */ 27 | } -------------------------------------------------------------------------------- /app/frontend/src/components/UserChatMessage/UserChatMessage.tsx: -------------------------------------------------------------------------------- 1 | import styles from "./UserChatMessage.module.css"; 2 | import { Stack} from "@fluentui/react"; 3 | import { AttachmentType } from "../AttachmentType"; 4 | import { getImage} from "../../api"; 5 | 6 | interface Props { 7 | message: string; 8 | attachments?: string[]; 9 | } 10 | 11 | // 12 | 13 | export const UserChatMessage = ({message, attachments}: Props) => { 14 | return ( 15 | <> 16 | {attachments && ( 17 | <> 18 | {attachments.map((attachment, index) => ( 19 | 20 | 21 | 22 | 23 | ))} 24 | > 25 | )} 26 | 27 | {message} 28 | 29 | > 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /app/frontend/src/components/UserChatMessage/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./UserChatMessage"; 2 | -------------------------------------------------------------------------------- /app/frontend/src/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html, 6 | body { 7 | height: 100%; 8 | margin: 0; 9 | padding: 0; 10 | } 11 | 12 | html { 13 | background: #f2f2f2; 14 | 15 | font-family: 16 | "Segoe UI", 17 | -apple-system, 18 | BlinkMacSystemFont, 19 | "Roboto", 20 | "Oxygen", 21 | "Ubuntu", 22 | "Cantarell", 23 | "Fira Sans", 24 | "Droid Sans", 25 | "Helvetica Neue", 26 | sans-serif; 27 | -webkit-font-smoothing: antialiased; 28 | -moz-osx-font-smoothing: grayscale; 29 | } 30 | 31 | #root { 32 | height: 100%; 33 | } 34 | -------------------------------------------------------------------------------- /app/frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import { createHashRouter, RouterProvider } from "react-router-dom"; 4 | import { initializeIcons } from "@fluentui/react"; 5 | import { MsalProvider } from "@azure/msal-react"; 6 | import { PublicClientApplication, EventType, AccountInfo } from "@azure/msal-browser"; 7 | import { msalConfig, useLogin } from "./authConfig"; 8 | 9 | import "./index.css"; 10 | 11 | import Layout from "./pages/layout/Layout"; 12 | import Chat from "./pages/chat/Chat"; 13 | 14 | var layout; 15 | if (useLogin) { 16 | var msalInstance = new PublicClientApplication(msalConfig); 17 | 18 | // Default to using the first account if no account is active on page load 19 | if (!msalInstance.getActiveAccount() && msalInstance.getAllAccounts().length > 0) { 20 | // Account selection logic is app dependent. Adjust as needed for different use cases. 21 | msalInstance.setActiveAccount(msalInstance.getActiveAccount()); 22 | } 23 | 24 | // Listen for sign-in event and set active account 25 | msalInstance.addEventCallback(event => { 26 | if (event.eventType === EventType.LOGIN_SUCCESS && event.payload) { 27 | const account = event.payload as AccountInfo; 28 | msalInstance.setActiveAccount(account); 29 | } 30 | }); 31 | 32 | layout = ( 33 | 34 | 35 | 36 | ); 37 | } else { 38 | layout = ; 39 | } 40 | 41 | initializeIcons(); 42 | 43 | const router = createHashRouter([ 44 | { 45 | path: "/", 46 | element: layout, 47 | children: [ 48 | { 49 | index: true, 50 | element: 51 | }, 52 | { 53 | path: "*", 54 | lazy: () => import("./pages/NoPage") 55 | } 56 | ] 57 | } 58 | ]); 59 | 60 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( 61 | 62 | 63 | 64 | ); 65 | -------------------------------------------------------------------------------- /app/frontend/src/pages/NoPage.tsx: -------------------------------------------------------------------------------- 1 | export function Component(): JSX.Element { 2 | return 404; 3 | } 4 | 5 | Component.displayName = "NoPage"; 6 | -------------------------------------------------------------------------------- /app/frontend/src/pages/chat/Chat.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | flex: 1; 3 | display: flex; 4 | flex-direction: column; 5 | margin-top: 20px; 6 | } 7 | 8 | .chatRoot { 9 | flex: 1; 10 | display: flex; 11 | } 12 | 13 | .chatContainer { 14 | flex: 1; 15 | display: flex; 16 | flex-direction: column; 17 | align-items: center; 18 | width: 100%; 19 | } 20 | 21 | .chatEmptyState { 22 | flex-grow: 1; 23 | display: flex; 24 | flex-direction: column; 25 | justify-content: center; 26 | align-items: center; 27 | max-height: 1024px; 28 | padding-top: 60px; 29 | } 30 | 31 | .chatEmptyStateTitle { 32 | font-size: 4rem; 33 | font-weight: 600; 34 | margin-top: 0; 35 | margin-bottom: 30px; 36 | } 37 | 38 | .chatEmptyStateSubtitle { 39 | font-weight: 600; 40 | margin-bottom: 10px; 41 | } 42 | 43 | @media only screen and (max-height: 780px) { 44 | .chatEmptyState { 45 | padding-top: 0; 46 | } 47 | 48 | .chatEmptyStateTitle { 49 | font-size: 3rem; 50 | margin-bottom: 0px; 51 | } 52 | } 53 | 54 | .chatMessageStream { 55 | flex-grow: 1; 56 | max-height: 1024px; 57 | max-width: 1028px; 58 | width: 100%; 59 | overflow-y: auto; 60 | padding-left: 24px; 61 | padding-right: 24px; 62 | display: flex; 63 | flex-direction: column; 64 | } 65 | 66 | .chatMessageGpt { 67 | margin-bottom: 20px; 68 | max-width: 80%; 69 | display: flex; 70 | min-width: 500px; 71 | } 72 | 73 | .chatMessageGptMinWidth { 74 | max-width: 500px; 75 | margin-bottom: 20px; 76 | } 77 | 78 | .chatInput { 79 | position: sticky; 80 | bottom: 0; 81 | flex: 0 0 100px; 82 | padding-top: 12px; 83 | padding-bottom: 24px; 84 | padding-left: 24px; 85 | padding-right: 24px; 86 | width: 100%; 87 | max-width: 1028px; 88 | background: #f2f2f2; 89 | } 90 | 91 | .chatAnalysisPanel { 92 | flex: 1; 93 | overflow-y: auto; 94 | max-height: 89vh; 95 | margin-left: 20px; 96 | margin-right: 20px; 97 | } 98 | 99 | .chatSettingsSeparator { 100 | margin-top: 15px; 101 | } 102 | 103 | .loadingLogo { 104 | font-size: 28px; 105 | } 106 | 107 | .commandsContainer { 108 | display: flex; 109 | align-self: flex-end; 110 | } 111 | 112 | .commandButton { 113 | margin-right: 20px; 114 | margin-bottom: 20px; 115 | } 116 | -------------------------------------------------------------------------------- /app/frontend/src/pages/layout/Layout.module.css: -------------------------------------------------------------------------------- 1 | .layout { 2 | display: flex; 3 | flex-direction: column; 4 | height: 100%; 5 | } 6 | 7 | .header { 8 | background-color: #222222; 9 | color: #f2f2f2; 10 | } 11 | 12 | .headerContainer { 13 | display: flex; 14 | align-items: center; 15 | justify-content: space-around; 16 | margin-right: 12px; 17 | margin-left: 12px; 18 | } 19 | 20 | .headerTitleContainer { 21 | display: flex; 22 | align-items: center; 23 | margin-right: 40px; 24 | color: #f2f2f2; 25 | text-decoration: none; 26 | } 27 | 28 | .headerLogo { 29 | height: 40px; 30 | } 31 | 32 | .headerTitle { 33 | margin-left: 12px; 34 | font-weight: 600; 35 | } 36 | 37 | .headerNavList { 38 | display: flex; 39 | list-style: none; 40 | padding-left: 0; 41 | } 42 | 43 | .headerNavPageLink { 44 | color: #f2f2f2; 45 | text-decoration: none; 46 | opacity: 0.75; 47 | 48 | transition-timing-function: cubic-bezier(0.16, 1, 0.3, 1); 49 | transition-duration: 500ms; 50 | transition-property: opacity; 51 | } 52 | 53 | .headerNavPageLink:hover { 54 | opacity: 1; 55 | } 56 | 57 | .headerNavPageLinkActive { 58 | color: #f2f2f2; 59 | text-decoration: none; 60 | } 61 | 62 | .headerNavLeftMargin { 63 | margin-left: 20px; 64 | } 65 | 66 | .headerRightText { 67 | font-weight: normal; 68 | margin-left: 40px; 69 | } 70 | 71 | .microsoftLogo { 72 | height: 23px; 73 | font-weight: 600; 74 | } 75 | 76 | .githubLogo { 77 | height: 20px; 78 | } 79 | -------------------------------------------------------------------------------- /app/frontend/src/pages/layout/Layout.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet, NavLink, Link } from "react-router-dom"; 2 | 3 | import github from "../../assets/github.svg"; 4 | 5 | import styles from "./Layout.module.css"; 6 | 7 | import { useLogin } from "../../authConfig"; 8 | 9 | import { LoginButton } from "../../components/LoginButton"; 10 | 11 | const Layout = () => { 12 | return ( 13 | 14 | 15 | 16 | 17 | Agents Java Sample 18 | 19 | 20 | 21 | 22 | (isActive ? styles.headerNavPageLinkActive : styles.headerNavPageLink)}> 23 | Chat 24 | 25 | 26 | 27 | 28 | 29 | 37 | 38 | 39 | 40 | 41 | Banking Assistance Copilot 42 | {useLogin && } 43 | 44 | 45 | 46 | 47 | 48 | ); 49 | }; 50 | 51 | export default Layout; 52 | -------------------------------------------------------------------------------- /app/frontend/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /app/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx", 18 | "types": ["vite/client"] 19 | }, 20 | "include": ["src"] 21 | } 22 | -------------------------------------------------------------------------------- /app/frontend/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | build: { 8 | outDir: "./build", 9 | emptyOutDir: true, 10 | sourcemap: true, 11 | rollupOptions: { 12 | output: { 13 | manualChunks: id => { 14 | if (id.includes("@fluentui/react-icons")) { 15 | return "fluentui-icons"; 16 | } else if (id.includes("@fluentui/react")) { 17 | return "fluentui-react"; 18 | } else if (id.includes("node_modules")) { 19 | return "vendor"; 20 | } 21 | } 22 | } 23 | }, 24 | target: "esnext" 25 | }, 26 | server: { 27 | proxy: { 28 | "/api/ask": { 29 | target: 'http://localhost:8080', 30 | changeOrigin: true 31 | }, 32 | "/api/chat": { 33 | target: 'http://localhost:8080', 34 | changeOrigin: true 35 | }, 36 | "/api/content": { 37 | target: 'http://localhost:8080', 38 | changeOrigin: true 39 | }, 40 | "/api/auth_setup": { 41 | target: 'http://localhost:8080', 42 | changeOrigin: true 43 | } 44 | } 45 | } 46 | }); 47 | -------------------------------------------------------------------------------- /app/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": {} 6 | } 7 | -------------------------------------------------------------------------------- /app/start-compose.ps1: -------------------------------------------------------------------------------- 1 | $output = azd -C ..\ env get-values 2 | 3 | foreach ($line in $output) { 4 | $name, $value = $line.Split("=") 5 | $value = $value -replace '^\"|\"$' 6 | [Environment]::SetEnvironmentVariable($name, $value) 7 | } 8 | 9 | Write-Host "Environment variables set." 10 | $roles = @( 11 | "a97b65f3-24c7-4388-baec-2e87135dc908", 12 | "5e0bd9bd-7b93-4f28-af87-19fc36ad61bd", 13 | "ba92f5b4-2d11-453d-a403-e96b0029c9fe" 14 | ) 15 | 16 | # Check if service principal exists 17 | $servicePrincipal = $(az ad sp list --display-name "agent-java-banking-spi" --query [].appId --output tsv) 18 | 19 | if ([string]::IsNullOrEmpty($servicePrincipal)) { 20 | Write-Host "Service principal not found. Creating service principal.." 21 | $servicePrincipal = $(az ad sp create-for-rbac --name "agent-java-banking-spi" --role reader --scopes "/subscriptions/$($env:AZURE_SUBSCRIPTION_ID)/resourceGroups/$($env:AZURE_RESOURCE_GROUP)" --query appId --output tsv) 22 | if ($LASTEXITCODE -ne 0) { 23 | Write-Host "Failed to create service principal" 24 | exit $LASTEXITCODE 25 | } 26 | $servicePrincipalObjectId = $(az ad sp show --id $servicePrincipal --query id --output tsv) 27 | Write-Host "Assigning Roles to service principal agent-java-banking-spi with principal id: $servicePrincipal and object id[$servicePrincipalObjectId]" 28 | foreach ($role in $roles) { 29 | Write-Host "Assigning Role[$role] to principal id[$servicePrincipal] for resource[/subscriptions/$($env:AZURE_SUBSCRIPTION_ID)/resourceGroups/$($env:AZURE_RESOURCE_GROUP)]" 30 | az role assignment create ` 31 | --role $role ` 32 | --assignee-object-id $servicePrincipalObjectId ` 33 | --scope "/subscriptions/$($env:AZURE_SUBSCRIPTION_ID)/resourceGroups/$($env:AZURE_RESOURCE_GROUP)" ` 34 | --assignee-principal-type ServicePrincipal 35 | } 36 | } 37 | 38 | $servicePrincipalPassword = $(az ad sp credential reset --id $servicePrincipal --query password --output tsv) 39 | $servicePrincipalTenant = $(az ad sp show --id $servicePrincipal --query appOwnerOrganizationId --output tsv) 40 | 41 | # Set environment variables 42 | [Environment]::SetEnvironmentVariable("servicePrincipal", $servicePrincipal) 43 | [Environment]::SetEnvironmentVariable("servicePrincipalPassword", $servicePrincipalPassword) 44 | [Environment]::SetEnvironmentVariable("servicePrincipalTenant", $servicePrincipalTenant) 45 | 46 | 47 | Write-Host "" 48 | Write-Host "Starting solution locally using docker compose." 49 | Write-Host "" 50 | 51 | docker compose -f ./compose.yaml up -------------------------------------------------------------------------------- /app/start-compose.sh: -------------------------------------------------------------------------------- 1 | echo "" 2 | echo "Loading azd .env file from current environment" 3 | echo "" 4 | 5 | while IFS='=' read -r key value; do 6 | value=$(echo "$value" | sed 's/^"//' | sed 's/"$//') 7 | export "$key=$value" 8 | echo "export $key=$value" 9 | done < 4 | 5 | The development of a vertical multi-agent architecture for a personal banking assistant is inspired by a blend of cutting-edge research and practical applications in the field of generative AI and agent-based systems: 6 | 7 | 1. **Agent and Loop Mechanism**: An Agent is made up of three building blocks: Instructions (a.k.a prompt), Tools and LLM model to use. The core idea of agents is to use a language model like gpt4 to choose a sequence of actions, which can be supported by tools, in order to solve a task based on the instructions provided in the prompt. This can best be thought of as a loop where the agent iteratively processes user input, decides on actions or responses, and updates its internal state (agent scratchpad). This mechanism allows for complex interactions and task execution beyond simple text generation. 8 | 9 | 2. **Integration of AI Agents with LLMs and RAG**: AI agents enhance copilot application based on Retrieval Augmented Generation (RAG) pattern by enabling real-world task execution, decision-making, and real-time interaction. While in RAG a sequence of actions are well-known and are developed as a predefined LLM chain in app code, with agents a LLM is used as a reasoning engine to determine which actions to take and in which order.This integration is crucial for applications requiring dynamic responses and actions based on user inputs. 10 | 11 | 3. **Multi-Agent Architectures**: The debate between single and multi-agent systems highlights the versatility of multi-agent architectures in handling complex tasks requiring collaboration and multiple execution paths. Vertical and horizontal architectures represent two approaches, with most systems falling somewhere in between. Specifically, vertical architectures, where one agent acts as a leader coordinating with other specialized agents, are particularly relevant for building a personal banking assistant. This structure allows for a clear division of labor and efficient collaboration among agents with different functional domains. 12 | 13 | 5. **MicroAgents Concept**: The idea of MicroAgents, as proposed by the semantic kernel team at Microsoft, offers a practical approach for implementing vertical multi-agent systems. By partitioning agents by functional domain and associating each with a microservice, a banking assistant can leverage specialized knowledge and services (e.g., account management, transaction history, payments) to provide a comprehensive and user-friendly experience. 14 | -------------------------------------------------------------------------------- /docs/troubleshooting.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure-Samples/agent-openai-java-banking-assistant/0a7fee1d6958674e1103b5d1ff729e8b716e4321/docs/troubleshooting.md -------------------------------------------------------------------------------- /infra/app/account.bicep: -------------------------------------------------------------------------------- 1 | param name string 2 | param location string = resourceGroup().location 3 | param tags object = {} 4 | 5 | param identityName string 6 | param applicationInsightsName string 7 | param containerAppsEnvironmentName string 8 | param containerRegistryName string 9 | param serviceName string = 'account' 10 | param corsAcaUrl string 11 | param exists bool 12 | 13 | @description('The environment variables for the container') 14 | param env array = [] 15 | 16 | resource apiIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { 17 | name: identityName 18 | location: location 19 | } 20 | 21 | 22 | module app '../shared/host/container-app-upsert.bicep' = { 23 | name: '${serviceName}-container-app' 24 | params: { 25 | name: name 26 | location: location 27 | tags: union(tags, { 'azd-service-name': serviceName }) 28 | identityType: 'UserAssigned' 29 | identityName: apiIdentity.name 30 | exists: exists 31 | containerAppsEnvironmentName: containerAppsEnvironmentName 32 | containerRegistryName: containerRegistryName 33 | containerCpuCoreCount: '1.0' 34 | containerMemory: '2.0Gi' 35 | targetPort: 8080 36 | external:false 37 | env: union(env, [ 38 | { 39 | name: 'AZURE_CLIENT_ID' 40 | value: apiIdentity.properties.clientId 41 | } 42 | 43 | { 44 | name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' 45 | value: applicationInsights.properties.ConnectionString 46 | } 47 | { 48 | name: 'API_ALLOW_ORIGINS' 49 | value: corsAcaUrl 50 | } 51 | ]) 52 | 53 | } 54 | } 55 | 56 | resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { 57 | name: applicationInsightsName 58 | } 59 | 60 | 61 | output SERVICE_API_IDENTITY_PRINCIPAL_ID string = apiIdentity.properties.principalId 62 | output SERVICE_API_NAME string = app.outputs.name 63 | output SERVICE_API_URI string = app.outputs.uri 64 | output SERVICE_API_IMAGE_NAME string = app.outputs.imageName 65 | -------------------------------------------------------------------------------- /infra/app/copilot.bicep: -------------------------------------------------------------------------------- 1 | param name string 2 | param location string = resourceGroup().location 3 | param tags object = {} 4 | 5 | param identityName string 6 | param applicationInsightsName string 7 | param containerAppsEnvironmentName string 8 | param containerRegistryName string 9 | param serviceName string = 'copilot' 10 | param corsAcaUrl string 11 | param exists bool 12 | 13 | @description('The environment variables for the container') 14 | param env array = [] 15 | 16 | resource apiIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { 17 | name: identityName 18 | location: location 19 | } 20 | 21 | 22 | module app '../shared/host/container-app-upsert.bicep' = { 23 | name: '${serviceName}-container-app' 24 | params: { 25 | name: name 26 | location: location 27 | tags: union(tags, { 'azd-service-name': serviceName }) 28 | identityType: 'UserAssigned' 29 | identityName: apiIdentity.name 30 | exists: exists 31 | containerAppsEnvironmentName: containerAppsEnvironmentName 32 | containerRegistryName: containerRegistryName 33 | containerCpuCoreCount: '1.0' 34 | containerMemory: '2.0Gi' 35 | targetPort: 8080 36 | external:false 37 | env: union(env, [ 38 | { 39 | name: 'AZURE_CLIENT_ID' 40 | value: apiIdentity.properties.clientId 41 | } 42 | 43 | { 44 | name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' 45 | value: applicationInsights.properties.ConnectionString 46 | } 47 | { 48 | name: 'API_ALLOW_ORIGINS' 49 | value: corsAcaUrl 50 | } 51 | ]) 52 | 53 | } 54 | } 55 | 56 | resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { 57 | name: applicationInsightsName 58 | } 59 | 60 | 61 | output SERVICE_API_IDENTITY_PRINCIPAL_ID string = apiIdentity.properties.principalId 62 | output SERVICE_API_NAME string = app.outputs.name 63 | output SERVICE_API_URI string = app.outputs.uri 64 | output SERVICE_API_IMAGE_NAME string = app.outputs.imageName 65 | -------------------------------------------------------------------------------- /infra/app/payment.bicep: -------------------------------------------------------------------------------- 1 | param name string 2 | param location string = resourceGroup().location 3 | param tags object = {} 4 | 5 | param identityName string 6 | param applicationInsightsName string 7 | param containerAppsEnvironmentName string 8 | param containerRegistryName string 9 | param serviceName string = 'payment' 10 | param corsAcaUrl string 11 | param exists bool 12 | 13 | @description('The environment variables for the container') 14 | param env array = [] 15 | 16 | resource apiIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { 17 | name: identityName 18 | location: location 19 | } 20 | 21 | 22 | module app '../shared/host/container-app-upsert.bicep' = { 23 | name: '${serviceName}-container-app' 24 | params: { 25 | name: name 26 | location: location 27 | tags: union(tags, { 'azd-service-name': serviceName }) 28 | identityType: 'UserAssigned' 29 | identityName: apiIdentity.name 30 | exists: exists 31 | containerAppsEnvironmentName: containerAppsEnvironmentName 32 | containerRegistryName: containerRegistryName 33 | containerCpuCoreCount: '1.0' 34 | containerMemory: '2.0Gi' 35 | targetPort: 8080 36 | external:false 37 | env: union(env, [ 38 | { 39 | name: 'AZURE_CLIENT_ID' 40 | value: apiIdentity.properties.clientId 41 | } 42 | 43 | { 44 | name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' 45 | value: applicationInsights.properties.ConnectionString 46 | } 47 | { 48 | name: 'API_ALLOW_ORIGINS' 49 | value: corsAcaUrl 50 | } 51 | ]) 52 | 53 | } 54 | } 55 | 56 | resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { 57 | name: applicationInsightsName 58 | } 59 | 60 | 61 | output SERVICE_API_IDENTITY_PRINCIPAL_ID string = apiIdentity.properties.principalId 62 | output SERVICE_API_NAME string = app.outputs.name 63 | output SERVICE_API_URI string = app.outputs.uri 64 | output SERVICE_API_IMAGE_NAME string = app.outputs.imageName 65 | -------------------------------------------------------------------------------- /infra/app/transaction.bicep: -------------------------------------------------------------------------------- 1 | param name string 2 | param location string = resourceGroup().location 3 | param tags object = {} 4 | 5 | param identityName string 6 | param applicationInsightsName string 7 | param containerAppsEnvironmentName string 8 | param containerRegistryName string 9 | param serviceName string = 'transaction' 10 | param corsAcaUrl string 11 | param exists bool 12 | 13 | @description('The environment variables for the container') 14 | param env array = [] 15 | 16 | resource apiIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { 17 | name: identityName 18 | location: location 19 | } 20 | 21 | 22 | module app '../shared/host/container-app-upsert.bicep' = { 23 | name: '${serviceName}-container-app' 24 | params: { 25 | name: name 26 | location: location 27 | tags: union(tags, { 'azd-service-name': serviceName }) 28 | identityType: 'UserAssigned' 29 | identityName: apiIdentity.name 30 | exists: exists 31 | containerAppsEnvironmentName: containerAppsEnvironmentName 32 | containerRegistryName: containerRegistryName 33 | containerCpuCoreCount: '1.0' 34 | containerMemory: '2.0Gi' 35 | targetPort: 8080 36 | external:false 37 | env: union(env, [ 38 | { 39 | name: 'AZURE_CLIENT_ID' 40 | value: apiIdentity.properties.clientId 41 | } 42 | 43 | { 44 | name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' 45 | value: applicationInsights.properties.ConnectionString 46 | } 47 | { 48 | name: 'API_ALLOW_ORIGINS' 49 | value: corsAcaUrl 50 | } 51 | ]) 52 | 53 | } 54 | } 55 | 56 | resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { 57 | name: applicationInsightsName 58 | } 59 | 60 | 61 | output SERVICE_API_IDENTITY_PRINCIPAL_ID string = apiIdentity.properties.principalId 62 | output SERVICE_API_NAME string = app.outputs.name 63 | output SERVICE_API_URI string = app.outputs.uri 64 | output SERVICE_API_IMAGE_NAME string = app.outputs.imageName 65 | -------------------------------------------------------------------------------- /infra/app/web.bicep: -------------------------------------------------------------------------------- 1 | param name string 2 | param location string = resourceGroup().location 3 | param tags object = {} 4 | 5 | param identityName string 6 | param apiBaseUrl string 7 | param applicationInsightsName string 8 | param containerAppsEnvironmentName string 9 | param containerRegistryName string 10 | param serviceName string = 'web' 11 | param exists bool 12 | 13 | resource webIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = { 14 | name: identityName 15 | location: location 16 | } 17 | 18 | module app '../shared/host/container-app-upsert.bicep' = { 19 | name: '${serviceName}-container-app' 20 | params: { 21 | name: name 22 | location: location 23 | tags: union(tags, { 'azd-service-name': serviceName }) 24 | identityType: 'UserAssigned' 25 | identityName: identityName 26 | exists: exists 27 | containerAppsEnvironmentName: containerAppsEnvironmentName 28 | containerRegistryName: containerRegistryName 29 | env: [ 30 | { 31 | name: 'REACT_APP_APPLICATIONINSIGHTS_CONNECTION_STRING' 32 | value: applicationInsights.properties.ConnectionString 33 | } 34 | { 35 | name: 'REACT_APP_API_BASE_URL' 36 | value: apiBaseUrl 37 | } 38 | { 39 | name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' 40 | value: applicationInsights.properties.ConnectionString 41 | } 42 | ] 43 | targetPort: 80 44 | } 45 | } 46 | 47 | resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = { 48 | name: applicationInsightsName 49 | } 50 | 51 | output SERVICE_WEB_IDENTITY_PRINCIPAL_ID string = webIdentity.properties.principalId 52 | output SERVICE_WEB_NAME string = app.outputs.name 53 | output SERVICE_WEB_URI string = app.outputs.uri 54 | output SERVICE_WEB_IMAGE_NAME string = app.outputs.imageName 55 | -------------------------------------------------------------------------------- /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 | "openAiServiceName": { 15 | "value": "${AZURE_OPENAI_SERVICE}" 16 | }, 17 | "openAiResourceGroupName": { 18 | "value": "${AZURE_OPENAI_RESOURCE_GROUP}" 19 | }, 20 | "openAiResourceGroupLocation": { 21 | "value": "${AZURE_OPENAI_SERVICE_LOCATION=eastus}" 22 | }, 23 | "openAiSkuName": { 24 | "value": "S0" 25 | }, 26 | "documentIntelligenceServiceName": { 27 | "value": "${AZURE_DOCUMENT_INTELLIGENCE_SERVICE}" 28 | }, 29 | "documentIntelligenceResourceGroupName": { 30 | "value": "${AZURE_DOCUMENT_INTELLIGENCE_RESOURCE_GROUP}" 31 | }, 32 | "documentIntelligenceResourceGroupLocation": { 33 | "value": "${AZURE_DOCUMENT_INTELLIGENCE_RESOURCE_GROUP_LOCATION=eastus}" 34 | }, 35 | "documentIntelligenceSkuName": { 36 | "value": "S0" 37 | }, 38 | "storageAccountName": { 39 | "value": "${AZURE_STORAGE_ACCOUNT}" 40 | }, 41 | "storageResourceGroupName": { 42 | "value": "${AZURE_STORAGE_RESOURCE_GROUP}" 43 | }, 44 | "storageSkuName": { 45 | "value": "${AZURE_STORAGE_SKU=Standard_LRS}" 46 | }, 47 | "chatGptModelName": { 48 | "value": "${AZURE_OPENAI_CHATGPT_MODEL=gpt-4o}" 49 | }, 50 | "chatGptModelVersion": { 51 | "value": "${AZURE_OPENAI_CHATGPT_VERSION=2024-11-20}" 52 | }, 53 | "chatGptDeploymentName": { 54 | "value": "${AZURE_OPENAI_CHATGPT_DEPLOYMENT=gpt-4o}" 55 | }, 56 | "chatGptDeploymentCapacity": { 57 | "value": "${AZURE_OPENAI_CHATGPT_DEPLOYMENT_CAPACITY=30}" 58 | }, 59 | "chatGptDeploymentSkuName": { 60 | "value": "${AZURE_OPENAI_CHATGPT_DEPLOYMENT_SKU_NAME=GlobalStandard}" 61 | }, 62 | "useApplicationInsights": { 63 | "value": "${AZURE_USE_APPLICATION_INSIGHTS=true}" 64 | } 65 | , 66 | "copilotAppExists": { 67 | "value": false 68 | }, 69 | "webAppExists": { 70 | "value": false 71 | }, 72 | "accountAppExists": { 73 | "value": false 74 | }, 75 | "paymentAppExists": { 76 | "value": false 77 | }, 78 | "transactionAppExists": { 79 | "value": false 80 | } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /infra/shared/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 = true 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 | 58 | -------------------------------------------------------------------------------- /infra/shared/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/shared/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 | 44 | -------------------------------------------------------------------------------- /infra/shared/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/shared/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 | 24 | -------------------------------------------------------------------------------- /infra/shared/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 | 35 | -------------------------------------------------------------------------------- /infra/shared/security/keyvault-access.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Assigns an Azure Key Vault access policy.' 2 | param name string = 'add' 3 | 4 | param keyVaultName string 5 | param permissions object = { secrets: [ 'get', 'list' ] } 6 | param principalId string 7 | 8 | resource keyVaultAccessPolicies 'Microsoft.KeyVault/vaults/accessPolicies@2022-07-01' = { 9 | parent: keyVault 10 | name: name 11 | properties: { 12 | accessPolicies: [ { 13 | objectId: principalId 14 | tenantId: subscription().tenantId 15 | permissions: permissions 16 | } ] 17 | } 18 | } 19 | 20 | resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { 21 | name: keyVaultName 22 | } 23 | -------------------------------------------------------------------------------- /infra/shared/security/keyvault-secret.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates or updates a secret in an Azure Key Vault.' 2 | param name string 3 | param tags object = {} 4 | param keyVaultName string 5 | param contentType string = 'string' 6 | @description('The value of the secret. Provide only derived values like blob storage access, but do not hard code any secrets in your templates') 7 | @secure() 8 | param secretValue string 9 | 10 | param enabled bool = true 11 | param exp int = 0 12 | param nbf int = 0 13 | 14 | resource keyVaultSecret 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = { 15 | name: name 16 | tags: tags 17 | parent: keyVault 18 | properties: { 19 | attributes: { 20 | enabled: enabled 21 | exp: exp 22 | nbf: nbf 23 | } 24 | contentType: contentType 25 | value: secretValue 26 | } 27 | } 28 | 29 | resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { 30 | name: keyVaultName 31 | } 32 | -------------------------------------------------------------------------------- /infra/shared/security/keyvault.bicep: -------------------------------------------------------------------------------- 1 | metadata description = 'Creates an Azure Key Vault.' 2 | param name string 3 | param location string = resourceGroup().location 4 | param tags object = {} 5 | 6 | param principalId string = '' 7 | 8 | @description('Allow the key vault to be used for template deployment.') 9 | param enabledForDeployment bool = false 10 | 11 | resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = { 12 | name: name 13 | location: location 14 | tags: tags 15 | properties: { 16 | tenantId: subscription().tenantId 17 | sku: { family: 'A', name: 'standard' } 18 | accessPolicies: !empty(principalId) ? [ 19 | { 20 | objectId: principalId 21 | permissions: { secrets: [ 'get', 'list' ] } 22 | tenantId: subscription().tenantId 23 | } 24 | ] : [] 25 | enabledForDeployment: enabledForDeployment 26 | } 27 | } 28 | 29 | output endpoint string = keyVault.properties.vaultUri 30 | output id string = keyVault.id 31 | output name string = keyVault.name 32 | -------------------------------------------------------------------------------- /infra/shared/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/shared/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 | --------------------------------------------------------------------------------
{error}
19 | Generating answer 20 | 21 |
{text}
{parsed.content}