Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix shoppingassistant demo #2675

Merged
merged 2 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion kustomize/components/alloydb/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ patches:
- name: ALLOYDB_PRIMARY_IP
value: ALLOYDB_PRIMARY_IP_VAL
- name: ALLOYDB_DATABASE_NAME
value: ALLOYDB_CARTS_DATABASE_NAME
value: ALLOYDB_CARTS_DATABASE_NAME_VAL
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was an omission; caught it on a re-run of the script.

- name: ALLOYDB_TABLE_NAME
value: ALLOYDB_CARTS_TABLE_NAME_VAL
- name: ALLOYDB_SECRET_NAME
Expand Down
91 changes: 55 additions & 36 deletions kustomize/components/shopping-assistant/README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,45 @@
# Shopping Assistant with RAG & AlloyDB

This demo adds a new service to Online Boutique called `shoppingassistantservice`, which, alongside an Alloy-DB backed products catalog, adds a RAG-featured AI assistant to the frontned experience, which helps users suggest new products for their room decor.
This demo adds a new service to Online Boutique called `shoppingassistantservice` which, alongside an Alloy-DB backed products catalog, adds a RAG-featured AI assistant to the frontned experience, helping users suggest products matching their home decor.

## Set-up instructions
## Setup instructions

> Note: Make sure you have the `owner` role to the Google Cloud project you want to deploy this to, else you will be unable to enable certain APIs or modify certain VPC rules that are needed for this demo.
**Note:** This demo requires a Google Cloud project where you to have the `owner` role, else you may be unable to enable APIs or modify VPC rules that are needed for this demo.

1. Set some environment variables.
```sh
export PROJECT_ID=<project_id>
export PROJECT_NUMBER=<project_number>
export PGPASSWORD=<pgpassword>
```

**Note**: The project ID and project number of your Google Cloud project can be found in the Console. The PostgreSQL password can be set to anything you want, but make sure to note it down.

1. Change your default Google Cloud project.
```sh
gcloud auth login
gcloud config set project $PROJECT_ID
```

1. Enable the Google Kubernetes Engine (GKE) and Artifact Registry (AR) APIs.
```sh
gcloud services enable container.googleapis.com
gcloud services enable artifactregistry.googleapis.com
```

1. Create a GKE Autopilot cluster.
1. Create a GKE Autopilot cluster. This may take a few minutes.
```sh
gcloud container clusters create-auto cymbal-shops \
--region=us-central1
```

1. Create an AR Docker image repository.
1. Change your Kubernetes context to your newly created GKE cluster.
```sh
gcloud container clusters get-credentials cymbal-shops \
--region us-central1
```

