├── .gcloudignore ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .gitpod.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── Vertex_AI_Chat.ipynb ├── bruno-export.json ├── chatbot-ui.sh ├── deploy.sh ├── img ├── bruno.png ├── chatbot-ui-chat.png ├── chatbot-ui-env.png ├── chatbox-settings.png ├── cloud-run-env.png ├── openai-api-cloud-run-vertex-dark.png ├── openai-api-cloud-run-vertex.excalidraw ├── openai-api-cloud-run-vertex.png ├── vscode-chat.png └── vscode-settings.png ├── requirements.txt └── vertex.py /.gcloudignore: -------------------------------------------------------------------------------- 1 | ** 2 | !Dockerfile 3 | !requirements.txt 4 | !vertex.py -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: Cyclenerd 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: 'Bug: Good title' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Contact me on Mastodon 4 | url: https://fosstodon.org/@cyclenerd 5 | about: Feel free to follow me on Mastodon and send me a message -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: 'Feature request: Good title' 5 | labels: 'enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | First off, thanks for taking the time to contribute! 2 | 3 | ## Please Complete the Following 4 | 5 | - [ ] I read [CONTRIBUTING.md](https://github.com/Cyclenerd/google-cloud-gcp-openai-api/blob/master/CONTRIBUTING.md) 6 | 7 | ## Notes 8 | 9 | Feel free to put whatever you want here. -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | 3 | on: 4 | push: 5 | branches: 6 | - "master" 7 | pull_request: 8 | branches: 9 | - "master" 10 | workflow_dispatch: 11 | 12 | jobs: 13 | test: 14 | name: CI/CD Test 15 | # https://github.com/actions/virtual-environments/ 16 | runs-on: ubuntu-latest 17 | steps: 18 | 19 | - name: 🛎️ Checkout 20 | uses: actions/checkout@v4 21 | 22 | - name: Install dependencies 🔧 23 | run: sudo apt-get install flake8 24 | 25 | # Check Bash scripts 26 | - name: Bash 🔎 27 | run: shellcheck *.sh 28 | 29 | # Check Python code 30 | - name: Python 🔎 31 | run: flake8 --ignore=W292 --max-line-length=127 --show-source --statistics *.py -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | faiss_index/ 3 | .venv -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | tasks: 2 | - init: | 3 | sudo apt-get install -y apt-transport-https ca-certificates gnupg 4 | echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list 5 | curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo tee /usr/share/keyrings/cloud.google.gpg 6 | sudo apt-get update -y 7 | sudo apt-get install -y google-cloud-cli 8 | pip install -r requirements.txt 9 | command: gcloud --version -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | nils [at] nkn-it (dot) de. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | First off, thanks for taking the time to contribute! 4 | 5 | ## Submitting changes 6 | 7 | Please send a GitHub Pull Request with a clear list of what you've done (read more about [pull requests](http://help.github.com/pull-requests/)). 8 | 9 | Always write a clear log message for your commits. One-line messages are fine for small changes, but bigger changes should look like this: 10 | 11 | ``` 12 | $ git commit -m "A brief summary of the commit 13 | > 14 | > A paragraph describing what changed and its impact." 15 | ``` 16 | 17 | ## Coding style 18 | 19 | Start reading the code and you'll get the hang of it. It is optimized for readability: 20 | 21 | * Please also update the documentation. 22 | * Please check you Bash scripts with [ShellCheck](https://www.shellcheck.net/). 23 | * Please check your Python code with [Flake8](https://flake8.pycqa.org/en/latest/index.html). 24 | * Be nice. 25 | 26 | One more thing: 27 | 28 | * Keep it simple! 👍 29 | 30 | Thanks! ❤️❤️❤️ 31 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2023-2024 Nils Knieling 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # The python:3.11-slim tag points to the latest release based on Debian 12 (bookworm) 16 | FROM python:3.11-slim 17 | 18 | # Labels 19 | LABEL org.opencontainers.image.title "OpenAI API for Google Cloud Vertex AI" 20 | LABEL org.opencontainers.image.description "Drop-in replacement REST API for Google Cloud Vertex AI that is compatible with the OpenAI API specifications" 21 | LABEL org.opencontainers.image.url "https://github.com/Cyclenerd/google-cloud-gcp-openai-api" 22 | LABEL org.opencontainers.image.authors "https://github.com/Cyclenerd/google-cloud-gcp-openai-api/graphs/contributors" 23 | LABEL org.opencontainers.image.documentation "https://github.com/Cyclenerd/google-cloud-gcp-openai-api/blob/master/README.md" 24 | LABEL org.opencontainers.image.source "https://github.com/Cyclenerd/google-cloud-gcp-openai-api/blob/master/Dockerfile" 25 | 26 | # Log Python messages immediately instead of being buffered 27 | ENV PYTHONUNBUFFERED True 28 | 29 | # Disable any healthcheck inherited from the base image 30 | HEALTHCHECK NONE 31 | 32 | # Default HTTP port 33 | EXPOSE 8000 34 | 35 | # Copy app to container 36 | WORKDIR /app 37 | COPY requirements.txt ./ 38 | COPY vertex.py ./ 39 | RUN pip install -r requirements.txt; \ 40 | pip cache purge 41 | CMD ["python", "vertex.py"] 42 | 43 | # If you're reading this and have any feedback on how this image could be 44 | # improved, please open an issue or a pull request so we can discuss it! 45 | # 46 | # https://github.com/Cyclenerd/google-cloud-gcp-openai-api -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenAI API for Google Cloud Vertex AI 2 | 3 | [![Badge: Google Cloud](https://img.shields.io/badge/Google%20Cloud-%234285F4.svg?logo=google-cloud&logoColor=white)](#readme) 4 | [![Badge: OpenAI](https://img.shields.io/badge/OpenAI-%23412991.svg?logo=openai&logoColor=white)](#readme) 5 | [![Badge: Python](https://img.shields.io/badge/Python-3670A0?logo=python&logoColor=ffdd54)](#readme) 6 | 7 | This project is a drop-in replacement REST API for Vertex AI (**PaLM 2, Codey, Gemini**) that is compatible with the OpenAI API specifications. 8 | 9 | Examples: 10 | 11 | | Chat with Gemini in Chatbot UI | Get help from Gemini in VSCode | 12 | |-----------------------------------------------------------|---------------------------------------------------| 13 | | ![Screenshot: Chatbot UI chat](./img/chatbot-ui-chat.png) | ![Screenshot: VSCode chat](./img/vscode-chat.png) | 14 | 15 | This project is inspired by the idea of [LocalAI](https://github.com/go-skynet/LocalAI) 16 | but with the focus on making [Google Cloud Platform Vertex AI PaLM](https://ai.google/) more accessible to anyone. 17 | 18 | A Google Cloud Run service is installed that translates the OpenAI API calls to Vertex AI (PaLM 2, Codey, Gemini). 19 | 20 |

21 | 22 | 23 | Diagram: OpenAI, Google Cloud Run and Vertex AI 24 | 25 |

