├── .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 | [](#readme)
4 | [](#readme)
5 | [](#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 | |  |  |
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 |
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 | 
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 | 
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 | 
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 | 
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 | 
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 | "[](https://colab.research.google.com/github/Cyclenerd/google-cloud-gcp-openai-api/blob/master/Vertex_AI_Chat.ipynb)\n",
35 | "[](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 | "[](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 |
--------------------------------------------------------------------------------