1. Create an Artifact Registry container image repository.
```sh
gcloud artifacts repositories create images \
--repository-format=docker \
Expand All @@ -31,26 +52,19 @@ This demo adds a new service to Online Boutique called `shoppingassistantservice
&& cd microservices-demo/
```

1. Context into the right project and GKE cluster.
1. Run script #1. If it asks about policy bindings, select the option `None`. This may take a few minutes.
```sh
gcloud auth login
gcloud config set project <PROJECT_ID>
gcloud container clusters get-credentials cymbal-shops \
--region us-central1
./kustomize/components/shopping-assistant/scripts/1_deploy_alloydb_infra.sh
```

1. Replace the placeholder variables into infra script #1 and run it. If it asks about policy bindings, select the option for "None".
```sh
vim kustomize/components/shopping-assistant/scripts/1_deploy_alloydb_infra.sh
./scripts/1_deploy_alloydb_infra.sh
```
**Note**: If you are on macOS and use a non-GNU version of `sed`, you may have to tweak the script to use `gsed` instead.

1. Create micro Linux VM on GCP
1. Create a Linux VM in Compute Engine (GCE).
```sh
gcloud compute instances create gce-linux \
--zone=us-central1-a \
--machine-type=e2-micro \
--image-family=debian-12-bookworm-v20240312 \
--image-family=debian-12 \
--image-project=debian-cloud
```

Expand All @@ -60,62 +74,67 @@ This demo adds a new service to Online Boutique called `shoppingassistantservice
--zone "us-central1-a"
```

1. Install the Postgres client and context into the right project.
1. Install the Postgres client and set your default Google Cloud project.
```sh
sudo apt-get install -y postgresql-client
gcloud auth login
gcloud config set project <PROJECT_ID>
```

1. Copy script #2, the python script, and the updated products.json to the VM. Make sure the scripts are executable.
1. Copy script #2, the python script, and the products.json to the VM. Make sure the scripts are executable.
```sh
vim 2_create_populate_alloydb_tables.sh
vim generate_sql_from_products.py
vim products.json
nano 2_create_populate_alloydb_tables.sh # paste content
nano generate_sql_from_products.py # paste content
nano products.json # paste content
chmod +x 2_create_populate_alloydb_tables.sh
chmod +x generate_sql_from_products.py
```

> Note: You can find the files at the following places:
> - kustomize/components/shopping-assistant/scripts/2_create_populate_alloydb_tables.sh
> - kustomize/components/shopping-assistant/scripts/generate_sql_from_products.py
> - src/productcatalogservice/products.json
**Note:** You can find the files at the following places:
- `kustomize/components/shopping-assistant/scripts/2_create_populate_alloydb_tables.sh`
- `kustomize/components/shopping-assistant/scripts/generate_sql_from_products.py`
- `src/productcatalogservice/products.json`

1. Run script #2 in the VM. If it asks for a postgres password, it should be the same that you set in script #1 earlier.
1. Run script #2 in the VM. If it asks for a postgres password, it should be the same that you set in script #1 earlier. This may take a few minutes.
```sh
./2_create_populate_alloydb_tables.sh
```

1. Exit SSH
1. Exit SSH.
```sh
exit
```

1. Create an API key in the [Credentials page](https://pantheon.corp.google.com/apis/credentials) with permissions for "Generative Language API", and make note of the secret key.

1. Paste this secret key in the shopping assistant service envs, replacing `GOOGLE_API_KEY_VAL`.
1. Replace the PostgreSQL password placeholder in the shoppingassistant service.
```sh
vim kustomize/components/shopping-assistant/shoppingassistantservice.yaml
sed -i "s/GOOGLE_API_KEY_VAL/${PGPASSWORD}/g" kustomize/components/shopping-assistant/shoppingassistantservice.yaml
```

1. Change the commented-out components in `kubernetes-manifests/kustomization.yaml` to look like this:
1. Edit the root Kustomize file to enable the `alloydb` and `shopping-assistant` components.
```sh
nano kubernetes-manifests/kustomization.yaml # make the modifications below
```

```yaml
components: # remove comment
# ...head of the file
components: # remove this comment
# - ../kustomize/components/cymbal-branding
# - ../kustomize/components/google-cloud-operations
# - ../kustomize/components/memorystore
# - ../kustomize/components/network-policies
- ../kustomize/components/alloydb # remove comment
- ../kustomize/components/shopping-assistant # remove comment
- ../kustomize/components/alloydb # remove this comment
- ../kustomize/components/shopping-assistant # remove this comment
# - ../kustomize/components/spanner
# - ../kustomize/components/container-images-tag
# - ../kustomize/components/container-images-tag-suffix
# - ../kustomize/components/container-images-registry
```

1. Deploy to the GKE cluster.
```
skaffold run --default-repo=us-central1-docker.pkg.dev/<PROJECT_ID>/images
```sh
skaffold run --default-repo=us-central1-docker.pkg.dev/$PROJECT_ID/images
```

1. Wait for all the pods to be up and running. You can then find the external IP and navigate to it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ set -e
set -x

# Replace me
PROJECT_ID=<project_id>
PROJECT_NUMBER=<project_number>
PGPASSWORD=<password>
PROJECT_ID=$PROJECT_ID
PROJECT_NUMBER=$PROJECT_NUMBER
PGPASSWORD=$PGPASSWORD

# Set sensible defaults
REGION=us-central1
Expand Down Expand Up @@ -82,7 +82,8 @@ gcloud alloydb instances create ${ALLOYDB_INSTANCE_NAME}-replica \
gcloud beta alloydb instances update ${ALLOYDB_INSTANCE_NAME} \
--cluster=${ALLOYDB_CLUSTER_NAME} \
--region=${REGION} \
--assign-inbound-public-ip=ASSIGN_IPV4
--assign-inbound-public-ip=ASSIGN_IPV4 \
--database-flags password.enforce_complexity=on
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This flag is now required or else the AlloyDB instance update fails with an error.