26 | 27 | Supported OpenAI API services: 28 | 29 | | OpenAI | API | Supported | 30 | |----------------------|------------------------|-----------| 31 | | List models | `/v1/models` | ✅ | 32 | | Chat Completions | `/v1/chat/completions` | ✅ | 33 | | Completions (Legacy) | `/v1/completions` | ❌ | 34 | | Embeddings | `/v1/embeddings` | ❌ | 35 | 36 | The software is developed in [Python](https://www.python.org/) 37 | and based on [FastAPI](https://fastapi.tiangolo.com/) 38 | and [LangChain](https://docs.langchain.com/docs/). 39 | 40 | Everything is designed to be very simple, 41 | so you can easily adjust the source code to your individual needs. 42 | 43 | 44 | ## Step by Step Guide 45 | 46 | A Jupyter notebook [`Vertex_AI_Chat.ipynb`](./Vertex_AI_Chat.ipynb) with step-by-step instructions is prepared. 47 | It will help you to deploy the API backend and [Chatbot UI](https://github.com/mckaywrigley/chatbot-ui) frontend as Google Cloud Run service. 48 | 49 | * [Open in Colab](https://colab.research.google.com/github/Cyclenerd/google-cloud-gcp-openai-api/blob/master/Vertex_AI_Chat.ipynb) 50 | * [Open in Vertex AI Workbench](https://console.cloud.google.com/vertex-ai/workbench/deploy-notebook?download_url=https://raw.githubusercontent.com/Cyclenerd/google-cloud-gcp-openai-api/master/Vertex_AI_Chat.ipynb) 51 | 52 | 53 | ## Deploying to Cloud Run 54 | 55 | Requirements: 56 | 57 | Your user (the one used for deployment) must have proper permissions in the project. 58 | For a fast and hassle-free deployemnt the "Owner" role is recommended. 59 | 60 | In addition, the default compute service account (`[PROJECT_NR]-compute@developer.gserviceaccount.com`) 61 | must have the role "Role Vertex AI User" (`roles/aiplatform.user`). 62 | 63 | 64 | Authenticate: 65 | 66 | ```bash 67 | gcloud auth login 68 | ``` 69 | 70 | Set default project: 71 | 72 | ```bash 73 | gcloud config set project [PROJECT_ID] 74 | ``` 75 | 76 | Run the following script to create a container image 77 | and deploy that container as a public API (which allows unauthenticated calls) in Google Cloud Run: 78 | 79 | ```bash 80 | bash deploy.sh 81 | ``` 82 | 83 | > Note: You can change the generated *fake* OpenAI API key and Google Cloud region with environment variables: 84 | > 85 | > ```bash 86 | > export OPENAI_API_KEY="sk-XYZ" 87 | > export GOOGLE_CLOUD_LOCATION="europe-west1" 88 | > bash deploy.sh 89 | > ``` 90 | 91 | 92 | ## Running Locally 93 | 94 | The software was tested on GNU/Linux and macOS with Python 3.11 and 3.12.3 ([3.12.4](https://github.com/pydantic/pydantic/issues/9609) currently not working). 95 | If you want to use the software under Windows, you must set the environment variables with `set` instead of `export`. 96 | 97 | You should also create a [virtual environment](https://docs.python.org/3/library/venv.html) with the version of Python you want to use, and activate it before proceeding. 98 | 99 | You also need the [Google Cloud CLI](https://cloud.google.com/sdk/docs/install). 100 | The Google Cloud CLI includes the `gcloud` command-line tool. 101 | 102 | Initiate a Python virtual environment and install requirements: 103 | 104 | ```bash 105 | python3 -m venv .venv && \ 106 | source .venv/bin/activate && \ 107 | pip install -r requirements.txt 108 | ``` 109 | 110 | Authenticate: 111 | 112 | ```bash 113 | gcloud auth application-default login 114 | ``` 115 | 116 | Set default project: 117 | 118 | ```bash 119 | gcloud auth application-default set-quota-project [PROJECT_ID] 120 | ``` 121 | 122 | Run with default model: 123 | 124 | ```bash 125 | export DEBUG="True" 126 | export OPENAI_API_KEY="sk-XYZ" 127 | uvicorn vertex:app --reload 128 | ``` 129 | 130 | Example for Windows: 131 | 132 | ```powershell 133 | set DEBUG=True 134 | set OPENAI_API_KEY=sk-XYZ 135 | uvicorn vertex:app --reload 136 | ``` 137 | 138 | Run with Gemini `gemini-pro` model: 139 | 140 | ```bash 141 | export DEBUG="True" 142 | export OPENAI_API_KEY="sk-XYZ" 143 | export MODEL_NAME="gemini-pro" 144 | uvicorn vertex:app --reload 145 | ``` 146 | 147 | Run with Codey `codechat-bison-32k` model: 148 | 149 | ```bash 150 | export DEBUG="True" 151 | export OPENAI_API_KEY="sk-XYZ" 152 | export MODEL_NAME="codechat-bison-32k" 153 | export MAX_OUTPUT_TOKENS="16000" 154 | uvicorn vertex:app --reload 155 | ``` 156 | 157 | The application will now be running on your local computer. 158 | You can access it by opening a web browser and navigating to the following address: 159 | 160 | ```text 161 | http://localhost:8000/ 162 | ``` 163 | 164 | ## Usage 165 | 166 | HTTP request and response formats are consistent with the [OpenAI API](https://platform.openai.com/docs/api-reference/chat/object). 167 | 168 | For example, to generate a chat completion, you can send a POST request to the `/v1/chat/completions` endpoint with the instruction as the request body: 169 | 170 | ```bash 171 | curl --location 'http://[ENDPOINT]/v1/chat/completions' \ 172 | --header 'Content-Type: application/json' \ 173 | --header 'Authorization: Bearer [API-KEY]' \ 174 | --data '{ 175 | "model": "gpt-3.5-turbo", 176 | "messages": [ 177 | { 178 | "role": "user", 179 | "content": "Say this is a test!" 180 | } 181 | ] 182 | }' 183 | ``` 184 | 185 | Response: 186 | 187 | ```json 188 | { 189 | "id": "cmpl-efccdeb3d2a6cfe144fdde11", 190 | "created": 1691577522, 191 | "object": "chat.completion", 192 | "model": "gpt-3.5-turbo", 193 | "usage": { 194 | "prompt_tokens": 0, 195 | "completion_tokens": 0, 196 | "total_tokens": 0 197 | }, 198 | "choices": [ 199 | { 200 | "message": { 201 | "role": "assistant", 202 | "content": "Sure, this is a test." 203 | }, 204 | "finish_reason": "stop", 205 | "index": 0 206 | } 207 | ] 208 | } 209 | ``` 210 | 211 | ### Bruno API client 212 | 213 | ![Screenshot: Bruno API client](./img/bruno.png) 214 | 215 | Download export for [Bruno](https://www.usebruno.com/) API client: [`bruno-export.json`](./bruno-export.json) 216 | 217 | ## Configuration 218 | 219 | The configuration of the software can be done with environment variables. 220 | 221 | ![Screenshot: Google Cloud run](./img/cloud-run-env.png) 222 | 223 | The following variables with default values exist: 224 | 225 | | Variable | Default | Description | 226 | |-------------------------|------------------------|-------------| 227 | | DEBUG | False | Show debug messages that help during development. | 228 | | GOOGLE_CLOUD_LOCATION | us-central1 | [Google Cloud Platform region](https://gcloud-compute.com/regions.html) for API calls. | 229 | | GOOGLE_CLOUD_PROJECT_ID | [DEFAULT_AUTH_PROJECT] | Identifier for your project. If not specified, the project of authentication is used. | 230 | | HOST | 0.0.0.0 | Bind socket to this host. | 231 | | MAX_OUTPUT_TOKENS | 512 | Token limit determines the maximum amount of text output from one prompt. Can be overridden by the end user as required by the OpenAI API specification. | 232 | | MODEL_NAME | chat-bison | One of the [foundation models](https://cloud.google.com/vertex-ai/docs/generative-ai/learn/models#foundation_models) that are available in Vertex AI. | 233 | | OPENAI_API_KEY | sk-[RANDOM_HEX] | Self-generated *fake* OpenAI API key used for authentication against the application. | 234 | | PORT | 8000 | Bind socket to this port. | 235 | | TEMPERATURE | 0.2 | Sampling temperature, it controls the degree of randomness in token selection. Can be overridden by the end user as required by the OpenAI API specification. | 236 | | TOP_K | 40 | How the model selects tokens for output, the next token is selected from. | 237 | | TOP_P | 0.8 | Tokens are selected from most probable to least until the sum of their. Can be overridden by the end user as required by the OpenAI API specification. | 238 | 239 | ### OpenAI Client Library 240 | 241 | If your application uses [client libraries](https://github.com/openai/openai-python) provided by OpenAI, 242 | you only need to modify the `OPENAI_API_BASE` environment variable to match your Google Cloud Run endpoint URL: 243 | 244 | ```bash 245 | export OPENAI_API_BASE="https://openai-api-vertex-XYZ.a.run.app/v1" 246 | python your_openai_app.py 247 | ``` 248 | 249 | ### Chatbot UI 250 | 251 | When deploying the [Chatbot UI](https://github.com/mckaywrigley/chatbot-ui) application, 252 | the following environment variables must be set: 253 | 254 | | Variable | Value | 255 | |-----------------|-------------------------------------| 256 | | OPENAI_API_KEY | API key generated during deployment | 257 | | OPENAI_API_HOST | Google Cloud Run URL | 258 | 259 | ![Screenshot: Chatbot UI container](./img/chatbot-ui-env.png) 260 | 261 | #### Deploying Chatbot UI to Cloud Run 262 | 263 | Run the following script to create a container image from the GitHub source code 264 | and deploy that container as a public website (which allows unauthenticated calls) in Google Cloud Run: 265 | 266 | ```bash 267 | export OPENAI_API_KEY="sk-XYZ" 268 | export OPENAI_API_HOST="https://openai-api-vertex-XYZ.a.run.app" 269 | bash chatbot-ui.sh 270 | ``` 271 | 272 | ### Chatbox 273 | 274 | Set the following [Chatbox](https://chatboxai.app/) settings: 275 | 276 | | Setting | Value | 277 | |----------------|-------------------------------------| 278 | | AI Provider | OpenAI API | 279 | | OpenAI API Key | API key generated during deployment | 280 | | API Host | Google Cloud Run URL | 281 | 282 | ![Screenshot: Chatbot UI container](./img/chatbox-settings.png) 283 | 284 | ### VSCode-OpenAI 285 | 286 | The [VSCode-OpenAI extension](https://marketplace.visualstudio.com/items?itemName=AndrewButson.vscode-openai) is a powerful and versatile tool designed to integrate OpenAI features seamlessly into your code editor. 287 | 288 | To activate the setup, you have two options: 289 | 290 | * either use the command "vscode-openai.configuration.show.quickpick" or 291 | * access it through the vscode-openai Status Bar located at the bottom left corner of VSCode. 292 | 293 | ![Screenshot: VSCode settings](./img/vscode-settings.png) 294 | 295 | Select `openai.com` and enter the Google Cloud Run URL with `/v1` during setup. 296 | 297 | ### ChatGPT Discord Bot 298 | 299 | When deploying the [Discord Bot](https://github.com/openai/gpt-discord-bot) application, 300 | the following environment variables must be set: 301 | 302 | | Variable | Value | 303 | |-----------------|-------------------------------------| 304 | | OPENAI_API_KEY | API key generated during deployment | 305 | | OPENAI_API_BASE | Google Cloud Run URL with `/v1` | 306 | 307 | ### ChatGPT in Slack 308 | 309 | When deploying the [ChatGPT in Slack](https://github.com/seratch/ChatGPT-in-Slack) application, 310 | the following environment variables must be set: 311 | 312 | | Variable | Value | 313 | |-----------------|-------------------------------------| 314 | | OPENAI_API_KEY | API key generated during deployment | 315 | | OPENAI_API_BASE | Google Cloud Run URL with `/v1` | 316 | 317 | ### ChatGPT Telegram Bot 318 | 319 | When deploying the [ChatGPT Telegram Bot](https://github.com/karfly/chatgpt_telegram_bot) application, 320 | the following environment variables must be set: 321 | 322 | | Variable | Value | 323 | |-----------------|-------------------------------------| 324 | | OPENAI_API_KEY | API key generated during deployment | 325 | | OPENAI_API_BASE | Google Cloud Run URL with `/v1` | 326 | 327 | ## Contributing 328 | 329 | Have a patch that will benefit this project? 330 | Awesome! Follow these steps to have it accepted. 331 | 332 | 1. Please read [how to contribute](CONTRIBUTING.md). 333 | 1. Fork this Git repository and make your changes. 334 | 1. Create a Pull Request. 335 | 1. Incorporate review feedback to your changes. 336 | 1. Accepted! 337 | 338 | 339 | ## License 340 | 341 | All files in this repository are under the [Apache License, Version 2.0](LICENSE) unless noted otherwise. -------------------------------------------------------------------------------- /Vertex_AI_Chat.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": { 7 | "id": "jbl5pojWzAD0" 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "# Copyright 2023-2024 Nils Knieling\n", 12 | "#\n", 13 | "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", 14 | "# you may not use this file except in compliance with the License.\n", 15 | "# You may obtain a copy of the License at\n", 16 | "#\n", 17 | "# https://www.apache.org/licenses/LICENSE-2.0\n", 18 | "#\n", 19 | "# Unless required by applicable law or agreed to in writing, software\n", 20 | "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", 21 | "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", 22 | "# See the License for the specific language governing permissions and\n", 23 | "# limitations under the License." 24 | ] 25 | }, 26 | { 27 | "cell_type": "markdown", 28 | "metadata": { 29 | "id": "aVjTZHl4zKKi" 30 | }, 31 | "source": [ 32 | "# 💬 Chat with Vertex AI\n", 33 | "\n", 34 | "[![Open in Colab](https://img.shields.io/badge/Open%20in%20Colab-%23F9AB00.svg?logo=googlecolab&logoColor=white)](https://colab.research.google.com/github/Cyclenerd/google-cloud-gcp-openai-api/blob/master/Vertex_AI_Chat.ipynb)\n", 35 | "[![Open in Vertex AI Workbench](https://img.shields.io/badge/Open%20in%20Vertex%20AI%20Workbench-%234285F4.svg?logo=googlecloud&logoColor=white)](https://console.cloud.google.com/vertex-ai/workbench/deploy-notebook?download_url=https://raw.githubusercontent.com/Cyclenerd/google-cloud-gcp-openai-api/master/Vertex_AI_Chat.ipynb)\n", 36 | "[![View on GitHub](https://img.shields.io/badge/View%20on%20GitHub-181717.svg?logo=github&logoColor=white)](https://github.com/Cyclenerd/google-cloud-gcp-openai-api/blob/master/Vertex_AI_Chat.ipynb)\n", 37 | "\n", 38 | "This notebook describes how to deploy the [OpenAI API for Google Cloud Vertex AI](https://github.com/Cyclenerd/google-cloud-gcp-openai-api#readme) API backend and [Chatbot UI](https://github.com/mckaywrigley/chatbot-ui) frontend as Google Cloud Run service.\n", 39 | "\n", 40 | "> By default, [**Google Cloud does not use Customer Data to train its foundation models**](https://cloud.google.com/vertex-ai/docs/generative-ai/data-governance#foundation_model_development) as part of Google Cloud's AI/ML Privacy Commitment. More details about how Google processes data can also be found in [Google's Customer Data Processing Addendum (CDPA)](https://cloud.google.com/terms/data-processing-addendum)." 41 | ] 42 | }, 43 | { 44 | "cell_type": "markdown", 45 | "metadata": { 46 | "id": "ofbTwaIazX4Y" 47 | }, 48 | "source": [ 49 | "## Setup\n" 50 | ] 51 | }, 52 | { 53 | "cell_type": "markdown", 54 | "metadata": { 55 | "id": "a3rLBQnqtAQv" 56 | }, 57 | "source": [ 58 | "### Configuration\n", 59 | "\n", 60 | "Regions: \n", 61 | "\n", 62 | "Model: One of the [foundation models](https://cloud.google.com/vertex-ai/docs/generative-ai/learn/models#foundation_models) that are available in Vertex AI.\n", 63 | "\n", 64 | "Key `openai_key` is used for authentication the chat frondend against the API backend application. It is not a real OpenAI API key." 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": null, 70 | "metadata": { 71 | "cellView": "form", 72 | "id": "xZK1M-0OK6GD" 73 | }, 74 | "outputs": [], 75 | "source": [ 76 | "# @markdown ✏️ Replace the placeholder text below:\n", 77 | "\n", 78 | "# Please fill in these values.\n", 79 | "project_id = \"your-google-cloud-project\" # @param {type:\"string\"}\n", 80 | "region = \"europe-west1\" # @param {type:\"string\"}\n", 81 | "artifact_repository = \"docker-openai-api\" # @param {type:\"string\"}\n", 82 | "model = \"codechat-bison\" # @param {type:\"string\"}\n", 83 | "openai_key = \"sk-XYZ-replace-with-good-key\" # @param {type:\"string\"}\n", 84 | "\n", 85 | "# Quick input validations.\n", 86 | "assert project_id, \"⚠️ Please provide a Google Cloud project ID\"\n", 87 | "assert region, \"⚠️ Please provide a Google Cloud region\"\n", 88 | "assert artifact_repository, \"⚠️ Please provide a Google Cloud Artifact repository name\"\n", 89 | "assert model, \"⚠️ Please provide a valid model\"\n", 90 | "assert openai_key, \"⚠️ Please provide a valid key for authentication\"\n", 91 | "\n", 92 | "# Configure gcloud.\n", 93 | "!gcloud config set project \"{project_id}\"\n", 94 | "!gcloud config set storage/parallel_composite_upload_enabled \"True\"\n", 95 | "\n", 96 | "print(f\"\\n☁️ Google Cloud project ID: {project_id}\")\n", 97 | "print(\"☑️ Done\")" 98 | ] 99 | }, 100 | { 101 | "cell_type": "markdown", 102 | "metadata": { 103 | "id": "yq1eutyctGO9" 104 | }, 105 | "source": [ 106 | "### Authenticate" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": null, 112 | "metadata": { 113 | "cellView": "form", 114 | "id": "rriTKU3R1PdS" 115 | }, 116 | "outputs": [], 117 | "source": [ 118 | "#@markdown #### (Colab only!) Authenticate your Google Cloud Account\n", 119 | "\n", 120 | "# Authenticate gcloud.\n", 121 | "from google.colab import auth\n", 122 | "auth.authenticate_user()\n", 123 | "\n", 124 | "print(\"☑️ OK\")" 125 | ] 126 | }, 127 | { 128 | "cell_type": "code", 129 | "execution_count": null, 130 | "metadata": { 131 | "cellView": "form", 132 | "id": "iv07-mmUKrNJ" 133 | }, 134 | "outputs": [], 135 | "source": [ 136 | "#@markdown #### Check authenticated user and project\n", 137 | "current_user = !gcloud auth list \\\n", 138 | " --filter=\"status:ACTIVE\" \\\n", 139 | " --format=\"value(account)\" \\\n", 140 | " --quiet\n", 141 | "\n", 142 | "current_user = current_user[0]\n", 143 | "print(f\"Current user: {current_user}\")\n", 144 | "\n", 145 | "project_number = !gcloud projects list \\\n", 146 | " --filter=\"projectId:{project_id}\" \\\n", 147 | " --format=\"value(PROJECT_NUMBER)\" \\\n", 148 | " --quiet\n", 149 | "\n", 150 | "project_number = project_number[0]\n", 151 | "print(f\"Project number: {project_number}\")" 152 | ] 153 | }, 154 | { 155 | "cell_type": "code", 156 | "execution_count": null, 157 | "metadata": {}, 158 | "outputs": [], 159 | "source": [ 160 | "#@markdown #### Add IAM policy binding for default compute service account\n", 161 | "\n", 162 | "!gcloud projects add-iam-policy-binding \"{project_id}\" \\\n", 163 | " --member=\"serviceAccount:{project_number}-compute@developer.gserviceaccount.com\" \\\n", 164 | " --role=\"roles/aiplatform.user\" \\\n", 165 | " --quiet\n", 166 | "\n", 167 | "print(\"☑️ Done\")" 168 | ] 169 | }, 170 | { 171 | "cell_type": "markdown", 172 | "metadata": { 173 | "id": "RNdTod9pspSz" 174 | }, 175 | "source": [ 176 | "### APIs" 177 | ] 178 | }, 179 | { 180 | "cell_type": "code", 181 | "execution_count": null, 182 | "metadata": { 183 | "cellView": "form", 184 | "id": "ODuKfAD6Ku2M" 185 | }, 186 | "outputs": [], 187 | "source": [ 188 | "#@markdown #### Enable Google Cloud APIs\n", 189 | "#@markdown > Only necessary if the APIs are not yet activated in the project!\n", 190 | "\n", 191 | "# Enable APIs\n", 192 | "my_google_apis = [\n", 193 | " \"aiplatform.googleapis.com\",\n", 194 | " \"run.googleapis.com\",\n", 195 | " \"artifactregistry.googleapis.com\",\n", 196 | " \"cloudbuild.googleapis.com\",\n", 197 | " \"containeranalysis.googleapis.com\",\n", 198 | " \"containerscanning.googleapis.com\",\n", 199 | "]\n", 200 | "\n", 201 | "for api in my_google_apis :\n", 202 | " print(f\"Enable API: {api}\")\n", 203 | " !gcloud services enable \"{api}\" \\\n", 204 | " --project=\"{project_id}\" \\\n", 205 | " --quiet\n", 206 | "\n", 207 | "print(\"☑️ OK\")" 208 | ] 209 | }, 210 | { 211 | "cell_type": "markdown", 212 | "metadata": { 213 | "id": "3trFNcHdz2JS" 214 | }, 215 | "source": [ 216 | "### Registry" 217 | ] 218 | }, 219 | { 220 | "cell_type": "code", 221 | "execution_count": null, 222 | "metadata": { 223 | "cellView": "form", 224 | "id": "kASHGe6Vz5vR" 225 | }, 226 | "outputs": [], 227 | "source": [ 228 | "#@markdown #### Create Artifact Registry repositoriy for Docker cointainer images\n", 229 | "#@markdown > Only necessary if the repositoriy does not already exist in the project and region!\n", 230 | "\n", 231 | "!gcloud artifacts repositories create \"{artifact_repository}\" \\\n", 232 | " --repository-format=\"docker\"\\\n", 233 | " --description=\"Docker contrainer registry\" \\\n", 234 | " --location=\"{region}\" \\\n", 235 | " --project=\"{project_id}\" \\\n", 236 | " --quiet\n", 237 | "\n", 238 | "print(\"☑️ Done\")" 239 | ] 240 | }, 241 | { 242 | "cell_type": "markdown", 243 | "metadata": { 244 | "id": "rPv9RMgPn5fu" 245 | }, 246 | "source": [ 247 | "## Deploy" 248 | ] 249 | }, 250 | { 251 | "cell_type": "markdown", 252 | "metadata": { 253 | "id": "8GPSgec-n-rO" 254 | }, 255 | "source": [ 256 | "### Backend" 257 | ] 258 | }, 259 | { 260 | "cell_type": "code", 261 | "execution_count": null, 262 | "metadata": { 263 | "cellView": "form", 264 | "id": "2d6E978LnTFv" 265 | }, 266 | "outputs": [], 267 | "source": [ 268 | "#@markdown #### Build Docker cointainer for API backend\n", 269 | "\n", 270 | "backend_git = \"https://github.com/Cyclenerd/google-cloud-gcp-openai-api.git\" # @param {type:\"string\"}\n", 271 | "backend_git_rev = \"master\" # @param {type:\"string\"}\n", 272 | "assert backend_git, \"⚠️ Please provide a Git repo\"\n", 273 | "assert backend_git_rev, \"⚠️ Please provide a Git revision\"\n", 274 | "\n", 275 | "!gcloud builds submit \"{backend_git}\" \\\n", 276 | " --git-source-revision=\"{backend_git_rev}\" \\\n", 277 | " --tag=\"{region}-docker.pkg.dev/{project_id}/{artifact_repository}/vertex:latest\" \\\n", 278 | " --timeout=\"1h\" \\\n", 279 | " --region=\"{region}\" \\\n", 280 | " --default-buckets-behavior=\"regional-user-owned-bucket\" \\\n", 281 | " --quiet" 282 | ] 283 | }, 284 | { 285 | "cell_type": "code", 286 | "execution_count": null, 287 | "metadata": { 288 | "cellView": "form", 289 | "id": "tJTs9GDNnU_X" 290 | }, 291 | "outputs": [], 292 | "source": [ 293 | "#@markdown #### Deploy Cloud Run service with API backend\n", 294 | "\n", 295 | "!gcloud run deploy \"openai-api-vertex\" \\\n", 296 | " --image=\"{region}-docker.pkg.dev/{project_id}/{artifact_repository}/vertex:latest\" \\\n", 297 | " --description=\"OpenAI API for Google Cloud Vertex AI\" \\\n", 298 | " --region=\"{region}\" \\\n", 299 | " --set-env-vars=\"OPENAI_API_KEY={openai_key},GOOGLE_CLOUD_LOCATION={region},MODEL_NAME={model}\" \\\n", 300 | " --max-instances=4 \\\n", 301 | " --allow-unauthenticated \\\n", 302 | " --quiet" 303 | ] 304 | }, 305 | { 306 | "cell_type": "markdown", 307 | "metadata": { 308 | "id": "TnCpVBv0qPRq" 309 | }, 310 | "source": [ 311 | "### Frontend\n", 312 | "\n", 313 | "* [Chatbot UI](https://github.com/mckaywrigley/chatbot-ui):\n", 314 | " * Git: `https://github.com/mckaywrigley/chatbot-ui.git`\n", 315 | " * Rev: `main`\n", 316 | "* [Chatbot UI fork](https://github.com/Cyclenerd/chatbot-ui) with less OpenAI branding:\n", 317 | " * Git: `https://github.com/Cyclenerd/chatbot-ui.git`\n", 318 | " * Rev: `google`\n", 319 | "\n" 320 | ] 321 | }, 322 | { 323 | "cell_type": "code", 324 | "execution_count": null, 325 | "metadata": { 326 | "cellView": "form", 327 | "id": "2L1IKPAHqRgM" 328 | }, 329 | "outputs": [], 330 | "source": [ 331 | "#@markdown #### Build Docker cointainer for chat frontend\n", 332 | "\n", 333 | "frontend_git = \"https://github.com/Cyclenerd/chatbot-ui.git\" # @param {type:\"string\"}\n", 334 | "frontend_git_rev = \"google\" # @param {type:\"string\"}\n", 335 | "\n", 336 | "assert frontend_git, \"⚠️ Please provide a Git repo\"\n", 337 | "assert frontend_git_rev, \"⚠️ Please provide a Git revision\"\n", 338 | "\n", 339 | "!gcloud builds submit \"{frontend_git}\" \\\n", 340 | " --git-source-revision=\"{frontend_git_rev}\" \\\n", 341 | " --tag=\"{region}-docker.pkg.dev/{project_id}/{artifact_repository}/chatbot-ui:main\" \\\n", 342 | " --timeout=\"1h\" \\\n", 343 | " --region=\"{region}\" \\\n", 344 | " --default-buckets-behavior=\"regional-user-owned-bucket\" \\\n", 345 | " --quiet" 346 | ] 347 | }, 348 | { 349 | "cell_type": "code", 350 | "execution_count": null, 351 | "metadata": { 352 | "cellView": "form", 353 | "id": "X1cMzomWqxju" 354 | }, 355 | "outputs": [], 356 | "source": [ 357 | "#@markdown #### Deploy Cloud Run service with chat frontend\n", 358 | "\n", 359 | "# @markdown ✏️ Replace the Cloud Run service URL from the backend below:\n", 360 | "\n", 361 | "backend_url = \"https://openai-api-vertex-XYZ-AB.a.run.app\" # @param {type:\"string\"}\n", 362 | "assert backend_url, \"⚠️ Please provide a Cloud Run backend URL\"\n", 363 | "\n", 364 | "!gcloud run deploy \"chatbot-ui\" \\\n", 365 | " --image=\"{region}-docker.pkg.dev/{project_id}/{artifact_repository}/chatbot-ui:main\" \\\n", 366 | " --description=\"Chatbot UI\" \\\n", 367 | " --region=\"{region}\" \\\n", 368 | " --set-env-vars=\"OPENAI_API_KEY={openai_key},OPENAI_API_HOST={backend_url}\" \\\n", 369 | " --max-instances=2 \\\n", 370 | " --allow-unauthenticated \\\n", 371 | " --quiet" 372 | ] 373 | }, 374 | { 375 | "cell_type": "markdown", 376 | "metadata": { 377 | "id": "Z403ny7WnmaZ" 378 | }, 379 | "source": [ 380 | "## 🗑️ Clean up\n", 381 | "\n", 382 | "If you don't need the infrastructure anymore, you can delete it with the following snippets." 383 | ] 384 | }, 385 | { 386 | "cell_type": "code", 387 | "execution_count": null, 388 | "metadata": { 389 | "cellView": "form", 390 | "id": "jnpf_oOerltB" 391 | }, 392 | "outputs": [], 393 | "source": [ 394 | "#@markdown ### Delete Cloud Run service with chat frontend\n", 395 | "\n", 396 | "!gcloud run services delete \"chatbot-ui\" \\\n", 397 | " --region=\"{region}\" \\\n", 398 | " --project=\"{project_id}\" \\\n", 399 | " --quiet" 400 | ] 401 | }, 402 | { 403 | "cell_type": "code", 404 | "execution_count": null, 405 | "metadata": { 406 | "cellView": "form", 407 | "id": "rZNIMYbrsr8L" 408 | }, 409 | "outputs": [], 410 | "source": [ 411 | "#@markdown ### Delete Cloud Run service with API backend\n", 412 | "\n", 413 | "!gcloud run services delete \"openai-api-vertex\" \\\n", 414 | " --region=\"{region}\" \\\n", 415 | " --project=\"{project_id}\" \\\n", 416 | " --quiet" 417 | ] 418 | }, 419 | { 420 | "cell_type": "code", 421 | "execution_count": null, 422 | "metadata": { 423 | "cellView": "form", 424 | "id": "XxOVb5NZjT4y" 425 | }, 426 | "outputs": [], 427 | "source": [ 428 | "#@markdown ### Delete Artifact Registry repositoriy\n", 429 | "!gcloud artifacts repositories delete \"{artifact_repository}\" \\\n", 430 | " --location=\"{region}\" \\\n", 431 | " --project=\"{project_id}\" \\\n", 432 | " --quiet" 433 | ] 434 | } 435 | ], 436 | "metadata": { 437 | "colab": { 438 | "provenance": [], 439 | "toc_visible": true 440 | }, 441 | "kernelspec": { 442 | "display_name": "Python 3", 443 | "name": "python3" 444 | }, 445 | "language_info": { 446 | "name": "python" 447 | } 448 | }, 449 | "nbformat": 4, 450 | "nbformat_minor": 0 451 | } 452 | -------------------------------------------------------------------------------- /bruno-export.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "OpenAI -> Vertex AI", 3 | "version": "1", 4 | "items": [ 5 | { 6 | "type": "http", 7 | "name": "Versions", 8 | "seq": 1, 9 | "request": { 10 | "url": "{{baseUrl}}/", 11 | "method": "GET", 12 | "headers": [], 13 | "body": { 14 | "mode": "none", 15 | "formUrlEncoded": [], 16 | "multipartForm": [] 17 | }, 18 | "auth": { 19 | "mode": "none", 20 | "basic": { 21 | "username": "", 22 | "password": "" 23 | }, 24 | "bearer": { 25 | "token": "" 26 | } 27 | }, 28 | "script": {}, 29 | "vars": {}, 30 | "assertions": [], 31 | "tests": "", 32 | "query": [] 33 | } 34 | }, 35 | { 36 | "type": "http", 37 | "name": "Models", 38 | "seq": 2, 39 | "request": { 40 | "url": "{{baseUrl}}/v1/models", 41 | "method": "GET", 42 | "headers": [], 43 | "body": { 44 | "mode": "none", 45 | "formUrlEncoded": [], 46 | "multipartForm": [] 47 | }, 48 | "auth": { 49 | "mode": "none", 50 | "basic": { 51 | "username": "", 52 | "password": "" 53 | }, 54 | "bearer": { 55 | "token": "" 56 | } 57 | }, 58 | "script": {}, 59 | "vars": {}, 60 | "assertions": [], 61 | "tests": "", 62 | "query": [] 63 | } 64 | }, 65 | { 66 | "type": "http", 67 | "name": "Chat", 68 | "seq": 3, 69 | "request": { 70 | "url": "{{baseUrl}}/v1/chat/completions", 71 | "method": "POST", 72 | "headers": [], 73 | "body": { 74 | "mode": "json", 75 | "json": "{\n \"model\": \"gpt-3.5-turbo\",\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"Say this is a test!\"\n }\n ]\n}", 76 | "formUrlEncoded": [], 77 | "multipartForm": [] 78 | }, 79 | "auth": { 80 | "mode": "bearer", 81 | "basic": { 82 | "username": "", 83 | "password": "" 84 | }, 85 | "bearer": { 86 | "token": "{{openAiKey}}" 87 | } 88 | }, 89 | "script": {}, 90 | "vars": {}, 91 | "assertions": [], 92 | "tests": "", 93 | "query": [] 94 | } 95 | } 96 | ], 97 | "activeEnvironmentUid": "v8GNrlp7tgKwaZxgnKG5l", 98 | "environments": [ 99 | { 100 | "variables": [ 101 | { 102 | "name": "baseUrl", 103 | "value": "http://127.0.0.1:8000", 104 | "enabled": true, 105 | "secret": false, 106 | "type": "text" 107 | }, 108 | { 109 | "name": "openAiKey", 110 | "value": "", 111 | "enabled": true, 112 | "secret": true, 113 | "type": "text" 114 | } 115 | ], 116 | "name": "localhost (OpenAI -> Vertex AI)" 117 | } 118 | ] 119 | } -------------------------------------------------------------------------------- /chatbot-ui.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2023-2024 Nils Knieling 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # Check commands 18 | command -v gcloud >/dev/null 2>&1 || { echo >&2 "❌ Google Cloud CLI 'gcloud' it's not installed. Please install!"; exit 1; } 19 | 20 | # Google Cloud project 21 | MY_PROJECT_ID=$(gcloud config get-value project --quiet) 22 | echo "Google Cloud project ID: $MY_PROJECT_ID" 23 | 24 | # Set location 25 | MY_GOOGLE_CLOUD_LOCATION=${GOOGLE_CLOUD_LOCATION:-"us-central1"} 26 | echo "Google Cloud location: $MY_GOOGLE_CLOUD_LOCATION" 27 | 28 | # Set API key 29 | MY_OPENAI_API_KEY=${OPENAI_API_KEY:-""} 30 | if [ -z "$MY_OPENAI_API_KEY" ]; then 31 | echo "❌ API key 'OPENAI_API_KEY' missing!" 32 | exit 1 33 | fi 34 | echo "API key: $MY_OPENAI_API_KEY" 35 | 36 | # Set API host 37 | MY_OPENAI_API_HOST=${OPENAI_API_HOST:-""} 38 | if [ -z "$MY_OPENAI_API_HOST" ]; then 39 | echo "❌ API host 'OPENAI_API_HOST' missing!" 40 | exit 1 41 | fi 42 | echo "API host: $MY_OPENAI_API_HOST" 43 | 44 | gcloud builds submit "https://github.com/mckaywrigley/chatbot-ui.git" \ 45 | --git-source-revision="main" \ 46 | --tag="$MY_GOOGLE_CLOUD_LOCATION-docker.pkg.dev/$MY_PROJECT_ID/docker-openai-api/chatbot-ui:latest" \ 47 | --timeout="1h" \ 48 | --region="$MY_GOOGLE_CLOUD_LOCATION" \ 49 | --default-buckets-behavior="regional-user-owned-bucket" \ 50 | --quiet 51 | 52 | # Deploy Cloud Run service 53 | gcloud run deploy "chatbot-ui" \ 54 | --image="$MY_GOOGLE_CLOUD_LOCATION-docker.pkg.dev/$MY_PROJECT_ID/docker-openai-api/chatbot-ui:latest" \ 55 | --description="Chatbot UI" \ 56 | --region="$MY_GOOGLE_CLOUD_LOCATION" \ 57 | --set-env-vars="OPENAI_API_KEY=$MY_OPENAI_API_KEY,OPENAI_API_HOST=$MY_OPENAI_API_HOST" \ 58 | --max-instances=2 \ 59 | --allow-unauthenticated \ 60 | --quiet -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2023-2024 Nils Knieling 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # Check commands 18 | command -v gcloud >/dev/null 2>&1 || { echo >&2 "❌ Google Cloud CLI 'gcloud' it's not installed. Please install!"; exit 1; } 19 | command -v openssl >/dev/null 2>&1 || { echo >&2 "❌ OpenSSL 'openssl' it's not installed. Please install!"; exit 1; } 20 | 21 | # Check Dockerfile 22 | if [ ! -f "Dockerfile" ]; then 23 | echo "❌ Dockerfile does not exist!" 24 | exit 1 25 | fi 26 | 27 | # Google Cloud project 28 | MY_PROJECT_ID=$(gcloud config get-value project --quiet) 29 | echo "Google Cloud project ID: $MY_PROJECT_ID" 30 | 31 | # Enable APIs 32 | echo "Enable APIs..." 33 | # Define the list of GCP services to enable 34 | MY_GCP_SERVICES=( 35 | 'aiplatform.googleapis.com' # Vertex AI Platform 36 | 'run.googleapis.com' # Cloud Run 37 | 'artifactregistry.googleapis.com' # Artifact Registry 38 | 'cloudbuild.googleapis.com' # Cloud Build 39 | 'containeranalysis.googleapis.com' # Container Analysis 40 | 'containerscanning.googleapis.com' # Container Scanning 41 | ) 42 | # Enable each GCP service 43 | for MY_GCP_SERVICE in "${MY_GCP_SERVICES[@]}"; do 44 | gcloud services enable "$MY_GCP_SERVICE" --quiet 45 | done 46 | 47 | # Generate a random string for the OpenAI API key 48 | MY_RANDOM=$(openssl rand -hex 21) 49 | # Set the OpenAI API key from environment variable 50 | # or generate one if not set 51 | MY_OPENAI_API_KEY=${OPENAI_API_KEY:-"sk-$MY_RANDOM"} 52 | echo 53 | # Output a line with API key 54 | echo "🔑 API key: $MY_OPENAI_API_KEY" 55 | # Check if macOS clipboard is available 56 | if command -v pbcopy >/dev/null 2>&1; then 57 | # Copy API key to macOS clipboard 58 | echo "$MY_OPENAI_API_KEY" | pbcopy 59 | # Output a line with emoji 60 | echo "(📋 Copied to macOS clipboard)" 61 | fi 62 | echo 63 | 64 | # Set location 65 | MY_GOOGLE_CLOUD_LOCATION=${GOOGLE_CLOUD_LOCATION:-"us-central1"} 66 | echo "Google Cloud location: $MY_GOOGLE_CLOUD_LOCATION" 67 | 68 | # Create Artifact Registry for Docker cointainer images 69 | gcloud artifacts repositories create "docker-openai-api" \ 70 | --repository-format="docker"\ 71 | --description="Docker contrainer registry for OpenAI API" \ 72 | --location="$MY_GOOGLE_CLOUD_LOCATION" \ 73 | --quiet 74 | 75 | # Create container 76 | gcloud builds submit \ 77 | --tag="$MY_GOOGLE_CLOUD_LOCATION-docker.pkg.dev/$MY_PROJECT_ID/docker-openai-api/vertex:latest" \ 78 | --timeout="30m" \ 79 | --region="$MY_GOOGLE_CLOUD_LOCATION" \ 80 | --default-buckets-behavior="regional-user-owned-bucket" \ 81 | --quiet 82 | 83 | # Deploy Cloud Run service 84 | gcloud run deploy "openai-api-vertex" \ 85 | --image="$MY_GOOGLE_CLOUD_LOCATION-docker.pkg.dev/$MY_PROJECT_ID/docker-openai-api/vertex:latest" \ 86 | --description="OpenAI API for Google Cloud Vertex AI" \ 87 | --region="$MY_GOOGLE_CLOUD_LOCATION" \ 88 | --set-env-vars="OPENAI_API_KEY=$MY_OPENAI_API_KEY,GOOGLE_CLOUD_LOCATION=$MY_GOOGLE_CLOUD_LOCATION" \ 89 | --max-instances=4 \ 90 | --allow-unauthenticated \ 91 | --quiet -------------------------------------------------------------------------------- /img/bruno.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cyclenerd/google-cloud-gcp-openai-api/f060659f349976cf8420d43f29b87ab555c3afe5/img/bruno.png -------------------------------------------------------------------------------- /img/chatbot-ui-chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cyclenerd/google-cloud-gcp-openai-api/f060659f349976cf8420d43f29b87ab555c3afe5/img/chatbot-ui-chat.png -------------------------------------------------------------------------------- /img/chatbot-ui-env.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cyclenerd/google-cloud-gcp-openai-api/f060659f349976cf8420d43f29b87ab555c3afe5/img/chatbot-ui-env.png -------------------------------------------------------------------------------- /img/chatbox-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cyclenerd/google-cloud-gcp-openai-api/f060659f349976cf8420d43f29b87ab555c3afe5/img/chatbox-settings.png -------------------------------------------------------------------------------- /img/cloud-run-env.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cyclenerd/google-cloud-gcp-openai-api/f060659f349976cf8420d43f29b87ab555c3afe5/img/cloud-run-env.png -------------------------------------------------------------------------------- /img/openai-api-cloud-run-vertex-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cyclenerd/google-cloud-gcp-openai-api/f060659f349976cf8420d43f29b87ab555c3afe5/img/openai-api-cloud-run-vertex-dark.png -------------------------------------------------------------------------------- /img/openai-api-cloud-run-vertex.excalidraw: -------------------------------------------------------------------------------- 1 | { 2 | "type": "excalidraw", 3 | "version": 2, 4 | "source": "https://excalidraw.com", 5 | "elements": [ 6 | { 7 | "type": "rectangle", 8 | "version": 504, 9 | "versionNonce": 196568429, 10 | "isDeleted": false, 11 | "id": "phmuAq8vpDsXYCEB5Hyfz", 12 | "fillStyle": "solid", 13 | "strokeWidth": 1, 14 | "strokeStyle": "solid", 15 | "roughness": 1, 16 | "opacity": 100, 17 | "angle": 0, 18 | "x": 887.36328125, 19 | "y": 299.546875, 20 | "strokeColor": "#1e1e1e", 21 | "backgroundColor": "#b2f2bb", 22 | "width": 615.76171875, 23 | "height": 423.37890625, 24 | "seed": 179892255, 25 | "groupIds": [ 26 | "gdmjNyUG7ie4haKK7uVQ1" 27 | ], 28 | "frameId": null, 29 | "roundness": { 30 | "type": 3 31 | }, 32 | "boundElements": [ 33 | { 34 | "id": "ZB7f3_0kNeKNzY62ROKGW", 35 | "type": "arrow" 36 | }, 37 | { 38 | "id": "AhuD1ss3X-UEsEfUm80hM", 39 | "type": "arrow" 40 | }, 41 | { 42 | "id": "dcwo5gS6oN7kj61GzoiqW", 43 | "type": "arrow" 44 | }, 45 | { 46 | "id": "uzgLVQ61-6x7EkOzOaz5N", 47 | "type": "arrow" 48 | } 49 | ], 50 | "updated": 1692114356579, 51 | "link": null, 52 | "locked": false 53 | }, 54 | { 55 | "type": "ellipse", 56 | "version": 480, 57 | "versionNonce": 2095262701, 58 | "isDeleted": false, 59 | "id": "k06tPSuezwyYZCVJMwk6w", 60 | "fillStyle": "solid", 61 | "strokeWidth": 1, 62 | "strokeStyle": "solid", 63 | "roughness": 1, 64 | "opacity": 100, 65 | "angle": 0, 66 | "x": 1360.3736249867022, 67 | "y": 549.6320999692309, 68 | "strokeColor": "#1864ab", 69 | "backgroundColor": "#669df6", 70 | "width": 8.629783861565102, 71 | "height": 8.629783861565102, 72 | "seed": 1420187409, 73 | "groupIds": [ 74 | "w7WA9I2tiAn8fFk-ZQBN_", 75 | "XzRtSvB_V9Arw_Yz51yof", 76 | "gdmjNyUG7ie4haKK7uVQ1" 77 | ], 78 | "frameId": null, 79 | "roundness": null, 80 | "boundElements": [], 81 | "updated": 1692114346918, 82 | "link": null, 83 | "locked": false 84 | }, 85 | { 86 | "type": "ellipse", 87 | "version": 511, 88 | "versionNonce": 388470115, 89 | "isDeleted": false, 90 | "id": "f1CB5zRPkm6uzw-55NfNy", 91 | "fillStyle": "solid", 92 | "strokeWidth": 1, 93 | "strokeStyle": "solid", 94 | "roughness": 1, 95 | "opacity": 100, 96 | "angle": 0, 97 | "x": 1380.6989701127677, 98 | "y": 534.5293135293932, 99 | "strokeColor": "#1864ab", 100 | "backgroundColor": "#4285f4", 101 | "width": 8.629783861565102, 102 | "height": 8.629783861565102, 103 | "seed": 1959480561, 104 | "groupIds": [ 105 | "w7WA9I2tiAn8fFk-ZQBN_", 106 | "XzRtSvB_V9Arw_Yz51yof", 107 | "gdmjNyUG7ie4haKK7uVQ1" 108 | ], 109 | "frameId": null, 110 | "roundness": null, 111 | "boundElements": [], 112 | "updated": 1692114346918, 113 | "link": null, 114 | "locked": false 115 | }, 116 | { 117 | "type": "ellipse", 118 | "version": 561, 119 | "versionNonce": 13706829, 120 | "isDeleted": false, 121 | "id": "ZQVSw6x1TLdMlu0yvKqft", 122 | "fillStyle": "solid", 123 | "strokeWidth": 1, 124 | "strokeStyle": "solid", 125 | "roughness": 1, 126 | "opacity": 100, 127 | "angle": 0, 128 | "x": 1401.0026292628627, 129 | "y": 519.581424605979, 130 | "strokeColor": "#1864ab", 131 | "backgroundColor": "#4285f4", 132 | "width": 8.629783861565102, 133 | "height": 8.629783861565102, 134 | "seed": 345409233, 135 | "groupIds": [ 136 | "w7WA9I2tiAn8fFk-ZQBN_", 137 | "XzRtSvB_V9Arw_Yz51yof", 138 | "gdmjNyUG7ie4haKK7uVQ1" 139 | ], 140 | "frameId": null, 141 | "roundness": null, 142 | "boundElements": [], 143 | "updated": 1692114346918, 144 | "link": null, 145 | "locked": false 146 | }, 147 | { 148 | "type": "ellipse", 149 | "version": 522, 150 | "versionNonce": 217379075, 151 | "isDeleted": false, 152 | "id": "hrXPt14p8rlsdyydN9E7T", 153 | "fillStyle": "solid", 154 | "strokeWidth": 1, 155 | "strokeStyle": "solid", 156 | "roughness": 1, 157 | "opacity": 100, 158 | "angle": 0, 159 | "x": 1400.9056758419347, 160 | "y": 561.5324156874485, 161 | "strokeColor": "#1864ab", 162 | "backgroundColor": "#4285f4", 163 | "width": 8.629783861565102, 164 | "height": 8.629783861565102, 165 | "seed": 1855947953, 166 | "groupIds": [ 167 | "w7WA9I2tiAn8fFk-ZQBN_", 168 | "XzRtSvB_V9Arw_Yz51yof", 169 | "gdmjNyUG7ie4haKK7uVQ1" 170 | ], 171 | "frameId": null, 172 | "roundness": null, 173 | "boundElements": [], 174 | "updated": 1692114346918, 175 | "link": null, 176 | "locked": false 177 | }, 178 | { 179 | "type": "ellipse", 180 | "version": 518, 181 | "versionNonce": 500225197, 182 | "isDeleted": false, 183 | "id": "wSvAOdCOopV6gCTHCfWce", 184 | "fillStyle": "solid", 185 | "strokeWidth": 1, 186 | "strokeStyle": "solid", 187 | "roughness": 1, 188 | "opacity": 100, 189 | "angle": 0, 190 | "x": 1400.7211924582934, 191 | "y": 576.8140120157209, 192 | "strokeColor": "#1864ab", 193 | "backgroundColor": "#4285f4", 194 | "width": 8.629783861565102, 195 | "height": 8.629783861565102, 196 | "seed": 2123220625, 197 | "groupIds": [ 198 | "w7WA9I2tiAn8fFk-ZQBN_", 199 | "XzRtSvB_V9Arw_Yz51yof", 200 | "gdmjNyUG7ie4haKK7uVQ1" 201 | ], 202 | "frameId": null, 203 | "roundness": null, 204 | "boundElements": [], 205 | "updated": 1692114346918, 206 | "link": null, 207 | "locked": false 208 | }, 209 | { 210 | "type": "ellipse", 211 | "version": 521, 212 | "versionNonce": 64346275, 213 | "isDeleted": false, 214 | "id": "ElM9C_91V_BuS8ZFkFqir", 215 | "fillStyle": "solid", 216 | "strokeWidth": 1, 217 | "strokeStyle": "solid", 218 | "roughness": 1, 219 | "opacity": 100, 220 | "angle": 0, 221 | "x": 1380.6350404104714, 222 | "y": 591.4496829955388, 223 | "strokeColor": "#1864ab", 224 | "backgroundColor": "#4285f4", 225 | "width": 8.629783861565102, 226 | "height": 8.629783861565102, 227 | "seed": 1595460721, 228 | "groupIds": [ 229 | "w7WA9I2tiAn8fFk-ZQBN_", 230 | "XzRtSvB_V9Arw_Yz51yof", 231 | "gdmjNyUG7ie4haKK7uVQ1" 232 | ], 233 | "frameId": null, 234 | "roundness": null, 235 | "boundElements": [], 236 | "updated": 1692114346918, 237 | "link": null, 238 | "locked": false 239 | }, 240 | { 241 | "type": "ellipse", 242 | "version": 534, 243 | "versionNonce": 797685517, 244 | "isDeleted": false, 245 | "id": "uIv0hhyQ7QaroflyB2XGx", 246 | "fillStyle": "solid", 247 | "strokeWidth": 1, 248 | "strokeStyle": "solid", 249 | "roughness": 1, 250 | "opacity": 100, 251 | "angle": 0, 252 | "x": 1380.5445881584592, 253 | "y": 576.7379816313105, 254 | "strokeColor": "#1864ab", 255 | "backgroundColor": "#4285f4", 256 | "width": 8.629783861565102, 257 | "height": 8.629783861565102, 258 | "seed": 1745360465, 259 | "groupIds": [ 260 | "w7WA9I2tiAn8fFk-ZQBN_", 261 | "XzRtSvB_V9Arw_Yz51yof", 262 | "gdmjNyUG7ie4haKK7uVQ1" 263 | ], 264 | "frameId": null, 265 | "roundness": null, 266 | "boundElements": [], 267 | "updated": 1692114346918, 268 | "link": null, 269 | "locked": false 270 | }, 271 | { 272 | "type": "ellipse", 273 | "version": 542, 274 | "versionNonce": 695782467, 275 | "isDeleted": false, 276 | "id": "WmmGydpOuZlGXLasClQjE", 277 | "fillStyle": "solid", 278 | "strokeWidth": 1, 279 | "strokeStyle": "solid", 280 | "roughness": 1, 281 | "opacity": 100, 282 | "angle": 0, 283 | "x": 1359.8696965421004, 284 | "y": 606.5050492804585, 285 | "strokeColor": "#1864ab", 286 | "backgroundColor": "#669df6", 287 | "width": 8.629783861565102, 288 | "height": 8.629783861565102, 289 | "seed": 204678193, 290 | "groupIds": [ 291 | "w7WA9I2tiAn8fFk-ZQBN_", 292 | "XzRtSvB_V9Arw_Yz51yof", 293 | "gdmjNyUG7ie4haKK7uVQ1" 294 | ], 295 | "frameId": null, 296 | "roundness": null, 297 | "boundElements": [], 298 | "updated": 1692114346918, 299 | "link": null, 300 | "locked": false 301 | }, 302 | { 303 | "type": "ellipse", 304 | "version": 533, 305 | "versionNonce": 1618693485, 306 | "isDeleted": false, 307 | "id": "PdlfTcZs_Z2PAMQjmhcmq", 308 | "fillStyle": "solid", 309 | "strokeWidth": 1, 310 | "strokeStyle": "solid", 311 | "roughness": 1, 312 | "opacity": 100, 313 | "angle": 0, 314 | "x": 1339.7508259514277, 315 | "y": 591.350852281169, 316 | "strokeColor": "#669df6", 317 | "backgroundColor": "#aecbfa", 318 | "width": 8.629783861565102, 319 | "height": 8.629783861565102, 320 | "seed": 2004973073, 321 | "groupIds": [ 322 | "w7WA9I2tiAn8fFk-ZQBN_", 323 | "XzRtSvB_V9Arw_Yz51yof", 324 | "gdmjNyUG7ie4haKK7uVQ1" 325 | ], 326 | "frameId": null, 327 | "roundness": null, 328 | "boundElements": [], 329 | "updated": 1692114346918, 330 | "link": null, 331 | "locked": false 332 | }, 333 | { 334 | "type": "ellipse", 335 | "version": 534, 336 | "versionNonce": 1908165603, 337 | "isDeleted": false, 338 | "id": "WNhQ8BQDTtFQXskOlOpzh", 339 | "fillStyle": "solid", 340 | "strokeWidth": 1, 341 | "strokeStyle": "solid", 342 | "roughness": 1, 343 | "opacity": 100, 344 | "angle": 0, 345 | "x": 1340.0221179732082, 346 | "y": 549.1412224131523, 347 | "strokeColor": "#669df6", 348 | "backgroundColor": "#aecbfa", 349 | "width": 8.629783861565102, 350 | "height": 8.629783861565102, 351 | "seed": 1827310577, 352 | "groupIds": [ 353 | "w7WA9I2tiAn8fFk-ZQBN_", 354 | "XzRtSvB_V9Arw_Yz51yof", 355 | "gdmjNyUG7ie4haKK7uVQ1" 356 | ], 357 | "frameId": null, 358 | "roundness": null, 359 | "boundElements": [], 360 | "updated": 1692114346918, 361 | "link": null, 362 | "locked": false 363 | }, 364 | { 365 | "type": "ellipse", 366 | "version": 522, 367 | "versionNonce": 1340676045, 368 | "isDeleted": false, 369 | "id": "VJMU2P1pPzwel-DhiWNDT", 370 | "fillStyle": "solid", 371 | "strokeWidth": 1, 372 | "strokeStyle": "solid", 373 | "roughness": 1, 374 | "opacity": 100, 375 | "angle": 0, 376 | "x": 1339.964448998303, 377 | "y": 534.404071238318, 378 | "strokeColor": "#669df6", 379 | "backgroundColor": "#aecbfa", 380 | "width": 8.629783861565102, 381 | "height": 8.629783861565102, 382 | "seed": 1685448145, 383 | "groupIds": [ 384 | "w7WA9I2tiAn8fFk-ZQBN_", 385 | "XzRtSvB_V9Arw_Yz51yof", 386 | "gdmjNyUG7ie4haKK7uVQ1" 387 | ], 388 | "frameId": null, 389 | "roundness": null, 390 | "boundElements": [], 391 | "updated": 1692114346918, 392 | "link": null, 393 | "locked": false 394 | }, 395 | { 396 | "type": "ellipse", 397 | "version": 526, 398 | "versionNonce": 1353635715, 399 | "isDeleted": false, 400 | "id": "4q4sb8PcxyV0mtLe_ebh9", 401 | "fillStyle": "solid", 402 | "strokeWidth": 1, 403 | "strokeStyle": "solid", 404 | "roughness": 1, 405 | "opacity": 100, 406 | "angle": 0, 407 | "x": 1319.4458073819442, 408 | "y": 547.1600397367797, 409 | "strokeColor": "#669df6", 410 | "backgroundColor": "#aecbfa", 411 | "width": 8.629783861565102, 412 | "height": 8.629783861565102, 413 | "seed": 1765475249, 414 | "groupIds": [ 415 | "w7WA9I2tiAn8fFk-ZQBN_", 416 | "XzRtSvB_V9Arw_Yz51yof", 417 | "gdmjNyUG7ie4haKK7uVQ1" 418 | ], 419 | "frameId": null, 420 | "roundness": null, 421 | "boundElements": [], 422 | "updated": 1692114346918, 423 | "link": null, 424 | "locked": false 425 | }, 426 | { 427 | "type": "ellipse", 428 | "version": 518, 429 | "versionNonce": 2012124717, 430 | "isDeleted": false, 431 | "id": "vgaTSb23kjgZeTgZSHahW", 432 | "fillStyle": "solid", 433 | "strokeWidth": 1, 434 | "strokeStyle": "solid", 435 | "roughness": 1, 436 | "opacity": 100, 437 | "angle": 0, 438 | "x": 1319.9108166666858, 439 | "y": 561.974742551055, 440 | "strokeColor": "#669df6", 441 | "backgroundColor": "#aecbfa", 442 | "width": 8.629783861565102, 443 | "height": 8.629783861565102, 444 | "seed": 929129873, 445 | "groupIds": [ 446 | "w7WA9I2tiAn8fFk-ZQBN_", 447 | "XzRtSvB_V9Arw_Yz51yof", 448 | "gdmjNyUG7ie4haKK7uVQ1" 449 | ], 450 | "frameId": null, 451 | "roundness": null, 452 | "boundElements": [], 453 | "updated": 1692114346918, 454 | "link": null, 455 | "locked": false 456 | }, 457 | { 458 | "type": "ellipse", 459 | "version": 538, 460 | "versionNonce": 881782563, 461 | "isDeleted": false, 462 | "id": "JCGKu9XAGJql3LmJwHRDa", 463 | "fillStyle": "solid", 464 | "strokeWidth": 1, 465 | "strokeStyle": "solid", 466 | "roughness": 1, 467 | "opacity": 100, 468 | "angle": 0, 469 | "x": 1319.8436456276827, 470 | "y": 576.3711418467465, 471 | "strokeColor": "#669df6", 472 | "backgroundColor": "#aecbfa", 473 | "width": 8.629783861565102, 474 | "height": 8.629783861565102, 475 | "seed": 438340465, 476 | "groupIds": [ 477 | "w7WA9I2tiAn8fFk-ZQBN_", 478 | "XzRtSvB_V9Arw_Yz51yof", 479 | "gdmjNyUG7ie4haKK7uVQ1" 480 | ], 481 | "frameId": null, 482 | "roundness": null, 483 | "boundElements": [], 484 | "updated": 1692114346918, 485 | "link": null, 486 | "locked": false 487 | }, 488 | { 489 | "type": "line", 490 | "version": 647, 491 | "versionNonce": 1400195213, 492 | "isDeleted": false, 493 | "id": "wYgKvmciR8hkOblhRbfUg", 494 | "fillStyle": "solid", 495 | "strokeWidth": 1, 496 | "strokeStyle": "solid", 497 | "roughness": 1, 498 | "opacity": 100, 499 | "angle": 0, 500 | "x": 1349.3223614664907, 501 | "y": 568.8266162378743, 502 | "strokeColor": "#669df6", 503 | "backgroundColor": "#aecbfa", 504 | "width": 10.160675189148606, 505 | "height": 22.52785185463381, 506 | "seed": 163575121, 507 | "groupIds": [ 508 | "w7WA9I2tiAn8fFk-ZQBN_", 509 | "XzRtSvB_V9Arw_Yz51yof", 510 | "gdmjNyUG7ie4haKK7uVQ1" 511 | ], 512 | "frameId": null, 513 | "roundness": null, 514 | "boundElements": [], 515 | "updated": 1692114346918, 516 | "link": null, 517 | "locked": false, 518 | "startBinding": null, 519 | "endBinding": null, 520 | "lastCommittedPoint": null, 521 | "startArrowhead": null, 522 | "endArrowhead": null, 523 | "points": [ 524 | [ 525 | 0, 526 | 0 527 | ], 528 | [ 529 | -0.048448967211243144, 530 | 12.812352035984986 531 | ], 532 | [ 533 | -1.4133043958793223, 534 | 15.651114643924302 535 | ], 536 | [ 537 | -4.1939461309887065, 538 | 17.03402521750877 539 | ], 540 | [ 541 | -6.58410893313885, 542 | 16.970997289490697 543 | ], 544 | [ 545 | -9.4685156505534, 546 | 14.823587475455042 547 | ], 548 | [ 549 | -10.160675189148606, 550 | 12.562939188195287 551 | ], 552 | [ 553 | -9.989182050126146, 554 | 0.14203449067917973 555 | ], 556 | [ 557 | -9.700590094663994, 558 | -2.728907975600123 559 | ], 560 | [ 561 | -8.015451164041679, 562 | -4.76326190021291 563 | ], 564 | [ 565 | -5.637938810943297, 566 | -5.493826637125039 567 | ], 568 | [ 569 | -3.048305814816331, 570 | -5.097849323423361 571 | ], 572 | [ 573 | -0.9350339548402644, 574 | -2.9714575131934 575 | ] 576 | ] 577 | }, 578 | { 579 | "type": "line", 580 | "version": 656, 581 | "versionNonce": 1070273219, 582 | "isDeleted": false, 583 | "id": "bDAMLUmgPcJk2rpqI4uRl", 584 | "fillStyle": "solid", 585 | "strokeWidth": 1, 586 | "strokeStyle": "solid", 587 | "roughness": 1, 588 | "opacity": 100, 589 | "angle": 0, 590 | "x": 1369.3032468286474, 591 | "y": 583.6285154224026, 592 | "strokeColor": "#1864ab", 593 | "backgroundColor": "#669df6", 594 | "width": 10.160675189148606, 595 | "height": 22.52785185463381, 596 | "seed": 160899889, 597 | "groupIds": [ 598 | "w7WA9I2tiAn8fFk-ZQBN_", 599 | "XzRtSvB_V9Arw_Yz51yof", 600 | "gdmjNyUG7ie4haKK7uVQ1" 601 | ], 602 | "frameId": null, 603 | "roundness": null, 604 | "boundElements": [], 605 | "updated": 1692114346918, 606 | "link": null, 607 | "locked": false, 608 | "startBinding": null, 609 | "endBinding": null, 610 | "lastCommittedPoint": null, 611 | "startArrowhead": null, 612 | "endArrowhead": null, 613 | "points": [ 614 | [ 615 | 0, 616 | 0 617 | ], 618 | [ 619 | -0.048448967211243144, 620 | 12.812352035984986 621 | ], 622 | [ 623 | -1.4133043958793223, 624 | 15.651114643924302 625 | ], 626 | [ 627 | -4.1939461309887065, 628 | 17.03402521750877 629 | ], 630 | [ 631 | -6.58410893313885, 632 | 16.970997289490697 633 | ], 634 | [ 635 | -9.4685156505534, 636 | 14.823587475455042 637 | ], 638 | [ 639 | -10.160675189148606, 640 | 12.562939188195287 641 | ], 642 | [ 643 | -9.989182050126146, 644 | 0.14203449067917973 645 | ], 646 | [ 647 | -9.700590094663994, 648 | -2.728907975600123 649 | ], 650 | [ 651 | -8.015451164041679, 652 | -4.76326190021291 653 | ], 654 | [ 655 | -5.637938810943297, 656 | -5.493826637125039 657 | ], 658 | [ 659 | -3.048305814816331, 660 | -5.097849323423361 661 | ], 662 | [ 663 | -0.9350339548402644, 664 | -2.9714575131934 665 | ] 666 | ] 667 | }, 668 | { 669 | "type": "line", 670 | "version": 696, 671 | "versionNonce": 1144434413, 672 | "isDeleted": false, 673 | "id": "7lcFceyurYxbJVIlhSvD6", 674 | "fillStyle": "solid", 675 | "strokeWidth": 1, 676 | "strokeStyle": "solid", 677 | "roughness": 1, 678 | "opacity": 100, 679 | "angle": 0, 680 | "x": 1390.4091986432172, 681 | "y": 554.078297573845, 682 | "strokeColor": "#1864ab", 683 | "backgroundColor": "#4285f4", 684 | "width": 10.160675189148606, 685 | "height": 22.52785185463381, 686 | "seed": 1175295249, 687 | "groupIds": [ 688 | "w7WA9I2tiAn8fFk-ZQBN_", 689 | "XzRtSvB_V9Arw_Yz51yof", 690 | "gdmjNyUG7ie4haKK7uVQ1" 691 | ], 692 | "frameId": null, 693 | "roundness": null, 694 | "boundElements": [], 695 | "updated": 1692114346918, 696 | "link": null, 697 | "locked": false, 698 | "startBinding": null, 699 | "endBinding": null, 700 | "lastCommittedPoint": null, 701 | "startArrowhead": null, 702 | "endArrowhead": null, 703 | "points": [ 704 | [ 705 | 0, 706 | 0 707 | ], 708 | [ 709 | -0.048448967211243144, 710 | 12.812352035984986 711 | ], 712 | [ 713 | -1.4133043958793223, 714 | 15.651114643924302 715 | ], 716 | [ 717 | -4.1939461309887065, 718 | 17.03402521750877 719 | ], 720 | [ 721 | -6.58410893313885, 722 | 16.970997289490697 723 | ], 724 | [ 725 | -9.4685156505534, 726 | 14.823587475455042 727 | ], 728 | [ 729 | -10.160675189148606, 730 | 12.562939188195287 731 | ], 732 | [ 733 | -9.989182050126146, 734 | 0.14203449067917973 735 | ], 736 | [ 737 | -9.700590094663994, 738 | -2.728907975600123 739 | ], 740 | [ 741 | -8.015451164041679, 742 | -4.76326190021291 743 | ], 744 | [ 745 | -5.637938810943297, 746 | -5.493826637125039 747 | ], 748 | [ 749 | -3.048305814816331, 750 | -5.097849323423361 751 | ], 752 | [ 753 | -0.9350339548402644, 754 | -2.9714575131934 755 | ] 756 | ] 757 | }, 758 | { 759 | "type": "line", 760 | "version": 701, 761 | "versionNonce": 1124188771, 762 | "isDeleted": false, 763 | "id": "gPK7A0MXVJajpgTkEkyk5", 764 | "fillStyle": "solid", 765 | "strokeWidth": 1, 766 | "strokeStyle": "solid", 767 | "roughness": 1, 768 | "opacity": 100, 769 | "angle": 0, 770 | "x": 1410.0366963896113, 771 | "y": 539.1058833777981, 772 | "strokeColor": "#1864ab", 773 | "backgroundColor": "#4285f4", 774 | "width": 10.160675189148606, 775 | "height": 22.52785185463381, 776 | "seed": 1922747121, 777 | "groupIds": [ 778 | "w7WA9I2tiAn8fFk-ZQBN_", 779 | "XzRtSvB_V9Arw_Yz51yof", 780 | "gdmjNyUG7ie4haKK7uVQ1" 781 | ], 782 | "frameId": null, 783 | "roundness": null, 784 | "boundElements": [], 785 | "updated": 1692114346918, 786 | "link": null, 787 | "locked": false, 788 | "startBinding": null, 789 | "endBinding": null, 790 | "lastCommittedPoint": null, 791 | "startArrowhead": null, 792 | "endArrowhead": null, 793 | "points": [ 794 | [ 795 | 0, 796 | 0 797 | ], 798 | [ 799 | -0.048448967211243144, 800 | 12.812352035984986 801 | ], 802 | [ 803 | -1.4133043958793223, 804 | 15.651114643924302 805 | ], 806 | [ 807 | -4.1939461309887065, 808 | 17.03402521750877 809 | ], 810 | [ 811 | -6.58410893313885, 812 | 16.970997289490697 813 | ], 814 | [ 815 | -9.4685156505534, 816 | 14.823587475455042 817 | ], 818 | [ 819 | -10.160675189148606, 820 | 12.562939188195287 821 | ], 822 | [ 823 | -9.989182050126146, 824 | 0.14203449067917973 825 | ], 826 | [ 827 | -9.700590094663994, 828 | -2.728907975600123 829 | ], 830 | [ 831 | -8.015451164041679, 832 | -4.76326190021291 833 | ], 834 | [ 835 | -5.637938810943297, 836 | -5.493826637125039 837 | ], 838 | [ 839 | -3.048305814816331, 840 | -5.097849323423361 841 | ], 842 | [ 843 | -0.9350339548402644, 844 | -2.9714575131934 845 | ] 846 | ] 847 | }, 848 | { 849 | "type": "line", 850 | "version": 709, 851 | "versionNonce": 951863629, 852 | "isDeleted": false, 853 | "id": "wopWbM540oUu_Q9mIAcwY", 854 | "fillStyle": "solid", 855 | "strokeWidth": 1, 856 | "strokeStyle": "solid", 857 | "roughness": 1, 858 | "opacity": 100, 859 | "angle": 0, 860 | "x": 1328.8038983919819, 861 | "y": 523.8141353309231, 862 | "strokeColor": "#669df6", 863 | "backgroundColor": "#aecbfa", 864 | "width": 10.160675189148606, 865 | "height": 22.52785185463381, 866 | "seed": 1741046993, 867 | "groupIds": [ 868 | "w7WA9I2tiAn8fFk-ZQBN_", 869 | "XzRtSvB_V9Arw_Yz51yof", 870 | "gdmjNyUG7ie4haKK7uVQ1" 871 | ], 872 | "frameId": null, 873 | "roundness": null, 874 | "boundElements": [], 875 | "updated": 1692114346918, 876 | "link": null, 877 | "locked": false, 878 | "startBinding": null, 879 | "endBinding": null, 880 | "lastCommittedPoint": null, 881 | "startArrowhead": null, 882 | "endArrowhead": null, 883 | "points": [ 884 | [ 885 | 0, 886 | 0 887 | ], 888 | [ 889 | -0.048448967211243144, 890 | 12.812352035984986 891 | ], 892 | [ 893 | -1.4133043958793223, 894 | 15.651114643924302 895 | ], 896 | [ 897 | -4.1939461309887065, 898 | 17.03402521750877 899 | ], 900 | [ 901 | -6.58410893313885, 902 | 16.970997289490697 903 | ], 904 | [ 905 | -9.4685156505534, 906 | 14.823587475455042 907 | ], 908 | [ 909 | -10.160675189148606, 910 | 12.562939188195287 911 | ], 912 | [ 913 | -9.989182050126146, 914 | 0.14203449067917973 915 | ], 916 | [ 917 | -9.700590094663994, 918 | -2.728907975600123 919 | ], 920 | [ 921 | -8.015451164041679, 922 | -4.76326190021291 923 | ], 924 | [ 925 | -5.637938810943297, 926 | -5.493826637125039 927 | ], 928 | [ 929 | -3.048305814816331, 930 | -5.097849323423361 931 | ], 932 | [ 933 | -0.9350339548402644, 934 | -2.9714575131934 935 | ] 936 | ] 937 | }, 938 | { 939 | "type": "ellipse", 940 | "version": 523, 941 | "versionNonce": 1996586499, 942 | "isDeleted": false, 943 | "id": "RHTu6RV98z8XcFGbOZdWL", 944 | "fillStyle": "solid", 945 | "strokeWidth": 1, 946 | "strokeStyle": "solid", 947 | "roughness": 1, 948 | "opacity": 100, 949 | "angle": 0, 950 | "x": 1360.6644254730288, 951 | "y": 564.2660499219028, 952 | "strokeColor": "#1864ab", 953 | "backgroundColor": "#669df6", 954 | "width": 8.629783861565102, 955 | "height": 8.629783861565102, 956 | "seed": 19366577, 957 | "groupIds": [ 958 | "w7WA9I2tiAn8fFk-ZQBN_", 959 | "XzRtSvB_V9Arw_Yz51yof", 960 | "gdmjNyUG7ie4haKK7uVQ1" 961 | ], 962 | "frameId": null, 963 | "roundness": null, 964 | "boundElements": [], 965 | "updated": 1692114346918, 966 | "link": null, 967 | "locked": false 968 | }, 969 | { 970 | "type": "line", 971 | "version": 901, 972 | "versionNonce": 308254637, 973 | "isDeleted": false, 974 | "id": "2QugedCyCHaWrZlyTr_Uf", 975 | "fillStyle": "solid", 976 | "strokeWidth": 1, 977 | "strokeStyle": "solid", 978 | "roughness": 1, 979 | "opacity": 100, 980 | "angle": 0.7853981633974474, 981 | "x": 1402.9109320731113, 982 | "y": 587.2418011520856, 983 | "strokeColor": "#1864ab", 984 | "backgroundColor": "#669df6", 985 | "width": 18.307789132723883, 986 | "height": 59.737567473064644, 987 | "seed": 996541585, 988 | "groupIds": [ 989 | "w7WA9I2tiAn8fFk-ZQBN_", 990 | "XzRtSvB_V9Arw_Yz51yof", 991 | "gdmjNyUG7ie4haKK7uVQ1" 992 | ], 993 | "frameId": null, 994 | "roundness": null, 995 | "boundElements": [], 996 | "updated": 1692114346918, 997 | "link": null, 998 | "locked": false, 999 | "startBinding": null, 1000 | "endBinding": null, 1001 | "lastCommittedPoint": null, 1002 | "startArrowhead": null, 1003 | "endArrowhead": null, 1004 | "points": [ 1005 | [ 1006 | 0, 1007 | 0 1008 | ], 1009 | [ 1010 | -8.19556291078652, 1011 | 50.02206765441582 1012 | ], 1013 | [ 1014 | -9.5604183394546, 1015 | 52.86083026235514 1016 | ], 1017 | [ 1018 | -12.341060074563984, 1019 | 54.243740835939604 1020 | ], 1021 | [ 1022 | -14.731222876714128, 1023 | 54.18071290792153 1024 | ], 1025 | [ 1026 | -17.615629594128677, 1027 | 52.03330309388588 1028 | ], 1029 | [ 1030 | -18.307789132723883, 1031 | 49.77265480662612 1032 | ], 1033 | [ 1034 | -9.989182050126146, 1035 | 0.14203449067917973 1036 | ], 1037 | [ 1038 | -9.700590094663994, 1039 | -2.728907975600123 1040 | ], 1041 | [ 1042 | -8.015451164041679, 1043 | -4.76326190021291 1044 | ], 1045 | [ 1046 | -5.637938810943297, 1047 | -5.493826637125039 1048 | ], 1049 | [ 1050 | -3.048305814816331, 1051 | -5.097849323423361 1052 | ], 1053 | [ 1054 | -0.9350339548402644, 1055 | -2.9714575131934 1056 | ] 1057 | ] 1058 | }, 1059 | { 1060 | "type": "line", 1061 | "version": 998, 1062 | "versionNonce": 1992869283, 1063 | "isDeleted": false, 1064 | "id": "IDGtSKF6u0Az8aPfvznkc", 1065 | "fillStyle": "solid", 1066 | "strokeWidth": 1, 1067 | "strokeStyle": "solid", 1068 | "roughness": 1, 1069 | "opacity": 100, 1070 | "angle": 2.3561944901923457, 1071 | "x": 1326.5042796299595, 1072 | "y": 587.5444104421217, 1073 | "strokeColor": "#669df6", 1074 | "backgroundColor": "#aecbfa", 1075 | "width": 18.307789132723883, 1076 | "height": 59.737567473064644, 1077 | "seed": 305744497, 1078 | "groupIds": [ 1079 | "w7WA9I2tiAn8fFk-ZQBN_", 1080 | "XzRtSvB_V9Arw_Yz51yof", 1081 | "gdmjNyUG7ie4haKK7uVQ1" 1082 | ], 1083 | "frameId": null, 1084 | "roundness": null, 1085 | "boundElements": [], 1086 | "updated": 1692114346918, 1087 | "link": null, 1088 | "locked": false, 1089 | "startBinding": null, 1090 | "endBinding": null, 1091 | "lastCommittedPoint": null, 1092 | "startArrowhead": null, 1093 | "endArrowhead": null, 1094 | "points": [ 1095 | [ 1096 | 0, 1097 | 0 1098 | ], 1099 | [ 1100 | 8.19556291078652, 1101 | 50.02206765441582 1102 | ], 1103 | [ 1104 | 9.5604183394546, 1105 | 52.86083026235514 1106 | ], 1107 | [ 1108 | 12.341060074563984, 1109 | 54.243740835939604 1110 | ], 1111 | [ 1112 | 14.731222876714128, 1113 | 54.18071290792153 1114 | ], 1115 | [ 1116 | 17.615629594128677, 1117 | 52.03330309388588 1118 | ], 1119 | [ 1120 | 18.307789132723883, 1121 | 49.77265480662612 1122 | ], 1123 | [ 1124 | 9.989182050126146, 1125 | 0.14203449067917973 1126 | ], 1127 | [ 1128 | 9.700590094663994, 1129 | -2.728907975600123 1130 | ], 1131 | [ 1132 | 8.015451164041679, 1133 | -4.76326190021291 1134 | ], 1135 | [ 1136 | 5.637938810943297, 1137 | -5.493826637125039 1138 | ], 1139 | [ 1140 | 3.048305814816331, 1141 | -5.097849323423361 1142 | ], 1143 | [ 1144 | 0.9350339548402644, 1145 | -2.9714575131934 1146 | ] 1147 | ] 1148 | }, 1149 | { 1150 | "type": "ellipse", 1151 | "version": 568, 1152 | "versionNonce": 1263147533, 1153 | "isDeleted": false, 1154 | "id": "GBTuyAyePZEM4d2nPzvds", 1155 | "fillStyle": "solid", 1156 | "strokeWidth": 1, 1157 | "strokeStyle": "solid", 1158 | "roughness": 1, 1159 | "opacity": 100, 1160 | "angle": 0, 1161 | "x": 1355.4565586209505, 1162 | "y": 624.0321327463516, 1163 | "strokeColor": "#1864ab", 1164 | "backgroundColor": "#4285f4", 1165 | "width": 17.95721071960313, 1166 | "height": 18.98979320860685, 1167 | "seed": 510186577, 1168 | "groupIds": [ 1169 | "CGZX0KwetSu8y3f-FceuM", 1170 | "w7WA9I2tiAn8fFk-ZQBN_", 1171 | "XzRtSvB_V9Arw_Yz51yof", 1172 | "gdmjNyUG7ie4haKK7uVQ1" 1173 | ], 1174 | "frameId": null, 1175 | "roundness": null, 1176 | "boundElements": [], 1177 | "updated": 1692114346918, 1178 | "link": null, 1179 | "locked": false 1180 | }, 1181 | { 1182 | "type": "ellipse", 1183 | "version": 535, 1184 | "versionNonce": 2038831427, 1185 | "isDeleted": false, 1186 | "id": "KKSa7j1rDV_d61cxRfu09", 1187 | "fillStyle": "solid", 1188 | "strokeWidth": 1, 1189 | "strokeStyle": "solid", 1190 | "roughness": 1, 1191 | "opacity": 100, 1192 | "angle": 0, 1193 | "x": 1360.744589484352, 1194 | "y": 629.6242380095093, 1195 | "strokeColor": "#1864ab", 1196 | "backgroundColor": "white", 1197 | "width": 7.381148992800263, 1198 | "height": 7.805582682291515, 1199 | "seed": 1942757937, 1200 | "groupIds": [ 1201 | "CGZX0KwetSu8y3f-FceuM", 1202 | "w7WA9I2tiAn8fFk-ZQBN_", 1203 | "XzRtSvB_V9Arw_Yz51yof", 1204 | "gdmjNyUG7ie4haKK7uVQ1" 1205 | ], 1206 | "frameId": null, 1207 | "roundness": null, 1208 | "boundElements": [], 1209 | "updated": 1692114346918, 1210 | "link": null, 1211 | "locked": false 1212 | }, 1213 | { 1214 | "type": "text", 1215 | "version": 307, 1216 | "versionNonce": 1551192173, 1217 | "isDeleted": false, 1218 | "id": "4bQ899UMcX2vIttGelNjQ", 1219 | "fillStyle": "hachure", 1220 | "strokeWidth": 1, 1221 | "strokeStyle": "solid", 1222 | "roughness": 1, 1223 | "opacity": 100, 1224 | "angle": 0, 1225 | "x": 1276.15234375, 1226 | "y": 667.9453125, 1227 | "strokeColor": "#1e1e1e", 1228 | "backgroundColor": "transparent", 1229 | "width": 176.2998809814453, 1230 | "height": 25, 1231 | "seed": 1672740127, 1232 | "groupIds": [ 1233 | "XzRtSvB_V9Arw_Yz51yof", 1234 | "gdmjNyUG7ie4haKK7uVQ1" 1235 | ], 1236 | "frameId": null, 1237 | "roundness": null, 1238 | "boundElements": [ 1239 | { 1240 | "id": "KKFKRKnBVTkfqOc0VPSPN", 1241 | "type": "arrow" 1242 | } 1243 | ], 1244 | "updated": 1692114346918, 1245 | "link": null, 1246 | "locked": false, 1247 | "fontSize": 20, 1248 | "fontFamily": 1, 1249 | "text": "Vertex AI (PaLM)", 1250 | "textAlign": "center", 1251 | "verticalAlign": "top", 1252 | "containerId": null, 1253 | "originalText": "Vertex AI (PaLM)", 1254 | "lineHeight": 1.25, 1255 | "baseline": 18 1256 | }, 1257 | { 1258 | "type": "line", 1259 | "version": 704, 1260 | "versionNonce": 227337347, 1261 | "isDeleted": false, 1262 | "id": "1c4B6zhphvFMOJaRY9qpx", 1263 | "fillStyle": "solid", 1264 | "strokeWidth": 1, 1265 | "strokeStyle": "solid", 1266 | "roughness": 1, 1267 | "opacity": 100, 1268 | "angle": 0, 1269 | "x": 1001.9399875933095, 1270 | "y": 549.4731866324223, 1271 | "strokeColor": "#669df6", 1272 | "backgroundColor": "#aecbfa", 1273 | "width": 31.10358450893958, 1274 | "height": 54.56611844379475, 1275 | "seed": 1846775135, 1276 | "groupIds": [ 1277 | "tnDjpUI1DwYXH4vvV6ldT", 1278 | "LAFLs8Zft8rTGKMGEJtil", 1279 | "gdmjNyUG7ie4haKK7uVQ1" 1280 | ], 1281 | "frameId": null, 1282 | "roundness": null, 1283 | "boundElements": [], 1284 | "updated": 1692114346918, 1285 | "link": null, 1286 | "locked": false, 1287 | "startBinding": null, 1288 | "endBinding": null, 1289 | "lastCommittedPoint": null, 1290 | "startArrowhead": null, 1291 | "endArrowhead": null, 1292 | "points": [ 1293 | [ 1294 | 0, 1295 | 0 1296 | ], 1297 | [ 1298 | 17.596767231887785, 1299 | -0.08373547416552363 1300 | ], 1301 | [ 1302 | 4.584074512734757, 1303 | -45.72920167115939 1304 | ], 1305 | [ 1306 | -13.506817277051796, 1307 | -54.56611844379475 1308 | ], 1309 | [ 1310 | 0, 1311 | 0 1312 | ] 1313 | ] 1314 | }, 1315 | { 1316 | "type": "line", 1317 | "version": 712, 1318 | "versionNonce": 159635757, 1319 | "isDeleted": false, 1320 | "id": "fgPWeeXcM5EPzhTRaVXb8", 1321 | "fillStyle": "solid", 1322 | "strokeWidth": 1, 1323 | "strokeStyle": "solid", 1324 | "roughness": 1, 1325 | "opacity": 100, 1326 | "angle": 0, 1327 | "x": 1037.8732683750598, 1328 | "y": 549.294600267131, 1329 | "strokeColor": "#669df6", 1330 | "backgroundColor": "#aecbfa", 1331 | "width": 72.43672707411116, 1332 | "height": 52.964695946122106, 1333 | "seed": 1746438527, 1334 | "groupIds": [ 1335 | "tnDjpUI1DwYXH4vvV6ldT", 1336 | "LAFLs8Zft8rTGKMGEJtil", 1337 | "gdmjNyUG7ie4haKK7uVQ1" 1338 | ], 1339 | "frameId": null, 1340 | "roundness": null, 1341 | "boundElements": [], 1342 | "updated": 1692114346918, 1343 | "link": null, 1344 | "locked": false, 1345 | "startBinding": null, 1346 | "endBinding": null, 1347 | "lastCommittedPoint": null, 1348 | "startArrowhead": null, 1349 | "endArrowhead": null, 1350 | "points": [ 1351 | [ 1352 | 0, 1353 | 0 1354 | ], 1355 | [ 1356 | 54.21240697907884, 1357 | -0.20599385738771492 1358 | ], 1359 | [ 1360 | -18.22432009503232, 1361 | -52.964695946122106 1362 | ], 1363 | [ 1364 | 0.021118325969255558, 1365 | -1.085544260441858 1366 | ] 1367 | ] 1368 | }, 1369 | { 1370 | "type": "line", 1371 | "version": 799, 1372 | "versionNonce": 606297123, 1373 | "isDeleted": false, 1374 | "id": "banlOHFS5v2Y4DnxdY2Ov", 1375 | "fillStyle": "solid", 1376 | "strokeWidth": 1, 1377 | "strokeStyle": "solid", 1378 | "roughness": 1, 1379 | "opacity": 100, 1380 | "angle": 3.141592653589793, 1381 | "x": 1072.919736070768, 1382 | "y": 603.2212789686446, 1383 | "strokeColor": "#1864ab", 1384 | "backgroundColor": "#4285f4", 1385 | "width": 72.43672707411116, 1386 | "height": 52.964695946122106, 1387 | "seed": 745079199, 1388 | "groupIds": [ 1389 | "tnDjpUI1DwYXH4vvV6ldT", 1390 | "LAFLs8Zft8rTGKMGEJtil", 1391 | "gdmjNyUG7ie4haKK7uVQ1" 1392 | ], 1393 | "frameId": null, 1394 | "roundness": null, 1395 | "boundElements": [], 1396 | "updated": 1692114346918, 1397 | "link": null, 1398 | "locked": false, 1399 | "startBinding": null, 1400 | "endBinding": null, 1401 | "lastCommittedPoint": null, 1402 | "startArrowhead": null, 1403 | "endArrowhead": null, 1404 | "points": [ 1405 | [ 1406 | 0, 1407 | 0 1408 | ], 1409 | [ 1410 | -54.21240697907885, 1411 | -0.20599385738771492 1412 | ], 1413 | [ 1414 | 18.22432009503231, 1415 | -52.964695946122106 1416 | ], 1417 | [ 1418 | -0.021118325969256446, 1419 | -1.085544260441858 1420 | ] 1421 | ] 1422 | }, 1423 | { 1424 | "type": "line", 1425 | "version": 794, 1426 | "versionNonce": 661269389, 1427 | "isDeleted": false, 1428 | "id": "CHsapr7TxuvyU1DpaNAMu", 1429 | "fillStyle": "solid", 1430 | "strokeWidth": 1, 1431 | "strokeStyle": "solid", 1432 | "roughness": 1, 1433 | "opacity": 100, 1434 | "angle": 3.141592653589793, 1435 | "x": 1004.8852819844874, 1436 | "y": 604.5937608523766, 1437 | "strokeColor": "#1864ab", 1438 | "backgroundColor": "#4285f4", 1439 | "width": 31.103584508939583, 1440 | "height": 54.56611844379475, 1441 | "seed": 1547007423, 1442 | "groupIds": [ 1443 | "tnDjpUI1DwYXH4vvV6ldT", 1444 | "LAFLs8Zft8rTGKMGEJtil", 1445 | "gdmjNyUG7ie4haKK7uVQ1" 1446 | ], 1447 | "frameId": null, 1448 | "roundness": null, 1449 | "boundElements": [], 1450 | "updated": 1692114346918, 1451 | "link": null, 1452 | "locked": false, 1453 | "startBinding": null, 1454 | "endBinding": null, 1455 | "lastCommittedPoint": null, 1456 | "startArrowhead": null, 1457 | "endArrowhead": null, 1458 | "points": [ 1459 | [ 1460 | 0, 1461 | 0 1462 | ], 1463 | [ 1464 | -17.59676723188778, 1465 | -0.08373547416552363 1466 | ], 1467 | [ 1468 | -4.5840745127347615, 1469 | -45.72920167115939 1470 | ], 1471 | [ 1472 | 13.506817277051802, 1473 | -54.56611844379475 1474 | ], 1475 | [ 1476 | 0, 1477 | 0 1478 | ] 1479 | ] 1480 | }, 1481 | { 1482 | "type": "text", 1483 | "version": 376, 1484 | "versionNonce": 2085216195, 1485 | "isDeleted": false, 1486 | "id": "2U7Xjtn49tpwxe9UxzOwM", 1487 | "fillStyle": "hachure", 1488 | "strokeWidth": 1, 1489 | "strokeStyle": "solid", 1490 | "roughness": 1, 1491 | "opacity": 100, 1492 | "angle": 0, 1493 | "x": 915.0000991821289, 1494 | "y": 641.4296875, 1495 | "strokeColor": "#1e1e1e", 1496 | "backgroundColor": "transparent", 1497 | "width": 249.9998016357422, 1498 | "height": 75, 1499 | "seed": 1895995935, 1500 | "groupIds": [ 1501 | "LAFLs8Zft8rTGKMGEJtil", 1502 | "gdmjNyUG7ie4haKK7uVQ1" 1503 | ], 1504 | "frameId": null, 1505 | "roundness": null, 1506 | "boundElements": [ 1507 | { 1508 | "id": "KKFKRKnBVTkfqOc0VPSPN", 1509 | "type": "arrow" 1510 | } 1511 | ], 1512 | "updated": 1692114346918, 1513 | "link": null, 1514 | "locked": false, 1515 | "fontSize": 20, 1516 | "fontFamily": 1, 1517 | "text": "Cloud Run\n(OpenAI compatible API) \n", 1518 | "textAlign": "center", 1519 | "verticalAlign": "top", 1520 | "containerId": null, 1521 | "originalText": "Cloud Run\n(OpenAI compatible API) \n", 1522 | "lineHeight": 1.25, 1523 | "baseline": 68 1524 | }, 1525 | { 1526 | "type": "line", 1527 | "version": 1276, 1528 | "versionNonce": 1226075213, 1529 | "isDeleted": false, 1530 | "id": "cqltOv0wMML-phZ6WIwa0", 1531 | "fillStyle": "solid", 1532 | "strokeWidth": 1, 1533 | "strokeStyle": "solid", 1534 | "roughness": 1, 1535 | "opacity": 100, 1536 | "angle": 0, 1537 | "x": 952.8541477681985, 1538 | "y": 355.46552823185436, 1539 | "strokeColor": "#b42d21", 1540 | "backgroundColor": "#ea4335", 1541 | "width": 92.87747087149785, 1542 | "height": 46.32092179923228, 1543 | "seed": 1270568991, 1544 | "groupIds": [ 1545 | "g15wKU6OJIBTpqVYNHOg-", 1546 | "tUzWJXrOKaMPpEDlg6CBB", 1547 | "gdmjNyUG7ie4haKK7uVQ1" 1548 | ], 1549 | "frameId": null, 1550 | "roundness": null, 1551 | "boundElements": [], 1552 | "updated": 1692114346918, 1553 | "link": null, 1554 | "locked": false, 1555 | "startBinding": null, 1556 | "endBinding": null, 1557 | "lastCommittedPoint": null, 1558 | "startArrowhead": null, 1559 | "endArrowhead": null, 1560 | "points": [ 1561 | [ 1562 | 0, 1563 | 0 1564 | ], 1565 | [ 1566 | 3.3961960365032837, 1567 | -5.959588412580899 1568 | ], 1569 | [ 1570 | 7.632909314385756, 1571 | -9.481527394261946 1572 | ], 1573 | [ 1574 | 13.114855536099185, 1575 | -11.798695531384794 1576 | ], 1577 | [ 1578 | 19.03361353380933, 1579 | -13.072136188375623 1580 | ], 1581 | [ 1582 | 24.72634151064119, 1583 | -13.629229315396117 1584 | ], 1585 | [ 1586 | 32.08423482960666, 1587 | -12.895302608095506 1588 | ], 1589 | [ 1590 | 38.24711240571128, 1591 | -11.148586930899853 1592 | ], 1593 | [ 1594 | 43.752115183863225, 1595 | -7.811058307516248 1596 | ], 1597 | [ 1598 | 61.704974996632586, 1599 | -24.471114586139663 1600 | ], 1601 | [ 1602 | 55.437716780037704, 1603 | -28.79492397966061 1604 | ], 1605 | [ 1606 | 49.2737132105334, 1607 | -33.006965374124604 1608 | ], 1609 | [ 1610 | 43.336444723195086, 1611 | -35.15881242423228 1612 | ], 1613 | [ 1614 | 34.141193258351336, 1615 | -38.55352993669186 1616 | ], 1617 | [ 1618 | 23.108036435883605, 1619 | -38.80076967436702 1620 | ], 1621 | [ 1622 | 13.56440050848596, 1623 | -37.24895082671077 1624 | ], 1625 | [ 1626 | 3.4017733869881113, 1627 | -34.54596157731686 1628 | ], 1629 | [ 1630 | -5.043850602774796, 1631 | -30.38802574420805 1632 | ], 1633 | [ 1634 | -13.936988567483809, 1635 | -23.061849659886946 1636 | ], 1637 | [ 1638 | -19.848695952316803, 1639 | -17.110369452114867 1640 | ], 1641 | [ 1642 | -24.30234711745686, 1643 | -9.807028934873472 1644 | ], 1645 | [ 1646 | -27.638949690193954, 1647 | -3.302443931842845 1648 | ], 1649 | [ 1650 | -31.172495874865263, 1651 | 7.5201521248652625 1652 | ], 1653 | [ 1654 | -0.2726377289870925, 1655 | -0.38748905576517245 1656 | ] 1657 | ] 1658 | }, 1659 | { 1660 | "type": "line", 1661 | "version": 946, 1662 | "versionNonce": 1374751491, 1663 | "isDeleted": false, 1664 | "id": "ffvKRNBbGCXSTn6_smmgV", 1665 | "fillStyle": "solid", 1666 | "strokeWidth": 1, 1667 | "strokeStyle": "solid", 1668 | "roughness": 1, 1669 | "opacity": 100, 1670 | "angle": 0, 1671 | "x": 977.321426610468, 1672 | "y": 406.30476163029175, 1673 | "strokeColor": "#216f36", 1674 | "backgroundColor": "#34a853", 1675 | "width": 55.63998518318965, 1676 | "height": 26.568519329202445, 1677 | "seed": 1006173247, 1678 | "groupIds": [ 1679 | "g15wKU6OJIBTpqVYNHOg-", 1680 | "tUzWJXrOKaMPpEDlg6CBB", 1681 | "gdmjNyUG7ie4haKK7uVQ1" 1682 | ], 1683 | "frameId": null, 1684 | "roundness": null, 1685 | "boundElements": [], 1686 | "updated": 1692114346918, 1687 | "link": null, 1688 | "locked": false, 1689 | "startBinding": null, 1690 | "endBinding": null, 1691 | "lastCommittedPoint": null, 1692 | "startArrowhead": null, 1693 | "endArrowhead": null, 1694 | "points": [ 1695 | [ 1696 | 0, 1697 | 0 1698 | ], 1699 | [ 1700 | -0.040041167160552504, 1701 | 24.881265574487998 1702 | ], 1703 | [ 1704 | -34.92333773908945, 1705 | 24.63729332233288 1706 | ], 1707 | [ 1708 | -43.518276872306046, 1709 | 22.786181219692708 1710 | ], 1711 | [ 1712 | -50.974278943292006, 1713 | 19.45566111597509 1714 | ], 1715 | [ 1716 | -55.63998518318965, 1717 | 16.626408017914855 1718 | ], 1719 | [ 1720 | -38.097123770878284, 1721 | -1.6872537547144475 1722 | ], 1723 | [ 1724 | -31.993334540005435, 1725 | 0.20771947400317003 1726 | ], 1727 | [ 1728 | -0.395381532866395, 1729 | 0.149052060883605 1730 | ] 1731 | ] 1732 | }, 1733 | { 1734 | "type": "line", 1735 | "version": 1821, 1736 | "versionNonce": 1237015213, 1737 | "isDeleted": false, 1738 | "id": "YkdjEP4wCUnk5sgIHZFcP", 1739 | "fillStyle": "solid", 1740 | "strokeWidth": 1, 1741 | "strokeStyle": "solid", 1742 | "roughness": 1, 1743 | "opacity": 100, 1744 | "angle": 0, 1745 | "x": 1014.1519130396168, 1746 | "y": 330.51379336015395, 1747 | "strokeColor": "#1864ab", 1748 | "backgroundColor": "#4285f4", 1749 | "width": 70.61835979593207, 1750 | "height": 100.29745693864498, 1751 | "seed": 2032999519, 1752 | "groupIds": [ 1753 | "g15wKU6OJIBTpqVYNHOg-", 1754 | "tUzWJXrOKaMPpEDlg6CBB", 1755 | "gdmjNyUG7ie4haKK7uVQ1" 1756 | ], 1757 | "frameId": null, 1758 | "roundness": null, 1759 | "boundElements": [], 1760 | "updated": 1692114346918, 1761 | "link": null, 1762 | "locked": false, 1763 | "startBinding": null, 1764 | "endBinding": null, 1765 | "lastCommittedPoint": null, 1766 | "startArrowhead": null, 1767 | "endArrowhead": null, 1768 | "points": [ 1769 | [ 1770 | 0, 1771 | 0 1772 | ], 1773 | [ 1774 | -17.31472673087285, 1775 | 17.124039222454144 1776 | ], 1777 | [ 1778 | -13.67090685614221, 1779 | 21.334749419113734 1780 | ], 1781 | [ 1782 | -10.33716662176721, 1783 | 26.03244649952859 1784 | ], 1785 | [ 1786 | -7.434103077855525, 1787 | 32.3370834876751 1788 | ], 1789 | [ 1790 | -6.3403951710668025, 1791 | 38.30434338799847 1792 | ], 1793 | [ 1794 | -5.845989358836164, 1795 | 44.51119784651138 1796 | ], 1797 | [ 1798 | -0.24683459051721002, 1799 | 45.45122343918371 1800 | ], 1801 | [ 1802 | 4.161461139547441, 1803 | 48.57825443662438 1804 | ], 1805 | [ 1806 | 8.318186792834126, 1807 | 54.172978894463824 1808 | ], 1809 | [ 1810 | 9.65123669854529, 1811 | 61.26837894834313 1812 | ], 1813 | [ 1814 | 8.06747962688587, 1815 | 67.79392110890359 1816 | ], 1817 | [ 1818 | 3.78062281115308, 1819 | 72.53440593851025 1820 | ], 1821 | [ 1822 | -1.1199740705818613, 1823 | 75.03574239796609 1824 | ], 1825 | [ 1826 | -7.480700262661571, 1827 | 75.71211716224417 1828 | ], 1829 | [ 1830 | -36.53557086813032, 1831 | 75.48285648740563 1832 | ], 1833 | [ 1834 | -36.54335811220358, 1835 | 100.29745693864498 1836 | ], 1837 | [ 1838 | -1.2360250538793025, 1839 | 100.0605352993669 1840 | ], 1841 | [ 1842 | 8.409297548491395, 1843 | 98.28338096881731 1844 | ], 1845 | [ 1846 | 16.557048929148777, 1847 | 93.71124793743257 1848 | ], 1849 | [ 1850 | 23.47689924568965, 1851 | 88.23310062803063 1852 | ], 1853 | [ 1854 | 28.87234391837285, 1855 | 80.0798771299165 1856 | ], 1857 | [ 1858 | 32.8473531788793, 1859 | 70.80846852269661 1860 | ], 1861 | [ 1862 | 34.07500168372849, 1863 | 59.77390157765359 1864 | ], 1865 | [ 1866 | 32.78353987068965, 1867 | 48.97542493096722 1868 | ], 1869 | [ 1870 | 28.23071709994622, 1871 | 39.592248324690274 1872 | ], 1873 | [ 1874 | 23.13571272225215, 1875 | 32.104855241446614 1876 | ], 1877 | [ 1878 | 16.82568780307122, 1879 | 27.235838791419667 1880 | ], 1881 | [ 1882 | 12.217344086745697, 1883 | 15.611061885439085 1884 | ], 1885 | [ 1886 | 8.231706290409534, 1887 | 8.649839203933197 1888 | ], 1889 | [ 1890 | 0.57017359240308, 1891 | 0.19406548861798 1892 | ] 1893 | ] 1894 | }, 1895 | { 1896 | "type": "line", 1897 | "version": 1794, 1898 | "versionNonce": 1178514083, 1899 | "isDeleted": false, 1900 | "id": "zmJaRudxzGjVK6DVmsmut", 1901 | "fillStyle": "solid", 1902 | "strokeWidth": 1, 1903 | "strokeStyle": "solid", 1904 | "roughness": 1, 1905 | "opacity": 100, 1906 | "angle": 0, 1907 | "x": 959.9228689467255, 1908 | "y": 383.63119711249607, 1909 | "strokeColor": "#c7970f", 1910 | "backgroundColor": "#fbbc05", 1911 | "width": 72.52458844866078, 1912 | "height": 73.14760480608254, 1913 | "seed": 388511871, 1914 | "groupIds": [ 1915 | "g15wKU6OJIBTpqVYNHOg-", 1916 | "tUzWJXrOKaMPpEDlg6CBB", 1917 | "gdmjNyUG7ie4haKK7uVQ1" 1918 | ], 1919 | "frameId": null, 1920 | "roundness": null, 1921 | "boundElements": [], 1922 | "updated": 1692114346918, 1923 | "link": null, 1924 | "locked": false, 1925 | "startBinding": null, 1926 | "endBinding": null, 1927 | "lastCommittedPoint": null, 1928 | "startArrowhead": null, 1929 | "endArrowhead": null, 1930 | "points": [ 1931 | [ 1932 | 0, 1933 | 0 1934 | ], 1935 | [ 1936 | 18.289315359933084, 1937 | -17.80088152204246 1938 | ], 1939 | [ 1940 | 10.357709612165195, 1941 | -25.25177001953125 1942 | ], 1943 | [ 1944 | 1.7710222516741396, 1945 | -30.339268275669724 1946 | ], 1947 | [ 1948 | -9.81458391462047, 1949 | -33.649444580078125 1950 | ], 1951 | [ 1952 | -19.441179547991055, 1953 | -33.04327828543535 1954 | ], 1955 | [ 1956 | -31.769670758928555, 1957 | -29.82794625418535 1958 | ], 1959 | [ 1960 | -42.316850934709805, 1961 | -22.06218174525668 1962 | ], 1963 | [ 1964 | -49.09467424665172, 1965 | -13.42171805245539 1966 | ], 1967 | [ 1968 | -52.871377127511096, 1969 | -3.9096941266741396 1970 | ], 1971 | [ 1972 | -54.235273088727695, 1973 | 6.901288713727695 1974 | ], 1975 | [ 1976 | -52.75076729910711, 1977 | 16.51665823800215 1978 | ], 1979 | [ 1980 | -49.733058384486526, 1981 | 24.413016183035666 1982 | ], 1983 | [ 1984 | -45.37183489118297, 1985 | 31.929670061383945 1986 | ], 1987 | [ 1988 | -37.869131905691916, 1989 | 39.498160226004416 1990 | ], 1991 | [ 1992 | -20.8297075544084, 1993 | 20.336369105747735 1994 | ], 1995 | [ 1996 | -26.952689034598166, 1997 | 15.01813616071422 1998 | ], 1999 | [ 2000 | -29.407806396484375, 2001 | 6.79973057338168 2002 | ], 2003 | [ 2004 | -26.90032958984375, 2005 | -1.187896728515625 2006 | ], 2007 | [ 2008 | -21.88862040295578, 2009 | -6.95288732853885 2010 | ], 2011 | [ 2012 | -13.367650607763835, 2013 | -8.842867662280582 2014 | ], 2015 | [ 2016 | -4.857958253077982, 2017 | -5.384067939723309 2018 | ] 2019 | ] 2020 | }, 2021 | { 2022 | "type": "text", 2023 | "version": 327, 2024 | "versionNonce": 1211464973, 2025 | "isDeleted": false, 2026 | "id": "qGP8DH-0SI3N2OHDgs_EB", 2027 | "fillStyle": "hachure", 2028 | "strokeWidth": 1, 2029 | "strokeStyle": "solid", 2030 | "roughness": 1, 2031 | "opacity": 100, 2032 | "angle": 0, 2033 | "x": 1076.4338073730469, 2034 | "y": 356.91870910628256, 2035 | "strokeColor": "#1e1e1e", 2036 | "backgroundColor": "transparent", 2037 | "width": 309.81988525390625, 2038 | "height": 35, 2039 | "seed": 1714544735, 2040 | "groupIds": [ 2041 | "tUzWJXrOKaMPpEDlg6CBB", 2042 | "gdmjNyUG7ie4haKK7uVQ1" 2043 | ], 2044 | "frameId": null, 2045 | "roundness": null, 2046 | "boundElements": [], 2047 | "updated": 1692114346918, 2048 | "link": null, 2049 | "locked": false, 2050 | "fontSize": 28, 2051 | "fontFamily": 1, 2052 | "text": "Google Cloud Platfrom", 2053 | "textAlign": "center", 2054 | "verticalAlign": "top", 2055 | "containerId": null, 2056 | "originalText": "Google Cloud Platfrom", 2057 | "lineHeight": 1.25, 2058 | "baseline": 25 2059 | }, 2060 | { 2061 | "type": "arrow", 2062 | "version": 1322, 2063 | "versionNonce": 2027363, 2064 | "isDeleted": false, 2065 | "id": "KKFKRKnBVTkfqOc0VPSPN", 2066 | "fillStyle": "solid", 2067 | "strokeWidth": 2, 2068 | "strokeStyle": "solid", 2069 | "roughness": 2, 2070 | "opacity": 100, 2071 | "angle": 0, 2072 | "x": 1174.12890625, 2073 | "y": 655.9656290048324, 2074 | "strokeColor": "#1e1e1e", 2075 | "backgroundColor": "#b2f2bb", 2076 | "width": 101.0417277218337, 2077 | "height": 0.5470589993609565, 2078 | "seed": 1339202865, 2079 | "groupIds": [], 2080 | "frameId": null, 2081 | "roundness": { 2082 | "type": 2 2083 | }, 2084 | "boundElements": [], 2085 | "updated": 1692114346918, 2086 | "link": null, 2087 | "locked": false, 2088 | "startBinding": { 2089 | "elementId": "2U7Xjtn49tpwxe9UxzOwM", 2090 | "focus": -0.6204461398448048, 2091 | "gap": 9.129005432128906 2092 | }, 2093 | "endBinding": { 2094 | "elementId": "4bQ899UMcX2vIttGelNjQ", 2095 | "focus": 1.8070107350606774, 2096 | "gap": 11.474696377275734 2097 | }, 2098 | "lastCommittedPoint": null, 2099 | "startArrowhead": null, 2100 | "endArrowhead": "arrow", 2101 | "points": [ 2102 | [ 2103 | 0, 2104 | 0 2105 | ], 2106 | [ 2107 | 101.0417277218337, 2108 | 0.5470589993609565 2109 | ] 2110 | ] 2111 | }, 2112 | { 2113 | "type": "line", 2114 | "version": 3353, 2115 | "versionNonce": 514427715, 2116 | "isDeleted": false, 2117 | "id": "wziZoLDG6lsmzivlHHqFG", 2118 | "fillStyle": "solid", 2119 | "strokeWidth": 1, 2120 | "strokeStyle": "solid", 2121 | "roughness": 1, 2122 | "opacity": 100, 2123 | "angle": 0, 2124 | "x": 1060.9465390895114, 2125 | "y": 131.88106485721846, 2126 | "strokeColor": "#000000", 2127 | "backgroundColor": "#e01e5a", 2128 | "width": 23.008854737100382, 2129 | "height": 50.700486945078886, 2130 | "seed": 670558239, 2131 | "groupIds": [ 2132 | "TV598GJRciWLlnHJWBg53", 2133 | "HLGFen4YFImEWdV018KQh", 2134 | "HLNu-yehrUOujZYry0vh5", 2135 | "ITqtdT-3jUL1S2DXodQqq" 2136 | ], 2137 | "frameId": null, 2138 | "roundness": { 2139 | "type": 2 2140 | }, 2141 | "boundElements": [], 2142 | "updated": 1692114319143, 2143 | "link": null, 2144 | "locked": false, 2145 | "startBinding": null, 2146 | "endBinding": null, 2147 | "lastCommittedPoint": null, 2148 | "startArrowhead": null, 2149 | "endArrowhead": null, 2150 | "points": [ 2151 | [ 2152 | 0, 2153 | 0 2154 | ], 2155 | [ 2156 | 3.6329799165396164, 2157 | 18.423146596248372 2158 | ], 2159 | [ 2160 | 21.595987056150623, 2161 | 16.507135629627406 2162 | ], 2163 | [ 2164 | 23.008854737100382, 2165 | -31.83518872924625 2166 | ], 2167 | [ 2168 | 1.8165306104832222, 2169 | -32.277340348830506 2170 | ], 2171 | [ 2172 | 0, 2173 | 0 2174 | ] 2175 | ] 2176 | }, 2177 | { 2178 | "type": "line", 2179 | "version": 3753, 2180 | "versionNonce": 586808941, 2181 | "isDeleted": false, 2182 | "id": "YS_PfiuFYs6fuXjNXOlwX", 2183 | "fillStyle": "solid", 2184 | "strokeWidth": 1, 2185 | "strokeStyle": "solid", 2186 | "roughness": 1, 2187 | "opacity": 100, 2188 | "angle": 0, 2189 | "x": 1034.990400426644, 2190 | "y": 95.73109996123546, 2191 | "strokeColor": "#000000", 2192 | "backgroundColor": "#e01e5a", 2193 | "width": 25.34738823404125, 2194 | "height": 24.388471834205635, 2195 | "seed": 35884095, 2196 | "groupIds": [ 2197 | "TV598GJRciWLlnHJWBg53", 2198 | "HLGFen4YFImEWdV018KQh", 2199 | "HLNu-yehrUOujZYry0vh5", 2200 | "ITqtdT-3jUL1S2DXodQqq" 2201 | ], 2202 | "frameId": null, 2203 | "roundness": { 2204 | "type": 2 2205 | }, 2206 | "boundElements": [], 2207 | "updated": 1692114319143, 2208 | "link": null, 2209 | "locked": false, 2210 | "startBinding": null, 2211 | "endBinding": null, 2212 | "lastCommittedPoint": null, 2213 | "startArrowhead": null, 2214 | "endArrowhead": null, 2215 | "points": [ 2216 | [ 2217 | 0, 2218 | 0 2219 | ], 2220 | [ 2221 | -6.61003773361201, 2222 | 5.451087517398957 2223 | ], 2224 | [ 2225 | -9.012751919045787, 2226 | 15.857183043031924 2227 | ], 2228 | [ 2229 | 2.7318756518744736, 2230 | 23.61479770476088 2231 | ], 2232 | [ 2233 | 15.87476538154422, 2234 | 17.9780753172117 2235 | ], 2236 | [ 2237 | 16.28817164342598, 2238 | -0.7528017770369946 2239 | ], 2240 | [ 2241 | 16.334636314995464, 2242 | -0.7736741294447577 2243 | ], 2244 | [ 2245 | 0, 2246 | 0 2247 | ] 2248 | ] 2249 | }, 2250 | { 2251 | "type": "line", 2252 | "version": 3451, 2253 | "versionNonce": 545073891, 2254 | "isDeleted": false, 2255 | "id": "gv2w8VDu9MmlONSX_ViHp", 2256 | "fillStyle": "solid", 2257 | "strokeWidth": 1, 2258 | "strokeStyle": "solid", 2259 | "roughness": 1, 2260 | "opacity": 100, 2261 | "angle": 1.5707963267948957, 2262 | "x": 1047.0630866486501, 2263 | "y": 84.82897744315548, 2264 | "strokeColor": "#000000", 2265 | "backgroundColor": "#36c5f0", 2266 | "width": 23.008854737100382, 2267 | "height": 50.700486945078886, 2268 | "seed": 998616159, 2269 | "groupIds": [ 2270 | "TV598GJRciWLlnHJWBg53", 2271 | "HLGFen4YFImEWdV018KQh", 2272 | "HLNu-yehrUOujZYry0vh5", 2273 | "ITqtdT-3jUL1S2DXodQqq" 2274 | ], 2275 | "frameId": null, 2276 | "roundness": { 2277 | "type": 2 2278 | }, 2279 | "boundElements": [], 2280 | "updated": 1692114319143, 2281 | "link": null, 2282 | "locked": false, 2283 | "startBinding": null, 2284 | "endBinding": null, 2285 | "lastCommittedPoint": null, 2286 | "startArrowhead": null, 2287 | "endArrowhead": null, 2288 | "points": [ 2289 | [ 2290 | 0, 2291 | 0 2292 | ], 2293 | [ 2294 | 3.6329799165396164, 2295 | 18.423146596248372 2296 | ], 2297 | [ 2298 | 21.595987056150623, 2299 | 16.507135629627406 2300 | ], 2301 | [ 2302 | 23.008854737100382, 2303 | -31.83518872924625 2304 | ], 2305 | [ 2306 | 1.8165306104832222, 2307 | -32.277340348830506 2308 | ], 2309 | [ 2310 | 0, 2311 | 0 2312 | ] 2313 | ] 2314 | }, 2315 | { 2316 | "type": "line", 2317 | "version": 3835, 2318 | "versionNonce": 292137165, 2319 | "isDeleted": false, 2320 | "id": "mhcsgcKN9BL6X92-0MqZe", 2321 | "fillStyle": "solid", 2322 | "strokeWidth": 1, 2323 | "strokeStyle": "solid", 2324 | "roughness": 1, 2325 | "opacity": 100, 2326 | "angle": 1.5707963267948957, 2327 | "x": 1069.724234584988, 2328 | "y": 31.879264949896356, 2329 | "strokeColor": "#000000", 2330 | "backgroundColor": "#36c5f0", 2331 | "width": 25.34738823404125, 2332 | "height": 24.388471834205635, 2333 | "seed": 535742591, 2334 | "groupIds": [ 2335 | "TV598GJRciWLlnHJWBg53", 2336 | "HLGFen4YFImEWdV018KQh", 2337 | "HLNu-yehrUOujZYry0vh5", 2338 | "ITqtdT-3jUL1S2DXodQqq" 2339 | ], 2340 | "frameId": null, 2341 | "roundness": { 2342 | "type": 2 2343 | }, 2344 | "boundElements": [], 2345 | "updated": 1692114319143, 2346 | "link": null, 2347 | "locked": false, 2348 | "startBinding": null, 2349 | "endBinding": null, 2350 | "lastCommittedPoint": null, 2351 | "startArrowhead": null, 2352 | "endArrowhead": null, 2353 | "points": [ 2354 | [ 2355 | 0, 2356 | 0 2357 | ], 2358 | [ 2359 | -6.61003773361201, 2360 | 5.451087517398957 2361 | ], 2362 | [ 2363 | -9.012751919045787, 2364 | 15.857183043031924 2365 | ], 2366 | [ 2367 | 2.7318756518744736, 2368 | 23.61479770476088 2369 | ], 2370 | [ 2371 | 15.87476538154422, 2372 | 17.9780753172117 2373 | ], 2374 | [ 2375 | 16.28817164342598, 2376 | -0.7528017770369946 2377 | ], 2378 | [ 2379 | 16.334636314995464, 2380 | -0.7736741294447577 2381 | ], 2382 | [ 2383 | 0, 2384 | 0 2385 | ] 2386 | ] 2387 | }, 2388 | { 2389 | "type": "line", 2390 | "version": 3437, 2391 | "versionNonce": 1375458947, 2392 | "isDeleted": false, 2393 | "id": "W1FO_17uHZ6SvNmQuh2u5", 2394 | "fillStyle": "solid", 2395 | "strokeWidth": 1, 2396 | "strokeStyle": "solid", 2397 | "roughness": 1, 2398 | "opacity": 100, 2399 | "angle": 3.141592653589793, 2400 | "x": 1092.9126322857676, 2401 | "y": 68.14001119581539, 2402 | "strokeColor": "#000000", 2403 | "backgroundColor": "#2eb67d", 2404 | "width": 23.008854737100382, 2405 | "height": 50.70048694507888, 2406 | "seed": 757031071, 2407 | "groupIds": [ 2408 | "TV598GJRciWLlnHJWBg53", 2409 | "HLGFen4YFImEWdV018KQh", 2410 | "HLNu-yehrUOujZYry0vh5", 2411 | "ITqtdT-3jUL1S2DXodQqq" 2412 | ], 2413 | "frameId": null, 2414 | "roundness": { 2415 | "type": 2 2416 | }, 2417 | "boundElements": [], 2418 | "updated": 1692114319143, 2419 | "link": null, 2420 | "locked": false, 2421 | "startBinding": null, 2422 | "endBinding": null, 2423 | "lastCommittedPoint": null, 2424 | "startArrowhead": null, 2425 | "endArrowhead": null, 2426 | "points": [ 2427 | [ 2428 | 0, 2429 | 0 2430 | ], 2431 | [ 2432 | 3.6329799165396164, 2433 | 18.423146596248372 2434 | ], 2435 | [ 2436 | 21.595987056150623, 2437 | 16.507135629627406 2438 | ], 2439 | [ 2440 | 23.008854737100382, 2441 | -31.83518872924625 2442 | ], 2443 | [ 2444 | 1.8165306104832222, 2445 | -32.277340348830506 2446 | ], 2447 | [ 2448 | 0, 2449 | 0 2450 | ] 2451 | ] 2452 | }, 2453 | { 2454 | "type": "line", 2455 | "version": 3819, 2456 | "versionNonce": 1026885421, 2457 | "isDeleted": false, 2458 | "id": "LnGi8wDIYgu7kJjwZxaYK", 2459 | "fillStyle": "solid", 2460 | "strokeWidth": 1, 2461 | "strokeStyle": "solid", 2462 | "roughness": 1, 2463 | "opacity": 100, 2464 | "angle": 3.141592653589793, 2465 | "x": 1137.02007537292, 2466 | "y": 63.05123588552203, 2467 | "strokeColor": "#000000", 2468 | "backgroundColor": "#2eb67d", 2469 | "width": 25.34738823404125, 2470 | "height": 24.388471834205635, 2471 | "seed": 1422390463, 2472 | "groupIds": [ 2473 | "TV598GJRciWLlnHJWBg53", 2474 | "HLGFen4YFImEWdV018KQh", 2475 | "HLNu-yehrUOujZYry0vh5", 2476 | "ITqtdT-3jUL1S2DXodQqq" 2477 | ], 2478 | "frameId": null, 2479 | "roundness": { 2480 | "type": 2 2481 | }, 2482 | "boundElements": [], 2483 | "updated": 1692114319143, 2484 | "link": null, 2485 | "locked": false, 2486 | "startBinding": null, 2487 | "endBinding": null, 2488 | "lastCommittedPoint": null, 2489 | "startArrowhead": null, 2490 | "endArrowhead": null, 2491 | "points": [ 2492 | [ 2493 | 0, 2494 | 0 2495 | ], 2496 | [ 2497 | -6.61003773361201, 2498 | 5.451087517398957 2499 | ], 2500 | [ 2501 | -9.012751919045787, 2502 | 15.857183043031924 2503 | ], 2504 | [ 2505 | 2.7318756518744736, 2506 | 23.61479770476088 2507 | ], 2508 | [ 2509 | 15.87476538154422, 2510 | 17.9780753172117 2511 | ], 2512 | [ 2513 | 16.28817164342598, 2514 | -0.7528017770369946 2515 | ], 2516 | [ 2517 | 16.334636314995464, 2518 | -0.7736741294447577 2519 | ], 2520 | [ 2521 | 0, 2522 | 0 2523 | ] 2524 | ] 2525 | }, 2526 | { 2527 | "type": "line", 2528 | "version": 3506, 2529 | "versionNonce": 568593955, 2530 | "isDeleted": false, 2531 | "id": "Ia-LyDLbu_PSGg8Z_1T2z", 2532 | "fillStyle": "solid", 2533 | "strokeWidth": 1, 2534 | "strokeStyle": "solid", 2535 | "roughness": 1, 2536 | "opacity": 100, 2537 | "angle": 4.712388980384688, 2538 | "x": 1108.7267731700965, 2539 | "y": 117.29959320293369, 2540 | "strokeColor": "#000000", 2541 | "backgroundColor": "#ecb22e", 2542 | "width": 23.008854737100382, 2543 | "height": 50.70048694507888, 2544 | "seed": 2122908895, 2545 | "groupIds": [ 2546 | "TV598GJRciWLlnHJWBg53", 2547 | "HLGFen4YFImEWdV018KQh", 2548 | "HLNu-yehrUOujZYry0vh5", 2549 | "ITqtdT-3jUL1S2DXodQqq" 2550 | ], 2551 | "frameId": null, 2552 | "roundness": { 2553 | "type": 2 2554 | }, 2555 | "boundElements": [], 2556 | "updated": 1692114319143, 2557 | "link": null, 2558 | "locked": false, 2559 | "startBinding": null, 2560 | "endBinding": null, 2561 | "lastCommittedPoint": null, 2562 | "startArrowhead": null, 2563 | "endArrowhead": null, 2564 | "points": [ 2565 | [ 2566 | 0, 2567 | 0 2568 | ], 2569 | [ 2570 | 3.6329799165396164, 2571 | 18.423146596248372 2572 | ], 2573 | [ 2574 | 21.595987056150623, 2575 | 16.507135629627406 2576 | ], 2577 | [ 2578 | 23.008854737100382, 2579 | -31.83518872924625 2580 | ], 2581 | [ 2582 | 1.8165306104832222, 2583 | -32.277340348830506 2584 | ], 2585 | [ 2586 | 0, 2587 | 0 2588 | ] 2589 | ] 2590 | }, 2591 | { 2592 | "type": "line", 2593 | "version": 3904, 2594 | "versionNonce": 1757780365, 2595 | "isDeleted": false, 2596 | "id": "uPVvpwvQXgmx7peQ9iv_f", 2597 | "fillStyle": "solid", 2598 | "strokeWidth": 1, 2599 | "strokeStyle": "solid", 2600 | "roughness": 1, 2601 | "opacity": 100, 2602 | "angle": 4.712388980384688, 2603 | "x": 1099.586224284403, 2604 | "y": 129.95885991650403, 2605 | "strokeColor": "#000000", 2606 | "backgroundColor": "#ecb22e", 2607 | "width": 25.34738823404125, 2608 | "height": 24.388471834205635, 2609 | "seed": 1240666367, 2610 | "groupIds": [ 2611 | "TV598GJRciWLlnHJWBg53", 2612 | "HLGFen4YFImEWdV018KQh", 2613 | "HLNu-yehrUOujZYry0vh5", 2614 | "ITqtdT-3jUL1S2DXodQqq" 2615 | ], 2616 | "frameId": null, 2617 | "roundness": { 2618 | "type": 2 2619 | }, 2620 | "boundElements": [], 2621 | "updated": 1692114319143, 2622 | "link": null, 2623 | "locked": false, 2624 | "startBinding": null, 2625 | "endBinding": null, 2626 | "lastCommittedPoint": null, 2627 | "startArrowhead": null, 2628 | "endArrowhead": null, 2629 | "points": [ 2630 | [ 2631 | 0, 2632 | 0 2633 | ], 2634 | [ 2635 | -6.61003773361201, 2636 | 5.451087517398957 2637 | ], 2638 | [ 2639 | -9.012751919045787, 2640 | 15.857183043031924 2641 | ], 2642 | [ 2643 | 2.7318756518744736, 2644 | 23.61479770476088 2645 | ], 2646 | [ 2647 | 15.87476538154422, 2648 | 17.9780753172117 2649 | ], 2650 | [ 2651 | 16.28817164342598, 2652 | -0.7528017770369946 2653 | ], 2654 | [ 2655 | 16.334636314995464, 2656 | -0.7736741294447577 2657 | ], 2658 | [ 2659 | 0, 2660 | 0 2661 | ] 2662 | ] 2663 | }, 2664 | { 2665 | "type": "text", 2666 | "version": 417, 2667 | "versionNonce": 1879029645, 2668 | "isDeleted": false, 2669 | "id": "Gpip8r6mIMp4heaKfxVkZ", 2670 | "fillStyle": "solid", 2671 | "strokeWidth": 1, 2672 | "strokeStyle": "solid", 2673 | "roughness": 2, 2674 | "opacity": 100, 2675 | "angle": 0, 2676 | "x": 1001.3058395385742, 2677 | "y": 183.31640625, 2678 | "strokeColor": "#1e1e1e", 2679 | "backgroundColor": "#b2f2bb", 2680 | "width": 176.73988342285156, 2681 | "height": 25, 2682 | "seed": 1315581233, 2683 | "groupIds": [ 2684 | "ITqtdT-3jUL1S2DXodQqq" 2685 | ], 2686 | "frameId": null, 2687 | "roundness": null, 2688 | "boundElements": [ 2689 | { 2690 | "id": "dcwo5gS6oN7kj61GzoiqW", 2691 | "type": "arrow" 2692 | } 2693 | ], 2694 | "updated": 1692114352359, 2695 | "link": null, 2696 | "locked": false, 2697 | "fontSize": 20, 2698 | "fontFamily": 1, 2699 | "text": "ChatGPT in Slack", 2700 | "textAlign": "center", 2701 | "verticalAlign": "top", 2702 | "containerId": null, 2703 | "originalText": "ChatGPT in Slack", 2704 | "lineHeight": 1.25, 2705 | "baseline": 18 2706 | }, 2707 | { 2708 | "type": "line", 2709 | "version": 2153, 2710 | "versionNonce": 1297407981, 2711 | "isDeleted": false, 2712 | "id": "obzflOhom-jWhKBnWlObZ", 2713 | "fillStyle": "solid", 2714 | "strokeWidth": 1, 2715 | "strokeStyle": "solid", 2716 | "roughness": 1, 2717 | "opacity": 100, 2718 | "angle": 0, 2719 | "x": 1336.2735942213249, 2720 | "y": 54.88515322921995, 2721 | "strokeColor": "#000000", 2722 | "backgroundColor": "#5865f2", 2723 | "width": 127.23762597804352, 2724 | "height": 100.33316743696518, 2725 | "seed": 239410129, 2726 | "groupIds": [ 2727 | "WxR9v94mYZyw9zLxIu-7y", 2728 | "9FKdvlgB6kdZEMyeC8HZy" 2729 | ], 2730 | "frameId": null, 2731 | "roundness": { 2732 | "type": 2 2733 | }, 2734 | "boundElements": [], 2735 | "updated": 1692114319143, 2736 | "link": null, 2737 | "locked": false, 2738 | "startBinding": null, 2739 | "endBinding": null, 2740 | "lastCommittedPoint": null, 2741 | "startArrowhead": null, 2742 | "endArrowhead": null, 2743 | "points": [ 2744 | [ 2745 | 0, 2746 | 0 2747 | ], 2748 | [ 2749 | -27.38475436238639, 2750 | 9.128777729796333 2751 | ], 2752 | [ 2753 | -45.48632919916851, 2754 | 49.832343801246694 2755 | ], 2756 | [ 2757 | -45.07124552268717, 2758 | 83.13003337481854 2759 | ], 2760 | [ 2761 | -14.026667385744341, 2762 | 99.50748408160354 2763 | ], 2764 | [ 2765 | -6.513728841393585, 2766 | 88.12757725335737 2767 | ], 2768 | [ 2769 | -17.796399974868393, 2770 | 81.13828807672462 2771 | ], 2772 | [ 2773 | -10.185624277840446, 2774 | 79.73934964855896 2775 | ], 2776 | [ 2777 | 3.9353317915955826, 2778 | 86.1441270622144 2779 | ], 2780 | [ 2781 | 26.507823988894184, 2782 | 87.65426509919376 2783 | ], 2784 | [ 2785 | 51.37267275117539, 2786 | 79.13043125814377 2787 | ], 2788 | [ 2789 | 51.64486005353635, 2790 | 83.71066105380068 2791 | ], 2792 | [ 2793 | 39.906885285368936, 2794 | 87.74607022978476 2795 | ], 2796 | [ 2797 | 48.29401783505776, 2798 | 98.1335515311889 2799 | ], 2800 | [ 2801 | 69.33760403580601, 2802 | 89.54546193720226 2803 | ], 2804 | [ 2805 | 81.75129677887502, 2806 | 73.95389624506504 2807 | ], 2808 | [ 2809 | 75.30472311924483, 2810 | 33.84565327510745 2811 | ], 2812 | [ 2813 | 61.34173001079874, 2814 | 7.221194366386985 2815 | ], 2816 | [ 2817 | 34.27058836403539, 2818 | -0.8256833553616423 2819 | ], 2820 | [ 2821 | 32.45689458832667, 2822 | 7.620692798048813 2823 | ], 2824 | [ 2825 | 4.070164015604255, 2826 | 7.5444104262353235 2827 | ], 2828 | [ 2829 | 0, 2830 | 0 2831 | ] 2832 | ] 2833 | }, 2834 | { 2835 | "type": "ellipse", 2836 | "version": 615, 2837 | "versionNonce": 115024227, 2838 | "isDeleted": false, 2839 | "id": "MkDl6Fj8zxYtDgSyDnWnO", 2840 | "fillStyle": "solid", 2841 | "strokeWidth": 1, 2842 | "strokeStyle": "solid", 2843 | "roughness": 1, 2844 | "opacity": 100, 2845 | "angle": 0, 2846 | "x": 1320.5117507507412, 2847 | "y": 95.17819414509324, 2848 | "strokeColor": "#000000", 2849 | "backgroundColor": "#fff", 2850 | "width": 23.507042470319412, 2851 | "height": 25.937822066247822, 2852 | "seed": 1068726705, 2853 | "groupIds": [ 2854 | "WxR9v94mYZyw9zLxIu-7y", 2855 | "9FKdvlgB6kdZEMyeC8HZy" 2856 | ], 2857 | "frameId": null, 2858 | "roundness": null, 2859 | "boundElements": [], 2860 | "updated": 1692114319143, 2861 | "link": null, 2862 | "locked": false 2863 | }, 2864 | { 2865 | "type": "ellipse", 2866 | "version": 702, 2867 | "versionNonce": 1089231437, 2868 | "isDeleted": false, 2869 | "id": "iXyBl_fk8Tu-Qz1WBpQN8", 2870 | "fillStyle": "solid", 2871 | "strokeWidth": 1, 2872 | "strokeStyle": "solid", 2873 | "roughness": 1, 2874 | "opacity": 100, 2875 | "angle": 0, 2876 | "x": 1362.3842790488125, 2877 | "y": 94.83979889084105, 2878 | "strokeColor": "#000000", 2879 | "backgroundColor": "#fff", 2880 | "width": 23.507042470319412, 2881 | "height": 25.937822066247822, 2882 | "seed": 1398479761, 2883 | "groupIds": [ 2884 | "WxR9v94mYZyw9zLxIu-7y", 2885 | "9FKdvlgB6kdZEMyeC8HZy" 2886 | ], 2887 | "frameId": null, 2888 | "roundness": null, 2889 | "boundElements": [], 2890 | "updated": 1692114319143, 2891 | "link": null, 2892 | "locked": false 2893 | }, 2894 | { 2895 | "type": "text", 2896 | "version": 389, 2897 | "versionNonce": 1449840397, 2898 | "isDeleted": false, 2899 | "id": "qUA7Lrka-FDYzIK0890HR", 2900 | "fillStyle": "solid", 2901 | "strokeWidth": 1, 2902 | "strokeStyle": "solid", 2903 | "roughness": 2, 2904 | "opacity": 100, 2905 | "angle": 0, 2906 | "x": 1242.3886795043945, 2907 | "y": 183.31640625, 2908 | "strokeColor": "#1e1e1e", 2909 | "backgroundColor": "#b2f2bb", 2910 | "width": 221.33982849121094, 2911 | "height": 25, 2912 | "seed": 1713090431, 2913 | "groupIds": [ 2914 | "9FKdvlgB6kdZEMyeC8HZy" 2915 | ], 2916 | "frameId": null, 2917 | "roundness": null, 2918 | "boundElements": [ 2919 | { 2920 | "id": "uzgLVQ61-6x7EkOzOaz5N", 2921 | "type": "arrow" 2922 | } 2923 | ], 2924 | "updated": 1692114356578, 2925 | "link": null, 2926 | "locked": false, 2927 | "fontSize": 20, 2928 | "fontFamily": 1, 2929 | "text": "ChatGPT Discord Bot", 2930 | "textAlign": "center", 2931 | "verticalAlign": "top", 2932 | "containerId": null, 2933 | "originalText": "ChatGPT Discord Bot", 2934 | "lineHeight": 1.25, 2935 | "baseline": 18 2936 | }, 2937 | { 2938 | "type": "rectangle", 2939 | "version": 232, 2940 | "versionNonce": 476416173, 2941 | "isDeleted": false, 2942 | "id": "95cp-C47bPf9hciOmqeHO", 2943 | "fillStyle": "hachure", 2944 | "strokeWidth": 1, 2945 | "strokeStyle": "solid", 2946 | "roughness": 1, 2947 | "opacity": 100, 2948 | "angle": 0, 2949 | "x": 768.0078125, 2950 | "y": -6.57421875, 2951 | "strokeColor": "#000000", 2952 | "backgroundColor": "#ced4da", 2953 | "width": 100, 2954 | "height": 160, 2955 | "seed": 746390239, 2956 | "groupIds": [ 2957 | "CknNh4lqcqFmUgTZNUfs9", 2958 | "-bOdghFvZhb_Ri8hhQTs6" 2959 | ], 2960 | "frameId": null, 2961 | "roundness": { 2962 | "type": 1 2963 | }, 2964 | "boundElements": [], 2965 | "updated": 1692114319143, 2966 | "link": null, 2967 | "locked": false 2968 | }, 2969 | { 2970 | "type": "rectangle", 2971 | "version": 251, 2972 | "versionNonce": 1830184099, 2973 | "isDeleted": false, 2974 | "id": "KBgKYqwUVZBVy4CzH95TO", 2975 | "fillStyle": "solid", 2976 | "strokeWidth": 1, 2977 | "strokeStyle": "solid", 2978 | "roughness": 1, 2979 | "opacity": 100, 2980 | "angle": 0, 2981 | "x": 778.0078125, 2982 | "y": 3.42578125, 2983 | "strokeColor": "#000000", 2984 | "backgroundColor": "#ffffff", 2985 | "width": 80, 2986 | "height": 120, 2987 | "seed": 1421299455, 2988 | "groupIds": [ 2989 | "CknNh4lqcqFmUgTZNUfs9", 2990 | "-bOdghFvZhb_Ri8hhQTs6" 2991 | ], 2992 | "frameId": null, 2993 | "roundness": null, 2994 | "boundElements": [], 2995 | "updated": 1692114319143, 2996 | "link": null, 2997 | "locked": false 2998 | }, 2999 | { 3000 | "type": "ellipse", 3001 | "version": 224, 3002 | "versionNonce": 1634331405, 3003 | "isDeleted": false, 3004 | "id": "Z5VOglDa3yBiZhZv26SNS", 3005 | "fillStyle": "cross-hatch", 3006 | "strokeWidth": 1, 3007 | "strokeStyle": "solid", 3008 | "roughness": 1, 3009 | "opacity": 100, 3010 | "angle": 0, 3011 | "x": 808.0078125, 3012 | "y": 133.42578125, 3013 | "strokeColor": "#000000", 3014 | "backgroundColor": "#868e96", 3015 | "width": 20, 3016 | "height": 20, 3017 | "seed": 1947408159, 3018 | "groupIds": [ 3019 | "CknNh4lqcqFmUgTZNUfs9", 3020 | "-bOdghFvZhb_Ri8hhQTs6" 3021 | ], 3022 | "frameId": null, 3023 | "roundness": null, 3024 | "boundElements": [], 3025 | "updated": 1692114319143, 3026 | "link": null, 3027 | "locked": false 3028 | }, 3029 | { 3030 | "type": "text", 3031 | "version": 228, 3032 | "versionNonce": 403709891, 3033 | "isDeleted": false, 3034 | "id": "cBBBTnpcCHHnSbUdx74z0", 3035 | "fillStyle": "solid", 3036 | "strokeWidth": 1, 3037 | "strokeStyle": "solid", 3038 | "roughness": 2, 3039 | "opacity": 100, 3040 | "angle": 0, 3041 | "x": 696.0578994750977, 3042 | "y": 183.31640625, 3043 | "strokeColor": "#1e1e1e", 3044 | "backgroundColor": "#b2f2bb", 3045 | "width": 243.8998260498047, 3046 | "height": 25, 3047 | "seed": 553704255, 3048 | "groupIds": [ 3049 | "-bOdghFvZhb_Ri8hhQTs6" 3050 | ], 3051 | "frameId": null, 3052 | "roundness": null, 3053 | "boundElements": [ 3054 | { 3055 | "id": "ZB7f3_0kNeKNzY62ROKGW", 3056 | "type": "arrow" 3057 | } 3058 | ], 3059 | "updated": 1692114337891, 3060 | "link": null, 3061 | "locked": false, 3062 | "fontSize": 20, 3063 | "fontFamily": 1, 3064 | "text": "ChatGPT or OpenAI app", 3065 | "textAlign": "center", 3066 | "verticalAlign": "top", 3067 | "containerId": null, 3068 | "originalText": "ChatGPT or OpenAI app", 3069 | "lineHeight": 1.25, 3070 | "baseline": 18 3071 | }, 3072 | { 3073 | "type": "line", 3074 | "version": 761, 3075 | "versionNonce": 544407917, 3076 | "isDeleted": false, 3077 | "id": "9go0HN11LTc8eQKgLNgyP", 3078 | "fillStyle": "cross-hatch", 3079 | "strokeWidth": 1, 3080 | "strokeStyle": "solid", 3081 | "roughness": 1, 3082 | "opacity": 100, 3083 | "angle": 0, 3084 | "x": 1546.6120794195124, 3085 | "y": 91.97096820504137, 3086 | "strokeColor": "#2489ca", 3087 | "backgroundColor": "#2489ca", 3088 | "width": 82.25161174304603, 3089 | "height": 81.41915931562033, 3090 | "seed": 1509748227, 3091 | "groupIds": [ 3092 | "J0-Q1F7nQ8BAR9ZI8GgZb", 3093 | "YkAsPW2qzE2Y-PfN8-5-Q", 3094 | "mERqMPWfjVmnsuY3uiObT" 3095 | ], 3096 | "frameId": null, 3097 | "roundness": null, 3098 | "boundElements": [], 3099 | "updated": 1692114319143, 3100 | "link": null, 3101 | "locked": false, 3102 | "startBinding": null, 3103 | "endBinding": null, 3104 | "lastCommittedPoint": null, 3105 | "startArrowhead": null, 3106 | "endArrowhead": null, 3107 | "points": [ 3108 | [ 3109 | 0, 3110 | 0 3111 | ], 3112 | [ 3113 | 0, 3114 | 0 3115 | ], 3116 | [ 3117 | -0.029178162068434262, 3118 | -0.022222096372520473 3119 | ], 3120 | [ 3121 | -0.108375757954911, 3122 | -0.08764345962094704 3123 | ], 3124 | [ 3125 | -0.2250880882128406, 3126 | -0.19438822052013352 3127 | ], 3128 | [ 3129 | -0.36681013537978746, 3130 | -0.340583902013482 3131 | ], 3132 | [ 3133 | -0.5210371470065221, 3134 | -0.5243614192130086 3135 | ], 3136 | [ 3137 | -0.6752641056306176, 3138 | -0.7438449028935826 3139 | ], 3140 | [ 3141 | -0.8169862058001879, 3142 | -0.997165268167162 3143 | ], 3144 | [ 3145 | -0.9336985095568019, 3146 | -1.2824466458085877 3147 | ], 3148 | [ 3149 | -1.012896158445919, 3150 | -1.5978165587613655 3151 | ], 3152 | [ 3153 | -1.0420742410104031, 3154 | -1.941405922137382 3155 | ], 3156 | [ 3157 | -1.0087278457930933, 3158 | -2.311338866711555 3159 | ], 3160 | [ 3161 | -0.9647002272505554, 3162 | -2.505601576994198 3163 | ], 3164 | [ 3165 | -0.9003521408408196, 3166 | -2.7057463075958617 3167 | ], 3168 | [ 3169 | -0.8141205123837076, 3170 | -2.9115389988844953 3171 | ], 3172 | [ 3173 | -0.7044422146964087, 3174 | -3.122752375565174 3175 | ], 3176 | [ 3177 | -0.5697540940948278, 3178 | -3.3391455936686603 3179 | ], 3180 | [ 3181 | -0.40849310290010177, 3182 | -3.560484593562898 3183 | ], 3184 | [ 3185 | -0.21909614043074543, 3186 | -3.786538707784371 3187 | ], 3188 | [ 3189 | -5.329070518200751e-15, 3190 | -4.017073876701018 3191 | ], 3192 | [ 3193 | 0.2503582600657044, 3194 | -4.251852648512211 3195 | ], 3196 | [ 3197 | 0.5335418994557528, 3198 | -4.490644355754403 3199 | ], 3200 | [ 3201 | 7.992014263492071, 3202 | -11.159919159004057 3203 | ], 3204 | [ 3205 | 7.992014263492071, 3206 | -11.159919159004057 3207 | ], 3208 | [ 3209 | 8.016532857993791, 3210 | -11.184658244463542 3211 | ], 3212 | [ 3213 | 8.088123727848453, 3214 | -11.252576243787408 3215 | ], 3216 | [ 3217 | 8.203839926601583, 3218 | -11.354232751815442 3219 | ], 3220 | [ 3221 | 8.36073450779865, 3222 | -11.480183971218864 3223 | ], 3224 | [ 3225 | 8.555860524985064, 3226 | -11.620982712500242 3227 | ], 3228 | [ 3229 | 8.786270183664255, 3230 | -11.767188570499341 3231 | ], 3232 | [ 3233 | 9.04901653738161, 3234 | -11.909354355718747 3235 | ], 3236 | [ 3237 | 9.341152639682647, 3238 | -12.038039662998242 3239 | ], 3240 | [ 3241 | 9.659731544112729, 3242 | -12.143800695009027 3243 | ], 3244 | [ 3245 | 10.001806304217512, 3246 | -12.217190262253647 3247 | ], 3248 | [ 3249 | 10.36442912550017, 3250 | -12.248767959571868 3251 | ], 3252 | [ 3253 | 10.744653061506181, 3254 | -12.22908659746638 3255 | ], 3256 | [ 3257 | 11.139531165781055, 3258 | -12.148705770776765 3259 | ], 3260 | [ 3261 | 11.546116491870276, 3262 | -11.998181682174401 3263 | ], 3264 | [ 3265 | 11.752878495330878, 3266 | -11.893662183864171 3267 | ], 3268 | [ 3269 | 11.96146209331934, 3270 | -11.76806714216176 3271 | ], 3272 | [ 3273 | 12.171498387502437, 3274 | -11.620212690232398 3275 | ], 3276 | [ 3277 | 12.382620175631484, 3278 | -11.448921745578609 3279 | ], 3280 | [ 3281 | 81.20953750203556, 3282 | 40.66034741911328 3283 | ], 3284 | [ 3285 | 81.20953750203563, 3286 | 65.64789565842146 3287 | ], 3288 | [ 3289 | 81.20953750203563, 3290 | 65.64789565842146 3291 | ], 3292 | [ 3293 | 81.20928648156098, 3294 | 65.65913730509816 3295 | ], 3296 | [ 3297 | 81.20792961412839, 3298 | 65.69185816322809 3299 | ], 3300 | [ 3301 | 81.19826871801051, 3302 | 65.81564517907708 3303 | ], 3304 | [ 3305 | 81.17326843557032, 3306 | 66.00713987979776 3307 | ], 3308 | [ 3309 | 81.12564917303371, 3310 | 66.25421187054553 3311 | ], 3312 | [ 3313 | 81.04813812096356, 3314 | 66.54471718780134 3315 | ], 3316 | [ 3317 | 80.9334489012482, 3318 | 66.86653900539457 3319 | ], 3320 | [ 3321 | 80.7743154887877, 3322 | 67.2075333598064 3323 | ], 3324 | [ 3325 | 80.56345150547092, 3326 | 67.55558342486647 3327 | ], 3328 | [ 3329 | 80.29357735752325, 3330 | 67.89855880572917 3331 | ], 3332 | [ 3333 | 79.95742023550747, 3334 | 68.22431553887607 3335 | ], 3336 | [ 3337 | 79.76220771802251, 3338 | 68.37694955633583 3339 | ], 3340 | [ 3341 | 79.54769376131263, 3342 | 68.52073679813671 3343 | ], 3344 | [ 3345 | 79.31297604853587, 3346 | 68.65415078841788 3347 | ], 3348 | [ 3349 | 79.05713190983838, 3350 | 68.77567861999194 3351 | ], 3352 | [ 3353 | 78.77925902837708, 3354 | 68.88381417000922 3355 | ], 3356 | [ 3357 | 78.47844830297299, 3358 | 68.9770241782712 3359 | ], 3360 | [ 3361 | 78.15378384810889, 3362 | 69.05380930626518 3363 | ], 3364 | [ 3365 | 77.80436334694288, 3366 | 69.11264307812985 3367 | ], 3368 | [ 3369 | 77.42927091395711, 3370 | 69.15200580234091 3371 | ], 3372 | [ 3373 | 77.02760423231001, 3374 | 69.17039135604847 3375 | ], 3376 | [ 3377 | 76.59844741648433, 3378 | 69.16628004772858 3379 | ], 3380 | [ 3381 | 76.14088458096357, 3382 | 69.13815218585702 3383 | ], 3384 | [ 3385 | 0, 3386 | 0 3387 | ] 3388 | ] 3389 | }, 3390 | { 3391 | "type": "line", 3392 | "version": 765, 3393 | "versionNonce": 357160931, 3394 | "isDeleted": false, 3395 | "id": "oDMGgw2dxY1mTyiYdt-fm", 3396 | "fillStyle": "cross-hatch", 3397 | "strokeWidth": 1, 3398 | "strokeStyle": "solid", 3399 | "roughness": 1, 3400 | "opacity": 100, 3401 | "angle": 0, 3402 | "x": 1564.548792511456, 3403 | "y": 108.48992121953253, 3404 | "strokeColor": "#1070b3", 3405 | "backgroundColor": "#1070b3", 3406 | "width": 32.69801934793733, 3407 | "height": 28.252259477809226, 3408 | "seed": 1221997987, 3409 | "groupIds": [ 3410 | "J2-nibcGGQ-NJy7ovE1dd", 3411 | "YkAsPW2qzE2Y-PfN8-5-Q", 3412 | "mERqMPWfjVmnsuY3uiObT" 3413 | ], 3414 | "frameId": null, 3415 | "roundness": null, 3416 | "boundElements": [], 3417 | "updated": 1692114319143, 3418 | "link": null, 3419 | "locked": false, 3420 | "startBinding": null, 3421 | "endBinding": null, 3422 | "lastCommittedPoint": null, 3423 | "startArrowhead": null, 3424 | "endArrowhead": null, 3425 | "points": [ 3426 | [ 3427 | 0, 3428 | 0 3429 | ], 3430 | [ 3431 | -17.74027059502523, 3432 | 16.12853363646109 3433 | ], 3434 | [ 3435 | -17.74027059502523, 3436 | 16.12853363646109 3437 | ], 3438 | [ 3439 | -17.760297958325925, 3440 | 16.14435471072177 3441 | ], 3442 | [ 3443 | -17.81503950281896, 3444 | 16.191532991342903 3445 | ], 3446 | [ 3447 | -17.89648409237441, 3448 | 16.269647849420537 3449 | ], 3450 | [ 3451 | -17.996620908878306, 3452 | 16.37827865605056 3453 | ], 3454 | [ 3455 | -18.10743892220605, 3456 | 16.51700478232908 3457 | ], 3458 | [ 3459 | -18.220927367246432, 3460 | 16.685392030677754 3461 | ], 3462 | [ 3463 | -18.329075107869574, 3464 | 16.883019772192522 3465 | ], 3466 | [ 3467 | -18.42387132596144, 3468 | 17.109467377969303 3469 | ], 3470 | [ 3471 | -18.49730499139763, 3472 | 17.364314219104305 3473 | ], 3474 | [ 3475 | -18.5413651800588, 3476 | 17.647139666693203 3477 | ], 3478 | [ 3479 | -18.548040967825706, 3480 | 17.957509523157995 3481 | ], 3482 | [ 3483 | -18.509321377576516, 3484 | 18.295003159594412 3485 | ], 3486 | [ 3487 | -18.41719553819448, 3488 | 18.65919994709845 3489 | ], 3490 | [ 3491 | -18.263652419555232, 3492 | 19.049679256766314 3493 | ], 3494 | [ 3495 | -18.0406810445368, 3496 | 19.466020459693755 3497 | ], 3498 | [ 3499 | -17.900656459728705, 3500 | 19.683750192218053 3501 | ], 3502 | [ 3503 | -17.74027059502518, 3504 | 19.907789358302605 3505 | ], 3506 | [ 3507 | -9.50371659463083, 3508 | 27.399603983350655 3509 | ], 3510 | [ 3511 | -9.50371659463083, 3512 | 27.399603983350655 3513 | ], 3514 | [ 3515 | -9.481039947668407, 3516 | 27.42261645500256 3517 | ], 3518 | [ 3519 | -9.414010696512749, 3520 | 27.48520874965461 3521 | ], 3522 | [ 3523 | -9.304126483592082, 3524 | 27.577726755526175 3525 | ], 3526 | [ 3527 | -9.152884951334851, 3528 | 27.690516360836227 3529 | ], 3530 | [ 3531 | -8.961783742169466, 3532 | 27.813923453804055 3533 | ], 3534 | [ 3535 | -8.732320498524361, 3536 | 27.938293922648818 3537 | ], 3538 | [ 3539 | -8.465994558912119, 3540 | 28.053966871252534 3541 | ], 3542 | [ 3543 | -8.164303565761239, 3544 | 28.151288187834446 3545 | ], 3546 | [ 3547 | -7.828745161500249, 3548 | 28.220603760613592 3549 | ], 3550 | [ 3551 | -7.460816988557232, 3552 | 28.252259477809233 3553 | ], 3554 | [ 3555 | -7.0620166893608065, 3556 | 28.236601227640396 3557 | ], 3558 | [ 3559 | -6.63384360242369, 3560 | 28.163968113989277 3561 | ], 3562 | [ 3563 | -6.177795370174131, 3564 | 28.024706025074977 3565 | ], 3566 | [ 3567 | -5.939785557789833, 3568 | 27.927072628983737 3569 | ], 3570 | [ 3571 | -5.695369635040738, 3572 | 27.809160849116672 3573 | ], 3574 | [ 3575 | -5.44473247511452, 3576 | 27.669769857796354 3577 | ], 3578 | [ 3579 | -5.188064039451822, 3580 | 27.50767847433361 3581 | ], 3582 | [ 3583 | -4.925549201240031, 3584 | 27.321692655387924 3585 | ], 3586 | [ 3587 | -4.657376225835639, 3588 | 27.1106047889448 3589 | ], 3590 | [ 3591 | 14.149978380111627, 3592 | 12.84947352486336 3593 | ], 3594 | [ 3595 | 0, 3596 | 0 3597 | ] 3598 | ] 3599 | }, 3600 | { 3601 | "type": "line", 3602 | "version": 763, 3603 | "versionNonce": 516574157, 3604 | "isDeleted": false, 3605 | "id": "Vp9mHT72bMa9EpzxDheB9", 3606 | "fillStyle": "cross-hatch", 3607 | "strokeWidth": 1, 3608 | "strokeStyle": "solid", 3609 | "roughness": 1, 3610 | "opacity": 100, 3611 | "angle": 0, 3612 | "x": 1595.6929131160684, 3613 | "y": 108.29014751866904, 3614 | "strokeColor": "#0877b9", 3615 | "backgroundColor": "#0877b9", 3616 | "width": 49.530478158399454, 3617 | "height": 53.11015839792923, 3618 | "seed": 140872003, 3619 | "groupIds": [ 3620 | "WyTRPyXZ4oO6as6Se3CdV", 3621 | "YkAsPW2qzE2Y-PfN8-5-Q", 3622 | "mERqMPWfjVmnsuY3uiObT" 3623 | ], 3624 | "frameId": null, 3625 | "roundness": null, 3626 | "boundElements": [], 3627 | "updated": 1692114319143, 3628 | "link": null, 3629 | "locked": false, 3630 | "startBinding": null, 3631 | "endBinding": null, 3632 | "lastCommittedPoint": null, 3633 | "startArrowhead": null, 3634 | "endArrowhead": null, 3635 | "points": [ 3636 | [ 3637 | 0, 3638 | 0 3639 | ], 3640 | [ 3641 | 32.53494354655182, 3642 | -24.843048642105018 3643 | ], 3644 | [ 3645 | 32.3237539150513, 3646 | -49.69721257260706 3647 | ], 3648 | [ 3649 | 32.3237539150513, 3650 | -49.69721257260706 3651 | ], 3652 | [ 3653 | 32.319622253719814, 3654 | -49.712687221651535 3655 | ], 3656 | [ 3657 | 32.30701695527379, 3658 | -49.7574413738006 3659 | ], 3660 | [ 3661 | 32.28558523418057, 3662 | -49.82897203266288 3663 | ], 3664 | [ 3665 | 32.25499465791926, 3666 | -49.924775777826035 3667 | ], 3668 | [ 3669 | 32.21490600963192, 3670 | -50.042348764856065 3671 | ], 3672 | [ 3673 | 32.16497328812325, 3674 | -50.17918842138251 3675 | ], 3676 | [ 3677 | 32.034232326683814, 3678 | -50.50065194116959 3679 | ], 3680 | [ 3681 | 31.860044470062117, 3682 | -50.86913897388612 3683 | ], 3684 | [ 3685 | 31.63969598339343, 3686 | -51.26462258025273 3687 | ], 3688 | [ 3689 | 31.37046634747611, 3690 | -51.667074972947404 3691 | ], 3692 | [ 3693 | 31.049641827445555, 3694 | -52.056468788669385 3695 | ], 3696 | [ 3697 | 30.67450190409985, 3698 | -52.412776664117764 3699 | ], 3700 | [ 3701 | 30.46570714361743, 3702 | -52.57226498221595 3703 | ], 3704 | [ 3705 | 30.242319273900225, 3706 | -52.7159714480025 3707 | ], 3708 | [ 3709 | 30.003985509416516, 3710 | -52.841392853075945 3711 | ], 3712 | [ 3713 | 29.750380201982146, 3714 | -52.94602556501242 3715 | ], 3716 | [ 3717 | 29.481150566064738, 3718 | -53.02736637541034 3719 | ], 3720 | [ 3721 | 29.19596416914359, 3722 | -53.082911863856836 3723 | ], 3724 | [ 3725 | 28.89448179436046, 3726 | -53.110158397929226 3727 | ], 3728 | [ 3729 | 28.576357440520233, 3730 | -53.106602769224864 3731 | ], 3732 | [ 3733 | 28.24125867510187, 3734 | -53.069741345321304 3735 | ], 3736 | [ 3737 | 27.88883949690996, 3738 | -52.99707091781565 3739 | ], 3740 | [ 3741 | 27.51876068908694, 3742 | -52.88608806629592 3743 | ], 3744 | [ 3745 | 27.130683034774357, 3746 | -52.734289158338825 3747 | ], 3748 | [ 3749 | 26.72426731711397, 3750 | -52.539170985542 3751 | ], 3752 | [ 3753 | 26.299174319248372, 3754 | -52.29822991548288 3755 | ], 3756 | [ 3757 | -16.99553461184764, 3758 | -12.89393128628272 3759 | ], 3760 | [ 3761 | 0, 3762 | 0 3763 | ] 3764 | ] 3765 | }, 3766 | { 3767 | "type": "line", 3768 | "version": 785, 3769 | "versionNonce": 1118495619, 3770 | "isDeleted": false, 3771 | "id": "4svbmD7aNlNvWjzbo6WJn", 3772 | "fillStyle": "cross-hatch", 3773 | "strokeWidth": 1, 3774 | "strokeStyle": "solid", 3775 | "roughness": 1, 3776 | "opacity": 100, 3777 | "angle": 0, 3778 | "x": 1622.744341248714, 3779 | "y": 160.97885326595045, 3780 | "strokeColor": "#3c99d4", 3781 | "backgroundColor": "#3c99d4", 3782 | "width": 33.30034228860758, 3783 | "height": 108.35391207123872, 3784 | "seed": 772368611, 3785 | "groupIds": [ 3786 | "uiuIoh2K7X_b1BelThwvF", 3787 | "YkAsPW2qzE2Y-PfN8-5-Q", 3788 | "mERqMPWfjVmnsuY3uiObT" 3789 | ], 3790 | "frameId": null, 3791 | "roundness": null, 3792 | "boundElements": [], 3793 | "updated": 1692114319143, 3794 | "link": null, 3795 | "locked": false, 3796 | "startBinding": null, 3797 | "endBinding": null, 3798 | "lastCommittedPoint": null, 3799 | "startArrowhead": null, 3800 | "endArrowhead": null, 3801 | "points": [ 3802 | [ 3803 | 0, 3804 | 0 3805 | ], 3806 | [ 3807 | 0, 3808 | 0 3809 | ], 3810 | [ 3811 | 0.3583419045336598, 3812 | 0.3333348377564424 3813 | ], 3814 | [ 3815 | 0.7221180631337765, 3816 | 0.6111737975317174 3817 | ], 3818 | [ 3819 | 1.0873935602465417, 3820 | 0.8382184249788458 3821 | ], 3822 | [ 3823 | 1.450219911643984, 3824 | 1.0191499127391348 3825 | ], 3826 | [ 3827 | 1.806668986109683, 3828 | 1.1586562377911624 3829 | ], 3830 | [ 3831 | 2.1527990837527655, 3832 | 1.2614321614508879 3833 | ], 3834 | [ 3835 | 2.4846617203451355, 3836 | 1.3321588763596233 3837 | ], 3838 | [ 3839 | 2.7983219803331623, 3840 | 1.3755379281703433 3841 | ], 3842 | [ 3843 | 3.0898313794888708, 3844 | 1.3962505095243678 3845 | ], 3846 | [ 3847 | 3.355261786595726, 3848 | 1.398984597400525 3849 | ], 3850 | [ 3851 | 3.590671501762933, 3852 | 1.388434953114372 3853 | ], 3854 | [ 3855 | 3.7921120407623827, 3856 | 1.3692827693074228 3857 | ], 3858 | [ 3859 | 3.955648488040631, 3860 | 1.346229591632606 3861 | ], 3862 | [ 3863 | 4.077332359369489, 3864 | 1.3239566127314364 3865 | ], 3866 | [ 3867 | 4.153235523532402, 3868 | 1.3071518095823433 3869 | ], 3870 | [ 3871 | 4.179416280638745, 3872 | 1.300509943501272 3873 | ], 3874 | [ 3875 | 29.533773277257993, 3876 | -11.193267568321364 3877 | ], 3878 | [ 3879 | 29.533773277257993, 3880 | -11.193267568321364 3881 | ], 3882 | [ 3883 | 30.099987272420577, 3884 | -11.613462274756635 3885 | ], 3886 | [ 3887 | 30.585528714395437, 3888 | -12.041363988207381 3889 | ], 3890 | [ 3891 | 30.996503506628134, 3892 | -12.472162613625882 3893 | ], 3894 | [ 3895 | 31.3389971995525, 3896 | -12.901054840302148 3897 | ], 3898 | [ 3899 | 31.61908855926545, 3900 | -13.323237357525826 3901 | ], 3902 | [ 3903 | 31.842883489212255, 3904 | -13.733906854586285 3905 | ], 3906 | [ 3907 | 32.01645397115222, 3908 | -14.128266805110405 3909 | ], 3910 | [ 3911 | 32.14590590853091, 3912 | -14.501507114050817 3913 | ], 3914 | [ 3915 | 32.237324851782205, 3916 | -14.848824470697082 3917 | ], 3918 | [ 3919 | 32.296789567002776, 3920 | -15.165415564338868 3921 | ], 3922 | [ 3923 | 32.3304059576381, 3924 | -15.446477084265934 3925 | ], 3926 | [ 3927 | 32.34424600544755, 3928 | -15.6872125041048 3929 | ], 3930 | [ 3931 | 32.34441561387667, 3932 | -15.882811728808319 3933 | ], 3934 | [ 3935 | 32.3370003333591, 3936 | -16.028471447665936 3937 | ], 3938 | [ 3939 | 32.328078929991676, 3940 | -16.1193883499674 3941 | ], 3942 | [ 3943 | 32.32375730721977, 3944 | -16.150759125002125 3945 | ], 3946 | [ 3947 | 32.32375730721989, 3948 | -90.75771631030716 3949 | ], 3950 | [ 3951 | 32.32375730721989, 3952 | -90.75771631030716 3953 | ], 3954 | [ 3955 | 32.31412354845048, 3956 | -91.05887303690665 3957 | ], 3958 | [ 3959 | 32.2860567456132, 3960 | -91.34764665210153 3961 | ], 3962 | [ 3963 | 32.17951551482906, 3964 | -91.88877895277791 3965 | ], 3966 | [ 3967 | 32.01396411941387, 3968 | -92.38259389392152 3969 | ], 3970 | [ 3971 | 31.799246632589334, 3972 | -92.8305755492855 3973 | ], 3974 | [ 3975 | 31.545186774564762, 3976 | -93.23420290437237 3977 | ], 3978 | [ 3979 | 31.261621834225494, 3980 | -93.59495833685133 3981 | ], 3982 | [ 3983 | 30.95838910045482, 3984 | -93.9143242243924 3985 | ], 3986 | [ 3987 | 30.645319077799854, 3988 | -94.19378124858048 3989 | ], 3990 | [ 3991 | 30.332255839482094, 3992 | -94.43481348317039 3993 | ], 3994 | [ 3995 | 30.029023105711083, 3996 | -94.63889991366284 3997 | ], 3998 | [ 3999 | 29.74545816537161, 4000 | -94.80752291772777 4001 | ], 4002 | [ 4003 | 29.491398307347144, 4004 | -94.94216487303518 4005 | ], 4006 | [ 4007 | 29.276674036185938, 4008 | -95.04430730921231 4009 | ], 4010 | [ 4011 | 29.11112942510771, 4012 | -95.11543175588733 4013 | ], 4014 | [ 4015 | 29.00458819432338, 4016 | -95.1570197426873 4017 | ], 4018 | [ 4019 | 28.96688763271672, 4020 | -95.17055279924014 4021 | ], 4022 | [ 4023 | 6.991625799141445, 4024 | -105.76358419359755 4025 | ], 4026 | [ 4027 | 6.991625799141445, 4028 | -105.76358419359755 4029 | ], 4030 | [ 4031 | 6.546349398220322, 4032 | -106.02316968222947 4033 | ], 4034 | [ 4035 | 6.111045972926291, 4036 | -106.24667521779392 4037 | ], 4038 | [ 4039 | 5.685993681083339, 4040 | -106.43592663503009 4041 | ], 4042 | [ 4043 | 5.271463896178211, 4044 | -106.59274976867658 4045 | ], 4046 | [ 4047 | 4.867734776033778, 4048 | -106.71896981743957 4049 | ], 4050 | [ 4051 | 4.475064125463021, 4052 | -106.81641304008022 4053 | ], 4054 | [ 4055 | 4.093736886625777, 4056 | -106.88690463530399 4057 | ], 4058 | [ 4059 | 3.7240312173468766, 4060 | -106.93227043785018 4061 | ], 4062 | [ 4063 | 3.366204922437305, 4064 | -106.9543361764525 4065 | ], 4066 | [ 4067 | 3.020536159721317, 4068 | -106.9549274738382 4069 | ], 4070 | [ 4071 | 2.6873030870222436, 4072 | -106.93586995273557 4073 | ], 4074 | [ 4075 | 2.366770293489701, 4076 | -106.89898923587316 4077 | ], 4078 | [ 4079 | 1.7649113908804779, 4080 | -106.77906112980021 4081 | ], 4082 | [ 4083 | 1.2171440084601883, 4084 | -106.60974856146603 4085 | ], 4086 | [ 4087 | 0.7256527027948039, 4088 | -106.40565661869991 4089 | ], 4090 | [ 4091 | 0.2926084617758278, 4092 | -106.18139081335217 4093 | ], 4094 | [ 4095 | -0.07979737369375783, 4096 | -105.95155655126847 4097 | ], 4098 | [ 4099 | -0.38938703138473585, 4100 | -105.73075902628386 4101 | ], 4102 | [ 4103 | -0.6339759547317172, 4104 | -105.5336034322336 4105 | ], 4106 | [ 4107 | -0.8113795871678136, 4108 | -105.37469517496262 4109 | ], 4110 | [ 4111 | -0.8742093336206196, 4112 | -105.31414814595436 4113 | ], 4114 | [ 4115 | -0.9194269408021398, 4116 | -105.26863966031678 4117 | ], 4118 | [ 4119 | -0.9467542508887803, 4120 | -105.23999597681052 4121 | ], 4122 | [ 4123 | -0.9559266747309181, 4124 | -105.23004229414208 4125 | ], 4126 | [ 4127 | -0.9442576148131678, 4128 | -105.23822823295876 4129 | ], 4130 | [ 4131 | -0.910003496484741, 4132 | -105.26140225663937 4133 | ], 4134 | [ 4135 | -0.854283735377166, 4136 | -105.29748814600151 4137 | ], 4138 | [ 4139 | -0.7782109627854581, 4140 | -105.34440989387356 4141 | ], 4142 | [ 4143 | -0.5694976143487409, 4144 | -105.46245736048387 4145 | ], 4146 | [ 4147 | -0.292818776228259, 4148 | -105.5989365990988 4149 | ], 4150 | [ 4151 | 0.042863442186302336, 4152 | -105.73723934033627 4153 | ], 4154 | [ 4155 | 0.4286005001781419, 4156 | -105.86075773883596 4157 | ], 4158 | [ 4159 | 0.8554302883574181, 4160 | -105.95288352521487 4161 | ], 4162 | [ 4163 | 1.314404266007692, 4164 | -105.99700885411332 4165 | ], 4166 | [ 4167 | 1.7965671080762533, 4168 | -105.97652566815988 4169 | ], 4170 | [ 4171 | 2.043544118103547, 4172 | -105.93686570517656 4173 | ], 4174 | [ 4175 | 2.2929567051723225, 4176 | -105.87482569797191 4177 | ], 4178 | [ 4179 | 2.5436922379874503, 4180 | -105.78832942736264 4181 | ], 4182 | [ 4183 | 2.794624516580228, 4184 | -105.67530109818898 4185 | ], 4186 | [ 4187 | 3.0446341253187867, 4188 | -105.53366449126787 4189 | ], 4190 | [ 4191 | 3.292608432909084, 4192 | -105.36134359942878 4193 | ], 4194 | [ 4195 | 3.5374212393823115, 4196 | -105.1562626275106 4197 | ], 4198 | [ 4199 | 3.7779599134436452, 4200 | -104.91634556834174 4201 | ], 4202 | [ 4203 | 4.013098255124397, 4204 | -104.63951577871883 4205 | ], 4206 | [ 4207 | 4.241723633130071, 4208 | -104.32369809951336 4209 | ], 4210 | [ 4211 | 4.462709847491709, 4212 | -103.96681652355377 4213 | ], 4214 | [ 4215 | 4.674937482578194, 4216 | -103.56679419562639 4217 | ], 4218 | [ 4219 | 4.877293907094241, 4220 | -103.12155595660171 4221 | ], 4222 | [ 4223 | 5.068652921072058, 4224 | -102.62902495126586 4225 | ], 4226 | [ 4227 | 5.06865292107203, 4228 | -3.979332609071434 4229 | ], 4230 | [ 4231 | 5.06865292107203, 4232 | -3.979332609071434 4233 | ], 4234 | [ 4235 | 5.041556278448924, 4236 | -3.473757019458635 4237 | ], 4238 | [ 4239 | 4.960273134916807, 4240 | -2.9775573838029663 4241 | ], 4242 | [ 4243 | 4.8248034904758805, 4244 | -2.495944073044603 4245 | ], 4246 | [ 4247 | 4.6351473451258896, 4248 | -2.0341274581235345 4249 | ], 4250 | [ 4251 | 4.384506793031117, 4252 | -1.6029692628354413 4253 | ], 4254 | [ 4255 | 4.231404656307308, 4256 | -1.3955042324365041 4257 | ], 4258 | [ 4259 | 4.057664565938237, 4260 | -1.1963025247003378 4261 | ], 4262 | [ 4263 | 3.861719340039435, 4264 | -1.007521558844146 4265 | ], 4266 | [ 4267 | 3.6419882280526092, 4268 | -0.8313187540854043 4269 | ], 4270 | [ 4271 | 3.3968836950816472, 4272 | -0.6698243922928913 4273 | ], 4274 | [ 4275 | 3.124838559242252, 4276 | -0.5252094613580898 4277 | ], 4278 | [ 4279 | 2.824258501301681, 4280 | -0.39960424315003484 4281 | ], 4282 | [ 4283 | 2.4935763393753128, 4284 | -0.2951661568856092 4285 | ], 4286 | [ 4287 | 2.1312113229047975, 4288 | -0.21405262178265083 4289 | ], 4290 | [ 4291 | 1.7355759169942222, 4292 | -0.15839391970966177 4293 | ], 4294 | [ 4295 | 1.30510293975935, 4296 | -0.13036103855823156 4297 | ], 4298 | [ 4299 | 0.8381980719670823, 4300 | -0.13208426019729397 4301 | ], 4302 | [ 4303 | 0.3332941317333482, 4304 | -0.16572100384405197 4305 | ], 4306 | [ 4307 | -0.2111896315006614, 4308 | -0.2334286887158877 4309 | ], 4310 | [ 4311 | 0, 4312 | 0 4313 | ] 4314 | ] 4315 | }, 4316 | { 4317 | "id": "iwY7ShYY2FVLeJDS7qDiZ", 4318 | "type": "text", 4319 | "x": 1528.0038146972656, 4320 | "y": 183.31640625, 4321 | "width": 145.67987060546875, 4322 | "height": 25, 4323 | "angle": 0, 4324 | "strokeColor": "#1e1e1e", 4325 | "backgroundColor": "#b2f2bb", 4326 | "fillStyle": "solid", 4327 | "strokeWidth": 2, 4328 | "strokeStyle": "solid", 4329 | "roughness": 1, 4330 | "opacity": 100, 4331 | "groupIds": [ 4332 | "mERqMPWfjVmnsuY3uiObT" 4333 | ], 4334 | "frameId": null, 4335 | "roundness": null, 4336 | "seed": 1041078435, 4337 | "version": 113, 4338 | "versionNonce": 1218854979, 4339 | "isDeleted": false, 4340 | "boundElements": [ 4341 | { 4342 | "id": "AhuD1ss3X-UEsEfUm80hM", 4343 | "type": "arrow" 4344 | } 4345 | ], 4346 | "updated": 1692114342988, 4347 | "link": null, 4348 | "locked": false, 4349 | "text": "VSCode-OpenAI", 4350 | "fontSize": 20, 4351 | "fontFamily": 1, 4352 | "textAlign": "center", 4353 | "verticalAlign": "top", 4354 | "baseline": 18, 4355 | "containerId": null, 4356 | "originalText": "VSCode-OpenAI", 4357 | "lineHeight": 1.25 4358 | }, 4359 | { 4360 | "id": "ZB7f3_0kNeKNzY62ROKGW", 4361 | "type": "arrow", 4362 | "x": 808.1299941610162, 4363 | "y": 222.42578125, 4364 | "width": 119.04210740572717, 4365 | "height": 64.87109375, 4366 | "angle": 0, 4367 | "strokeColor": "#1e1e1e", 4368 | "backgroundColor": "#b2f2bb", 4369 | "fillStyle": "solid", 4370 | "strokeWidth": 2, 4371 | "strokeStyle": "solid", 4372 | "roughness": 1, 4373 | "opacity": 100, 4374 | "groupIds": [], 4375 | "frameId": null, 4376 | "roundness": { 4377 | "type": 2 4378 | }, 4379 | "seed": 1862929027, 4380 | "version": 175, 4381 | "versionNonce": 989692451, 4382 | "isDeleted": false, 4383 | "boundElements": null, 4384 | "updated": 1692114346918, 4385 | "link": null, 4386 | "locked": false, 4387 | "points": [ 4388 | [ 4389 | 0, 4390 | 0 4391 | ], 4392 | [ 4393 | 119.04210740572717, 4394 | 64.87109375 4395 | ] 4396 | ], 4397 | "lastCommittedPoint": null, 4398 | "startBinding": { 4399 | "elementId": "cBBBTnpcCHHnSbUdx74z0", 4400 | "focus": 0.4050841889925998, 4401 | "gap": 14.109375 4402 | }, 4403 | "endBinding": { 4404 | "elementId": "phmuAq8vpDsXYCEB5Hyfz", 4405 | "focus": 0.2051711234510354, 4406 | "gap": 12.25 4407 | }, 4408 | "startArrowhead": null, 4409 | "endArrowhead": "arrow" 4410 | }, 4411 | { 4412 | "id": "AhuD1ss3X-UEsEfUm80hM", 4413 | "type": "arrow", 4414 | "x": 1604.3856416845515, 4415 | "y": 214.23828125, 4416 | "width": 113.38635318101774, 4417 | "height": 71.84765625, 4418 | "angle": 0, 4419 | "strokeColor": "#1e1e1e", 4420 | "backgroundColor": "#b2f2bb", 4421 | "fillStyle": "solid", 4422 | "strokeWidth": 2, 4423 | "strokeStyle": "solid", 4424 | "roughness": 1, 4425 | "opacity": 100, 4426 | "groupIds": [], 4427 | "frameId": null, 4428 | "roundness": { 4429 | "type": 2 4430 | }, 4431 | "seed": 1176120397, 4432 | "version": 176, 4433 | "versionNonce": 1089422787, 4434 | "isDeleted": false, 4435 | "boundElements": null, 4436 | "updated": 1692114346918, 4437 | "link": null, 4438 | "locked": false, 4439 | "points": [ 4440 | [ 4441 | 0, 4442 | 0 4443 | ], 4444 | [ 4445 | -113.38635318101774, 4446 | 71.84765625 4447 | ] 4448 | ], 4449 | "lastCommittedPoint": null, 4450 | "startBinding": { 4451 | "elementId": "iwY7ShYY2FVLeJDS7qDiZ", 4452 | "focus": -0.35250847083198517, 4453 | "gap": 5.921875 4454 | }, 4455 | "endBinding": { 4456 | "elementId": "phmuAq8vpDsXYCEB5Hyfz", 4457 | "focus": -0.09278771382170713, 4458 | "gap": 13.4609375 4459 | }, 4460 | "startArrowhead": null, 4461 | "endArrowhead": "arrow" 4462 | }, 4463 | { 4464 | "id": "dcwo5gS6oN7kj61GzoiqW", 4465 | "type": "arrow", 4466 | "x": 1082.28515625, 4467 | "y": 219.578125, 4468 | "width": 1.78125, 4469 | "height": 64.2421875, 4470 | "angle": 0, 4471 | "strokeColor": "#1e1e1e", 4472 | "backgroundColor": "#b2f2bb", 4473 | "fillStyle": "solid", 4474 | "strokeWidth": 2, 4475 | "strokeStyle": "solid", 4476 | "roughness": 1, 4477 | "opacity": 100, 4478 | "groupIds": [], 4479 | "frameId": null, 4480 | "roundness": { 4481 | "type": 2 4482 | }, 4483 | "seed": 498492995, 4484 | "version": 56, 4485 | "versionNonce": 1520636867, 4486 | "isDeleted": false, 4487 | "boundElements": null, 4488 | "updated": 1692114352359, 4489 | "link": null, 4490 | "locked": false, 4491 | "points": [ 4492 | [ 4493 | 0, 4494 | 0 4495 | ], 4496 | [ 4497 | 1.78125, 4498 | 64.2421875 4499 | ] 4500 | ], 4501 | "lastCommittedPoint": null, 4502 | "startBinding": { 4503 | "elementId": "Gpip8r6mIMp4heaKfxVkZ", 4504 | "focus": 0.09073245612864393, 4505 | "gap": 11.26171875 4506 | }, 4507 | "endBinding": { 4508 | "elementId": "phmuAq8vpDsXYCEB5Hyfz", 4509 | "focus": -0.33425343111625405, 4510 | "gap": 15.7265625 4511 | }, 4512 | "startArrowhead": null, 4513 | "endArrowhead": "arrow" 4514 | }, 4515 | { 4516 | "id": "uzgLVQ61-6x7EkOzOaz5N", 4517 | "type": "arrow", 4518 | "x": 1352.2890625, 4519 | "y": 218.7578125, 4520 | "width": 2.08203125, 4521 | "height": 61.16015625, 4522 | "angle": 0, 4523 | "strokeColor": "#1e1e1e", 4524 | "backgroundColor": "#b2f2bb", 4525 | "fillStyle": "solid", 4526 | "strokeWidth": 2, 4527 | "strokeStyle": "solid", 4528 | "roughness": 1, 4529 | "opacity": 100, 4530 | "groupIds": [], 4531 | "frameId": null, 4532 | "roundness": { 4533 | "type": 2 4534 | }, 4535 | "seed": 624010083, 4536 | "version": 38, 4537 | "versionNonce": 901198915, 4538 | "isDeleted": false, 4539 | "boundElements": null, 4540 | "updated": 1692114356579, 4541 | "link": null, 4542 | "locked": false, 4543 | "points": [ 4544 | [ 4545 | 0, 4546 | 0 4547 | ], 4548 | [ 4549 | 2.08203125, 4550 | 61.16015625 4551 | ] 4552 | ], 4553 | "lastCommittedPoint": null, 4554 | "startBinding": { 4555 | "elementId": "qUA7Lrka-FDYzIK0890HR", 4556 | "focus": 0.013956549601726084, 4557 | "gap": 10.44140625 4558 | }, 4559 | "endBinding": { 4560 | "elementId": "phmuAq8vpDsXYCEB5Hyfz", 4561 | "focus": 0.5300168671237729, 4562 | "gap": 19.62890625 4563 | }, 4564 | "startArrowhead": null, 4565 | "endArrowhead": "arrow" 4566 | } 4567 | ], 4568 | "appState": { 4569 | "gridSize": null, 4570 | "viewBackgroundColor": "#ffffff" 4571 | }, 4572 | "files": {} 4573 | } -------------------------------------------------------------------------------- /img/openai-api-cloud-run-vertex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cyclenerd/google-cloud-gcp-openai-api/f060659f349976cf8420d43f29b87ab555c3afe5/img/openai-api-cloud-run-vertex.png -------------------------------------------------------------------------------- /img/vscode-chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cyclenerd/google-cloud-gcp-openai-api/f060659f349976cf8420d43f29b87ab555c3afe5/img/vscode-chat.png -------------------------------------------------------------------------------- /img/vscode-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cyclenerd/google-cloud-gcp-openai-api/f060659f349976cf8420d43f29b87ab555c3afe5/img/vscode-settings.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi==0.111.0 2 | uvicorn==0.30.1 3 | pydantic==1.10.15 4 | sse-starlette==2.1.0 5 | langchain==0.3.0 6 | langchain-community==0.3.0 7 | transformers==4.50.0 8 | google-cloud-aiplatform==1.53.0 -------------------------------------------------------------------------------- /vertex.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright 2023-2024 Nils Knieling 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import json 18 | import os 19 | import secrets 20 | import time 21 | import datetime 22 | import uvicorn 23 | 24 | # FastAPI 25 | from typing import List, Optional 26 | from fastapi import FastAPI, HTTPException, Request, status 27 | from fastapi.middleware.cors import CORSMiddleware 28 | from fastapi.responses import JSONResponse 29 | from pydantic import BaseModel 30 | from sse_starlette.sse import EventSourceResponse 31 | 32 | # Google Vertex AI 33 | import google.auth 34 | from google.cloud import aiplatform 35 | 36 | # LangChain 37 | import langchain 38 | from langchain_community.chat_models import ChatVertexAI 39 | from langchain.chains import ConversationChain 40 | from langchain.memory import ConversationBufferMemory 41 | 42 | # Google authentication 43 | credentials, project_id = google.auth.default() 44 | 45 | # Get environment variable 46 | host = os.environ.get("HOST", "0.0.0.0") 47 | port = int(os.environ.get("PORT", 8000)) 48 | debug = os.environ.get("DEBUG", False) 49 | print(f"Endpoint: http://{host}:{port}/") 50 | # Google Cloud 51 | project = os.environ.get("GOOGLE_CLOUD_PROJECT_ID", project_id) 52 | location = os.environ.get("GOOGLE_CLOUD_LOCATION", "us-central1") 53 | print(f"Google Cloud project identifier: {project}") 54 | print(f"Google Cloud location: {location}") 55 | # LLM chat model name to use 56 | model_name = os.environ.get("MODEL_NAME", "chat-bison") 57 | print(f"LLM chat model name: {model_name}") 58 | # Token limit determines the maximum amount of text output from one prompt 59 | default_max_output_tokens = os.environ.get("MAX_OUTPUT_TOKENS", "512") 60 | # Sampling temperature, 61 | # it controls the degree of randomness in token selection 62 | default_temperature = os.environ.get("TEMPERATURE", "0.2") 63 | # How the model selects tokens for output, the next token is selected from 64 | default_top_k = os.environ.get("TOP_K", "40") 65 | # Tokens are selected from most probable to least until the sum of their 66 | default_top_p = os.environ.get("TOP_P", "0.8") 67 | # API key 68 | default_api_key = f"sk-{secrets.token_hex(21)}" 69 | api_key = os.environ.get("OPENAI_API_KEY", default_api_key) 70 | print(f"API key: {api_key}") 71 | 72 | app = FastAPI( 73 | title='OpenAI API', 74 | description='APIs for sampling from and fine-tuning language models', 75 | version='2.0.0', 76 | servers=[{'url': 'https://api.openai.com/'}], 77 | contact={ 78 | "name": "GitHub", 79 | "url": "https://github.com/Cyclenerd/google-cloud-gcp-openai-api", 80 | }, 81 | license_info={ 82 | "name": "Apache 2.0", 83 | "url": "https://www.apache.org/licenses/LICENSE-2.0.html", 84 | }, 85 | docs_url=None, 86 | redoc_url=None 87 | ) 88 | 89 | app.add_middleware( 90 | CORSMiddleware, 91 | allow_origins=['*'], 92 | allow_credentials=True, 93 | allow_methods=['*'], 94 | allow_headers=['*'], 95 | ) 96 | 97 | aiplatform.init( 98 | project=project, 99 | location=location, 100 | ) 101 | 102 | 103 | class Message(BaseModel): 104 | role: str 105 | content: str 106 | 107 | 108 | class ChatBody(BaseModel): 109 | messages: List[Message] 110 | model: str 111 | stream: Optional[bool] = False 112 | max_tokens: Optional[int] 113 | temperature: Optional[float] 114 | top_p: Optional[float] 115 | 116 | 117 | @app.get("/") 118 | def read_root(): 119 | return { 120 | "LangChain": langchain.__version__, 121 | "Vertex AI": aiplatform.__version__ 122 | } 123 | 124 | 125 | @app.get("/v1/models") 126 | def get_models(): 127 | """ 128 | Lists the currently available models, 129 | and provides basic information about each one 130 | such as the owner and availability. 131 | 132 | https://platform.openai.com/docs/api-reference/models/list 133 | """ 134 | id = f"modelperm-{secrets.token_hex(12)}" 135 | ts = int(time.time()) 136 | models = {"data": [], "object": "list"} 137 | models['data'].append({ 138 | "id": "gpt-3.5-turbo", 139 | "object": "model", 140 | "created": ts, 141 | "owned_by": "openai", 142 | "permission": [ 143 | { 144 | "id": id, 145 | "created": ts, 146 | "object": "model_permission", 147 | "allow_create_engine": False, 148 | "allow_sampling": True, 149 | "allow_logprobs": True, 150 | "allow_search_indices": False, 151 | "allow_view": True, 152 | "allow_fine_tuning": False, 153 | "organization": "*", 154 | "group": None, 155 | "is_blocking": False 156 | } 157 | ], 158 | "root": "gpt-3.5-turbo", 159 | "parent": None, 160 | }) 161 | models['data'].append({ 162 | "id": "text-embedding-ada-002", 163 | "object": "model", 164 | "created": ts, 165 | "owned_by": "openai-internal", 166 | "permission": [ 167 | { 168 | "id": id, 169 | "created": ts, 170 | "object": "model_permission", 171 | "allow_create_engine": False, 172 | "allow_sampling": True, 173 | "allow_logprobs": True, 174 | "allow_search_indices": True, 175 | "allow_view": True, 176 | "allow_fine_tuning": False, 177 | "organization": "*", 178 | "group": None, 179 | "is_blocking": False 180 | } 181 | ], 182 | "root": "text-embedding-ada-002", 183 | "parent": None 184 | }) 185 | return models 186 | 187 | 188 | def generate_stream_response_start(): 189 | ts = int(time.time()) 190 | id = f"cmpl-{secrets.token_hex(12)}" 191 | return { 192 | "id": id, 193 | "created": ts, 194 | "object": "chat.completion.chunk", 195 | "model": "gpt-3.5-turbo", 196 | "choices": [{ 197 | "delta": {"role": "assistant"}, 198 | "index": 0, 199 | "finish_reason": None 200 | }] 201 | } 202 | 203 | 204 | def generate_stream_response(content: str): 205 | ts = int(time.time()) 206 | id = f"cmpl-{secrets.token_hex(12)}" 207 | return { 208 | "id": id, 209 | "created": ts, 210 | "object": "chat.completion.chunk", 211 | "model": "gpt-3.5-turbo", 212 | "choices": [{ 213 | "delta": {"content": content}, 214 | "index": 0, 215 | "finish_reason": None 216 | }] 217 | } 218 | 219 | 220 | def generate_stream_response_stop(): 221 | ts = int(time.time()) 222 | id = f"cmpl-{secrets.token_hex(12)}" 223 | return { 224 | "id": id, 225 | "created": ts, 226 | "object": "chat.completion.chunk", 227 | "model": "gpt-3.5-turbo", 228 | "choices": [{ 229 | "delta": {}, 230 | "index": 0, 231 | "finish_reason": "stop" 232 | }] 233 | } 234 | 235 | 236 | def generate_response(content: str): 237 | ts = int(time.time()) 238 | id = f"cmpl-{secrets.token_hex(12)}" 239 | return { 240 | "id": id, 241 | "created": ts, 242 | "object": "chat.completion", 243 | "model": "gpt-3.5-turbo", 244 | "usage": { 245 | "prompt_tokens": 0, 246 | "completion_tokens": 0, 247 | "total_tokens": 0, 248 | }, 249 | "choices": [{ 250 | "message": {"role": "assistant", "content": content}, 251 | "finish_reason": "stop", "index": 0} 252 | ] 253 | } 254 | 255 | 256 | @app.post("/v1/chat/completions") 257 | async def chat_completions(body: ChatBody, request: Request): 258 | """ 259 | Creates a model response for the given chat conversation. 260 | 261 | https://platform.openai.com/docs/api-reference/chat/create 262 | """ 263 | 264 | # Authorization via OPENAI_API_KEY 265 | if request.headers.get("Authorization").split(" ")[1] != api_key: 266 | raise HTTPException(status.HTTP_401_UNAUTHORIZED, "API key is wrong!") 267 | 268 | if debug: 269 | print(f"body = {body}") 270 | 271 | # Get user question 272 | question = body.messages[-1] 273 | if question.role == 'user' or question.role == 'assistant': 274 | question = question.content 275 | else: 276 | raise HTTPException(status.HTTP_400_BAD_REQUEST, "No Question Found") 277 | 278 | # Overwrite defaults 279 | temperature = float(body.temperature or default_temperature) 280 | top_k = int(default_top_k) 281 | top_p = float(body.top_p or default_top_p) 282 | max_output_tokens = int(body.max_tokens or default_max_output_tokens) 283 | # Note: Max output token: 284 | # - gemini-pro: 8192 285 | # https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/gemini 286 | # - chat-bison: 1024 287 | # - codechat-bison: 2048 288 | # - ..-32k: The total amount of input and output tokens adds up to 32k. 289 | # For example, if you specify 16k of input tokens, 290 | # then you can receive up to 16k of output tokens. 291 | if model_name == 'codechat-bison': 292 | if max_output_tokens > 2048: 293 | max_output_tokens = 2048 294 | elif model_name.find("gemini-pro"): 295 | if max_output_tokens > 8192: 296 | max_output_tokens = 8192 297 | elif model_name.find("32k"): 298 | if max_output_tokens > 16000: 299 | max_output_tokens = 16000 300 | elif max_output_tokens > 1024: 301 | max_output_tokens = 1024 302 | 303 | # Wrapper around Vertex AI large language models 304 | llm = ChatVertexAI( 305 | model_name=model_name, 306 | temperature=temperature, 307 | top_k=top_k, 308 | top_p=top_p, 309 | max_output_tokens=max_output_tokens 310 | ) 311 | 312 | # Buffer for storing conversation memory 313 | # Note: Max input token: 314 | # - chat-bison: 4096 315 | # - codechat-bison: 6144 316 | memory = ConversationBufferMemory( 317 | memory_key="history", 318 | max_token_limit=2048, 319 | return_messages=True 320 | ) 321 | # Today 322 | memory.chat_memory.add_user_message("What day is today?") 323 | memory.chat_memory.add_ai_message( 324 | datetime.date.today().strftime("Today is %A, %B %d, %Y") 325 | ) 326 | # Add history 327 | for message in body.messages: 328 | # if message.role == 'system': 329 | # system_prompt = message.content 330 | if message.role == 'user': 331 | memory.chat_memory.add_user_message(message.content) 332 | elif message.role == 'assistant': 333 | memory.chat_memory.add_ai_message(message.content) 334 | 335 | # Get Vertex AI output 336 | conversation = ConversationChain( 337 | llm=llm, 338 | memory=memory, 339 | ) 340 | answer = conversation.predict(input=question) 341 | 342 | if debug: 343 | print(f"stream = {body.stream}") 344 | print(f"model = {body.model}") 345 | print(f"temperature = {temperature}") 346 | print(f"top_k = {top_k}") 347 | print(f"top_p = {top_p}") 348 | print(f"max_output_tokens = {max_output_tokens}") 349 | print(f"history = {memory.buffer}") 350 | 351 | # Return output 352 | if body.stream: 353 | async def stream(): 354 | yield json.dumps( 355 | generate_stream_response_start(), 356 | ensure_ascii=False 357 | ) 358 | yield json.dumps( 359 | generate_stream_response(answer), 360 | ensure_ascii=False 361 | ) 362 | yield json.dumps( 363 | generate_stream_response_stop(), 364 | ensure_ascii=False 365 | ) 366 | return EventSourceResponse(stream(), ping=10000) 367 | else: 368 | return JSONResponse(content=generate_response(answer)) 369 | 370 | if __name__ == "__main__": 371 | uvicorn.run(app, host=host, port=port) 372 | --------------------------------------------------------------------------------