├── .azdo ├── azure-pipeline.yaml └── pr-ci.yaml ├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── LICENSE.md ├── README.ja.md ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── app ├── backend │ ├── .env.sample │ ├── app.py │ ├── approaches │ │ ├── approach.py │ │ └── chatreadretrieveread.py │ ├── chat_log │ │ ├── cosmosdb_logging.py │ │ └── temp_dev.py │ ├── requirements.txt │ ├── static │ │ ├── assets │ │ │ ├── github-8c68b25d.svg │ │ │ ├── index-51849b94.css │ │ │ ├── index-5316f52c.js │ │ │ └── index-5316f52c.js.map │ │ ├── favicon.svg │ │ └── index.html │ └── text.py └── frontend │ ├── .env.sample │ ├── .prettierrc.json │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── public │ └── favicon.svg │ ├── src │ ├── Avatar.tsx │ ├── Bar.tsx │ ├── GraphService.ts │ ├── aadConfig.ts │ ├── api │ │ ├── api.ts │ │ ├── index.ts │ │ └── models.ts │ ├── assets │ │ └── github.svg │ ├── components │ │ ├── Answer │ │ │ ├── Answer.module.css │ │ │ ├── Answer.tsx │ │ │ ├── AnswerError.tsx │ │ │ ├── AnswerIcon.tsx │ │ │ ├── AnswerLoading.tsx │ │ │ ├── AnswerParser.tsx │ │ │ └── index.ts │ │ ├── ClearChatButton │ │ │ ├── ClearChatButton.module.css │ │ │ ├── ClearChatButton.tsx │ │ │ └── index.tsx │ │ ├── QuestionInput │ │ │ ├── QuestionInput.module.css │ │ │ ├── QuestionInput.tsx │ │ │ └── index.ts │ │ └── UserChatMessage │ │ │ ├── UserChatMessage.module.css │ │ │ ├── UserChatMessage.tsx │ │ │ ├── UserChatMessageIcon.tsx │ │ │ └── index.ts │ ├── context │ │ └── AppContext.tsx │ ├── index.css │ ├── index.tsx │ ├── pages │ │ ├── NoPage.tsx │ │ ├── chat │ │ │ ├── Chat.module.css │ │ │ └── Chat.tsx │ │ └── layout │ │ │ ├── Layout.module.css │ │ │ └── Layout.tsx │ ├── requirements.txt │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── vite.config.ts │ └── yarn.lock └── docs ├── en ├── .gitkeep └── logging_cosmosdb.md ├── images ├── appcomponents.png ├── chatscreen.png └── network-control.png └── jp ├── ai-security-draft.md └── openai-ai-scale.md /.azdo/azure-pipeline.yaml: -------------------------------------------------------------------------------- 1 | # Starter pipeline 2 | # Start with a minimal pipeline that you can customize to build and deploy your code. 3 | # Add steps that build, run tests, deploy, and more: 4 | # https://aka.ms/yaml 5 | 6 | trigger: 7 | - main 8 | 9 | pool: 10 | vmImage: ubuntu-latest 11 | 12 | steps: 13 | - script: echo Hello, world! 14 | displayName: 'Run a one-line script' 15 | 16 | - script: | 17 | echo Add other tasks to build, test, and deploy your project. 18 | echo See https://aka.ms/yaml 19 | displayName: 'Run a multi-line script' 20 | -------------------------------------------------------------------------------- /.azdo/pr-ci.yaml: -------------------------------------------------------------------------------- 1 | trigger: 2 | branches: 3 | include: 4 | - '*' 5 | paths: 6 | exclude: 7 | - 'README.md' 8 | - 'docs/*' 9 | 10 | pr: 11 | branches: 12 | include: 13 | - main 14 | 15 | jobs: 16 | - job: PrepEnv 17 | displayName: 'install dependency' 18 | - job: Build 19 | displayName: 'Build Job' 20 | steps: 21 | - task: Bash@3 22 | displayName: 'build frontend' 23 | inputs: 24 | targetType: 'inline' 25 | script: | 26 | ls 27 | pwd 28 | cd app/frontend/src 29 | npm install 30 | npm run build 31 | - task: Bash@3 32 | displayName: 'build backend' 33 | inputs: 34 | targetType: 'inline' 35 | script: | 36 | cd app/backend 37 | python -m venv .venv 38 | source .venv/bin/activate 39 | pip install -r requirements.txt 40 | python app.py & 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # Install Python 2 | # [Choice] Python version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.10, 3.9, 3.8, 3.7, 3.6, 3-bullseye, 3.10-bullseye, 3.9-bullseye, 3.8-bullseye, 3.7-bullseye, 3.6-bullseye, 3-buster, 3.10-buster, 3.9-buster, 3.8-buster, 3.7-buster, 3.6-buster 3 | ARG VARIANT=3.10-bullseye 4 | FROM mcr.microsoft.com/vscode/devcontainers/python:${VARIANT} 5 | 6 | # Install Node.js 7 | # [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 8 | ARG NODE_VERSION="16" 9 | RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi 10 | 11 | # Install Azure CLI 12 | RUN curl -sL https://aka.ms/InstallAzureCLIDeb | bash 13 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "OpenAI at Scale", 3 | "build": { 4 | "dockerfile": "Dockerfile", 5 | "context": "..", 6 | "args": { 7 | "VARIANT": "3.10-bullseye", 8 | // Options 9 | "NODE_VERSION": "16" 10 | } 11 | }, 12 | 13 | // Configure tool-specific properties. 14 | "customizations": { 15 | // Configure properties specific to VS Code. 16 | "vscode": { 17 | // Set *default* container specific settings.json values on container create. 18 | "settings": { 19 | "python.defaultInterpreterPath": "/usr/local/bin/python", 20 | "python.linting.enabled": true, 21 | "python.linting.pylintEnabled": true, 22 | "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", 23 | "python.formatting.blackPath": "/usr/local/py-utils/bin/black", 24 | "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", 25 | "python.linting.banditPath": "/usr/local/py-utils/bin/bandit", 26 | "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", 27 | "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", 28 | "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", 29 | "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", 30 | "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint" 31 | }, 32 | 33 | // Add the IDs of extensions you want installed when the container is created. 34 | "extensions": [ 35 | "ms-python.python", 36 | "ms-python.vscode-pylance", 37 | "dbaeumer.vscode-eslint" 38 | ] 39 | } 40 | }, 41 | 42 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 43 | // "forwardPorts": [], 44 | 45 | // Use 'postCreateCommand' to run commands after the container is created. 46 | // "postCreateCommand": "pip3 install --user -r requirements.txt", 47 | 48 | // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 49 | "remoteUser": "vscode" 50 | } -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | > Please provide us with the following information: 5 | > --------------------------------------------------------------- 6 | 7 | ### This issue is for a: (mark with an `x`) 8 | ``` 9 | - [ ] bug report -> please search issues before submitting 10 | - [ ] feature request 11 | - [ ] documentation issue or request 12 | - [ ] regression (a behavior that used to work and stopped in a new release) 13 | ``` 14 | 15 | ### Minimal steps to reproduce 16 | > 17 | 18 | ### Any log messages given by the failure 19 | > 20 | 21 | ### Expected/desired behavior 22 | > 23 | 24 | ### OS and Version? 25 | > Windows 7, 8 or 10. Linux (which distribution). macOS (Yosemite? El Capitan? Sierra?) 26 | 27 | ### Versions 28 | > 29 | 30 | ### Mention any other details that might be useful 31 | 32 | > --------------------------------------------------------------- 33 | > Thanks! We'll be in touch soon. 34 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Purpose 2 | 3 | * ... 4 | 5 | ## Does this introduce a breaking change? 6 | 7 | ``` 8 | [ ] Yes 9 | [ ] No 10 | ``` 11 | 12 | ## Pull Request Type 13 | What kind of change does this Pull Request introduce? 14 | 15 | 16 | ``` 17 | [ ] Bugfix 18 | [ ] Feature 19 | [ ] Code style update (formatting, local variables) 20 | [ ] Refactoring (no functional changes, no api changes) 21 | [ ] Documentation content changes 22 | [ ] Other... Please describe: 23 | ``` 24 | 25 | ## How to Test 26 | * Get the code 27 | 28 | ``` 29 | git clone [repo-address] 30 | cd [repo-name] 31 | git checkout [branch-name] 32 | npm install 33 | ``` 34 | 35 | * Test the code 36 | 37 | ``` 38 | ``` 39 | 40 | ## What to Check 41 | Verify that the following are valid 42 | * ... 43 | 44 | ## Other Information 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | 132 | #-------------------------------------- 133 | # Node related ignore conf 134 | #-------------------------------------- 135 | # Logs 136 | logs 137 | *.log 138 | npm-debug.log* 139 | yarn-debug.log* 140 | yarn-error.log* 141 | lerna-debug.log* 142 | .pnpm-debug.log* 143 | 144 | # Diagnostic reports (https://nodejs.org/api/report.html) 145 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 146 | 147 | # Runtime data 148 | pids 149 | *.pid 150 | *.seed 151 | *.pid.lock 152 | 153 | # Directory for instrumented libs generated by jscoverage/JSCover 154 | lib-cov 155 | 156 | # Coverage directory used by tools like istanbul 157 | coverage 158 | *.lcov 159 | 160 | # nyc test coverage 161 | .nyc_output 162 | 163 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 164 | .grunt 165 | 166 | # Bower dependency directory (https://bower.io/) 167 | bower_components 168 | 169 | # node-waf configuration 170 | .lock-wscript 171 | 172 | # Compiled binary addons (https://nodejs.org/api/addons.html) 173 | build/Release 174 | 175 | # Dependency directories 176 | node_modules/ 177 | jspm_packages/ 178 | 179 | # Snowpack dependency directory (https://snowpack.dev/) 180 | web_modules/ 181 | 182 | # TypeScript cache 183 | *.tsbuildinfo 184 | 185 | # Optional npm cache directory 186 | .npm 187 | 188 | # Optional eslint cache 189 | .eslintcache 190 | 191 | # Optional stylelint cache 192 | .stylelintcache 193 | 194 | # Microbundle cache 195 | .rpt2_cache/ 196 | .rts2_cache_cjs/ 197 | .rts2_cache_es/ 198 | .rts2_cache_umd/ 199 | 200 | # Optional REPL history 201 | .node_repl_history 202 | 203 | # Output of 'npm pack' 204 | *.tgz 205 | 206 | # Yarn Integrity file 207 | .yarn-integrity 208 | 209 | # dotenv environment variable files 210 | .env 211 | .env.development.local 212 | .env.test.local 213 | .env.production.local 214 | .env.local 215 | 216 | # parcel-bundler cache (https://parceljs.org/) 217 | .cache 218 | .parcel-cache 219 | 220 | # Next.js build output 221 | .next 222 | out 223 | 224 | # Nuxt.js build / generate output 225 | .nuxt 226 | dist 227 | 228 | # Gatsby files 229 | .cache/ 230 | # Comment in the public line in if your project uses Gatsby and not Next.js 231 | # https://nextjs.org/blog/next-9-1#public-directory-support 232 | # public 233 | 234 | # vuepress build output 235 | .vuepress/dist 236 | 237 | # vuepress v2.x temp and cache directory 238 | .temp 239 | .cache 240 | 241 | # Docusaurus cache and generated files 242 | .docusaurus 243 | 244 | # Serverless directories 245 | .serverless/ 246 | 247 | # FuseBox cache 248 | .fusebox/ 249 | 250 | # DynamoDB Local files 251 | .dynamodb/ 252 | 253 | # TernJS port file 254 | .tern-port 255 | 256 | # Stores VSCode versions used for testing VSCode extensions 257 | .vscode-test 258 | 259 | # yarn v2 260 | .yarn/cache 261 | .yarn/unplugged 262 | .yarn/build-state.yml 263 | .yarn/install-state.gz 264 | .pnp.* 265 | 266 | 267 | #-------------------------------------- 268 | # Node related ignore conf 269 | #-------------------------------------- 270 | # Logs 271 | logs 272 | *.log 273 | npm-debug.log* 274 | yarn-debug.log* 275 | yarn-error.log* 276 | lerna-debug.log* 277 | .pnpm-debug.log* 278 | 279 | # Diagnostic reports (https://nodejs.org/api/report.html) 280 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 281 | 282 | # Runtime data 283 | pids 284 | *.pid 285 | *.seed 286 | *.pid.lock 287 | 288 | # Directory for instrumented libs generated by jscoverage/JSCover 289 | lib-cov 290 | 291 | # Coverage directory used by tools like istanbul 292 | coverage 293 | *.lcov 294 | 295 | # nyc test coverage 296 | .nyc_output 297 | 298 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 299 | .grunt 300 | 301 | # Bower dependency directory (https://bower.io/) 302 | bower_components 303 | 304 | # node-waf configuration 305 | .lock-wscript 306 | 307 | # Compiled binary addons (https://nodejs.org/api/addons.html) 308 | build/Release 309 | 310 | # Dependency directories 311 | node_modules/ 312 | jspm_packages/ 313 | 314 | # Snowpack dependency directory (https://snowpack.dev/) 315 | web_modules/ 316 | 317 | # TypeScript cache 318 | *.tsbuildinfo 319 | 320 | # Optional npm cache directory 321 | .npm 322 | 323 | # Optional eslint cache 324 | .eslintcache 325 | 326 | # Optional stylelint cache 327 | .stylelintcache 328 | 329 | # Microbundle cache 330 | .rpt2_cache/ 331 | .rts2_cache_cjs/ 332 | .rts2_cache_es/ 333 | .rts2_cache_umd/ 334 | 335 | # Optional REPL history 336 | .node_repl_history 337 | 338 | # Output of 'npm pack' 339 | *.tgz 340 | 341 | # Yarn Integrity file 342 | .yarn-integrity 343 | 344 | # dotenv environment variable files 345 | .env 346 | .env.development.local 347 | .env.test.local 348 | .env.production.local 349 | .env.local 350 | 351 | # parcel-bundler cache (https://parceljs.org/) 352 | .cache 353 | .parcel-cache 354 | 355 | # Next.js build output 356 | .next 357 | out 358 | 359 | # Nuxt.js build / generate output 360 | .nuxt 361 | dist 362 | 363 | # Gatsby files 364 | .cache/ 365 | # Comment in the public line in if your project uses Gatsby and not Next.js 366 | # https://nextjs.org/blog/next-9-1#public-directory-support 367 | # public 368 | 369 | # vuepress build output 370 | .vuepress/dist 371 | 372 | # vuepress v2.x temp and cache directory 373 | .temp 374 | .cache 375 | 376 | # Docusaurus cache and generated files 377 | .docusaurus 378 | 379 | # Serverless directories 380 | .serverless/ 381 | 382 | # FuseBox cache 383 | .fusebox/ 384 | 385 | # DynamoDB Local files 386 | .dynamodb/ 387 | 388 | # TernJS port file 389 | .tern-port 390 | 391 | # Stores VSCode versions used for testing VSCode extensions 392 | .vscode-test 393 | 394 | # yarn v2 395 | .yarn/cache 396 | .yarn/unplugged 397 | .yarn/build-state.yml 398 | .yarn/install-state.gz 399 | .pnp.* 400 | 401 | .azure/* 402 | .azure 403 | 404 | 405 | # Azure az webapp deployment details 406 | .azure 407 | *_env 408 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to OpenAI at Scale 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 4 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 5 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 6 | 7 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 8 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 9 | provided by the bot. You will only need to do this once across all repos using our CLA. 10 | 11 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 12 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 13 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 14 | 15 | - [Code of Conduct](#coc) 16 | - [Issues and Bugs](#issue) 17 | - [Feature Requests](#feature) 18 | - [Submission Guidelines](#submit) 19 | 20 | ## Code of Conduct 21 | Help us keep this project open and inclusive. Please read and follow our [Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 22 | 23 | ## Found an Issue? 24 | If you find a bug in the source code or a mistake in the documentation, you can help us by 25 | [submitting an issue](#submit-issue) to the GitHub Repository. Even better, you can 26 | [submit a Pull Request](#submit-pr) with a fix. 27 | 28 | ## Want a Feature? 29 | You can *request* a new feature by [submitting an issue](#submit-issue) to the GitHub 30 | Repository. If you would like to *implement* a new feature, please submit an issue with 31 | a proposal for your work first, to be sure that we can use it. 32 | 33 | * **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). 34 | 35 | ## Submission Guidelines 36 | 37 | ### Submitting an Issue 38 | Before you submit an issue, search the archive, maybe your question was already answered. 39 | 40 | If your issue appears to be a bug, and hasn't been reported, open a new issue. 41 | Help us to maximize the effort we can spend fixing issues and adding new 42 | features, by not reporting duplicate issues. Providing the following information will increase the 43 | chances of your issue being dealt with quickly: 44 | 45 | * **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps 46 | * **Version** - what version is affected (e.g. 0.1.2) 47 | * **Motivation for or Use Case** - explain what are you trying to do and why the current behavior is a bug for you 48 | * **Browsers and Operating System** - is this a problem with all browsers? 49 | * **Reproduce the Error** - provide a live example or a unambiguous set of steps 50 | * **Related Issues** - has a similar issue been reported before? 51 | * **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be 52 | causing the problem (line of code or commit) 53 | 54 | You can file new issues by providing the above information at the corresponding repository's issues link: https://github.com/Azure/openai-at-scale/issues/new. 55 | 56 | ### Submitting a Pull Request (PR) 57 | Before you submit your Pull Request (PR) consider the following guidelines: 58 | 59 | * Search the repository (https://github.com/Azure/openai-at-scale/pulls) for an open or closed PR 60 | that relates to your submission. You don't want to duplicate effort. 61 | 62 | * Make your changes in a new git fork: 63 | 64 | * Commit your changes using a descriptive commit message 65 | * Push your fork to GitHub: 66 | * In GitHub, create a pull request 67 | * If we suggest changes then: 68 | * Make the required updates. 69 | * Rebase your fork and force push to your GitHub repository (this will update your Pull Request): 70 | 71 | ```shell 72 | git rebase master -i 73 | git push -f 74 | ``` 75 | 76 | That's it! Thank you for your contribution! 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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 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 22 | -------------------------------------------------------------------------------- /README.ja.md: -------------------------------------------------------------------------------- 1 | # OpenAI at Scale 2 | 3 | [![Open in GitHub Codespaces](https://img.shields.io/static/v1?style=for-the-badge&label=GitHub+Codespaces&message=Open&color=brightgreen&logo=github)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=622759641&machine=standardLinux32gb&devcontainer_path=.devcontainer%2Fdevcontainer.json&location=WestUs2&skip_quickstart=true&geo=SoutheastAsia) 4 | [![Open in Remote - Containers](https://img.shields.io/static/v1?style=for-the-badge&label=Remote%20-%20Containers&message=Open&color=blue&logo=visualstudiocode)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/Azure/openai-at-scale) 5 | 6 | 🌏 [English](README.md) | 日本語 7 | 8 | ✨ **OpenAI at Scale** は Microsoft 社の FastTrack for Azure チームによるワークショップです。Azure 上にシンプルは GhatGPT のインターフェースを持つアプリケーションをビルドしデプロイします。 9 | 10 | 11 | 12 | 13 | 14 | ## 🚀 ハンズオンセッション 15 | 16 | ハンズオンセッションの資料はこちらです。
17 | 👉 [セッション資料](./docs/jp/openai-ai-scale.md) 📖 18 | 19 | 20 | ## 🎯 機能 21 | 22 | - チャットインタフェース 23 | - システムプロンプトとハイパーパラメータの設定 24 | - Azure Active Directory による認証と Microsoft Graph からのユーザー情報の取得 25 | - Azure Log Analytics によるアプリケーションログの取得 26 | - プロンプトログの Azure Cosmos DB への格納 27 | 28 | 29 | 30 | 31 | ## 🙋🏾‍♂️ 質問やフィードバック 32 | 33 | [GitHub Issues](https://github.com/Azure/openai-at-scale/issues) でこのリポジトリに関する質問やフィードバックをお寄せください。 34 | 35 | 36 | ## 📚 参考情報 37 | 38 | - [ChatGPT + Enterprise data with Azure OpenAI and Cognitive Search](https://github.com/Azure-Samples/azure-search-openai-demo) 39 | - This repo is based on this sample code. 40 | 41 | ## 🤝 貢献 42 | 43 | お客様や Microsoft 社員からの貢献を歓迎しています。[CONTRIBUTING](./CONTRIBUTING.md) を参照してください。このリポジトリの発展に貢献いただく全ての方に感謝します! 44 | 45 | 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenAI at Scale 2 | 3 | [![Open in GitHub Codespaces](https://img.shields.io/static/v1?style=for-the-badge&label=GitHub+Codespaces&message=Open&color=brightgreen&logo=github)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=622759641&machine=standardLinux32gb&devcontainer_path=.devcontainer%2Fdevcontainer.json&location=WestUs2&skip_quickstart=true&geo=SoutheastAsia) 4 | [![Open in Remote - Containers](https://img.shields.io/static/v1?style=for-the-badge&label=Remote%20-%20Containers&message=Open&color=blue&logo=visualstudiocode)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/Azure/openai-at-scale) 5 | 6 | 🌏 English | [日本語](README.ja.md) 7 | 8 | ✨ **OpenAI at Scale** is a workshop by **FastTrack for Azure in Microsoft** team that helps customers to build and deploy simple ChatGPT UI application on Azure. 9 | 10 | 11 | 12 | --- 13 | 14 | ## 🎯 Features 15 | 16 | - Chat UI 17 | - Configure system prompts and hyperparameters 18 | - Authenticate with Azure Active Directory and get user information from Microsoft Graph 19 | - Collect application logs with Azure Log Analytics 20 | - Store prompt log data to Azure Cosmos DB 21 | 22 | 23 | 24 | --- 25 | 26 | ## 🚀 Getting Started 27 | 28 | ### ⚒️ Prerequisites 29 | 30 | #### To run locally 31 | 32 | - OS - Windows 11, MacOS or Linux 33 | > ⚠ For Windows client user, please use Ubuntu 20.04 LTS (Windows subsystem for Linux) to run this application.
34 | > ⚠ GitHub Codespaces is supported as Linux envionment. 35 | 36 | - [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) (v4.28.1 or higher) 37 | - [Node.js](https://nodejs.org/en/download/) (v16.20 or higher) 38 | - [Python](https://www.python.org/downloads/) (v3.9 or higher) 39 | - [git client](https://git-scm.com/downloads) 40 | - [Docker Desktop](https://www.docker.com/products/docker-desktop) or any other Docker environment 41 | - Docker is used for Visual Studio Code Dev Container. 42 | 43 | 44 | #### To run on Azure 45 | 46 | - Azure subscription 47 | - Resources 48 | - Azure OpenAI Service 49 | - Azure Active Directory application 50 | - Azure Log Analytics 51 | - (Optional) Azure Cosmos DB 52 | > ⚠ Free account is not supported 53 | - Role 54 | - Contributor role or higher for Azure subscription 55 | - Permission to create Azure Active Directory application 56 | - Permission to create Azure OpenAI Service 57 | 58 |
59 | 60 | ### 1. Creating Azure OpenAI Service 🧠 61 | 62 | There are some ways to create Azure OpenAI Service, we recommend to use Azure Portal if you are not familiar with Azure CLI. 63 | 64 | Before started you need to choose a location for your service. You can find the list of available locations here -> supported location is here : [Supported locations](https://azure.microsoft.com/en-us/explore/global-infrastructure/products-by-region/?regions=all&products=cognitive-services) 65 | 66 | - **Azure Portal** : 67 | You can follow the [official document](https://learn.microsoft.com/en-us/azure/cognitive-services/openai/how-to/create-resource?pivots=web-portal) to create Azure OpenAI Service. 68 | - **Azure CLI** : You can use the following command to create Azure OpenAI Service. 69 | 70 |
command example
71 | 72 | ```shell 73 | # Set environment variables 74 | export SUBSCRIPTION_ID= 75 | export RESOURCE_GROUP= 76 | export LOCATION=eastus #hard coded to avoid confusion, you can change any avaiable location. 77 | export AZURE_OPENAI_SERVICE= 78 | export AZURE_OPENAI_CHATGPT_DEPLOYMENT= 79 | ``` 80 | 81 | ```shell 82 | az login #check your subscription id 83 | az account set --subscription $SUBSCRIPTION_ID 84 | az group create --name $RESOURCE_GROUP --location $LOCATION 85 | az cognitiveservices account create \ 86 | --name $AZURE_OPENAI_SERVICE \ 87 | --kind OpenAI \ 88 | --sku S0 \ 89 | --resource-group $RESOURCE_GROUP \ 90 | --location $LOCATION \ 91 | --yes 92 | az cognitiveservices account deployment create \ 93 | -g $RESOURCE_GROUP \ 94 | -n $AZURE_OPENAI_SERVICE \ 95 | --deployment-name $AZURE_OPENAI_CHATGPT_DEPLOYMENT \ 96 | --model-name gpt-35-turbo \ 97 | --model-version "0301" \ 98 | --model-format OpenAI \ 99 | --scale-settings-scale-type "Standard" 100 | ``` 101 | 102 |
103 | 104 |
105 | 106 | ### 2. Creating Azure Active Directory application 🔑 107 | 108 | Follow the steps in [register your application](https://learn.microsoft.com/azure/active-directory/develop/quickstart-register-app) to register your application. 109 | 110 | - Select **`Single-page application (SPA)`** as platform type 111 | - The Redirect URI will be **`http://localhost:5000`** and **`http://localhost:5173`** for local development 112 | - Keep the **`Application (client) ID`** and **`Directory (tenant) ID`** for later use 113 | 114 |
115 | 116 | ### (Optional) 3. Creating Azure Cosmos DB 🪐 117 | 118 | You can create a Azure Cosmos DB account by following instructions on the Azure docs [here](https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/quickstart-portal) and please make sure you enable Analytical Store, more details can be found [here](https://learn.microsoft.com/en-us/azure/cosmos-db/analytical-store-introduction). 119 | 120 | - Select **`Core (SQL)`** as API 121 | - Container name will be **`chat_log`** and partition key will be **`/chat_session_id`** 122 | 123 |
124 | 125 | ### 4. Deploying to local environment 💻 126 | 127 | 128 | #### Environment variables 129 | 130 | You need to create `.env` files from `.env.sample` to set up your environment variables before you spin up your application. 131 | 132 | - `app/frontend/.env` 133 | - This file will be used for authentication function by Azure Active Directory SDK. 134 | 135 | ```shell 136 | # Azure Active Directory application 137 | VITE_CLIENTID="" 138 | VITE_TENANTID="" 139 | ``` 140 | 141 | - `app/backend/.env` 142 | - This file will be used for accessing to Azure OpenAI Service and Azure Cosmos DB. 143 | 144 | ```shell 145 | # Azure OpenAI Service 146 | AZURE_OPENAI_SERVICE="" 147 | OPENAI_API_KEY="" 148 | AZURE_OPENAI_CHATGPT_DEPLOYMENT="" 149 | 150 | 151 | # (Optional) Azure Cosmos DB 152 | AZURE_COSMOSDB_ENDPOINT="https://.documents.azure.com:443/" 153 | AZURE_COSMOSDB_KEY="" 154 | AZURE_COSMOSDB_DB="< your Azure Cosmos DB database name>" 155 | ``` 156 | > ⚠ Please use [Azure Key Vault](https://azure.microsoft.com/en-US/products/key-vault) to configure environment variables in production environments. 157 | 158 | 159 |
command examples to get environment variables from Azure CLI.
160 | 161 | ```shell 162 | export RESOURCE_GROUP= 163 | export AZURE_OPENAI_SERVICE= 164 | export AZURE_OPENAI_CHATGPT_DEPLOYMENT= 165 | export OPENAI_API_KEY=`az cognitiveservices account keys list \ 166 | -n $AZURE_OPENAI_SERVICE \ 167 | -g $RESOURCE_GROUP \ 168 | -o json \ 169 | | jq -r .key1` 170 | ``` 171 | 172 |
173 | 174 | #### Python environment 175 | 176 | Python is required to run the backend Flask application. 177 | 178 | ##### Install Python libraries 179 | 180 | ```shell 181 | cd app/backend 182 | python -m venv ./backend_env 183 | source .backend_env/bin/activate #bash 184 | pip install -r requirements.txt 185 | ``` 186 | 187 | ##### Start Backend (Flask) 188 | 189 | ```shell 190 | cd app/backend 191 | flask run --debug #hot reload 192 | #python ./app.py 193 | ``` 194 | 195 | #### Node.js environment 196 | 197 | Node.js is required to run the frontend React application. 198 | 199 | ##### Install Node.js packages 200 | 201 | ```shell 202 | cd app/frontend 203 | npm install 204 | ``` 205 | 206 | ##### Start Frontend (React) 207 | For development
208 | 209 | ```shell 210 | npm run dev 211 | ``` 212 | 213 | For production
214 | 215 | ```shell 216 | npm run build 217 | ``` 218 | > It is used to optimize and reduce the size of all application files which are deployed in app/backend/static folder.
219 | 220 |
221 | 222 | ### 5. Deploying to Azure ☁️ 223 | 224 | #### Deploy to Azure App Service 225 | 226 | > ⚠ Before you run following command, you must run `npm run build` on app/frontend to set frontend files to backend static dir. 227 | 228 | - Example of Azure App Service 229 | - Deploy app to Azure App Service with easist way. 230 | 231 | ```shell 232 | cd app/backend 233 | az webapp up --runtime "python:3.10" --sku B1 -g 234 | ``` 235 | 236 | - Deploy Azure App Service Plan and Web App separately. 237 | - You can deploy an app with above command but the command doesn't allow to change detailed App Service Plan and Web App settings. So if you want to change these settings you can deploy it separately with following command. 238 | - Create Azure App Service Plan resources 239 | 240 | ```shell 241 | az appservice plan create -g --is-linux -n --sku --location eastus 242 | ``` 243 | 244 | - Create WebApp Resource on above App Service Plan 245 | 246 | ```shell 247 | az webapp create -g -n -p -r "python:3.10" 248 | ``` 249 | 250 | ⚡️completely optional: if your system needs to add private endpoint and/or VNET integration, you can add it here with following options. 251 | 252 | - VNET Integration 253 | 254 | ```shell 255 | # you need create vnet/subnet before execute this command 256 | az webapp create -g -n -p -r "python:3.10" --vnet --subnet 257 | ``` 258 | 259 | - Private Endpoint 260 | 261 | ```shell 262 | # you need create vnet/subnet webapp before execute this command 263 | az network private-endpoint create \ 264 | -n \ 265 | -g \ 266 | --vnet-name \ 267 | --subnet \ 268 | --connection-name \ 269 | --private-connection-resource-id /subscriptions/SubscriptionID/resourceGroups/myResourceGroup/providers/Microsoft.Web/sites/ \ 270 | --group-id sites 271 | ``` 272 | 273 | - Update redirectURI at aadConfig.ts and rebuild frontend app 274 | - Update redirectURI with following FQDN, which is webapp endpoint. 275 | 276 | ```shell 277 | az webapp config hostname list -g --webapp-name -o json | jq '.[0].name' 278 | ``` 279 | 280 | - Rebuild frontend 281 | 282 | ```shell 283 | cd app/frontend 284 | npm run build 285 | ``` 286 | 287 | - Before deployed webapp, you must change the environment variable with application settings of Azure App Service. 288 | 289 | ```shell 290 | az webapp config appsettings set --name -g --settings SCM_DO_BUILD_DURING_DEPLOYMENT="true" 291 | ``` 292 | 293 | 294 | - Deploy demo app to WebApp 295 | 296 | ```shell 297 | cd app/backend 298 | zip -r deploy.zip . 299 | az webapp deploy -g -n --src-path deploy.zip --type zip 300 | ``` 301 | 302 | - After deployed webapp, you must change the environment variables with application settings of Azure App Service. 303 | 304 | ```shell 305 | az webapp config appsettings set --name -g --settings OPENAI_API_KEY= AZURE_OPENAI_CHATGPT_DEPLOYMENT= AZURE_OPENAI_SERVICE= 306 | ``` 307 | 308 |
309 | 310 | ### 6. Configuration ⚙️ 311 | #### Collect application logs with Azure Log Analytics 312 | 313 | - Example of Log collection 314 | - Deploy Azure Log Analytics workspace 315 | 316 | ```shell 317 | export APP_SERIVCE= 318 | export LOCATION= 319 | export RESOURCE_GROUP= 320 | export WORKSPACE= 321 | export DIAGSETTINNG_NAME= 322 | 323 | az monitor log-analytics workspace create --name $WORKSPACE --resource-group $RESOURCE_GROUP --location $LOCATION 324 | ``` 325 | 326 | - Enable diagnostics setting 327 | 328 | ```shell 329 | export RESOURCE_ID=`az webapp show -g $RESOURCE_GROUP -n $APP_SERIVCE --query id --output tsv | tr -d '\r'` 330 | export WORKSPACE_ID=`az monitor log-analytics workspace show -g $RESOURCE_GROUP --workspace-name $WORKSPACE --query id --output tsv | tr -d '\r'` 331 | 332 | az monitor diagnostic-settings create \ 333 | --resource $RESOURCE_ID \ 334 | --workspace $WORKSPACE_ID \ 335 | -n $DIAGSETTINNG_NAME \ 336 | --logs '[{"category": "AppServiceAppLogs", "enabled": true},{"category": "AppServicePlatformLogs", "enabled": true},{"category": "AppServiceConsoleLogs", "enabled": true},{"category": "AppServiceAuditLogs", "enabled": true},{"category": "AppServiceHTTPLogs", "enabled": true}]' 337 | ``` 338 | 339 | 340 | #### (Optional) Store prompt log data to Azure Cosmos DB 341 | The [logging chat on Azure Cosmos DB](docs/en/logging_cosmosdb.md) section explains in detail on how chat messages can be logged into Azure Cosmos DB and used in deriving insights further downstream. 342 | 343 | 344 | --- 345 | ## 🙋🏾‍♂️Question and Feedback 346 | 347 | You can ask question and feedback about this repo on [GitHub Issues](https://github.com/Azure/openai-at-scale/issues). 348 | 349 | --- 350 | ## 📚 Resources 351 | 352 | - [ChatGPT + Enterprise data with Azure OpenAI and Cognitive Search](https://github.com/Azure-Samples/azure-search-openai-demo) 353 | - This repo is based on this sample code. 354 | 355 | --- 356 | ## 🤝 Contributing 357 | We are welcome your contribution from customers and internal Microsoft employees. Please see [CONTRIBUTING](./CONTRIBUTING.md). We appreciate all contributors to make this repo thrive! 358 | 359 | 360 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## How to file issues and get help 4 | 5 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 6 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 7 | feature request as a new Issue. 8 | 9 | For help and questions about using this product, please join the discussion on existing 10 | [issues](https://github.com/azure/azure-sdk-for-rust/issues) or create a new issue. 11 | 12 | ## Microsoft Support Policy 13 | 14 | Support for this project is limited to the resources listed above. 15 | -------------------------------------------------------------------------------- /app/backend/.env.sample: -------------------------------------------------------------------------------- 1 | # Azure OpenAI Service 2 | AZURE_OPENAI_SERVICE="" 3 | OPENAI_API_KEY="" 4 | AZURE_OPENAI_CHATGPT_DEPLOYMENT="" 5 | 6 | # (Optional) Azure Cosmos DB 7 | # AZURE_COSMOSDB_ENDPOINT="https://.documents.azure.com:443/" 8 | # AZURE_COSMOSDB_KEY="" 9 | # AZURE_COSMOSDB_DB="< your Azure Cosmos DB database name>" 10 | -------------------------------------------------------------------------------- /app/backend/app.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | import openai 4 | from flask import Flask, request, jsonify 5 | from azure.identity import DefaultAzureCredential 6 | from approaches.chatreadretrieveread import ChatReadRetrieveReadApproach 7 | from dotenv import load_dotenv 8 | 9 | env_path = os.path.join(os.path.dirname(__file__), '.env') 10 | load_dotenv(env_path, verbose=True, override=True) 11 | 12 | # Replace these with your own values, either in environment variables or directly here 13 | AZURE_OPENAI_SERVICE = os.environ.get("AZURE_OPENAI_SERVICE") or "myopenai" 14 | AZURE_OPENAI_GPT_DEPLOYMENT = os.environ.get("AZURE_OPENAI_GPT_DEPLOYMENT") or "davinci" 15 | AZURE_OPENAI_CHATGPT_DEPLOYMENT = os.environ.get("AZURE_OPENAI_CHATGPT_DEPLOYMENT") or "chat" 16 | 17 | # Used by the OpenAI SDK 18 | openai.api_type = "azure" 19 | openai.api_base = f"https://{AZURE_OPENAI_SERVICE}.openai.azure.com" 20 | openai.api_version = "2023-03-15-preview" 21 | openai.api_key = os.getenv("OPENAI_API_KEY") 22 | 23 | chat_approaches = { 24 | "rrr": ChatReadRetrieveReadApproach(AZURE_OPENAI_CHATGPT_DEPLOYMENT, AZURE_OPENAI_GPT_DEPLOYMENT) 25 | } 26 | 27 | app = Flask(__name__) 28 | 29 | @app.route("/", defaults={"path": "index.html"}) 30 | @app.route("/") 31 | def static_file(path): 32 | print(path) 33 | return app.send_static_file(path) 34 | 35 | @app.route("/chat", methods=["POST"]) 36 | def chat(): 37 | 38 | # ensure_openai_token() 39 | approach = request.json["approach"] 40 | try: 41 | impl = chat_approaches.get(approach) 42 | if not impl: 43 | return jsonify({"error": "unknown approach"}), 400 44 | r = impl.run(request.json["history"], request.json.get("overrides") or {}, request.json.get("sessionConfig") or {}, request.json.get("userInfo") or {}, dict(request.headers) or {}) 45 | return jsonify(r) 46 | 47 | except Exception as e: 48 | logging.exception("Exception in /chat") 49 | return jsonify({"error": str(e)}), 500 50 | 51 | 52 | if __name__ == "__main__": 53 | app.run() 54 | -------------------------------------------------------------------------------- /app/backend/approaches/approach.py: -------------------------------------------------------------------------------- 1 | class Approach: 2 | def run(self, q: str, use_summaries: bool) -> any: 3 | raise NotImplementedError 4 | -------------------------------------------------------------------------------- /app/backend/approaches/chatreadretrieveread.py: -------------------------------------------------------------------------------- 1 | import openai 2 | import uuid 3 | from approaches.approach import Approach 4 | import chat_log.cosmosdb_logging as cosmosdb_logging 5 | 6 | class ChatReadRetrieveReadApproach(Approach): 7 | 8 | # def __init__(self, chatgpt_deployment: str, gpt_deployment: str, sourcepage_field: str, content_field: str): 9 | def __init__(self, chatgpt_deployment: str, gpt_deployment: str): 10 | self.chatgpt_deployment = chatgpt_deployment 11 | self.gpt_deployment = gpt_deployment 12 | 13 | def run(self, history: list[dict], overrides: dict, sessionConfig: dict, userInfo:dict, header: dict) -> any: 14 | request_uuid=uuid.uuid4() 15 | print("requestID:",request_uuid,",history:", history) 16 | print("requestID:",request_uuid,",override:", overrides) 17 | print("requestID:",request_uuid,",sessionConfig:", sessionConfig) 18 | print("requestID:",request_uuid,",userInfo:", userInfo) 19 | top_p = overrides.get("top") or 0.95 20 | temperature = overrides.get("temperature") or 0.7 21 | max_tokens = overrides.get("maxResponse") or 800 22 | promptSystemTemplate = overrides.get("prompt_system_template") or "You are an AI assistant that helps people find information." 23 | pastMessages = sessionConfig.get("pastMessages") or 10 24 | user_name= userInfo.get("username") or "anonymous user_name" 25 | user_id = userInfo.get("email") or "anonymous user_id" 26 | chat_session_id = header.get("Sessionid") or "anonymous-" + str(uuid.uuid4()) 27 | print("user:", {"name": user_name,"user_id":user_id} ) # For Azure Log Analytics 28 | print("parameters:", {"Max Response": max_tokens, "Temperature": temperature, "Top P": top_p, "Past message included": pastMessages}) 29 | 30 | # Step 31 | system_prompt_template = {} 32 | system_prompt_template["role"] = "system" 33 | system_prompt_template["content"] = promptSystemTemplate 34 | print("prompt:",[system_prompt_template]+self.get_chat_history_as_text(history, pastMessages)) # For Azure Log Analytics 35 | 36 | completion = openai.ChatCompletion.create( 37 | engine=self.chatgpt_deployment, 38 | messages = [system_prompt_template]+self.get_chat_history_as_text(history, pastMessages), 39 | temperature=temperature, 40 | max_tokens=max_tokens, 41 | top_p=top_p, 42 | frequency_penalty=0, 43 | presence_penalty=0, 44 | stop=None) 45 | print("completion: ", completion) # For Azure Log Analytics 46 | 47 | document_definition = { "id": str(uuid.uuid4()), 48 | "chat_session_id": chat_session_id, 49 | "user": {"name": user_name,"user_id":user_id}, 50 | 'message': {"id":completion.get("id") or "anonymous-id", 51 | "prompt":[system_prompt_template]+self.get_chat_history_as_text(history, pastMessages), 52 | "other_attr":[{"completion": completion}], 53 | "previous_message_id":"previous_message_id"}} 54 | 55 | #cosmosdb_logging.insert_chat_log(document_definition) # Store prompt log data into Azure Cosmos DB 56 | return {"answer": completion.choices[0].message["content"]} 57 | 58 | 59 | def get_chat_history_as_text(self, history, pastMessages) -> list: 60 | history_text = [] 61 | for h in history: 62 | user_text = {} 63 | user_text["role"] = "user" 64 | user_text["content"] = h["user"] 65 | 66 | if h.get("bot") is None: 67 | history_text = history_text + [user_text] 68 | else: 69 | bot_text = {} 70 | bot_text["role"] = "assistant" 71 | bot_text["content"] = h.get("bot") 72 | history_text = history_text + [user_text, bot_text] 73 | return history_text[-(pastMessages+1):] 74 | -------------------------------------------------------------------------------- /app/backend/chat_log/cosmosdb_logging.py: -------------------------------------------------------------------------------- 1 | import azure.cosmos.cosmos_client as cosmos_client 2 | import azure.cosmos.errors as errors 3 | import azure.cosmos.exceptions as cosmos_exception 4 | import os 5 | import json 6 | from dotenv import load_dotenv 7 | 8 | ## Read environment variables 9 | env_path = os.path.join(os.path.dirname(__file__), '../.env') 10 | load_dotenv(env_path, verbose=True, override=True) 11 | 12 | endpoint = os.environ.get("AZURE_COSMOSDB_ENDPOINT") or None 13 | key = os.environ.get("AZURE_COSMOSDB_KEY") or None 14 | database_name = os.environ.get("AZURE_COSMOSDB_DB") or None 15 | 16 | # Replace with your own values if you are changing the default document structure 17 | 18 | container_name = 'chat_log' 19 | partition_key = '/chat_session_id' 20 | 21 | class chat_log_result: 22 | def __init__(self,is_err,err_msg): 23 | self.is_err = is_err 24 | self.err_msg = err_msg 25 | 26 | 27 | def insert_chat_log(chat_message) -> chat_log_result: 28 | chat_log_inserted = False 29 | err_message = "" 30 | if endpoint is not None and key is not None and database_name is not None: 31 | try: 32 | client = cosmos_client.CosmosClient(endpoint, {'masterKey': key}) 33 | database = client.create_database_if_not_exists(database_name) 34 | container = database.create_container_if_not_exists(id=container_name,partition_key=partition_key,default_ttl=3600,offer_throughput=400) 35 | 36 | ## Ensure the message has partition key , chat_session_id and user_id 37 | try: 38 | if chat_message["chat_session_id"] is not None and len(str(chat_message["chat_session_id"])) > 0: 39 | document = container.create_item(body=chat_message) 40 | 41 | 42 | except cosmos_exception.CosmosHttpResponseError as err: 43 | print(err.message) 44 | chat_log_inserted = True 45 | err_message = err_message + err.message + " ::: " 46 | except: 47 | msg = "Missing partition key: chat_session_id or invalid message structure" + json.loads(chat_message) 48 | chat_log_inserted = True 49 | print(msg) 50 | err_message = err_message + msg + " ::: " 51 | 52 | 53 | except cosmos_exception.CosmosHttpResponseError as err: 54 | err_message = err_message + err.message + " ::: " 55 | chat_log_inserted = True 56 | print(err.message) 57 | 58 | else: 59 | msg = "Cannot initiate connection as one of the environment variables is empty - AZURE_COSMOSDB_ENDPOINT, AZURE_COSMOSDB_KEY ,AZURE_COSMOSDB_DB" 60 | chat_log_inserted = True 61 | print(msg) 62 | err_message = err_message + msg + " ::: " 63 | chat_log_res = chat_log_result(is_err=chat_log_inserted,err_msg=err_message) 64 | return chat_log_res 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /app/backend/chat_log/temp_dev.py: -------------------------------------------------------------------------------- 1 | import azure.cosmos.cosmos_client as cosmos_client 2 | 3 | import azure.cosmos.errors as errors 4 | import uuid 5 | import cosmosdb_logging 6 | 7 | ## Sample code to insert chat log data into Cosmos DB 8 | 9 | if __name__ == "__main__": 10 | chat_session_id = str(uuid.uuid4()) 11 | user_id = str(uuid.uuid4()) 12 | chat_id = str(uuid.uuid4()) 13 | previous_message_id = str(uuid.uuid4()) 14 | document_definition = { "id": str(uuid.uuid4()), 15 | "chat_session_id": chat_session_id, 16 | "user": {"name":'test',"user_id":user_id}, 17 | 'message': {"id":chat_id, 18 | "prompt":[{"role":"system","content":"test content"},{"role":"human","content":"test content works!"}], 19 | "other_attr":[{"completion": { 20 | "choices": [ 21 | { 22 | "finish_reason": "stop", 23 | "index": 0, 24 | "message": { 25 | "content": "", 26 | "role": "assistant" 27 | } 28 | } 29 | ]}},{"created":"datetime"},{"model": "gpt-35-turbo"},{"object": "chat.completion"},{"usage": { 30 | "completion_tokens": 95, 31 | "prompt_tokens": 664, 32 | "total_tokens": 759 33 | }}], 34 | "previous_message_id":previous_message_id}} 35 | #document = container.create_item(body=document_definition) 36 | 37 | result = cosmosdb_logging.insert_chat_log(document_definition) 38 | if result.is_err == True: 39 | print('chat log insert error: ' + result.err_msg) 40 | else: 41 | print('chat log inserted successfully') 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | # Create a container with partition key 52 | 53 | 54 | 55 | 56 | # Insert a JSON document with partition key 57 | 58 | 59 | 60 | 61 | ''' 62 | 63 | { 64 | : "", 65 | "user":{"name":"","user_id":""}, 66 | "message":{ 67 | "id": "unique ID identifying the message", 68 | "prompt":[{ 69 | "role": "system | human", 70 | "content": "message content" 71 | 72 | }], 73 | 74 | "other_attr": [{"completion": { 75 | "choices": [ 76 | { 77 | "finish_reason": "stop", 78 | "index": 0, 79 | "message": { 80 | "content": "", 81 | "role": "assistant" 82 | } 83 | } 84 | ]}},{"created":"datetime"},{"model": "gpt-35-turbo"},{"object": "chat.completion"},{"usage": { 85 | "completion_tokens": 95, 86 | "prompt_tokens": 664, 87 | "total_tokens": 759 88 | }}], 89 | "previous_message_id": "" 90 | 91 | } 92 | } 93 | 94 | ''' -------------------------------------------------------------------------------- /app/backend/requirements.txt: -------------------------------------------------------------------------------- 1 | azure-identity==1.13.0b3 2 | Flask==2.2.5 3 | openai==0.27.3 4 | azure-cosmos==4.3.1 5 | python-dotenv==1.0.0 6 | -------------------------------------------------------------------------------- /app/backend/static/assets/github-8c68b25d.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/backend/static/assets/index-51849b94.css: -------------------------------------------------------------------------------- 1 | *{box-sizing:border-box}html,body{height:100%;margin:0;padding:0}html{background:#ffffff;font-family:Segoe UI,-apple-system,BlinkMacSystemFont,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#root{height:100%}._layout_f6i7q_1{display:flex;flex-direction:column;height:100%}._header_f6i7q_7{background-color:#0078d4;height:44px}._headerTitle_f6i7q_11{display:flex;align-items:center;justify-content:left;text-decoration:none;color:#f2f2f2;width:1500px;float:left}._headerTitleText_f6i7q_20{display:flex;justify-content:center;text-decoration:none;color:#f2f2f2;width:150px;font-size:16px}._headerCommandBar_f6i7q_29{display:flex;align-items:center;justify-content:center;width:150px;height:40px}._headerContainer_f6i7q_37{display:flex;align-items:center;justify-content:space-between;width:100%;height:40px}._headerAvatar_f6i7q_45{display:flex;width:50px;align-items:center;justify-content:center}._container_1xd99_1{flex:1;display:flex;flex-direction:column;height:100%}._stack_1xd99_8{height:100%}._stackItem_1xd99_12{display:flex;height:100%;width:70%;margin-right:5px;flex-direction:row-reverse}._stackItemConfig_1xd99_20{display:flex;flex-direction:column;height:100%;margin-left:5px;background-color:#f2f2f2;padding:10px}._chatRoot_1xd99_30{flex:1;display:flex}._chatContainer_1xd99_35{flex:1;display:flex;flex-direction:column;align-items:center}._chatEmptyState_1xd99_43{flex-grow:1;display:flex;flex-direction:column;justify-content:center;align-items:center;max-height:1024px;padding-top:60px}._chatEmptyStateTitle_1xd99_53{font-size:4rem;font-weight:600;margin-top:0;margin-bottom:30px}._chatEmptyStateSubtitle_1xd99_60{font-weight:600;margin-bottom:10px}@media only screen and (max-height: 780px){._chatEmptyState_1xd99_43{padding-top:0}._chatEmptyStateTitle_1xd99_53{font-size:3rem;margin-bottom:0}}._chatMessageStream_1xd99_76{flex-grow:1;max-width:1028px;width:100%;overflow-y:auto;padding-left:24px;padding-right:24px;display:flex;flex-direction:column}._chatMessageGptMinWidth_1xd99_87{max-width:500px;margin-bottom:20px}._chatInput_1xd99_92{position:sticky;bottom:0;padding:12px 24px 24px;width:100%;max-width:1028px;background:#ffffff;display:flex;flex-direction:row}._chatAnalysisPanel_1xd99_107{flex:1;overflow-y:auto;max-height:89vh;margin-left:20px;margin-right:20px}._chatSettingsSeparator_1xd99_115{padding:15px}._chatSettingsLabel_1xd99_119{font-size:20px;font-weight:700;font-family:Segoe UI,-apple-system,BlinkMacSystemFont,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif}._loadingLogo_1xd99_126{font-size:28px}._commandsContainer_1xd99_130{display:flex;flex-direction:column;justify-content:center;padding:10px}._commandButton_1xd99_137{margin-right:20px}._answerContainer_13phk_1{outline:transparent solid 1px;display:flex;flex-direction:row;align-items:center}._answerLogo_13phk_8{padding:24px;font-size:28px}._answerText_13phk_13{padding:16px 20px;background:rgb(249,249,249);border-radius:8px;box-shadow:0 2px 4px #00000024,0 0 2px #0000001f;font-size:16px;font-weight:400;line-height:22px;white-space:pre-line;max-width:60%;display:flex;min-width:500px}._answerText_13phk_13 table{border-collapse:collapse}._answerText_13phk_13 td,._answerText_13phk_13 th{border:1px solid;padding:5px}._selected_13phk_39{outline:2px solid rgba(115,118,225,1)}._citationLearnMore_13phk_43{margin-right:5px;font-weight:600;line-height:24px}._citation_13phk_43{font-weight:500;line-height:24px;text-align:center;border-radius:4px;padding:0 8px;background:#d1dbfa;color:#123bb6;text-decoration:none;cursor:pointer}._citation_13phk_43:hover{text-decoration:underline}._followupQuestionsList_13phk_65{margin-top:10px}._followupQuestionLearnMore_13phk_69{margin-right:5px;font-weight:600;line-height:24px}._followupQuestion_13phk_65{font-weight:600;line-height:24px;text-align:center;border-radius:4px;padding:0 8px;background:#e8ebfa;color:#000;font-style:italic;text-decoration:none;cursor:pointer}._supContainer_13phk_88{text-decoration:none;cursor:pointer}._supContainer_13phk_88:hover{text-decoration:underline}sup{position:relative;display:inline-flex;align-items:center;justify-content:center;font-size:10px;font-weight:600;vertical-align:top;top:-1;margin:0 2px;min-width:14px;height:14px;border-radius:3px;background:#d1dbfa;color:#123bb6;text-decoration-color:transparent;outline:transparent solid 1px;cursor:pointer}._retryButton_13phk_117{margin-top:10px;width:fit-content}._isLoading_13phk_122{margin-left:50px;width:50%}._questionInputContainer_16lqj_1{border-radius:8px;box-shadow:0 8px 16px #00000024,0 0 2px #0000001f;width:100%;padding:15px;background:white}._questionLogo_16lqj_9{padding:24px}._questionInputTextArea_16lqj_13{width:100%;line-height:40px}._questionInputTextAreaInput_16lqj_18{font-size:16px}._questionInputButtonsContainer_16lqj_22{display:flex;flex-direction:column;justify-content:center}._questionInputSendButton_16lqj_28{cursor:pointer}._questionInputSendButtonDisabled_16lqj_32{opacity:.4}._container_hy3wk_1{display:flex;flex-direction:row;align-items:center;justify-content:flex-end;margin-top:20px;margin-bottom:20px;margin-left:auto;max-width:80%}._logo_hy3wk_12{display:flex;flex-direction:column;padding:10px;width:120px;justify-content:center;align-items:center;overflow:hidden}._message_hy3wk_22{padding:20px;background:#e8ebfa;border-radius:8px;box-shadow:0 2px 4px #00000024,0 0 2px #0000001f;outline:transparent solid 1px;max-width:80%}._container_1opm5_1{display:flex;align-items:center;gap:6px;cursor:pointer}._disabled_1opm5_8{opacity:.4} 2 | -------------------------------------------------------------------------------- /app/backend/static/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/backend/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | OpenAI at Scale 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/backend/text.py: -------------------------------------------------------------------------------- 1 | def nonewlines(s: str) -> str: 2 | return s.replace('\n', ' ').replace('\r', ' ') 3 | -------------------------------------------------------------------------------- /app/frontend/.env.sample: -------------------------------------------------------------------------------- 1 | VITE_CLIENTID="" 2 | VITE_TENANTID="" 3 | -------------------------------------------------------------------------------- /app/frontend/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "printWidth": 160, 4 | "arrowParens": "avoid", 5 | "trailingComma": "none" 6 | } 7 | -------------------------------------------------------------------------------- /app/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | OpenAI at Scale 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/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 | "watch": "tsc && vite build --watch" 10 | }, 11 | "dependencies": { 12 | "@azure/msal-browser": "^2.36.0", 13 | "@azure/msal-react": "^1.5.6", 14 | "@fluentui/example-data": "^8.4.7", 15 | "@fluentui/react": "^8.105.3", 16 | "@fluentui/react-components": "^9.18.6", 17 | "@fluentui/react-icons": "^2.0.195", 18 | "@microsoft/microsoft-graph-client": "^3.0.5", 19 | "@react-spring/web": "^9.7.1", 20 | "@types/md5": "^2.3.0", 21 | "@types/microsoft-graph": "^2.29.0", 22 | "@types/uuid": "^9.0.1", 23 | "dompurify": "^3.0.1", 24 | "md5": "^2.3.0", 25 | "react": "^18.2.0", 26 | "react-bootstrap": "^2.7.4", 27 | "react-dom": "^18.2.0", 28 | "react-router-dom": "^6.8.1", 29 | "uuid": "^9.0.0" 30 | }, 31 | "devDependencies": { 32 | "@types/dompurify": "^2.4.0", 33 | "@types/react": "^18.0.27", 34 | "@types/react-dom": "^18.0.10", 35 | "@vitejs/plugin-react": "^3.1.0", 36 | "prettier": "^2.8.3", 37 | "typescript": "^4.9.3", 38 | "vite": "^4.1.5" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/frontend/public/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/frontend/src/Avatar.tsx: -------------------------------------------------------------------------------- 1 | import { AuthenticatedTemplate, UnauthenticatedTemplate } from "@azure/msal-react"; 2 | 3 | import { IPersonaSharedProps, Persona, PersonaSize } from "@fluentui/react/lib/Persona"; 4 | import { 5 | IStackStyles, 6 | IStackTokens, 7 | IStackItemStyles, 8 | Label, 9 | Stack, 10 | Callout, 11 | DirectionalHint, 12 | mergeStyleSets, 13 | Layer, 14 | PersonaInitialsColor 15 | } from "@fluentui/react"; 16 | import { useId } from "@fluentui/react-hooks"; 17 | import { useAppContext } from "./context/AppContext"; 18 | import { useBoolean } from "@fluentui/react-hooks"; 19 | 20 | export default function Avatar() { 21 | const app = useAppContext(); 22 | const examplePersona: IPersonaSharedProps = { 23 | imageUrl: app.user?.avatar || "", 24 | text: app.user?.displayName || "Anonymous", 25 | secondaryText: app.user?.email || "" 26 | }; 27 | 28 | const styles = mergeStyleSets({ 29 | button: { 30 | width: 130 31 | }, 32 | callout: { 33 | background: "white", 34 | width: 250, 35 | maxWidth: "100%" 36 | } 37 | }); 38 | // Tokens definition 39 | const outerStackTokens: IStackTokens = { childrenGap: 3 }; 40 | const innerStackTokens: IStackTokens = { 41 | childrenGap: 5 42 | }; 43 | const stackStyles: IStackStyles = { 44 | root: { 45 | height: 150 46 | } 47 | }; 48 | const stackAvatarStyles: IStackItemStyles = { 49 | root: { 50 | alignItems: "center", 51 | display: "flex", 52 | justifyContent: "center", 53 | backgroundColor: "white", 54 | height: "100%" 55 | } 56 | }; 57 | const stackSignFormStyles: IStackItemStyles = { 58 | root: { 59 | alignItems: "center", 60 | display: "flex", 61 | justifyContent: "center", 62 | backgroundColor: "rgb(248, 248, 248)", 63 | height: "50px", 64 | fontSize: "5px" 65 | } 66 | }; 67 | 68 | const [isCalloutVisible, { toggle: toggleIsCalloutVisible }] = useBoolean(false); 69 | const buttonId = useId("callout-button"); 70 | const labelId = useId("callout-label"); 71 | const descriptionId = useId("callout-description"); 72 | return ( 73 | <> 74 | 75 | 76 | 77 | 78 | 79 | 80 | {isCalloutVisible && ( 81 | 82 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | )} 117 | 118 | ); 119 | } 120 | -------------------------------------------------------------------------------- /app/frontend/src/Bar.tsx: -------------------------------------------------------------------------------- 1 | import { Label, Stack, Callout, CommandBar, ICommandBarItemProps, DefaultPalette, mergeStyleSets, ThemeProvider, PartialTheme } from "@fluentui/react"; 2 | import { useBoolean } from "@fluentui/react-hooks"; 3 | import { useId } from "@fluentui/react-hooks"; 4 | import { FontIcon } from "@fluentui/react/lib/Icon"; 5 | import github from "./assets/github.svg"; 6 | 7 | const styles = mergeStyleSets({ 8 | commandBar: { 9 | backgroundColor: DefaultPalette.blue, 10 | alignItems: "center", 11 | justifyContent: "center", 12 | height: "40px%" 13 | }, 14 | button: { 15 | width: 130 16 | }, 17 | callout: { 18 | background: "white", 19 | width: 250, 20 | height: 200, 21 | maxWidth: "100%", 22 | padding: "10px" 23 | }, 24 | stack: { 25 | display: "flex", 26 | alignItems: "center", 27 | justifyContent: "left", 28 | padding: "10px", 29 | textDecoration: "none" 30 | } 31 | }); 32 | 33 | export default function Bar() { 34 | const buttonHelpId = useId("callout-button"); 35 | const buttonInfoId = useId("callout-button"); 36 | const [isHelpCalloutVisible, { toggle: toggleIsHelpCalloutVisible }] = useBoolean(false); 37 | const [isInfoCalloutVisible, { toggle: toggleIsInfoCalloutVisible }] = useBoolean(false); 38 | 39 | const _items: ICommandBarItemProps[] = [ 40 | { 41 | key: "help", 42 | text: "Help", 43 | iconProps: { 44 | iconName: "Help", 45 | styles: { 46 | root: { 47 | color: "white" 48 | } 49 | } 50 | }, 51 | style: { 52 | backgroundColor: "rgb(0, 120, 212)" 53 | }, 54 | iconOnly: true, 55 | id: buttonHelpId, 56 | onClick: toggleIsHelpCalloutVisible 57 | }, 58 | { 59 | key: "info", 60 | text: "Info", 61 | ariaLabel: "Info", 62 | iconOnly: true, 63 | iconProps: { 64 | iconName: "Info", 65 | styles: { 66 | root: { 67 | color: "white" 68 | } 69 | } 70 | }, 71 | style: { 72 | backgroundColor: "rgb(0, 120, 212)" 73 | }, 74 | id: buttonInfoId, 75 | onClick: toggleIsInfoCalloutVisible 76 | } 77 | ]; 78 | const darkTheme: PartialTheme = { 79 | semanticColors: { 80 | bodyBackground: DefaultPalette.blue, 81 | bodyText: "white" 82 | } 83 | }; 84 | return ( 85 | <> 86 | 87 | 88 | 89 | {isHelpCalloutVisible && ( 90 | 91 | 92 | 93 | 97 | 98 | You can ask question and feedback about this application on GitHub Issues. 99 | 100 | 106 | Azure/openai-at-scale 107 | 108 | 109 | 110 | 111 | 112 | )} 113 | {isInfoCalloutVisible && ( 114 | 115 | 116 | 117 | 121 | 122 | Visit the GitHub repo to check the source code of this application. 123 | 124 | 130 | Azure/openai-at-scale 131 | 132 | 133 | 134 | 135 | 136 | )} 137 | 138 | ); 139 | } 140 | -------------------------------------------------------------------------------- /app/frontend/src/GraphService.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. 2 | // Licensed under the MIT License. 3 | 4 | // 5 | import { Client, GraphRequestOptions, PageCollection, PageIterator } from "@microsoft/microsoft-graph-client"; 6 | import { AuthCodeMSALBrowserAuthenticationProvider } from "@microsoft/microsoft-graph-client/authProviders/authCodeMsalBrowser"; 7 | // import { endOfWeek, startOfWeek } from "date-fns"; 8 | // import { zonedTimeToUtc } from "date-fns-tz"; 9 | import { User, Event } from "microsoft-graph"; 10 | 11 | let graphClient: Client | undefined = undefined; 12 | 13 | function ensureClient(authProvider: AuthCodeMSALBrowserAuthenticationProvider) { 14 | if (!graphClient) { 15 | graphClient = Client.initWithMiddleware({ 16 | authProvider: authProvider 17 | }); 18 | } 19 | 20 | return graphClient; 21 | } 22 | 23 | export async function getUser(authProvider: AuthCodeMSALBrowserAuthenticationProvider): Promise { 24 | ensureClient(authProvider); 25 | 26 | // Return the /me API endpoint result as a User object 27 | const user: User = await graphClient! 28 | .api("/me") 29 | // Only retrieve the specific fields needed 30 | .select("displayName,mail,userPrincipalName,photo") 31 | .get(); 32 | 33 | return user; 34 | } 35 | 36 | export async function getProfilePhoto(authProvider: AuthCodeMSALBrowserAuthenticationProvider): Promise { 37 | ensureClient(authProvider); 38 | 39 | // Return the /me/photo API endpoint result as a ProfilePhoto object 40 | let blob = await graphClient!.api("/me/photo/$value").get(); 41 | const url = window.URL || window.webkitURL; 42 | return url.createObjectURL(blob); 43 | } 44 | -------------------------------------------------------------------------------- /app/frontend/src/aadConfig.ts: -------------------------------------------------------------------------------- 1 | export const aadConfig = { 2 | clientId: import.meta.env.VITE_CLIENTID, 3 | tenantId: import.meta.env.VITE_TENANTID, 4 | redirectUri: "http://localhost:5173", 5 | authorityBaseUrl: "https://login.microsoftonline.com/" 6 | }; 7 | -------------------------------------------------------------------------------- /app/frontend/src/api/api.ts: -------------------------------------------------------------------------------- 1 | import { AskResponse, ChatRequest } from "./models"; 2 | 3 | export async function chatApi(options: ChatRequest): Promise { 4 | console.log("options.approach:", options.approach); 5 | console.log("chatApi options: ", options.overrides?.promptSystemTemplate); 6 | console.log("sessionId: ", options.sessionId?.sessionId); 7 | const response = await fetch("/chat", { 8 | method: "POST", 9 | headers: { 10 | Authorization: "Bearer " + options.accessToken?.accessToken || "", 11 | SessionId: options.sessionId?.sessionId || "", 12 | "Content-Type": "application/json" 13 | }, 14 | body: JSON.stringify({ 15 | history: options.history, 16 | approach: options.approach, 17 | overrides: { 18 | top: options.overrides?.top, 19 | temperature: options.overrides?.temperature, 20 | prompt_system_template: options.overrides?.promptSystemTemplate, 21 | maxResponse: options.overrides?.maxResponse 22 | }, 23 | sessionConfig: { 24 | pastMessages: options.sessionConfig?.pastMessages 25 | }, 26 | userInfo: { 27 | username: options.userInfo?.username, 28 | email: options.userInfo?.email 29 | }, 30 | }) 31 | }); 32 | 33 | const parsedResponse: AskResponse = await response.json(); 34 | if (response.status > 299 || !response.ok) { 35 | throw Error(parsedResponse.error || "Unknown error"); 36 | } 37 | 38 | return parsedResponse; 39 | } 40 | -------------------------------------------------------------------------------- /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 | RetrieveThenRead = "rtr", 3 | ReadRetrieveRead = "rrr", 4 | ReadDecomposeAsk = "rda" 5 | } 6 | 7 | export type AskRequestOverrides = { 8 | promptSystemTemplate?: string; 9 | maxResponse?: number; 10 | temperature?: number; 11 | top?: number; 12 | }; 13 | 14 | export type SessionConfig = { 15 | pastMessages?: number; 16 | }; 17 | 18 | export type AskResponse = { 19 | answer: string | undefined; 20 | error?: string; 21 | }; 22 | 23 | export type ChatTurn = { 24 | user: string; 25 | bot: string | undefined; 26 | }; 27 | 28 | export type UserInfo = { 29 | username?: string; 30 | email?: string; 31 | }; 32 | 33 | export type AccessToken = { 34 | accessToken: string; 35 | }; 36 | 37 | export type SessionId = { 38 | sessionId: string; 39 | }; 40 | 41 | export type ChatRequest = { 42 | history: ChatTurn[]; 43 | approach: Approaches; 44 | overrides?: AskRequestOverrides; 45 | sessionConfig?: SessionConfig; 46 | userInfo?: UserInfo; 47 | accessToken?: AccessToken; 48 | sessionId?: SessionId; 49 | }; 50 | -------------------------------------------------------------------------------- /app/frontend/src/assets/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/frontend/src/components/Answer/Answer.module.css: -------------------------------------------------------------------------------- 1 | .answerContainer { 2 | outline: transparent solid 1px; 3 | display: flex; 4 | flex-direction: row; 5 | align-items: center; 6 | } 7 | 8 | .answerLogo { 9 | padding: 24px; 10 | font-size: 28px; 11 | } 12 | 13 | .answerText { 14 | padding: 20px; 15 | background: rgb(249, 249, 249); 16 | border-radius: 8px; 17 | box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.14), 0px 0px 2px rgba(0, 0, 0, 0.12); 18 | font-size: 16px; 19 | font-weight: 400; 20 | line-height: 22px; 21 | padding-top: 16px; 22 | padding-bottom: 16px; 23 | white-space: pre-line; 24 | max-width: 60%; 25 | display: flex; 26 | min-width: 500px; 27 | } 28 | 29 | .answerText table { 30 | border-collapse: collapse; 31 | } 32 | 33 | .answerText td, 34 | .answerText th { 35 | border: 1px solid; 36 | padding: 5px; 37 | } 38 | 39 | .selected { 40 | outline: 2px solid rgba(115, 118, 225, 1); 41 | } 42 | 43 | .citationLearnMore { 44 | margin-right: 5px; 45 | font-weight: 600; 46 | line-height: 24px; 47 | } 48 | 49 | .citation { 50 | font-weight: 500; 51 | line-height: 24px; 52 | text-align: center; 53 | border-radius: 4px; 54 | padding: 0px 8px; 55 | background: #d1dbfa; 56 | color: #123bb6; 57 | text-decoration: none; 58 | cursor: pointer; 59 | } 60 | 61 | .citation:hover { 62 | text-decoration: underline; 63 | } 64 | 65 | .followupQuestionsList { 66 | margin-top: 10px; 67 | } 68 | 69 | .followupQuestionLearnMore { 70 | margin-right: 5px; 71 | font-weight: 600; 72 | line-height: 24px; 73 | } 74 | 75 | .followupQuestion { 76 | font-weight: 600; 77 | line-height: 24px; 78 | text-align: center; 79 | border-radius: 4px; 80 | padding: 0px 8px; 81 | background: #e8ebfa; 82 | color: black; 83 | font-style: italic; 84 | text-decoration: none; 85 | cursor: pointer; 86 | } 87 | 88 | .supContainer { 89 | text-decoration: none; 90 | cursor: pointer; 91 | } 92 | 93 | .supContainer:hover { 94 | text-decoration: underline; 95 | } 96 | 97 | sup { 98 | position: relative; 99 | display: inline-flex; 100 | align-items: center; 101 | justify-content: center; 102 | font-size: 10px; 103 | font-weight: 600; 104 | vertical-align: top; 105 | top: -1; 106 | margin: 0px 2px; 107 | min-width: 14px; 108 | height: 14px; 109 | border-radius: 3px; 110 | background: #d1dbfa; 111 | color: #123bb6; 112 | text-decoration-color: transparent; 113 | outline: transparent solid 1px; 114 | cursor: pointer; 115 | } 116 | 117 | .retryButton { 118 | margin-top: 10px; 119 | width: fit-content; 120 | } 121 | 122 | .isLoading { 123 | margin-left: 50px; 124 | width: 50%; 125 | } 126 | -------------------------------------------------------------------------------- /app/frontend/src/components/Answer/Answer.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from "react"; 2 | import { Stack, IconButton } from "@fluentui/react"; 3 | import DOMPurify from "dompurify"; 4 | 5 | import styles from "./Answer.module.css"; 6 | 7 | import { AskResponse } from "../../api"; 8 | import { parseAnswerToHtml } from "./AnswerParser"; 9 | import { AnswerIcon } from "./AnswerIcon"; 10 | 11 | interface Props { 12 | answer: AskResponse; 13 | } 14 | 15 | export const Answer = ({ answer }: Props) => { 16 | if (answer == undefined) return null; 17 | if (answer.answer == undefined) return null; 18 | const generatedAnswer: string = answer.answer; 19 | const parsedAnswer = useMemo(() => parseAnswerToHtml(generatedAnswer), [answer]); 20 | const sanitizedAnswerHtml = DOMPurify.sanitize(parsedAnswer.answerHtml); 21 | 22 | return ( 23 | 24 | 25 |
26 | 27 |
28 |
29 | 30 |
31 |
32 |
33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /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 |
17 | 18 |

19 | {error} 20 |
21 | 22 |

23 |
24 |
25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /app/frontend/src/components/Answer/AnswerIcon.tsx: -------------------------------------------------------------------------------- 1 | import { BotAdd24Regular } from "@fluentui/react-icons"; 2 | 3 | export const AnswerIcon = () => { 4 | return