├── src
├── backend
│ ├── .funcignore
│ ├── host.json
│ ├── requirements.txt
│ ├── .gitignore
│ └── function_app.py
└── frontend
│ ├── src
│ ├── vite-env.d.ts
│ ├── main.tsx
│ ├── components
│ │ ├── ChatAnswer
│ │ │ ├── ChatAnswer.module.css
│ │ │ └── ChatAnswer.tsx
│ │ ├── ChatQuestion
│ │ │ ├── ChatQuestion.tsx
│ │ │ └── ChatQuestion.module.css
│ │ ├── ChatAnswerError
│ │ │ ├── ChatAnswerError.module.css
│ │ │ └── ChatAnswerError.tsx
│ │ ├── ChatAnswerLoading
│ │ │ ├── ChatAnswerLoading.module.css
│ │ │ └── ChatAnswerLoading.tsx
│ │ └── InputQuestion
│ │ │ ├── InputQuestion.module.css
│ │ │ └── InputQuestion.tsx
│ ├── App.css
│ ├── index.css
│ ├── assets
│ │ └── react.svg
│ └── App.tsx
│ ├── staticwebapp.config.json
│ ├── vite.config.ts
│ ├── tsconfig.node.json
│ ├── scripts
│ ├── prepackage.sh
│ └── prepackage.ps1
│ ├── index.html
│ ├── .gitignore
│ ├── .eslintrc.cjs
│ ├── tsconfig.json
│ ├── package.json
│ ├── README.md
│ └── public
│ └── vite.svg
├── data
└── 001018385.pdf
├── .gitignore
├── scripts
├── requirements.txt
├── cosmosreadwriterole.json
├── local.settings.json
├── postprovision.sh
├── postprovision.ps1
└── indexer.py
├── .vscode
├── extensions.json
├── settings.json
├── launch.json
└── tasks.json
├── swa-cli.config.json
├── infra
├── modules
│ ├── monitor
│ │ └── appi.bicep
│ ├── app
│ │ ├── asp.bicep
│ │ ├── stapp.bicep
│ │ └── func.bicep
│ ├── storage
│ │ └── storage.bicep
│ ├── security
│ │ └── role.bicep
│ ├── ai
│ │ ├── srch.bicep
│ │ └── cognitiveservices.bicep
│ └── db
│ │ └── cosmosdb.bicep
├── main.parameters.json
├── abbreviations.json
└── main.bicep
├── README.md
├── azure.yaml
└── LICENSE.md
/src/backend/.funcignore:
--------------------------------------------------------------------------------
1 | .venv
--------------------------------------------------------------------------------
/src/frontend/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/data/001018385.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noriyukitakei/aoai-rag-starter/HEAD/data/001018385.pdf
--------------------------------------------------------------------------------
/src/frontend/staticwebapp.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "platform": {
3 | "apiRuntime": "python:3.10"
4 | }
5 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | .azure/*
4 | .azurite/*
5 | .env
6 | scripts/.venv
7 | src/frontend/.env.production
8 |
--------------------------------------------------------------------------------
/scripts/requirements.txt:
--------------------------------------------------------------------------------
1 | azure-search-documents==11.6.0b2
2 | langchain==0.1.11
3 | azure-ai-formrecognizer==3.3.2
4 | openai==1.13.3
5 | azure-identity==1.15.0
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "ms-azuretools.vscode-azurefunctions",
4 | "ms-python.python",
5 | "ms-azuretools.vscode-azurestaticwebapps",
6 | "azurite.azurite"
7 | ]
8 | }
--------------------------------------------------------------------------------
/src/frontend/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react-swc'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/src/frontend/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import App from './App.tsx'
4 |
5 | ReactDOM.createRoot(document.getElementById('root')!).render(
6 |
7 |
8 | ,
9 | )
10 |
--------------------------------------------------------------------------------
/src/frontend/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true,
8 | "strict": true
9 | },
10 | "include": ["vite.config.ts"]
11 | }
12 |
--------------------------------------------------------------------------------
/src/frontend/scripts/prepackage.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # フロントエンドアプリケーションがビルドされる前に実行されるスクリプト
4 |
5 | # フロントエンドエンドアプリケーション内に埋め込むバックエンドAPIのエンドポイントを取得し、
6 | # .env.productionファイルに書き出す。この環境変数はビルドされたフロントエンドアプリケーション内で使われる。
7 | azd env get-values 2>/dev/null | grep "^API_ENDPOINT" | sed 's/^API_ENDPOINT="\([^"]*\)"$/VITE_API_ENDPOINT=\1/' > .env.production
--------------------------------------------------------------------------------
/src/frontend/src/components/ChatAnswer/ChatAnswer.module.css:
--------------------------------------------------------------------------------
1 | .chatMessage {
2 | padding: 10px;
3 | margin: 5px 0;
4 | border-radius: 20px;
5 | max-width: 66%;
6 | display: flex;
7 | align-items: center;
8 | word-break: break-word;
9 | }
10 |
11 | .otherMessage {
12 | background: #f0f0f0;
13 | align-self: flex-start;
14 | }
15 |
--------------------------------------------------------------------------------
/src/frontend/src/components/ChatAnswer/ChatAnswer.tsx:
--------------------------------------------------------------------------------
1 | import styles from './ChatAnswer.module.css';
2 |
3 | interface Props {
4 | answer: string|undefined;
5 | }
6 |
7 | export const ChatAnswer = (props: Props) => {
8 | return (
9 |
{props.answer}
10 | );
11 | };
--------------------------------------------------------------------------------
/src/frontend/src/components/ChatQuestion/ChatQuestion.tsx:
--------------------------------------------------------------------------------
1 | import styles from './ChatQuestion.module.css';
2 |
3 | interface Props {
4 | question: string;
5 | }
6 |
7 | export const ChatQuestion = (props: Props) => {
8 | return (
9 | {props.question}
10 | );
11 | };
--------------------------------------------------------------------------------
/src/frontend/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | カスタムチャットUI
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/frontend/src/components/ChatQuestion/ChatQuestion.module.css:
--------------------------------------------------------------------------------
1 | .chatMessage {
2 | padding: 10px;
3 | margin: 5px 0;
4 | border-radius: 20px;
5 | max-width: 66%;
6 | display: flex;
7 | align-items: center;
8 | word-break: break-word;
9 | }
10 |
11 | .myMessage {
12 | background: #007bff;
13 | color: white;
14 | align-self: flex-end;
15 | }
16 |
--------------------------------------------------------------------------------
/src/backend/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0",
3 | "logging": {
4 | "applicationInsights": {
5 | "samplingSettings": {
6 | "isEnabled": true,
7 | "excludedTypes": "Request"
8 | }
9 | }
10 | },
11 | "extensionBundle": {
12 | "id": "Microsoft.Azure.Functions.ExtensionBundle",
13 | "version": "[4.*, 5.0.0)"
14 | }
15 | }
--------------------------------------------------------------------------------
/swa-cli.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://aka.ms/azure/static-web-apps-cli/schema",
3 | "configurations": {
4 | "rag-sample": {
5 | "appLocation": "src/frontend",
6 | "outputLocation": "dist",
7 | "appBuildCommand": "npm run build",
8 | "run": "npm run dev",
9 | "appDevserverUrl": "http://localhost:5173"
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/src/backend/requirements.txt:
--------------------------------------------------------------------------------
1 | # Do not include azure-functions-worker in this file
2 | # The Python Worker is managed by the Azure Functions platform
3 | # Manually managing azure-functions-worker may cause unexpected issues
4 |
5 | azure-functions
6 | azure-search-documents==11.6.0b2
7 | openai==1.13.3
8 | azure-identity==1.15.0
9 | tiktoken==0.6.0
10 | azure-cosmos==4.6.0
--------------------------------------------------------------------------------
/src/frontend/src/components/ChatAnswerError/ChatAnswerError.module.css:
--------------------------------------------------------------------------------
1 | .chatMessage {
2 | padding: 10px;
3 | margin: 5px 0;
4 | border-radius: 20px;
5 | max-width: 66%;
6 | display: flex;
7 | align-items: center;
8 | word-break: break-word;
9 | color: red;
10 | }
11 |
12 | .otherMessage {
13 | background: #f0f0f0;
14 | align-self: flex-start;
15 | }
16 |
--------------------------------------------------------------------------------
/src/frontend/src/components/ChatAnswerLoading/ChatAnswerLoading.module.css:
--------------------------------------------------------------------------------
1 | .chatMessage {
2 | padding: 10px;
3 | margin: 5px 0;
4 | border-radius: 20px;
5 | width: 50%;
6 | max-width: 66%;
7 | display: flex;
8 | align-items: center;
9 | word-break: break-word;
10 | }
11 |
12 | .otherMessage {
13 | background: #f0f0f0;
14 | align-self: flex-start;
15 | }
16 |
--------------------------------------------------------------------------------
/src/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/src/frontend/src/components/ChatAnswerError/ChatAnswerError.tsx:
--------------------------------------------------------------------------------
1 | import styles from './ChatAnswerError.module.css';
2 |
3 | interface Props {
4 | errorMessage: string|undefined;
5 | }
6 |
7 | export const ChatAnswerError = (props: Props) => {
8 | return (
9 | {props.errorMessage}
10 | );
11 | };
--------------------------------------------------------------------------------
/src/frontend/src/components/InputQuestion/InputQuestion.module.css:
--------------------------------------------------------------------------------
1 | .button {
2 | background: #007bff;
3 | color: white;
4 | border: none;
5 | padding: 10px 20px;
6 | border-radius: 18px;
7 | cursor: pointer;
8 | }
9 |
10 | .button_disabled {
11 | background: #ccc;
12 | color: white;
13 | border: none;
14 | padding: 10px 20px;
15 | border-radius: 18px;
16 | cursor: pointer;
17 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "azureFunctions.deploySubpath": "src/backend",
3 | "azureFunctions.scmDoBuildDuringDeployment": true,
4 | "azureFunctions.pythonVenv": ".venv",
5 | "azureFunctions.projectLanguage": "Python",
6 | "azureFunctions.projectRuntime": "~4",
7 | "debug.internalConsoleOptions": "neverOpen",
8 | "azureFunctions.projectLanguageModel": 2,
9 | "azurite.location": ".azurite"
10 | }
--------------------------------------------------------------------------------
/src/frontend/scripts/prepackage.ps1:
--------------------------------------------------------------------------------
1 | # PowerShell
2 |
3 | # フロントエンドアプリケーションがビルドされる前に実行されるスクリプト
4 |
5 | # フロントエンドエンドアプリケーション内に埋め込むバックエンドAPIのエンドポイントを取得し、
6 | # .env.productionファイルに書き出す。この環境変数はビルドされたフロントエンドアプリケーション内で使われる。
7 | $API_ENDPOINT = (azd env get-values 2>$null | Select-String "^API_ENDPOINT" | ForEach-Object { $_ -replace '^API_ENDPOINT=', '' -replace '"', '' })
8 | "VITE_API_ENDPOINT=$API_ENDPOINT" | Out-File -FilePath .env.production
--------------------------------------------------------------------------------
/infra/modules/monitor/appi.bicep:
--------------------------------------------------------------------------------
1 | param name string
2 | param location string = resourceGroup().location
3 | param tags object = {}
4 |
5 | resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = {
6 | name: name
7 | location: location
8 | kind: 'web'
9 | tags: tags
10 | properties: {
11 | Application_Type: 'web'
12 | Request_Source: 'rest'
13 | }
14 | }
15 |
16 | output name string = applicationInsights.name
17 |
--------------------------------------------------------------------------------
/scripts/cosmosreadwriterole.json:
--------------------------------------------------------------------------------
1 | {
2 | "RoleName": "MyReadWriteRole",
3 | "Type": "CustomRole",
4 | "AssignableScopes": ["/"],
5 | "Permissions": [{
6 | "DataActions": [
7 | "Microsoft.DocumentDB/databaseAccounts/readMetadata",
8 | "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/items/*",
9 | "Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers/*"
10 | ]
11 | }]
12 | }
--------------------------------------------------------------------------------
/infra/modules/app/asp.bicep:
--------------------------------------------------------------------------------
1 | param name string
2 | param location string = resourceGroup().location
3 | param tags object = {}
4 |
5 | resource hostingPlan 'Microsoft.Web/serverfarms@2021-03-01' = {
6 | name: name
7 | location: location
8 | tags: tags
9 | sku: {
10 | name: 'Y1'
11 | tier: 'Dynamic'
12 | size: 'Y1'
13 | family: 'Y'
14 | capacity: 0
15 | }
16 | properties: {
17 | reserved: true
18 | }
19 | }
20 |
21 | output id string = hostingPlan.id
22 |
--------------------------------------------------------------------------------
/infra/modules/storage/storage.bicep:
--------------------------------------------------------------------------------
1 | param name string
2 | param location string = resourceGroup().location
3 | param sku object = {}
4 | param tags object = {}
5 |
6 | resource storageAccount 'Microsoft.Storage/storageAccounts@2022-05-01' = {
7 | name: name
8 | location: location
9 | sku: sku
10 | kind: 'Storage'
11 | tags: tags
12 | properties: {
13 | supportsHttpsTrafficOnly: true
14 | defaultToOAuthAuthentication: true
15 | }
16 | }
17 |
18 | output name string = storageAccount.name
19 |
--------------------------------------------------------------------------------
/src/frontend/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:@typescript-eslint/recommended',
7 | 'plugin:react-hooks/recommended',
8 | ],
9 | ignorePatterns: ['dist', '.eslintrc.cjs'],
10 | parser: '@typescript-eslint/parser',
11 | plugins: ['react-refresh'],
12 | rules: {
13 | 'react-refresh/only-export-components': [
14 | 'warn',
15 | { allowConstantExport: true },
16 | ],
17 | },
18 | }
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # rag-sample
2 |
3 | 本ソースコードは、生成AIによるチャットシステムを実現するRAG(Retrieval-Augmented Generation)のサンプルコードです。
4 |
5 | 「シンプル」「強力」「すぐ動く」をモットーにしたアプリケーションで、これらかRAGを始める人に参考にしてもらうべく開発しました。
6 |
7 | - シンプル
8 | 余計な機能がなく、シンプルなチャットのインターフェースのみを提供しています。これにより、ソースコードの可読性が高まり、中身を理解しやすくなっています。
9 |
10 | - 強力
11 | シンプルとはいえど機能は強力です。Azureの最先端の検索手法である「セマンティックハイブリッド検索」、Azure Static Web Apps、Azure Functionsによるフルサーバーレス構成といった最新技術が集結されています。
12 |
13 | - すぐ動く
14 | Azure Developer CLI、BicepによってIaC(Infrastructure as Code)を実現しており、コマンド一発でAzureにリソースがデプロイされて動くようになっています。
15 |
--------------------------------------------------------------------------------
/infra/modules/app/stapp.bicep:
--------------------------------------------------------------------------------
1 | metadata description = 'Creates an Azure Static Web Apps instance.'
2 | param name string
3 | param location string = resourceGroup().location
4 | param tags object = {}
5 |
6 |
7 | param sku object = {
8 | name: 'Free'
9 | tier: 'Free'
10 | }
11 |
12 | resource stapp 'Microsoft.Web/staticSites@2021-03-01' = {
13 | name: name
14 | location: location
15 | tags: tags
16 | sku: sku
17 | properties: {
18 | provider: 'Custom'
19 | }
20 | }
21 |
22 | output name string = stapp.name
23 | output uri string = 'https://${stapp.properties.defaultHostname}'
24 |
--------------------------------------------------------------------------------
/infra/modules/security/role.bicep:
--------------------------------------------------------------------------------
1 | param principalId string
2 |
3 | @allowed([
4 | 'Device'
5 | 'ForeignGroup'
6 | 'Group'
7 | 'ServicePrincipal'
8 | 'User'
9 | ])
10 | param principalType string = 'ServicePrincipal'
11 | param roleDefinitionId string
12 |
13 | resource role 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
14 | name: guid(subscription().id, resourceGroup().id, principalId, roleDefinitionId)
15 | properties: {
16 | principalId: principalId
17 | principalType: principalType
18 | roleDefinitionId: resourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/backend/.gitignore:
--------------------------------------------------------------------------------
1 | bin
2 | obj
3 | csx
4 | .vs
5 | edge
6 | Publish
7 |
8 | *.user
9 | *.suo
10 | *.cscfg
11 | *.Cache
12 | project.lock.json
13 |
14 | /packages
15 | /TestResults
16 |
17 | /tools/NuGet.exe
18 | /App_Data
19 | /secrets
20 | /data
21 | .secrets
22 | appsettings.json
23 | local.settings.json
24 |
25 | node_modules
26 | dist
27 |
28 | # Local python packages
29 | .python_packages/
30 |
31 | # Python Environments
32 | .env
33 | .venv
34 | env/
35 | venv/
36 | ENV/
37 | env.bak/
38 | venv.bak/
39 |
40 | # Byte-compiled / optimized / DLL files
41 | __pycache__/
42 | *.py[cod]
43 | *$py.class
44 |
45 | # Azurite artifacts
46 | __blobstorage__
47 | __queuestorage__
48 | __azurite_db*__.json
--------------------------------------------------------------------------------
/src/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true
22 | },
23 | "include": ["src"],
24 | "references": [{ "path": "./tsconfig.node.json" }]
25 | }
26 |
--------------------------------------------------------------------------------
/src/frontend/src/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | transition: filter 300ms;
13 | }
14 | .logo:hover {
15 | filter: drop-shadow(0 0 2em #646cffaa);
16 | }
17 | .logo.react:hover {
18 | filter: drop-shadow(0 0 2em #61dafbaa);
19 | }
20 |
21 | @keyframes logo-spin {
22 | from {
23 | transform: rotate(0deg);
24 | }
25 | to {
26 | transform: rotate(360deg);
27 | }
28 | }
29 |
30 | @media (prefers-reduced-motion: no-preference) {
31 | a:nth-of-type(2) .logo {
32 | animation: logo-spin infinite 20s linear;
33 | }
34 | }
35 |
36 | .card {
37 | padding: 2em;
38 | }
39 |
40 | .read-the-docs {
41 | color: #888;
42 | }
43 |
--------------------------------------------------------------------------------
/src/frontend/src/components/ChatAnswerLoading/ChatAnswerLoading.tsx:
--------------------------------------------------------------------------------
1 | import styles from './ChatAnswerLoading.module.css';
2 | import { useSpring, animated } from '@react-spring/web';
3 | import { useState } from 'react';
4 |
5 | export const ChatAnswerLoading = () => {
6 | const [dots, setDots] = useState('.');
7 | const stylesa = useSpring({
8 | from: { opacity: 0 },
9 | to: { opacity: 1 },
10 | reset: true,
11 | reverse: dots.length === 3,
12 | delay: 200,
13 | onRest: () => setDots(dots => dots.length < 3 ? dots + '.' : '.'),
14 | });
15 |
16 | return (
17 |
18 | 回答を生成中です🖊
19 |
{dots}
20 |
21 | );
22 | };
--------------------------------------------------------------------------------
/scripts/local.settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "IsEncrypted": false,
3 | "Values": {
4 | "FUNCTIONS_WORKER_RUNTIME": "python",
5 | "AzureWebJobsFeatureFlags": "EnableWorkerIndexing",
6 | "AzureWebJobsStorage": "UseDevelopmentStorage=true",
7 | "AOAI_ENDPOINT": "${AOAI_ENDPOINT}",
8 | "SEARCH_SERVICE_ENDPOINT": "${SEARCH_SERVICE_ENDPOINT}",
9 | "COSMOSDB_ENDPOINT": "${COSMOSDB_ENDPOINT}",
10 | "COSMOSDB_DATABASE": "${COSMOSDB_DATABASE}",
11 | "COSMOSDB_CONTAINER": "${COSMOSDB_CONTAINER}",
12 | "AOAI_MODEL": "${AOAI_MODEL}",
13 | "AOAI_GPT_35_TURBO_DEPLOYMENT": "${AOAI_GPT_35_TURBO_DEPLOYMENT}",
14 | "AOAI_GPT_4_DEPLOYMENT": "${AOAI_GPT_4_DEPLOYMENT}",
15 | "AOAI_GPT_4_32K_DEPLOYMENT": "${AOAI_GPT_4_32K_DEPLOYMENT}",
16 | "AOAI_TEXT_EMBEDDING_ADA_002_DEPLOYMENT": "${AOAI_TEXT_EMBEDDING_ADA_002_DEPLOYMENT}",
17 | "AOAI_API_VERSION": "${AOAI_API_VERSION}"
18 | }
19 | }
--------------------------------------------------------------------------------
/azure.yaml:
--------------------------------------------------------------------------------
1 | name: rag-sample
2 | metadata:
3 | template: rag-sample@0.0.1-beta
4 | services:
5 | web:
6 | project: ./src/frontend
7 | dist: dist
8 | language: ts
9 | host: staticwebapp
10 | hooks:
11 | prepackage:
12 | windows:
13 | shell: pwsh
14 | run: ./scripts/prepackage.ps1
15 | interactive: true
16 | continueOnError: false
17 | posix:
18 | shell: sh
19 | run: ./scripts/prepackage.sh
20 | interactive: true
21 | continueOnError: false
22 | api:
23 | project: ./src/backend
24 | language: py
25 | host: function
26 | hooks:
27 | postprovision:
28 | windows:
29 | shell: pwsh
30 | run: ./scripts/postprovision.ps1
31 | interactive: true
32 | continueOnError: false
33 | posix:
34 | shell: sh
35 | run: ./scripts/postprovision.sh
36 | interactive: true
37 | continueOnError: false
--------------------------------------------------------------------------------
/src/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "postbuild": "cpx staticwebapp.config.json ./dist",
10 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
11 | "preview": "vite preview"
12 | },
13 | "dependencies": {
14 | "@react-spring/web": "^9.7.3",
15 | "react": "^18.2.0",
16 | "react-dom": "^18.2.0"
17 | },
18 | "devDependencies": {
19 | "@types/react": "^18.2.56",
20 | "@types/react-dom": "^18.2.19",
21 | "@typescript-eslint/eslint-plugin": "^7.0.2",
22 | "@typescript-eslint/parser": "^7.0.2",
23 | "@vitejs/plugin-react-swc": "^3.5.0",
24 | "cpx": "^1.5.0",
25 | "eslint": "^8.56.0",
26 | "eslint-plugin-react-hooks": "^4.6.0",
27 | "eslint-plugin-react-refresh": "^0.4.5",
28 | "typescript": "^5.2.2",
29 | "vite": "^5.1.4"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Noriyuki Takei
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/infra/modules/ai/srch.bicep:
--------------------------------------------------------------------------------
1 | param name string
2 | param location string = resourceGroup().location
3 | param tags object = {}
4 |
5 | param sku object = {
6 | name: 'standard'
7 | }
8 |
9 | param authOptions object = {}
10 | param semanticSearch string = 'disabled'
11 |
12 | resource search 'Microsoft.Search/searchServices@2021-04-01-preview' = {
13 | name: name
14 | location: location
15 | tags: tags
16 | identity: {
17 | type: 'SystemAssigned'
18 | }
19 | properties: {
20 | authOptions: authOptions
21 | disableLocalAuth: false
22 | disabledDataExfiltrationOptions: []
23 | encryptionWithCmk: {
24 | enforcement: 'Unspecified'
25 | }
26 | hostingMode: 'default'
27 | networkRuleSet: {
28 | bypass: 'None'
29 | ipRules: []
30 | }
31 | partitionCount: 1
32 | publicNetworkAccess: 'Enabled'
33 | replicaCount: 1
34 | semanticSearch: semanticSearch
35 | }
36 | sku: sku
37 | }
38 |
39 | output id string = search.id
40 | output endpoint string = 'https://${name}.search.windows.net/'
41 | output name string = search.name
42 |
--------------------------------------------------------------------------------
/src/frontend/src/index.css:
--------------------------------------------------------------------------------
1 | body, html {
2 | height: 100%;
3 | margin: 0;
4 | font-family: Arial, sans-serif;
5 | display: flex;
6 | justify-content: center;
7 | align-items: center;
8 | background: #E7EFF9;
9 | }
10 |
11 | .chat-container {
12 | width: 600px;
13 | margin: 0 auto;
14 | padding: 20px;
15 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
16 | border-radius: 8px;
17 | background: white;
18 | display: flex;
19 | flex-direction: column;
20 | height: 80vh;
21 | overflow: auto;
22 | }
23 |
24 | .chat-message {
25 | padding: 10px;
26 | margin: 5px 0;
27 | border-radius: 20px;
28 | max-width: 66%;
29 | display: flex;
30 | align-items: center;
31 | word-break: break-word;
32 | }
33 |
34 | .other-message {
35 | background: #f0f0f0;
36 | align-self: flex-start;
37 | }
38 |
39 | .my-message {
40 | background: #007bff;
41 | color: white;
42 | align-self: flex-end;
43 | }
44 |
45 | .chat-input {
46 | border-top: 1px solid #f0f0f0;
47 | padding-top: 10px;
48 | display: flex;
49 | }
50 |
51 | .chat-input input {
52 | flex-grow: 1;
53 | border-radius: 18px;
54 | padding: 10px;
55 | border: 1px solid #007bff;
56 | margin-right: 8px;
57 | }
58 |
59 |
60 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "[Web]SWA: Run rag-sample",
6 | "request": "launch",
7 | "type": "chrome",
8 | "url": "http://localhost:4280",
9 | "preLaunchTask": "swa: start rag-sample",
10 | "webRoot": "${workspaceFolder}/src/frontend/src",
11 | "timeout": 30000
12 | },
13 | {
14 | "name": "[API]Attach to Python Functions",
15 | "type": "python",
16 | "request": "attach",
17 | "port": 9091,
18 | "preLaunchTask": "func: host start"
19 | },
20 | {
21 | "name": "[Indexer]Python: indexer",
22 | "type": "python",
23 | "program": "${workspaceFolder}/scripts/indexer.py",
24 | "request": "launch",
25 | "console": "integratedTerminal",
26 | "python": "${workspaceFolder}/scripts/.venv/bin/python",
27 | "envFile": "${workspaceFolder}/scripts/.env",
28 | "args": [
29 | "--docs",
30 | "./data/*",
31 | // "--remove"
32 | ]
33 | }
34 | ]
35 | }
--------------------------------------------------------------------------------
/infra/modules/ai/cognitiveservices.bicep:
--------------------------------------------------------------------------------
1 | param name string
2 | param location string = resourceGroup().location
3 | param tags object = {}
4 | param publicNetworkAccess string = 'Enabled'
5 | param customSubDomainName string = name
6 | param sku object = {
7 | name: 'S0'
8 | }
9 | param kind string
10 | param deployments array = []
11 |
12 | resource aisa 'Microsoft.CognitiveServices/accounts@2023-10-01-preview' = {
13 | name: name
14 | location: location
15 | tags: tags
16 | sku: sku
17 | kind: kind
18 | properties: {
19 | customSubDomainName: customSubDomainName
20 | networkAcls: {
21 | defaultAction: 'Allow'
22 | }
23 | publicNetworkAccess: publicNetworkAccess
24 | }
25 | }
26 |
27 | @batchSize(1)
28 | resource deployment 'Microsoft.CognitiveServices/accounts/deployments@2023-05-01' = [for deployment in deployments: {
29 | parent: aisa
30 | name: deployment.name
31 | properties: {
32 | model: deployment.model
33 | raiPolicyName: contains(deployment, 'raiPolicyName') ? deployment.raiPolicyName : null
34 | }
35 | sku: contains(deployment, 'sku') ? deployment.sku : {
36 | name: 'Standard'
37 | capacity: 20
38 | }
39 | }]
40 |
41 | output name string = aisa.name
42 | output endpoint string = aisa.properties.endpoint
43 |
--------------------------------------------------------------------------------
/src/frontend/README.md:
--------------------------------------------------------------------------------
1 | # React + TypeScript + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
10 | ## Expanding the ESLint configuration
11 |
12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
13 |
14 | - Configure the top-level `parserOptions` property like this:
15 |
16 | ```js
17 | export default {
18 | // other rules...
19 | parserOptions: {
20 | ecmaVersion: 'latest',
21 | sourceType: 'module',
22 | project: ['./tsconfig.json', './tsconfig.node.json'],
23 | tsconfigRootDir: __dirname,
24 | },
25 | }
26 | ```
27 |
28 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
29 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
31 |
--------------------------------------------------------------------------------
/src/frontend/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "type": "func",
6 | "label": "func: host start",
7 | "command": "host start",
8 | "problemMatcher": "$func-python-watch",
9 | "isBackground": true,
10 | "dependsOn": "pip install (functions)",
11 | "options": {
12 | "cwd": "${workspaceFolder}/src/backend"
13 | }
14 | },
15 | {
16 | "label": "pip install (functions)",
17 | "type": "shell",
18 | "osx": {
19 | "command": "${config:azureFunctions.pythonVenv}/bin/python -m pip install -r requirements.txt"
20 | },
21 | "windows": {
22 | "command": "${config:azureFunctions.pythonVenv}\\Scripts\\python -m pip install -r requirements.txt"
23 | },
24 | "linux": {
25 | "command": "${config:azureFunctions.pythonVenv}/bin/python -m pip install -r requirements.txt"
26 | },
27 | "problemMatcher": [],
28 | "options": {
29 | "cwd": "${workspaceFolder}/src/backend"
30 | }
31 | },
32 | {
33 | "type": "shell",
34 | "label": "swa start",
35 | "command": "swa start http://localhost:5173/ --api-devserver-url http://localhost:7071 --run \"npm run dev\"",
36 | "isBackground": true,
37 | "problemMatcher": [
38 | {
39 | "pattern": [
40 | {
41 | "regexp": ".",
42 | "file": 1,
43 | "location": 2,
44 | "message": 3
45 | }
46 | ],
47 | "background": {
48 | "activeOnStart": true,
49 | "beginsPattern": ".",
50 | "endsPattern": "Waiting for .+7071 to be ready",
51 | }
52 | }
53 | ],
54 | "options": {
55 | "cwd": "${workspaceFolder}/src/frontend"
56 | },
57 | },
58 | ]
59 | }
--------------------------------------------------------------------------------
/infra/modules/db/cosmosdb.bicep:
--------------------------------------------------------------------------------
1 | param name string
2 | param cosmosDbDatabaseName string
3 | param cosmosDbContainerName string
4 | param location string
5 | param tags object = {}
6 |
7 | resource cosmosDbAccount 'Microsoft.DocumentDB/databaseAccounts@2021-04-15' = {
8 | name: name
9 | location: location
10 | tags: tags
11 | kind: 'GlobalDocumentDB'
12 | properties: {
13 | consistencyPolicy: {
14 | defaultConsistencyLevel: 'Session'
15 | }
16 | databaseAccountOfferType: 'Standard'
17 | locations: [
18 | {
19 | locationName: location
20 | }]
21 | publicNetworkAccess: 'Enabled'
22 | }
23 | }
24 |
25 | resource cosmosDbDatabase 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2021-04-15' = {
26 | parent: cosmosDbAccount
27 | name: cosmosDbDatabaseName
28 | properties: {
29 | resource: {
30 | id: cosmosDbDatabaseName
31 | }
32 | options: {
33 | throughput: 400
34 | }
35 | }
36 |
37 | }
38 |
39 | resource cosmosDbContainer 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2021-04-15' = {
40 | parent: cosmosDbDatabase
41 | name: cosmosDbContainerName
42 | properties: {
43 | resource: {
44 | id: cosmosDbContainerName
45 | partitionKey: {
46 | paths: [
47 | '/id'
48 | ]
49 | kind: 'Hash'
50 | }
51 | }
52 | }
53 | }
54 |
55 | output id string = cosmosDbAccount.id
56 | output name string = cosmosDbAccount.name
57 | output endpoint string = cosmosDbAccount.properties.documentEndpoint
58 | output databaseName string = cosmosDbDatabase.name
59 | output containerName string = cosmosDbContainer.name
60 | output accountName string = cosmosDbAccount.name
61 |
--------------------------------------------------------------------------------
/src/frontend/src/components/InputQuestion/InputQuestion.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import styles from './InputQuestion.module.css';
3 |
4 | interface Props {
5 | onSend: (question: string) => void;
6 | isLoading: boolean;
7 | }
8 |
9 | export const InputQuestion = ({ onSend, isLoading }: Props) => {
10 | const [question, setQuestion] = useState("");
11 |
12 | // 漢字変換候補決定の際のEnterキーを押下しても質問が送信されないようにするためのステートを定義する
13 | const [composing, setComposition] = useState(false);
14 | const startComposition = () => setComposition(true);
15 | const endComposition = () => setComposition(false);
16 |
17 | // 質問を送信する関数を定義する
18 | const sendQuestion = () => {
19 | // 質問が空白の場合は何もしない
20 | if (!question.trim()) {
21 | return;
22 | }
23 |
24 | // 質問を送信する
25 | onSend(question);
26 |
27 | // 質問を空白にする
28 | setQuestion("");
29 | };
30 |
31 | // Enterキーが押されたときに質問を送信する
32 | const onEnterPress = (ev: React.KeyboardEvent) => {
33 | // 回答が生成中の場合は何もしない
34 | if (isLoading || composing) {
35 | return;
36 | }
37 |
38 |
39 | // Enterキーが押されたかつShiftキーが押されていない場合
40 | if (ev.key === "Enter" && !ev.shiftKey) {
41 | ev.preventDefault();
42 | sendQuestion();
43 | }
44 | };
45 |
46 | return (
47 | <>
48 | setQuestion(e.target.value)}
53 | onKeyDown={onEnterPress}
54 | onCompositionStart={startComposition}
55 | onCompositionEnd={endComposition}
56 | />
57 |
61 | >
62 |
63 | );
64 |
65 | }
--------------------------------------------------------------------------------
/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 | "location": {
9 | "value": "${AZURE_LOCATION}"
10 | },
11 | "principalId": {
12 | "value": "${AZURE_PRINCIPAL_ID}"
13 | },
14 | "resourceGroupName": {
15 | "value": "${AZURE_RESOURCE_GROUP}"
16 | },
17 | "webServiceName": {
18 | "value": "${AZURE_WEB_SERVICE_NAME}"
19 | },
20 | "webServiceLocation": {
21 | "value": "eastus2"
22 | },
23 | "apiServiceName": {
24 | "value": "${AZURE_API_SERVICE_NAME}"
25 | },
26 | "apiServiceLocation": {
27 | "value": "${AZURE_API_LOCATION}"
28 | },
29 | "storageServiceSkuName": {
30 | "value": "${AZURE_STORAGE_SERVICE_SKU}"
31 | },
32 | "storageServiceLocation": {
33 | "value": "${AZURE_STORAGE_SERVICE_LOCATION}"
34 | },
35 | "searchServiceName": {
36 | "value": "${AZURE_SEARCH_SERVICE_NAME}"
37 | },
38 | "searchServiceSkuName": {
39 | "value": "${AZURE_SEARCH_SERVICE_SKU}"
40 | },
41 | "searchServiceLocation": {
42 | "value": "${AZURE_SEARCH_SERVICE_LOCATION}"
43 | },
44 | "formRecognizerServiceName": {
45 | "value": "${AZURE_FORM_RECOGNIZER_SERVICE_NAME}"
46 | },
47 | "formRecognizerSkuName": {
48 | "value": "${AZURE_FORM_RECOGNIZER_SERVICE_SKU}"
49 | },
50 | "formRecognizerServiceLocation": {
51 | "value": "${AZURE_FORM_RECOGNIZER_SERVICE_LOCATION}"
52 | },
53 | "openAiServiceName": {
54 | "value": "${AZURE_OPENAI_SERVICE_NAME}"
55 | },
56 | "openAiModelName": {
57 | "value": "${AZURE_OPENAI_MODEL_NAME}"
58 | },
59 | "openAiSkuName": {
60 | "value": "${AZURE_OPENAI_SERVICE_SKU}"
61 | },
62 | "openAiServiceLocation": {
63 | "value": "${AZURE_OPENAI_SERVICE_LOCATION}"
64 | },
65 | "cognitiveServicesName": {
66 | "value": "${AZURE_COGNITIVE_SERVICES_NAME}"
67 | },
68 | "applicationInsightsServiceLocation": {
69 | "value": "${AZURE_APPLICATION_INSIGHTS_SERVICE_LOCATION}"
70 | },
71 | "cosmosDbLocation": {
72 | "value": "${AZURE_COSMOS_DB_LOCATION}"
73 | }
74 | }
75 | }
--------------------------------------------------------------------------------
/src/frontend/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/frontend/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useRef } from "react";
2 | import { ChatQuestion } from "./components/ChatQuestion/ChatQuestion";
3 | import { ChatAnswer } from "./components/ChatAnswer/ChatAnswer";
4 | import { ChatAnswerLoading } from "./components/ChatAnswerLoading/ChatAnswerLoading";
5 | import { ChatAnswerError } from "./components/ChatAnswerError/ChatAnswerError";
6 | import { InputQuestion } from "./components/InputQuestion/InputQuestion";
7 |
8 |
9 | import "./index.css";
10 |
11 | function App() {
12 | const [answers, setAnswers] = useState<{user: string, response: string}[]>([]);
13 |
14 | // 回答一覧の末尾にスクロールするための参照を定義する
15 | const chatMessageStreamEnd = useRef(null);
16 |
17 | // 回答生成中、つまりAPIからの返答を待っている状態かどうかを示すステートを定義する
18 | const [isLoading, setIsLoading] = useState(false);
19 |
20 | // 最後に送信された質問を保持するための参照を定義する
21 | const lastQuestionRef = useRef("");
22 |
23 | // エラーが発生したかどうかを示すステートを定義する
24 | const [isError, setIsError] = useState(false);
25 |
26 | // Azure OpenAI Serviceにリクエストを送る関数を定義する
27 | const makeApiRequest = async (question: string) => {
28 | // ユーザーからの質問が格納されているquestionを使ってAzure OpenAI Serviceにリクエストを送り、回答を取得する
29 |
30 | // 回答生成中の状態にする。これにより、送信ボタンが無効になる。
31 | setIsLoading(true);
32 |
33 | // エラーが発生している場合は、エラー状態を解除する
34 | setIsError(false);
35 |
36 | // 最後に送信された質問を保持する
37 | lastQuestionRef.current = question;
38 |
39 | // .env.productionにVITE_API_URLが定義されていれば、それを使ってAPIリクエストを送る。
40 | // 定義されていなければ空白とする。つまり、.env.productionにVITE_API_URLが定義されていない場合は、
41 | // ローカルの開発環境であると判断し、定義されている場合は本番環境であると判断する。
42 | const api_host = import.meta.env.VITE_API_ENDPOINT || "";
43 | const api_url = `${api_host}/api/GenerateAnswerWithAOAI`;
44 |
45 | // answersというステートには、ユーザーからの質問とそれに対する回答が格納されており、画面に表示されている内容を表している。
46 | // このanswersをAzure OpenAI Serviceに送信するためのリクエスト形式に変換して、historyという変数に格納する。
47 | const history: {user: string, assistant: string|undefined}[] = answers.map(a => ({ user: a.user, assistant: a.response }));
48 |
49 | // 新しい質問をhistoryに追加する。assistantはまだ回答が生成されていないのでundefinedとする。
50 | history.push({user: question, assistant: undefined});
51 |
52 | try {
53 | // Azure OpenAI Serviceにリクエストを送る
54 | const response = await fetch(api_url, {
55 | method: 'POST',
56 | headers: {
57 | 'Content-Type': 'application/json'
58 | },
59 | body: JSON.stringify(history)
60 | });
61 |
62 | // APIからのレスポンスをJSON形式で取得する
63 | const answer = await response.json();
64 |
65 | // 回答生成中の状態を解除する
66 | setIsLoading(false);
67 |
68 | // answersステートに新しい質問と回答を追加する。
69 | setAnswers([...answers, {user: question, response: answer.answer}]);
70 |
71 | } catch (error) {
72 | // エラーが発生した場合は、エラー状態を設定する
73 | setIsError(true);
74 |
75 | // 回答生成中の状態を解除する
76 | setIsLoading(false);
77 | }
78 |
79 | };
80 |
81 | // ページが読み込まれたときに、チャット画面の末尾にスクロールする
82 | useEffect(() => chatMessageStreamEnd.current?.scrollIntoView({ behavior: "smooth" }), [isLoading]);
83 |
84 | return (
85 | <>
86 |
87 |
88 | {answers.map((answer) => (
89 | <>
90 |
91 |
92 | >
93 | ))}
94 | {isLoading &&
95 | <>
96 |
97 |
98 | >
99 | }
100 | {isError &&
101 | <>
102 |
103 |
104 | >
105 | }
106 |
107 |
108 |
109 | makeApiRequest(question)} isLoading={isLoading}/>
110 |
111 | >
112 | );
113 | }
114 |
115 | export default App;
116 |
--------------------------------------------------------------------------------
/scripts/postprovision.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Azure Developer CLIによってazd provideコマンドが実行された後に実行されるスクリプト
4 |
5 | # Azuer Functionsをローカルで起動するための設定ファイルであるlocal.settings.jsonを作成する
6 | # まずは、azdコマンドを使って、Azure Functionsの設定値を取得する
7 | AOAI_ENDPOINT=$(azd env get-values 2>/dev/null | grep "^AOAI_ENDPOINT" | cut -d'=' -f2 | tr -d '"')
8 | SEARCH_SERVICE_ENDPOINT=$(azd env get-values 2>/dev/null | grep "^SEARCH_SERVICE_ENDPOINT" | cut -d'=' -f2 | tr -d '"')
9 | COSMOSDB_ENDPOINT=$(azd env get-values 2>/dev/null | grep "^COSMOSDB_ENDPOINT" | cut -d'=' -f2 | tr -d '"')
10 | COSMOSDB_DATABASE=$(azd env get-values 2>/dev/null | grep "^COSMOSDB_DATABASE" | cut -d'=' -f2 | tr -d '"')
11 | COSMOSDB_CONTAINER=$(azd env get-values 2>/dev/null | grep "^COSMOSDB_CONTAINER" | cut -d'=' -f2 | tr -d '"')
12 | AOAI_MODEL=$(azd env get-values 2>/dev/null | grep "^AOAI_MODEL" | cut -d'=' -f2 | tr -d '"')
13 | AOAI_GPT_35_TURBO_DEPLOYMENT=$(azd env get-values 2>/dev/null | grep "^AOAI_GPT_35_TURBO_DEPLOYMENT" | cut -d'=' -f2 | tr -d '"')
14 | AOAI_GPT_4_DEPLOYMENT=$(azd env get-values 2>/dev/null | grep "^AOAI_GPT_4_DEPLOYMENT" | cut -d'=' -f2 | tr -d '"')
15 | AOAI_GPT_4_32K_DEPLOYMENT=$(azd env get-values 2>/dev/null | grep "^AOAI_GPT_4_32K_DEPLOYMENT" | cut -d'=' -f2 | tr -d '"')
16 | AOAI_TEXT_EMBEDDING_ADA_002_DEPLOYMENT=$(azd env get-values 2>/dev/null | grep "^AOAI_TEXT_EMBEDDING_ADA_002_DEPLOYMENT" | cut -d'=' -f2 | tr -d '"')
17 | AOAI_API_VERSION=$(azd env get-values 2>/dev/null | grep "^AOAI_API_VERSION" | cut -d'=' -f2 | tr -d '"')
18 |
19 | # local.settings.jsonのテンプレートファイルを読み込み、先程取得した設定値で置換して、local.settings.jsonを作成する
20 | cat scripts/local.settings.json | \
21 | sed "s|\${AOAI_ENDPOINT}|$AOAI_ENDPOINT|g" | \
22 | sed "s|\${SEARCH_SERVICE_ENDPOINT}|$SEARCH_SERVICE_ENDPOINT|g" | \
23 | sed "s|\${COSMOSDB_ENDPOINT}|$COSMOSDB_ENDPOINT|g" | \
24 | sed "s|\${COSMOSDB_DATABASE}|$COSMOSDB_DATABASE|g" | \
25 | sed "s|\${COSMOSDB_CONTAINER}|$COSMOSDB_CONTAINER|g" | \
26 | sed "s|\${AOAI_MODEL}|$AOAI_MODEL|g" | \
27 | sed "s|\${AOAI_GPT_35_TURBO_DEPLOYMENT}|$AOAI_GPT_35_TURBO_DEPLOYMENT|g" | \
28 | sed "s|\${AOAI_GPT_4_DEPLOYMENT}|$AOAI_GPT_4_DEPLOYMENT|g" | \
29 | sed "s|\${AOAI_GPT_4_32K_DEPLOYMENT}|$AOAI_GPT_4_32K_DEPLOYMENT|g" | \
30 | sed "s|\${AOAI_TEXT_EMBEDDING_ADA_002_DEPLOYMENT}|$AOAI_TEXT_EMBEDDING_ADA_002_DEPLOYMENT|g" | \
31 | sed "s|\${AOAI_API_VERSION}|$AOAI_API_VERSION|g" \
32 | > ./src/backend/local.settings.json
33 |
34 | # このスクリプトで使う環境変数をexportし、さらにインデクサーで使う環境変数を.envファイルに書き出す
35 | echo "" > ./scripts/.env
36 | while IFS='=' read -r key value; do
37 | value=$(echo "$value" | sed 's/^"//' | sed 's/"$//')
38 | export "$key=$value" # このスクリプトで使う環境変数をexport
39 | echo "$key=$value" >> ./scripts/.env # インデクサーをローカル環境で使うための環境変数を.envファイルに書き出し
40 | done < func.HttpResponse:
113 | """
114 | HTTP POSTリクエストを受け取り、Azure OpenAIを用いて回答を生成する。
115 | """
116 |
117 | logging.info('Python HTTP trigger function processed a request.')
118 |
119 | # POSTリクエストから会話履歴が格納されたJSON配列を取得する。
120 | history = req.get_json()
121 |
122 | # [{question: "こんにちは。げんきですか?", answer: "元気です。"}, {question: "今日の天気は?", answer: "晴れです。"}...]というJSON配列から
123 | # 最も末尾に格納されているJSONオブジェクトのquestionを取得する。
124 | question = history[-1].get('user')
125 |
126 | # Azure AI Seacheにセマンティックハイブリッド検索を行い、回答を生成する。
127 | answer = semantic_hybrid_search(question, history)
128 |
129 | return func.HttpResponse(
130 | json.dumps({"answer": answer}),
131 | mimetype="application/json",
132 | status_code=200
133 | )
134 |
135 | def semantic_hybrid_search(query: str, history: list[dict]):
136 | """
137 | セマンティックサーチとハイブリッドサーチを組み合わせて回答を生成する。
138 | """
139 |
140 | # 利用するモデルからデプロイ名を取得する。
141 | gpt_deploy = gpt_models.get(gpt_model).get("deployment")
142 |
143 | # 利用するモデルの最大トークン数を取得する。
144 | max_tokens = gpt_models.get(gpt_model).get("max_tokens")*0.8 # ギリギリ際を攻めるとエラーが出るため、最大トークン数の80%のトークン数を指定する。
145 |
146 | # Azure OpenAI Serviceの埋め込み用APIを用いて、ユーザーからの質問をベクトル化する。
147 | # セマンティックハイブリッド検索に必要な「ベクトル化されたクエリ」「キーワード検索用クエリ」のうち、ベクトル化されたクエリを生成する。
148 | response = openai_client.embeddings.create(
149 | input = query,
150 | model = text_embedding_ada_002_deploy
151 | )
152 | vector_query = VectorizedQuery(vector=response.data[0].embedding, k_nearest_neighbors=3, fields="contentVector")
153 |
154 | # ユーザーからの質問を元に、Azure AI Searchに投げる検索クエリを生成する。
155 | # セマンティックハイブリッド検索に必要な「ベクトル化されたクエリ」「キーワード検索用クエリ」のうち、検索クエリを生成する。
156 | messages_for_search_query = []
157 |
158 | # 会話履歴の最後に、キーワード検索用クエリを生成するためのプロンプトを追加する。
159 | for h in history[:-1]:
160 | messages_for_search_query.append({"role": "user", "content": h["user"]})
161 | messages_for_search_query.append({"role": "assistant", "content": h["assistant"]})
162 | messages_for_search_query.append({"role": "user", "content": query_prompt_template.format(query=query)})
163 |
164 | messages_for_search_query = trim_messages(messages_for_search_query, max_tokens)
165 |
166 | response = openai_client.chat.completions.create(
167 | model=gpt_deploy,
168 | messages=messages_for_search_query
169 | )
170 | search_query = response.choices[0].message.content
171 |
172 | # 「ベクトル化されたクエリ」「キーワード検索用クエリ」を用いて、Azure AI Searchに対してセマンティックハイブリッド検索を行う。
173 | results = search_client.search(query_type='semantic', semantic_configuration_name='default',
174 | search_text=search_query,
175 | vector_queries=[vector_query],
176 | select=['id', 'content'], query_caption='extractive', query_answer="extractive", highlight_pre_tag='', highlight_post_tag='', top=2)
177 |
178 | # セマンティックアンサーを取得する。
179 | semantic_answers = results.get_answers()
180 |
181 | messages_for_semantic_answer = []
182 |
183 | messages_for_semantic_answer.append({"role": "system", "content": system_message_chat_conversation})
184 |
185 | # Azure OpenAI Serviceに回答を生成する際の会話履歴を生成する。まずは画面から渡された会話履歴を
186 | # Azure OpenAI Seriviceに渡す形式({"role": "XXX", "content": "XXX"})に変換する。
187 | for h in history[:-1]:
188 | messages_for_semantic_answer.append({"role": "user", "content": h["user"]})
189 | messages_for_semantic_answer.append({"role": "assistant", "content": h["assistant"]})
190 |
191 | # セマンティックアンサーの有無で返答を変える
192 | user_message = ""
193 | if len(semantic_answers) == 0:
194 | # Azure AI Searchがセマンティックアンサーを返さなかった場合は、
195 | # topで指定された複数のドキュメントを回答生成のための情報源として利用する。
196 | sources = ["[Source" + result["id"] + "]: " + result["content"] for result in results]
197 | source = "\n".join(sources)
198 |
199 | user_message = """
200 | {query}
201 |
202 | Sources:
203 | {source}
204 | """.format(query=query, source=source)
205 | else:
206 | # Azure AI Searchがセマンティックアンサーを返した場合は、それを回答生成のための情報源として利用する。
207 | user_message = """
208 | {query}
209 |
210 | Sources:
211 | {source}
212 | """.format(query=query, source=semantic_answers[0].text)
213 |
214 | messages_for_semantic_answer.append({"role": "user", "content": user_message})
215 |
216 | # Azure OpenAI ServiceのAPIの仕様で定められたトークン数の制限に基づき、
217 | # 指定されたトークン数に従って、会話履歴を古い順から削除する。
218 | messages_for_semantic_answer = trim_messages(messages_for_semantic_answer, max_tokens)
219 |
220 | # Azure OpenAI Serviceに回答生成を依頼する。
221 | response = openai_client.chat.completions.create(
222 | model=gpt_deploy,
223 | messages=messages_for_semantic_answer
224 | )
225 | response_text = response.choices[0].message.content
226 |
227 | # チャットログをCosmos DBに書き込む。
228 | write_chatlog("guest", query, response_text)
229 |
230 | return response_text
231 |
232 | def trim_messages(messages, max_tokens):
233 | """
234 | 会話履歴の合計のトークン数が最大トークン数を超えないように、古いメッセージから削除する。
235 | """
236 |
237 | # 利用するモデルからエンコーディングを取得する。
238 | encoding = gpt_models.get(gpt_model).get("encoding")
239 |
240 | # 各メッセージのトークン数を計算
241 | token_counts = [(message, len(encoding.encode(message["content"]))) for message in messages]
242 | total_tokens = sum(count for _, count in token_counts)
243 |
244 | # トークン数が最大トークン数を超えないように、古いメッセージから削除する
245 | # もし最大トークン数を超える場合は、systemメッセージ以外のメッセージを古い順から削除する。
246 | # この処理をトークン数が最大トークン数を下回るまで行う。
247 | while total_tokens > max_tokens:
248 | messages.pop(1)
249 | total_tokens -= token_counts.pop(1)[1]
250 | if total_tokens <= max_tokens:
251 | break
252 |
253 | return messages
254 |
255 | def write_chatlog(user_name: str, input: str, response: str):
256 | """
257 | チャットログをCosmos DBに書き込む。
258 | """
259 | properties = {
260 | "user" : user_name,
261 | "input" : input,
262 | "response" : response
263 | }
264 |
265 | container.create_item(body=properties, enable_automatic_id_generation=True)
--------------------------------------------------------------------------------
/infra/main.bicep:
--------------------------------------------------------------------------------
1 | targetScope = 'subscription'
2 |
3 | @minLength(1)
4 | @maxLength(64)
5 | @description('Name of the the environment which is used to generate a short unique hash used in all resources.')
6 | param environmentName string
7 |
8 | @minLength(1)
9 | @description('Primary location for all resources')
10 | param location string
11 |
12 | // 各リソースのリソース名を定義する。ここで何も定義されていなければ、リソース名は自動生成される。
13 | param resourceGroupName string = '' // ここで指定されたリソースグループ名に各リソースが所属する
14 | param webServiceName string = '' // フロントエンドのアプリをデプロイするAzure Static Web Appsの名前
15 | param webServiceLocation string = 'eastus2' // フロントエンドのアプリをデプロイするAzure Static Web Appsのリージョン(特に指定がなければazd upコマンド実施の際に指定されたリージョンとなる)
16 | //param webServiceLocation string = location
17 | param apiServiceName string = '' // バックエンドのAPIをデプロイするAzure Functionsの名前
18 | param apiServiceLocation string = '' // バックエンドのAPIをデプロイするAzure Functionsのリージョン(特に指定がなければazd upコマンド実施の際に指定されたリージョンとなる)
19 | param storageServiceSkuName string = '' // ストレージアカウントのSKU名(特に指定がなければStandard_LRSとなる)
20 | param searchServiceLocation string = '' // Azure AI Searchのリージョン(特に指定がなければazd upコマンド実施の際に指定されたリージョンとなる)
21 | param searchServiceName string = '' // Azure AI Searchのリソース名
22 | param searchServiceSkuName string = '' // Azure AI SearchのSKU名(特に指定がなければstandardとなる)
23 | param formRecognizerServiceName string = '' // Document Intelligenceのリソース名
24 | param formRecognizerSkuName string = '' // Document IntelligenceのSKU名(特に指定がなければS0となる)
25 | param formRecognizerLocation string = '' // Document Intelligenceのリージョン(特に指定がなければazd upコマンド実施の際に指定されたリージョンとなる)
26 | param openAiServiceName string = '' // Azure OpenAI Serviceのリソース名
27 | param openAiSkuName string = '' // Azure OpenAI ServiceのSKU名(特に指定がなければS0となる)
28 | param openAiServiceLocation string= '' // Azure OpenAI Serviceのリージョン(特に指定がなければazd upコマンド実施の際に指定されたリージョンとなる)
29 | param openAiModelName string = '' // Azure OpenAI Serviceのモデル名(特に指定がなければgpt-35-turboとなる)
30 | param openAiGpt35TurboDeploymentName string = 'gpt-35-turbo-deploy' // Azure OpenAI Serviceのgpt-35-turboのデプロイメント名
31 | param openAiGpt4DeploymentName string = 'gpt-4-deploy' // Azure OpenAI Serviceのgpt-4のデプロイメント名
32 | param openAiGpt432kDeploymentName string = 'gpt-4-32k-deploy' // Azure OpenAI Serviceのgpt-4-32kのデプロイメント名
33 | param openAiTextEmbeddingAda002DeploymentName string = 'text-embedding-ada-002-deploy' // Azure OpenAI Serviceのtext-embedding-ada-002のデプロイメント名
34 | param openAiApiVersion string = '2023-12-01-preview' // Azure OpenAI ServiceのAPIバージョン
35 | param applicationInsightsServiceLocation string = '' // Application Insightsのリージョン(特に指定がなければazd upコマンド実施の際に指定されたリージョンとなる)
36 | param cosmosDbDatabaseName string = 'ChatHistory' // Cosmos DBのデータベース名
37 | param cosmosDbContainerName string = 'Prompts' // Cosmos DBのコンテナ名
38 | param cosmosDbLocation string = '' // Cosmos DBのリージョン(特に指定がなければazd upコマンド実施の際に指定されたリージョンとなる)
39 |
40 | // az cliでログインした際のユーザーのプリンシパルIDを指定する。
41 | // ローカルで動かす場合に、az cliでログインしているユーザーに権限を与えるために利用する。
42 | param principalId string = ''
43 |
44 | // リソース名などに利用する一意の識別子を生成する。
45 | var resourceToken = toLower(uniqueString(subscription().id, environmentName, location))
46 |
47 | // タグを定義する。ここで定義されたタグは、すべてのリソースに付与される。
48 | var tags = { 'azd-env-name': environmentName }
49 |
50 | // Cloud Adoption Frameworkのリソース命名規則に準じた省略語を読み込む。
51 | var abbrs = loadJsonContent('./abbreviations.json')
52 |
53 | // リソースグループを作成する
54 | resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = {
55 | name: !empty(resourceGroupName) ? resourceGroupName : '${abbrs.resourcesResourceGroups}${environmentName}'
56 | location: location
57 | tags: tags
58 | }
59 |
60 |
61 | // RAGのフロントエンドアプリをデプロイするためのAzure Static Web Appsを作成する。
62 | module web './modules/app/stapp.bicep' = {
63 | name: 'web'
64 | scope: rg
65 | params: {
66 | name: !empty(webServiceName) ? webServiceName : '${abbrs.webStaticSites}${resourceToken}'
67 | location: !empty(webServiceLocation) ? webServiceLocation : location
68 | // このタグは、Azure Devloper CLIのazd-service-nameタグを使って、リソースを特定するために使われる。
69 | // キーがazd-service-name、値がwebというタグの付与されているリソースにフロントエンドのアプリがデプロイされる。
70 | tags: union(tags, { 'azd-service-name': 'web' })
71 | }
72 | }
73 |
74 | // RAGのバックエンドAPIをデプロイするためのAzure Functionsを作成する。
75 | module api './modules/app/func.bicep' = {
76 | name: 'api'
77 | scope: rg
78 | params: {
79 | name: !empty(apiServiceName) ? apiServiceName : '${abbrs.webSitesFunctions}${resourceToken}'
80 | location: !empty(apiServiceLocation) ? apiServiceLocation : location
81 | webServiceUri: web.outputs.uri
82 | appServicePlanId: appServicePlan.outputs.id
83 | // func.bicepでいろんなリソースのリソース情報を取得するために各リソースの名前を渡す。
84 | searchServiceName: searchService.outputs.name
85 | openAiServiceName: openAi.outputs.name
86 | openAiModelName: !empty(openAiModelName) ? openAiModelName : 'gpt-35-turbo'
87 | openAiApiVersion: openAiApiVersion
88 | openAiGpt35TurboDeploymentName: openAiGpt35TurboDeploymentName
89 | openAiGpt4DeploymentName: openAiGpt4DeploymentName
90 | openAiGpt432kDeploymentName: openAiGpt432kDeploymentName
91 | storageAccountName: storage.outputs.name
92 | cosmosDbAccountName: cosmosDb.outputs.name
93 | cosmosDbDatabaseName: cosmosDb.outputs.databaseName
94 | cosmosDbContainerName: cosmosDb.outputs.containerName
95 | applicationInsightsName: applicationInsights.outputs.name
96 | // このタグは、Azure Devloper CLIのazd-service-nameタグを使って、リソースを特定するために使われる。
97 | // キーがazd-service-name、値がapiというタグの付与されているリソースにバックエンドのAPIがデプロイされる。
98 | tags: union(tags, { 'azd-service-name': 'api' })
99 | }
100 | }
101 |
102 | // Azure FunctionsのApp Service Planを作成する。
103 | module appServicePlan './modules/app/asp.bicep' = {
104 | name: 'appServicePlan'
105 | scope: rg
106 | params: {
107 | name: '${abbrs.webServerFarms}${resourceToken}'
108 | location: !empty(apiServiceLocation) ? apiServiceLocation : location
109 |
110 | tags: tags
111 | }
112 | }
113 |
114 | // Azure Functionsに必要なストレージアカウントを作成する。
115 | module storage './modules/storage/storage.bicep' = {
116 | name: 'storage'
117 | scope: rg
118 | params: {
119 | name: '${abbrs.storageStorageAccounts}${resourceToken}'
120 | location: !empty(searchServiceLocation) ? searchServiceLocation : location
121 | sku: {
122 | // このパラメータは、ストレージアカウントのSKU名を指定する。
123 | // 特に指定がなければStandard_LRSとなる。
124 | name: !empty(storageServiceSkuName) ? storageServiceSkuName : 'Standard_LRS'
125 | }
126 | tags: tags
127 | }
128 | }
129 |
130 | // Azure Functionsのログ出力に必要なApplication Insightsを作成する。
131 | module applicationInsights './modules/monitor/appi.bicep' = {
132 | name: 'appinsights'
133 | scope: rg
134 | params: {
135 | name: '${abbrs.insightsComponents}${resourceToken}'
136 | location: !empty(applicationInsightsServiceLocation) ? applicationInsightsServiceLocation : location
137 | tags: tags
138 | }
139 | }
140 |
141 | // Azure AI Searchを作成する。
142 | module searchService './modules/ai/srch.bicep' = {
143 | name: 'search-service'
144 | scope: rg
145 | params: {
146 | name: !empty(searchServiceName) ? searchServiceName : '${abbrs.searchSearchServices}${resourceToken}'
147 | location: !empty(searchServiceLocation) ? searchServiceLocation : location
148 | tags: tags
149 | authOptions: {
150 | aadOrApiKey: {
151 | aadAuthFailureMode: 'http401WithBearerChallenge'
152 | }
153 | }
154 | sku: {
155 | // このパラメータは、Azure AI SearchのSKU名を指定する。
156 | // 特に指定がなければstandardとなる。
157 | name: !empty(searchServiceSkuName) ? searchServiceSkuName : 'standard'
158 | }
159 | semanticSearch: 'free'
160 | }
161 | }
162 |
163 | // Document Intelligenceを作成する。
164 | module documentIntelligence './modules/ai/cognitiveservices.bicep' = {
165 | name: 'documentintelligence'
166 | scope: rg
167 | params: {
168 | name: !empty(formRecognizerServiceName) ? formRecognizerServiceName : '${abbrs.cognitiveServicesFormRecognizer}${resourceToken}'
169 | kind: 'FormRecognizer'
170 | location: !empty(formRecognizerLocation) ? formRecognizerLocation : location
171 | tags: tags
172 | sku: {
173 | // このパラメータは、Document IntelligenceのSKU名を指定する。
174 | // 特に指定がなければS0となる。
175 | name: !empty(formRecognizerSkuName) ? formRecognizerSkuName : 'S0'
176 | }
177 | }
178 | }
179 |
180 | // Azure OpenAI Serviceを作成する。
181 | module openAi './modules/ai/cognitiveservices.bicep' = {
182 | name: 'openai'
183 | scope: rg
184 | params: {
185 | name: !empty(openAiServiceName) ? openAiServiceName : '${abbrs.cognitiveServicesAccounts}${resourceToken}'
186 | kind: 'OpenAI'
187 | location: !empty(openAiServiceLocation) ? openAiServiceLocation : location
188 | tags: tags
189 | sku: {
190 | // このパラメータは、Azure OpenAI ServiceのSKU名を指定する。
191 | // 特に指定がなければS0となる。
192 | name: !empty(openAiSkuName) ? openAiSkuName : 'S0'
193 | }
194 | // Azure OpenAI Serviceのモデルをデプロイする。現時点では最も汎用的なモデルであるgpt-35-turboをデプロイする。
195 | // また、ベクトル検索に利用するためのtext-embedding-ada-002もデプロイする。
196 | deployments: [
197 | {
198 | name: 'gpt-35-turbo-deploy'
199 | model: {
200 | format: 'OpenAI'
201 | name: 'gpt-35-turbo'
202 | version: '0613'
203 | }
204 | sku: {
205 | name: 'Standard'
206 | capacity: 80
207 | }
208 | }
209 | {
210 | name: 'gpt-4-deploy'
211 | model: {
212 | format: 'OpenAI'
213 | name: 'gpt-4'
214 | version: '0613'
215 | }
216 | sku: {
217 | name: 'Standard'
218 | capacity: 10
219 | }
220 | }
221 | {
222 | name: 'gpt-4-32k-deploy'
223 | model: {
224 | format: 'OpenAI'
225 | name: 'gpt-4-32k'
226 | version: '0613'
227 | }
228 | sku: {
229 | name: 'Standard'
230 | capacity: 20
231 | }
232 | }
233 | {
234 | name: 'text-embedding-ada-002-deploy'
235 | model: {
236 | format: 'OpenAI'
237 | name: 'text-embedding-ada-002'
238 | version: '2'
239 | }
240 | sku: {
241 | name: 'Standard'
242 | capacity: 80
243 | }
244 | }
245 | ]
246 | }
247 | }
248 |
249 | // Cosmos DBを作成する。
250 | module cosmosDb './modules/db/cosmosdb.bicep' = {
251 | name: 'cosmosdb'
252 | scope: rg
253 | params: {
254 | name: '${abbrs.documentDBDatabaseAccounts}${resourceToken}'
255 | location: !empty(cosmosDbLocation) ? cosmosDbLocation : location
256 | tags: union(tags, { 'azd-service-name': 'cosmosdb' })
257 | cosmosDbDatabaseName: cosmosDbDatabaseName
258 | cosmosDbContainerName: cosmosDbContainerName
259 | }
260 | }
261 |
262 | // ローカルで動かす場合に、az cliでログインしているユーザーに権限を与えるためのロールを定義する。
263 | // Azure OpenAI ServiceのAPIを発行するために、データプレーンへのアクセス権を与える。
264 | module openAiRoleUser './modules/security/role.bicep' = {
265 | scope: rg
266 | name: 'openai-role-user'
267 | params: {
268 | principalId: principalId
269 | roleDefinitionId: '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd'
270 | principalType: 'User'
271 | }
272 | }
273 |
274 | // ローカルで動かす場合に、az cliでログインしているユーザーに権限を与えるためのロールを定義する。
275 | // Azure AI Searchのインデックスを閲覧するために、データプレーンへのアクセス権を与える。
276 | module searchRoleUser './modules/security/role.bicep' = {
277 | scope: rg
278 | name: 'search-role-user'
279 | params: {
280 | principalId: principalId
281 | roleDefinitionId: '1407120a-92aa-4202-b7e9-c0e197c71c8f'
282 | principalType: 'User'
283 | }
284 | }
285 |
286 | // ローカルで動かす場合に、az cliでログインしているユーザーに権限を与えるためのロールを定義する。
287 | // Document IntelligenceのAPIを発行するために、データプレーンへのアクセス権を与える。
288 | module formRecognizerRoleUser './modules/security/role.bicep' = {
289 | scope: rg
290 | name: 'formrecognizer-role-user'
291 | params: {
292 | principalId: principalId
293 | roleDefinitionId: 'a97b65f3-24c7-4388-baec-2e87135dc908'
294 | principalType: 'User'
295 | }
296 | }
297 |
298 | // ローカルで動かす場合に、az cliでログインしているユーザーに権限を与えるためのロールを定義する。
299 | // Azure AI Searchにインデックスを登録するために、データプレーンへのアクセス権を与える。
300 | // この権限はインデクサーで利用する。
301 | module searchContribRoleUser './modules/security/role.bicep' = {
302 | scope: rg
303 | name: 'search-contrib-role-user'
304 | params: {
305 | principalId: principalId
306 | roleDefinitionId: '8ebe5a00-799e-43f5-93ac-243d3dce84a7'
307 | principalType: 'User'
308 | }
309 | }
310 |
311 | // Azure上で動かす場合に必要なマネージドIDに権限を与えるためのロールを定義する。
312 | // Azure OpenAI ServiceのAPIを発行するために、データプレーンへのアクセス権を与える。
313 | module openAiRoleBackend './modules/security/role.bicep' = {
314 | scope: rg
315 | name: 'openai-role-managed-identity'
316 | params: {
317 | principalId: api.outputs.identityPrincipalId
318 | roleDefinitionId: '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd'
319 | principalType: 'ServicePrincipal'
320 | }
321 | }
322 |
323 | // Azure上で動かす場合に必要なマネージドIDに権限を与えるためのロールを定義する。
324 | // Azure AI SearchのAPIを発行するために、データプレーンへのアクセス権を与える。
325 | module searchRoleBackend './modules/security/role.bicep' = {
326 | scope: rg
327 | name: 'search-role-managed-identity'
328 | params: {
329 | principalId: api.outputs.identityPrincipalId
330 | roleDefinitionId: '1407120a-92aa-4202-b7e9-c0e197c71c8f'
331 | principalType: 'ServicePrincipal'
332 | }
333 | }
334 |
335 | // 以降でoutputで定義される変数は環境変数として出力される。
336 |
337 | // フロントエンドのアプリをデプロイするときにAPIのエンドポイントを埋め込む必要がある。
338 | // そのため、APIのエンドポイントを出力する。
339 | var API_HOST = api.outputs.host
340 | output API_ENDPOINT string = 'https://${API_HOST}'
341 |
342 | // バックエンドのAPIやインデクサーで必要な各リソースのエンドポイントやデータベース名、コンテナ名などを出力する。
343 | output AOAI_ENDPOINT string = openAi.outputs.endpoint
344 | output SEARCH_SERVICE_ENDPOINT string = searchService.outputs.endpoint
345 | output COSMOSDB_ENDPOINT string = cosmosDb.outputs.endpoint
346 | output COSMOSDB_DATABASE string = cosmosDb.outputs.databaseName
347 | output COSMOSDB_CONTAINER string = cosmosDb.outputs.containerName
348 | output COSMOSDB_ACCOUNT string = cosmosDb.outputs.accountName
349 | output COSMOSDB_RESOURCE_GROUP string = rg.name
350 | output AOAI_MODEL string = !empty(openAiModelName) ? openAiModelName : 'gpt-4'
351 | output AOAI_GPT_35_TURBO_DEPLOYMENT string = openAiGpt35TurboDeploymentName
352 | output AOAI_GPT_4_DEPLOYMENT string = openAiGpt4DeploymentName
353 | output AOAI_GPT_4_32K_DEPLOYMENT string = openAiGpt432kDeploymentName
354 | output AOAI_TEXT_EMBEDDING_ADA_002_DEPLOYMENT string = openAiTextEmbeddingAda002DeploymentName
355 | output AOAI_API_VERSION string = openAiApiVersion
356 | output DOCUMENT_INTELLIGENCE_ENDPOINT string = documentIntelligence.outputs.endpoint
357 |
358 | // Azure Cosmos DBにAzure Functionsがアクセスするためのカスタムロールの作成が必要になる。
359 | // その作成したカスタムロールの権限の付与先となるマネージドIDを出力する。
360 | output BACKEND_IDENTITY_PRINCIPAL_ID string = api.outputs.identityPrincipalId
361 |
--------------------------------------------------------------------------------