# Fetch the primary and read IPs
ALLOYDB_PRIMARY_IP=`gcloud alloydb instances list --region=${REGION} --cluster=${ALLOYDB_CLUSTER_NAME} --filter="INSTANCE_TYPE:PRIMARY" --format=flattened | sed -nE "s/ipAddress:\s*(.*)/\1/p"`
Expand Down Expand Up @@ -119,9 +120,6 @@ gcloud projects add-iam-policy-binding ${PROJECT_ID} --member=serviceAccount:${A
gcloud projects add-iam-policy-binding ${PROJECT_ID} --member=serviceAccount:${ALLOYDB_USER_GSA_ID} --role=roles/alloydb.databaseUser
gcloud projects add-iam-policy-binding ${PROJECT_ID} --member=serviceAccount:${ALLOYDB_USER_GSA_ID} --role=roles/secretmanager.secretAccessor
gcloud projects add-iam-policy-binding ${PROJECT_ID} --member=serviceAccount:${ALLOYDB_USER_GSA_ID} --role=roles/serviceusage.serviceUsageConsumer
gcloud projects add-iam-policy-binding ${PROJECT_ID} --member=service-${PROJECT_NUMBER}@gcp-sa-alloydb.iam.gserviceaccount.com --role=roles/aiplatform.user
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line now fails with an error saying the member flag is in an invalid format. It doesn't seem needed (anymore).


# Add bindings to the Online Boutique services that need it
gcloud projects add-iam-policy-binding ${PROJECT_ID} --member=serviceAccount:service-${PROJECT_NUMBER}@gcp-sa-alloydb.iam.gserviceaccount.com --role=roles/aiplatform.user

gcloud iam service-accounts add-iam-policy-binding ${ALLOYDB_USER_GSA_ID} \
Expand Down
3 changes: 2 additions & 1 deletion src/productcatalogservice/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

FROM golang:1.22.5-alpine@sha256:0d3653dd6f35159ec6e3d10263a42372f6f194c3dea0b35235d72aabde86486e AS builder
FROM golang:1.22.6-alpine@sha256:1a478681b671001b7f029f94b5016aed984a23ad99c707f6a0ab6563860ae2f3 AS builder

WORKDIR /src
# restore dependencies
Expand All @@ -28,6 +28,7 @@ FROM scratch

WORKDIR /src
COPY --from=builder /productcatalogservice ./server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The productscatalog started failing with x509 issues, which this line fixes.

COPY products.json .

# Definition of this variable is used by 'skaffold debug' to identify a golang binary.
Expand Down
16 changes: 13 additions & 3 deletions src/productcatalogservice/catalog_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,18 @@ func loadCatalog(catalog *pb.ListProductsResponse) error {
defer catalogMutex.Unlock()

if os.Getenv("ALLOYDB_CLUSTER_NAME") != "" {
log.Info("Loading catalog from AlloyDB...")
return loadCatalogFromAlloyDB(catalog)
}

log.Info("Loading catalog from local products.json file...")
return loadCatalogFromLocalFile(catalog)
}

func loadCatalogFromLocalFile(catalog *pb.ListProductsResponse) error {
log.Info("loading catalog from local products.json file...")

catalogJSON, err := os.ReadFile("products.json")
if err != nil {
log.Fatalf("failed to open product catalog json file: %v", err)
log.Warnf("failed to open product catalog json file: %v", err)
return err
}

Expand All @@ -63,6 +63,7 @@ func getSecretPayload(project, secret, version string) (string, error) {
ctx := context.Background()
client, err := secretmanager.NewClient(ctx)
if err != nil {
log.Warnf("failed to create SecretManager client: %v", err)
return "", err
}
defer client.Close()
Expand All @@ -74,13 +75,16 @@ func getSecretPayload(project, secret, version string) (string, error) {
// Call the API.
result, err := client.AccessSecretVersion(ctx, req)
if err != nil {
log.Warnf("failed to access SecretVersion: %v", err)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warn logs are a great addition here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to troubleshoot what was happening with the products catalog, and figured I'd leave them for the next time there's a potential issue :)

return "", err
}

return string(result.Payload.Data), nil
}

func loadCatalogFromAlloyDB(catalog *pb.ListProductsResponse) error {
log.Info("loading catalog from AlloyDB...")

projectID := os.Getenv("PROJECT_ID")
region := os.Getenv("REGION")
pgClusterName := os.Getenv("ALLOYDB_CLUSTER_NAME")
Expand All @@ -96,6 +100,7 @@ func loadCatalogFromAlloyDB(catalog *pb.ListProductsResponse) error {

dialer, err := alloydbconn.NewDialer(context.Background())
if err != nil {
log.Warnf("failed to set-up dialer connection: %v", err)
return err
}
cleanup := func() error { return dialer.Close() }
Expand All @@ -108,6 +113,7 @@ func loadCatalogFromAlloyDB(catalog *pb.ListProductsResponse) error {

config, err := pgxpool.ParseConfig(dsn)
if err != nil {
log.Warnf("failed to parse DSN config: %v", err)
return err
}

Expand All @@ -118,13 +124,15 @@ func loadCatalogFromAlloyDB(catalog *pb.ListProductsResponse) error {

pool, err := pgxpool.NewWithConfig(context.Background(), config)
if err != nil {
log.Warnf("failed to set-up pgx pool: %v", err)
return err
}
defer pool.Close()

query := "SELECT id, name, description, picture, price_usd_currency_code, price_usd_units, price_usd_nanos, categories FROM " + pgTableName
rows, err := pool.Query(context.Background(), query)
if err != nil {
log.Warnf("failed to query database: %v", err)
return err
}
defer rows.Close()
Expand All @@ -139,6 +147,7 @@ func loadCatalogFromAlloyDB(catalog *pb.ListProductsResponse) error {
&product.Picture, &product.PriceUsd.CurrencyCode, &product.PriceUsd.Units,
&product.PriceUsd.Nanos, &categories)
if err != nil {
log.Warnf("failed to scan query result row: %v", err)
return err
}
categories = strings.ToLower(categories)
Expand All @@ -147,5 +156,6 @@ func loadCatalogFromAlloyDB(catalog *pb.ListProductsResponse) error {
catalog.Products = append(catalog.Products, product)
}

log.Info("successfully parsed product catalog from AlloyDB")
return nil
}
34 changes: 16 additions & 18 deletions src/productcatalogservice/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.22
require (
cloud.google.com/go/alloydbconn v1.11.1
cloud.google.com/go/profiler v0.4.1
cloud.google.com/go/secretmanager v1.13.5
cloud.google.com/go/secretmanager v1.13.6
github.com/golang/protobuf v1.5.4
github.com/jackc/pgx/v5 v5.6.0
github.com/pkg/errors v0.9.1
Expand All @@ -14,26 +14,25 @@ require (
go.opentelemetry.io/otel v1.28.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0
go.opentelemetry.io/otel/sdk v1.28.0
golang.org/x/net v0.27.0
golang.org/x/net v0.28.0
google.golang.org/grpc v1.65.0
)

require (
cloud.google.com/go v0.115.0 // indirect
cloud.google.com/go/alloydb v1.10.4 // indirect
cloud.google.com/go/auth v0.7.2 // indirect
cloud.google.com/go/auth v0.8.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.3 // indirect
cloud.google.com/go/compute v1.27.2 // indirect
cloud.google.com/go/compute/metadata v0.5.0 // indirect
cloud.google.com/go/iam v1.1.10 // indirect
cloud.google.com/go/longrunning v0.5.9 // indirect
cloud.google.com/go/iam v1.1.12 // indirect
cloud.google.com/go/longrunning v0.5.11 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/google/s2a-go v0.1.8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
Expand All @@ -47,16 +46,15 @@ require (
go.opentelemetry.io/otel/metric v1.28.0 // indirect
go.opentelemetry.io/otel/trace v1.28.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/api v0.189.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20240722135656-d784300faade // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240722135656-d784300faade // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/oauth2 v0.22.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.23.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/time v0.6.0 // indirect
google.golang.org/api v0.191.0 // indirect
google.golang.org/genproto v0.0.0-20240730163845-b1a4ccb954bf // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf // indirect
google.golang.org/protobuf v1.34.2 // indirect
)
Loading
Loading