-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: movie search sample app with Pinecone and PostgreSQL backend (#261
- Loading branch information
Showing
9 changed files
with
1,072 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# Byte-compiled / optimized / DLL files | ||
__pycache__/ | ||
*.py[cod] | ||
*$py.class | ||
|
||
# Environment | ||
.env | ||
.venv | ||
|
||
# Jupyter Notebook | ||
.ipynb_checkpoints | ||
|
||
# Misc | ||
.python-version |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
web: gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 movie_search:me |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
|
||
# Sample app for genai embeddings using Pinecone or PostgreSQL compaible database | ||
## Description | ||
- The demo shows a sample moview seach chat assistant using either Pinecone or PostgreSQL compatible database as a backend. | ||
- In both cases the Google AI studio is used for conversations and embedding generation. | ||
|
||
### Architecture | ||
- The application can be deployed on a VM or any other environment supporting Python 3.11 | ||
- It connects to a Pinecone environment using Pinecone API token | ||
- It uses Google AI Studio to generate responses (using model gemini-1.5-flash) or to generate embeddings (model textetext-embedding-004) | ||
|
||
## Requirements | ||
- Platform to deploy the application supporting Python 3.11 | ||
- Token in Google AI studio (you can get it from [here](https://ai.google.dev/gemini-api/docs/api-key)) | ||
- Token for Pinecone API (optional) | ||
- Project in Google Cloud with enabled APIs for all components. | ||
|
||
|
||
## Deployment for Pinecone Backend | ||
|
||
The dataset with movies and how to deploy it to the Pinecone environment is not discussed here. | ||
|
||
### Prepare Virtual machine | ||
- Enable the required APIs in Google Cloud | ||
``` | ||
gcloud services enable compute.googleapis.com | ||
``` | ||
- Create a GCE VM in a Google Cloud project | ||
- Connect to the VM ussing SSH | ||
- Clone the software | ||
``` | ||
git clone https://github.com/GoogleCloudPlatform/devrel-demos.git | ||
``` | ||
- Prepare Python 3.11 | ||
``` | ||
sudo apt install -y python3.11-venv git | ||
python3 -m venv .venv | ||
source .venv/bin/activate | ||
pip install --upgrade pip | ||
``` | ||
### Run the application | ||
- Change directory | ||
``` | ||
cd devrel-demos/infrastructure/movie-search-app | ||
``` | ||
- Install dependencies | ||
``` | ||
pip install -r requirements.txt | ||
``` | ||
- Set environment variables (Pinecone index name) | ||
``` | ||
export PINECONE_INDEX_NAME=netflix-index-01 | ||
export PORT=8080 | ||
``` | ||
- Start the application from command line | ||
``` | ||
gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 movie_search:me | ||
``` | ||
- Connect to the chat using the VM host:port to get the application interface | ||
|
||
### Work with application | ||
- Click at the bottom of the app to choose backend. | ||
- Put Google AI API token and Pinecone API token at the top (you need both to use the Pinecone backend). | ||
- Select Pinecone as a backend and confirm the choice. | ||
- Post your question in the input window at the bottom and click the arrow. | ||
|
||
Ask sample questions about the movies | ||
|
||
### You can deploy your application to Cloud Run | ||
Optionally you can deploy the application to Cloud Run. | ||
|
||
## Deployment with AlloyDB Backend | ||
You will need AlloyDB database as a backend for the application. | ||
|
||
Assuming all the actions are performed in the same Google Cloud project. | ||
### Enable all required APIs usng gcloud command | ||
``` | ||
gcloud services enable alloydb.googleapis.com \ | ||
compute.googleapis.com \ | ||
cloudresourcemanager.googleapis.com \ | ||
servicenetworking.googleapis.com \ | ||
vpcaccess.googleapis.com \ | ||
aiplatform.googleapis.com \ | ||
cloudbuild.googleapis.com \ | ||
artifactregistry.googleapis.com \ | ||
run.googleapis.com \ | ||
iam.googleapis.com | ||
``` | ||
|
||
### Create AlloyDB cluster | ||
Please follow instruction in the documentation to create an AlloyDB cluster and primary instance in the same project where the application is going to be deployed. | ||
|
||
Here is the [link to the documentation for AlloyDB](https://cloud.google.com/alloydb/docs/quickstart/create-and-connect) | ||
|
||
### Create a database in AlloyDB | ||
Create a database with the name movies and the user movies_owner. You can choose your own names for the database and the user. The application takes it from environment variables. Optionally you can modify the application to use secret manager in Google Cloud as more secured approach. | ||
|
||
### Migrate data from Pinecone to AlloyDB | ||
- Move the data from Pinecone to AlloyDB | ||
|
||
### Enable virtual environment for Python | ||
You can use either your laptop or a virtual machnie for deployment. Using a VM deployed in the same Google Cloud project simplifies deployeent and network configuration. On a Debian Linux you can enable it in the shell using the following command: | ||
``` | ||
sudo apt-get update | ||
sudo apt install python3.11-venv git postgresql-client | ||
python3 -m venv venv | ||
source venv/bin/activate | ||
``` | ||
|
||
### Clone the software | ||
Clone the software using git: | ||
``` | ||
git clone https://github.com/gotochkin/devrel-demos.git | ||
``` | ||
### Run the application | ||
- Change directory | ||
``` | ||
cd devrel-demos/infrastructure/movie-search-app | ||
``` | ||
- Install dependencies | ||
``` | ||
pip install -r requirements.txt | ||
``` | ||
- Set environment variables (Pinecone index name) | ||
``` | ||
export PINECONE_INDEX_NAME=netflix-index-01 | ||
export PORT=8080 | ||
export DB_USER=movies_owner | ||
export DB_PASS=DatabasePassword | ||
export DB_NAME=movies | ||
export INSTANCE_HOST=ALLOYDB_IP | ||
export DB_PORT=5432 | ||
``` | ||
- Start the application from command line | ||
``` | ||
gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 movie_search:me | ||
``` | ||
- Connect to the chat using the VM host:port to get the application interface | ||
|
||
### Deploy the applicaion to Cloud Run | ||
Create a service account cymbal-store-identity and grant role VertexAI User to the account - optional now since we are not using Vertex AI as of now. | ||
Build and deploy application to the Cloud Run service. | ||
|
||
``` | ||
gcloud alpha run deploy movie-search-app \ | ||
--source=./ \ | ||
--no-allow-unauthenticated \ | ||
--service-account movie-search-identity \ | ||
--region us-central1 \ | ||
--network=default \ | ||
--set-env-vars=DB_USER=cymbaldb_owner,DB_PASS=StrongPassword,DB_NAME=cymbaldb,INSTANCE_HOST=127.0.0.1,DB_PORT=5432 \ | ||
--quiet | ||
``` | ||
### Work with application | ||
- Click at the bottom of the app to choose backend. | ||
- Put Google AI API token and Pinecone API token at the top (you need both to use the Pinecone backend). | ||
- Select Pinecone as a backend and confirm the choice. | ||
- Post your question in the input window at the bottom and click the arrow. | ||
|
||
Ask sample questions about the movies | ||
|
||
# TO DO | ||
- Add support for other models and providers | ||
|
||
# License | ||
Apache License Version 2.0; | ||
Copyright 2024 Google LLC | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
# Copyright 2022 Google LLC | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
# TODO (https://github.com/GoogleCloudPlatform/python-docs-samples/issues/8253): remove old region tags | ||
# [START cloud_sql_postgres_sqlalchemy_connect_tcp] | ||
# [START cloud_sql_postgres_sqlalchemy_sslcerts] | ||
# [START cloud_sql_postgres_sqlalchemy_connect_tcp_sslcerts] | ||
import os | ||
import ssl | ||
import logging | ||
|
||
import sqlalchemy | ||
|
||
|
||
def connect_tcp_socket() -> sqlalchemy.engine.base.Engine: | ||
"""Initializes a TCP connection pool for a PostgreSQL instance of Postgres.""" | ||
# Note: Saving credentials in environment variables is convenient, but not | ||
# secure - consider a more secure solution such as | ||
# Cloud Secret Manager (https://cloud.google.com/secret-manager) to help | ||
# keep secrets safe. | ||
if os.environ.get("INSTANCE_HOST"): | ||
db_host = os.environ[ | ||
"INSTANCE_HOST" | ||
] # e.g. '127.0.0.1' ('172.17.0.1' if deployed to GAE Flex) | ||
else: | ||
db_host = "127.0.0.1" | ||
logging.warning(("INSTANCE_HOST is not set using default: %s", db_host)) | ||
if os.environ.get("DB_PORT"): | ||
db_port = os.environ["DB_PORT"] # e.g. '5432' | ||
else: | ||
db_port = "5432" | ||
logging.warning(("DB_PORT is not set using default: %s", db_port)) | ||
if os.environ.get("DB_USER"): | ||
db_user = os.environ["DB_USER"] # e.g. 'my-db-user' | ||
else: | ||
db_user = "movies_owner" | ||
logging.warning(("DB_USER is not set using default: %s", db_user)) | ||
if os.environ.get("DB_PASS"): | ||
db_pass = os.environ["DB_PASS"] # e.g. 'my-db-password' | ||
else: | ||
db_pass = "password" | ||
logging.warning(("DB_PASS is not set using default: %s", db_pass)) | ||
if os.environ.get("DB_NAME"): | ||
db_name = os.environ["DB_NAME"] # e.g. 'my-database' | ||
else: | ||
db_name = "movies" | ||
logging.warning(("DB_NAME is not set using default: %s", db_name)) | ||
|
||
|
||
# [END cloud_sql_postgres_sqlalchemy_connect_tcp] | ||
connect_args = {} | ||
# For deployments that connect directly to a PostgreSQL instance without | ||
# using the PostgreSQL Proxy, configuring SSL certificates will ensure the | ||
# connection is encrypted. | ||
if os.environ.get("DB_ROOT_CERT"): | ||
db_root_cert = os.environ["DB_ROOT_CERT"] # e.g. '/path/to/my/server-ca.pem' | ||
db_cert = os.environ["DB_CERT"] # e.g. '/path/to/my/client-cert.pem' | ||
db_key = os.environ["DB_KEY"] # e.g. '/path/to/my/client-key.pem' | ||
|
||
ssl_context = ssl.SSLContext() | ||
ssl_context.verify_mode = ssl.CERT_REQUIRED | ||
ssl_context.load_verify_locations(db_root_cert) | ||
ssl_context.load_cert_chain(db_cert, db_key) | ||
connect_args["ssl_context"] = ssl_context | ||
|
||
# [START cloud_sql_postgres_sqlalchemy_connect_tcp] | ||
pool = sqlalchemy.create_engine( | ||
# Equivalent URL: | ||
# postgresql+pg8000://<db_user>:<db_pass>@<db_host>:<db_port>/<db_name> | ||
sqlalchemy.engine.url.URL.create( | ||
drivername="postgresql+pg8000", | ||
username=db_user, | ||
password=db_pass, | ||
host=db_host, | ||
port=db_port, | ||
database=db_name, | ||
), | ||
# [END cloud_sql_postgres_sqlalchemy_connect_tcp] | ||
connect_args=connect_args, | ||
# [START cloud_sql_postgres_sqlalchemy_connect_tcp] | ||
# [START_EXCLUDE] | ||
# [START cloud_sql_postgres_sqlalchemy_limit] | ||
# Pool size is the maximum number of permanent connections to keep. | ||
pool_size=5, | ||
# Temporarily exceeds the set pool_size if no connections are available. | ||
max_overflow=2, | ||
# The total number of concurrent connections for your application will be | ||
# a total of pool_size and max_overflow. | ||
# [END cloud_sql_postgres_sqlalchemy_limit] | ||
# [START cloud_sql_postgres_sqlalchemy_backoff] | ||
# SQLAlchemy automatically uses delays between failed connection attempts, | ||
# but provides no arguments for configuration. | ||
# [END cloud_sql_postgres_sqlalchemy_backoff] | ||
# [START cloud_sql_postgres_sqlalchemy_timeout] | ||
# 'pool_timeout' is the maximum number of seconds to wait when retrieving a | ||
# new connection from the pool. After the specified amount of time, an | ||
# exception will be thrown. | ||
pool_timeout=30, # 30 seconds | ||
# [END cloud_sql_postgres_sqlalchemy_timeout] | ||
# [START cloud_sql_postgres_sqlalchemy_lifetime] | ||
# 'pool_recycle' is the maximum number of seconds a connection can persist. | ||
# Connections that live longer than the specified amount of time will be | ||
# re-established | ||
pool_recycle=1800, # 30 minutes | ||
# [END cloud_sql_postgres_sqlalchemy_lifetime] | ||
# [END_EXCLUDE] | ||
) | ||
return pool | ||
|
||
|
||
# [END cloud_sql_postgres_sqlalchemy_connect_tcp_sslcerts] | ||
# [END cloud_sql_postgres_sqlalchemy_sslcerts] | ||
# [END cloud_sql_postgres_sqlalchemy_connect_tcp] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
# Copyright 2024 Google LLC. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
import mesop as me | ||
from dataclasses import dataclass, field | ||
from enum import Enum | ||
from typing import Literal | ||
|
||
Role = Literal["user", "model"] | ||
|
||
# Data Model | ||
@dataclass(kw_only=True) | ||
class ChatMessage: | ||
role: Role = "user" | ||
content: str = "" | ||
in_progress: bool = False | ||
|
||
class Models(Enum): | ||
GEMINI_1_5_FLASH = "PostgreSQL" | ||
PINECONE = "Pinecone" | ||
|
||
@dataclass | ||
class Conversation: | ||
model: str = "" | ||
messages: list[ChatMessage] = field(default_factory=list) | ||
|
||
@me.stateclass | ||
class State: | ||
is_model_picker_dialog_open: bool = False | ||
input: str = "" | ||
conversations: list[Conversation] = field(default_factory=list) | ||
models: list[str] = field(default_factory=list) | ||
gemini_api_key: str = "" | ||
pinecone_api_key: str = "" | ||
location: str = "" | ||
in_progress: bool = False | ||
|
||
@me.stateclass | ||
class ModelDialogState: | ||
selected_models: list[str] = field(default_factory=list) |
Oops, something went wrong.