Using TypeSafe AI's Jev model with AlloyDB AI Functions for High-Speed AI Queries

1. Introduction

In this codelab, you will connect TypeSafe AI's Jev model to AlloyDB AI using AlloyDB's native AI functions (ai.if, ai.analyze_sentiment, and ml_predict_row). This allows you to run efficient batched inference using the Jev model directly on your operational data in AlloyDB, without moving your data or writing complex application-side code.

What you'll do

  • Provision Private Service Access (PSA) and an AlloyDB for PostgreSQL cluster and primary instance
  • Create a VPC firewall rule allowing Cloudshell and internal connectivity
  • Load an e-commerce sample dataset using gcloud alloydb clusters import
  • Enable Google Secret Manager and AlloyDB's google_ml_integration extension
  • Store and grant access to your TypeSafe AI API key in Secret Manager
  • Create PL/pgSQL input and output transformation functions for Jev's noul (boolean) and choice (sentiment) primitives
  • Register the custom jev-model model endpoint in AlloyDB
  • Run scalar and array-batched semantic queries (ai.if and ai.analyze_sentiment) directly in SQL
  • Query real customer product reviews for sentiment classification using ai.analyze_sentiment
  • (Bonus) Register a generic endpoint (jev-systemone) and evaluate continuous customer satisfaction intensity using Jev's score primitive

What you'll need

  • A web browser such as Chrome
  • A Google Cloud project with billing enabled
  • A TypeSafe AI API key (from TypeSafe AI)

2. Before you begin

Select your Google Cloud project

In the Google Cloud Console, select or create the Google Cloud project you wish to use.

Start Cloud Shell

Click Activate Cloud Shell at the top right of the Google Cloud console.

Verify that your Cloud Shell session is authenticated:

gcloud auth list

Set your project ID and environment variables:

export PROJECT_ID=$(gcloud config get-value project)
gcloud config set project $PROJECT_ID

export REGION="us-west4"
export ADBCLUSTER="my-alloydb-cluster"
export ADBINSTANCE="my-alloydb-primary"

Enable Required Google Cloud APIs

Run this command to enable all the necessary Google Cloud service APIs:

gcloud services enable \
  alloydb.googleapis.com \
  servicenetworking.googleapis.com \
  secretmanager.googleapis.com \
  compute.googleapis.com

3. Provision the AlloyDB Cluster and Primary Instance

In this step, you will allocate a Private Service Access IP range in your VPC network, establish private peering, and provision your AlloyDB cluster and primary instance with outbound internet access.

1. Create Private Service Access (PSA) IP Range

AlloyDB requires a private IP range allocation in your Virtual Private Cloud (VPC) network. Assuming you are using the default VPC network:

Create the private IP range allocation:

gcloud compute addresses create psa-range \
    --global \
    --purpose=VPC_PEERING \
    --prefix-length=24 \
    --description="VPC private service access" \
    --network=default

Establish the private VPC peering connection:

gcloud services vpc-peerings connect \
    --service=servicenetworking.googleapis.com \
    --ranges=psa-range \
    --network=default

2. Create the AlloyDB Cluster

Generate a secure database password for the initial postgres administrative user:

export PGPASSWORD="$(openssl rand -hex 8)@1aA"
echo "Your generated AlloyDB postgres password is: $PGPASSWORD"

Create an AlloyDB Free Trial cluster:

gcloud alloydb clusters create $ADBCLUSTER \
    --password=$PGPASSWORD \
    --network=default \
    --region=$REGION 

3. Create the Primary Instance with AI Flags and Outbound Connectivity

AlloyDB connects directly to TypeSafe AI's secure SaaS endpoint (https://api.typesafe.ai/v1/systemone) over outbound HTTPS. Therefore, you must specify --outbound-public-ip. Additionally, configure the AlloyDB AI database flags for the environment:

gcloud alloydb instances create $ADBINSTANCE \
    --instance-type=PRIMARY \
    --cpu-count=2 \
    --region=$REGION \
    --cluster=$ADBCLUSTER \
    --outbound-public-ip \
    --ssl-mode=ALLOW_UNENCRYPTED_AND_ENCRYPTED \
    --database-flags=\
google_ml_integration.enable_model_support=on,\
google_ml_integration.enable_ai_query_engine=on,\
google_ml_integration.enable_ai_function_acceleration=on,\
google_ml_integration.enable_cost_optimized_ai_functions=on,\
parameterized_views.enabled=on,\
password.enforce_complexity=on,\
password.min_uppercase_letters=1,\
password.min_numerical_chars=1,\
password.min_pass_length=10

4. Load Sample E-Commerce Data

To test realistic semantic queries, import a sample e-commerce dataset containing product catalogs and customer reviews directly from Cloud Storage:

gcloud alloydb clusters import $ADBCLUSTER \
    --region=$REGION \
    --project=$PROJECT_ID \
    --gcs-uri="gs://sample-data-and-media/ecomm-retail/ecom_generic.sql" \
    --sql \
    --database=postgres \
    --user=postgres

4. Store the TypeSafe AI API Key in Secret Manager

AlloyDB's Model Endpoint Management integrates natively with Google Secret Manager to authenticate external API calls without exposing plaintext credentials in database code or configuration logs.

1. Create the Secret

In Cloud Shell, create a new secret named typesafe-jev-api-key:

gcloud secrets create typesafe-jev-api-key \
  --replication-policy="automatic"

Add your TypeSafe AI API key as a secret version:

echo -n "<YOUR_TYPESAFE_API_KEY>" | \
  gcloud secrets versions add typesafe-jev-api-key --data-file=-

2. Grant Access to the AlloyDB Service Agent

AlloyDB uses its project service agent to retrieve secrets during query execution. Find your project number and grant the Secret Accessor role:

PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")
ALLOYDB_SA="service-${PROJECT_NUMBER}@gcp-sa-alloydb.iam.gserviceaccount.com"

gcloud secrets add-iam-policy-binding typesafe-jev-api-key \
  --member="serviceAccount:${ALLOYDB_SA}" \
  --role="roles/secretmanager.secretAccessor"

You should see output confirming that the service agent has received roles/secretmanager.secretAccessor.

5. Connect to AlloyDB via AlloyDB Studio and Initialize Database Extensions

To interact with your AlloyDB database directly from your browser without configuring local client tools or proxy tunnels, use AlloyDB Studio in the Google Cloud Console.

1. Open AlloyDB Studio

  1. In the Google Cloud Console, navigate to AlloyDB for PostgreSQL > Clusters.
  2. Click on your cluster (my-alloydb-cluster).
  3. In the left navigation menu, click AlloyDB Studio.
  4. Authenticate to the database:
    • Database: postgres
    • User: postgres
    • Password: Enter the $PGPASSWORD generated during cluster creation (e.g. run echo $PGPASSWORD in Cloud Shell to view it).
  5. Click Authenticate.

You are now in the AlloyDB Studio interactive SQL query editor.

2. Enable Database Extensions & Flags

In the AlloyDB Studio query editor, paste and run the following statements to enable the google_ml_integration extension and configure runtime settings:

-- Enable the AlloyDB AI integration extension
CREATE EXTENSION IF NOT EXISTS google_ml_integration;

-- Enable preview AI functions (ai.analyze_sentiment, ai.classify)
SET google_ml_integration.enable_preview_ai_functions = 'on';

-- Disable async batching for the session to use custom transform functions directly
SET google_ml_integration.enable_async_operation = 'off';

Click Run to execute the query.

3. Register the Secret in AlloyDB MEM

Tell AlloyDB where to find your TypeSafe AI API key:

CALL google_ml.create_sm_secret(
    secret_id => 'jev_api_key',
    secret_path => 'projects/<YOUR_PROJECT_ID>/secrets/typesafe-jev-api-key/versions/latest'
);

6. Create SQL Transformation Functions

TypeSafe AI's System One API accepts structured JSON with distinct reasoning primitives (noul for boolean constraints, choice for multi-class classification).

AlloyDB's Model Endpoint Management uses four transformation functions (scalar input, scalar output, batch input, batch output) to bridge standard PostgreSQL ai.* function signatures to the Jev REST API format.

1. Scalar Input Transform

Add the following function to convert single-row ai.if and ai.analyze_sentiment calls into Jev request payloads:

CREATE OR REPLACE FUNCTION public.jev_model_input_transform(
    model_id VARCHAR(100),
    input_text TEXT,
    generation_config JSON,
    system_instruction TEXT
) RETURNS JSON
LANGUAGE plpgsql IMMUTABLE AS $$
DECLARE
    full_instruction TEXT;
    enum_json JSON;
    criteria_obj JSONB := '{}'::JSONB;
    elem TEXT;
    q_obj JSON;
BEGIN
    -- Combine optional system instructions with the input prompt
    IF system_instruction IS NOT NULL AND length(trim(system_instruction)) > 0 THEN
        full_instruction := system_instruction || E'\n\n' || input_text;
    ELSE
        full_instruction := input_text;
    END IF;

    -- Inspect generation_config to determine whether caller expects a multi-class choice or boolean
    enum_json := COALESCE(
        generation_config->'generationConfig'->'responseSchema'->'enum',
        generation_config->'responseSchema'->'enum'
    );

    IF enum_json IS NOT NULL AND json_typeof(enum_json) = 'array' THEN
        -- Multi-class classification (ai.analyze_sentiment) -> Jev choice primitive
        FOR elem IN SELECT json_array_elements_text(enum_json) LOOP
            criteria_obj := criteria_obj || jsonb_build_object(elem, 'Category: ' || elem);
        END LOOP;
        q_obj := json_build_object(
            'type', 'choice',
            'instructions', full_instruction,
            'criteria', criteria_obj::JSON
        );
    ELSE
        -- Boolean filtering (ai.if) -> Jev noul primitive
        q_obj := json_build_object(
            'type', 'noul',
            'instructions', full_instruction
        );
    END IF;

    RETURN json_build_object(
        'model', 'jev-latest',
        'state', COALESCE(generation_config->>'state', 'Evaluate the question accurately based on the provided text.'),
        'questions', json_build_object('q_1', q_obj)
    );
END;
$$;

2. Scalar Output Transform

Add the function to unpack Jev's JSON response back into a PostgreSQL TEXT result:

CREATE OR REPLACE FUNCTION public.jev_model_output_transform(
    model_id VARCHAR(100),
    response_json JSON
) RETURNS TEXT
LANGUAGE plpgsql IMMUTABLE AS $$
DECLARE
    q_ans JSON := response_json->'answers'->'q_1';
    ans_type TEXT := q_ans->>'type';
BEGIN
    IF ans_type = 'choice' THEN
        RETURN q_ans->>'choice';
    ELSIF ans_type = 'noul' THEN
        -- Convert probability score (0.00 to 1.00) to boolean text
        RETURN CASE WHEN (q_ans->>'noul')::FLOAT >= 0.50 THEN 'true' ELSE 'false' END;
    ELSE
        RAISE EXCEPTION 'Unexpected Jev response payload: %', response_json::TEXT;
    END IF;
END;
$$;

3. Batch Input Transform

Jev can evaluate dozens of questions simultaneously inside a single HTTP round-trip without generative degradation.

Add the batch input transform to convert PostgreSQL prompts TEXT[] arrays into parallel q_1 .. q_N Jev questions:

CREATE OR REPLACE FUNCTION public.jev_model_batch_input_transform(
    model_id VARCHAR(100),
    prompts TEXT[],
    generation_config JSON,
    system_instructions JSON
) RETURNS JSON
LANGUAGE plpgsql IMMUTABLE AS $$
DECLARE
    questions_obj JSONB := '{}'::JSONB;
    enum_json JSON;
    criteria_obj JSONB := '{}'::JSONB;
    elem TEXT;
    is_choice BOOLEAN := FALSE;
    i INT;
BEGIN
    enum_json := COALESCE(
        generation_config->'generationConfig'->'responseSchema'->'items'->'enum',
        generation_config->'generationConfig'->'responseSchema'->'enum',
        generation_config->'responseSchema'->'items'->'enum',
        generation_config->'responseSchema'->'enum'
    );

    IF enum_json IS NOT NULL AND json_typeof(enum_json) = 'array' THEN
        IF NOT (enum_json::JSONB = '["true", "false"]'::JSONB OR enum_json::JSONB = '["false", "true"]'::JSONB) THEN
            is_choice := TRUE;
            FOR elem IN SELECT json_array_elements_text(enum_json) LOOP
                criteria_obj := criteria_obj || jsonb_build_object(elem, 'Category: ' || elem);
            END LOOP;
        END IF;
    END IF;

    -- Construct parallel questions for every prompt in the array
    FOR i IN 1 .. COALESCE(array_length(prompts, 1), 0) LOOP
        IF is_choice THEN
            questions_obj := questions_obj || jsonb_build_object(
                'q_' || i::TEXT,
                jsonb_build_object(
                    'type', 'choice',
                    'instructions', prompts[i],
                    'criteria', criteria_obj
                )
            );
        ELSE
            questions_obj := questions_obj || jsonb_build_object(
                'q_' || i::TEXT,
                jsonb_build_object(
                    'type', 'noul',
                    'instructions', prompts[i]
                )
            );
        END IF;
    END LOOP;

    RETURN json_build_object(
        'model', 'jev-latest',
        'state', COALESCE(generation_config->>'state', 'Evaluate each question independently based on the record data.'),
        'questions', questions_obj::JSON
    );
END;
$$;

4. Batch Output Transform

Add the function to extract answers in proper 1..N array index order:

CREATE OR REPLACE FUNCTION public.jev_model_batch_output_transform(
    model_id VARCHAR(100),
    response JSON
) RETURNS TEXT[]
LANGUAGE plpgsql IMMUTABLE AS $$
DECLARE
    results TEXT[] := ARRAY[]::TEXT[];
    answers JSON := response->'answers';
    i INT := 1;
    q_ans JSON;
    ans_type TEXT;
BEGIN
    LOOP
        q_ans := answers->('q_' || i::TEXT);
        EXIT WHEN q_ans IS NULL;
        ans_type := q_ans->>'type';
        IF ans_type = 'choice' THEN
            results := array_append(results, q_ans->>'choice');
        ELSE
            results := array_append(results, CASE WHEN (q_ans->>'noul')::FLOAT >= 0.50 THEN 'true' ELSE 'false' END);
        END IF;
        i := i + 1;
    END LOOP;
    RETURN results;
END;
$$;

7. Register the Custom Model Endpoint in AlloyDB

Now you will bind the transformation functions, secret credential, and the TypeSafe AI REST endpoint together into a registered model named jev-model.

1. Register jev-model

In the AlloyDB Studio query editor, paste and run:

-- Drop previous registration if updating
DO $$
BEGIN
    IF EXISTS (SELECT 1 FROM google_ml.model_info_view WHERE model_id = 'jev-model') THEN
        CALL google_ml.drop_model('jev-model');
    END IF;
END $$;

-- Register custom LLM endpoint
CALL google_ml.create_model(
    model_id => 'jev-model',
    model_request_url => 'https://api.typesafe.ai/v1/systemone',
    model_provider => 'custom',
    model_type => 'llm',
    model_qualified_name => 'jev-latest',
    model_auth_type => 'secret_manager',
    model_auth_id => 'jev_api_key',
    model_in_transform_fn => 'jev_model_input_transform',
    model_out_transform_fn => 'jev_model_output_transform',
    model_batch_in_transform_fn => 'jev_model_batch_input_transform',
    model_batch_out_transform_fn => 'jev_model_batch_output_transform'
);

2. Verify Registration

Query the model registry view to ensure jev-model is active and recognized:

SELECT model_id, model_type, model_provider, model_auth_type 
FROM google_ml.model_info_view 
WHERE model_id = 'jev-model';

You should see output similar to:

 model_id | model_type | model_provider | model_auth_type 
----------+------------+----------------+-----------------
 jev-model  | llm        | custom         | secret_manager
(1 row)

8. Run Test Queries Against Jev

With jev-model registered, you can now execute semantic operations directly inside standard PostgreSQL queries.

1. Test Scalar Boolean Filtering (ai.if)

Evaluate a single text statement using ai.if:

SELECT ai.if(
    prompt => 'Is the product "North Face Waterproof Gore-Tex Hiking Jacket ($249)" suitable for rainy outdoor conditions?',
    model_id => 'jev-model'
) AS is_suitable;

You should see output similar to:

 is_suitable 
-------------
 true
(1 row)

2. Test Scalar Sentiment Analysis (ai.analyze_sentiment)

Evaluate customer feedback into a categorical label (positive, negative, or neutral):

SET google_ml_integration.enable_preview_ai_functions = 'on';
SELECT ai.analyze_sentiment(
    input => 'The stitching on this jacket tore on the second day and the zipper jammed completely.',
    model_id => 'jev-model'
) AS sentiment;

You should see output similar to:

 sentiment 
-----------
 negative
(1 row)

3. Test Array-Batched Boolean Filtering

Evaluate multiple items in a single query by passing an array of prompts:

SET google_ml_integration.enable_async_operation = 'off';
SELECT unnest(prompts) AS product_prompt,
       unnest(ai.if(prompts => prompts, model_id => 'jev-model')) AS is_cold_weather
FROM (
    SELECT ARRAY[
        'Is "Arctic Expedition Down Parka (-30F)" cold-weather winter outerwear?',
        'Is "Men''s Boardshorts Beach Swim Trunk" cold-weather winter outerwear?',
        'Is "Merino Wool Thermal Base Layer" cold-weather winter outerwear?'
    ] AS prompts
) b;

You should see output similar to:

                             product_prompt                              | is_cold_weather 
-------------------------------------------------------------------------+-----------------
 Is "Arctic Expedition Down Parka (-30F)" cold-weather winter outerwear? | true
 Is "Men's Boardshorts Beach Swim Trunk" cold-weather winter outerwear?  | false
 Is "Merino Wool Thermal Base Layer" cold-weather winter outerwear?      | false
(3 rows)

4. Test Table Queries with Array Batching

Now query the ecomm.products table loaded earlier during cluster provisioning, using ai.if on a batch of items to check which of the products in the catalog are cold weather gear.

To run high-throughput semantic filtering over table data (e.g. 50 rows per batch), use PostgreSQL's row_number() window function to chunk rows into batches:

-- Evaluate a 50-row batch of real catalog items from ecomm.products
WITH numbered AS (
    SELECT 
        id, 
        name, 
        category,
        product_description,
        ((row_number() OVER (ORDER BY id) - 1) / 50) AS batch_id
    FROM ecomm.products
    ORDER BY id DESC
    LIMIT 50
),
batched AS (
    SELECT 
        batch_id,
        array_agg(id ORDER BY id) AS ids,
        array_agg(name ORDER BY id) AS names,
        array_agg(category ORDER BY id) AS categories,
        array_agg(product_description ORDER BY id) AS descs,
        ai.if(
            prompts => array_agg('Is "' || name || '" designed for cold weather winter outerwear?' ORDER BY id),
            model_id => 'jev-model'
        ) AS decisions
    FROM numbered
    GROUP BY batch_id
),
unrolled AS (
    SELECT 
        b.batch_id,
        u.id,
        u.name,
        u.category,
        u.product_description,
        u.decision AS cold_weather_gear
    FROM batched b
    CROSS JOIN LATERAL unnest(b.ids, b.names, b.categories, b.descs, b.decisions) 
      AS u(id, name, category, product_description, decision)
)
SELECT id, name, category, product_description, cold_weather_gear
FROM unrolled
WHERE cold_weather_gear = TRUE
LIMIT 5;

You should see output similar to:

  id   |                                 name                                 |  category   |                                                product_description                                                 | cold_weather_gear 
-------+----------------------------------------------------------------------+-------------+--------------------------------------------------------------------------------------------------------------------+-------------------
 29071 | Winter Striped Beanie with Pom                                       | Accessories | Brave the chill in style with the SoleStyle Winter Striped Beanie! This cozy beanie adds a pop of personality...  | true
 29077 | Knit Collegiate Rugby Stripe Winter Scarf & Beanie Hat Set           | Accessories | Stay cozy and show off your sporty side with our Knit Collegiate Rugby Stripe Scarf & Beanie Set...               | true
 29083 | Junction Finds Men's Muscle Wool Blend Trapper Hat                   | Accessories | Brave the elements in style with the Junction Finds Trapper Hat! This wool-blend hat will keep you warm...        | true
 29086 | Mens Soft Thermal Insulated Wrist Length Leather Gloves - Black      | Accessories | Brave the chill in style with these supple leather gloves from Gavel Goods! Lined with a soft, thermal knit...    | true
 29090 | Long Beanie-Red W16S24E                                              | Accessories | Stay cozy and stylish all season long with this vibrant red beanie from Enchant! This isn't just any hat...       | true
(5 rows)

5. Query Customer Product Reviews for Sentiment

Now analyze customer reviews from the ecomm.product_reviews table using ai.analyze_sentiment backed by Jev's choice primitive:

SET google_ml_integration.enable_preview_ai_functions = 'on'
SELECT 
    r.id,
    p.name AS product_name,
    r.rating AS customer_star_rating,
    substring(r.review_text FROM 1 FOR 70) || '...' AS review_snippet,
    ai.analyze_sentiment(
        input => r.review_text,
        model_id => 'jev-model'
    ) AS detected_sentiment
FROM ecomm.product_reviews r
JOIN ecomm.products p ON r.product_id = p.id
ORDER BY r.id
LIMIT 5;

You should see output similar to:

 id |                  product_name                  | customer_star_rating |                             review_snippet                              | detected_sentiment 
----+------------------------------------------------+----------------------+-------------------------------------------------------------------------+--------------------
  1 | Hugs & Kisses Juniors Peplum Skirt             |                    1 | This skirt was poorly made and the fabric felt extremely cheap for for...| negative
  2 | Bonkers Larry Full Length Pull on Pants        |                    1 | These pants are way too expensive for the terrible quality you get. Th...| negative
  3 | Gifts and Garb Brah Bra Extenders              |                    3 | These extenders are okay but nothing special. They do add a bit of ext...| neutral
  4 | Auto Forge Men's Tall Duck Traditional Coat    |                    1 | This coat is terribly stiff and heavy, making it very uncomfortable to...| negative
  5 | Weaver's Way Men's Shetland Zip Neck Sweater   |                    2 | This sweater is just okay and not really worth the high price tag. The...| negative
(5 rows)

9. Bonus: Continuous Sentiment Scoring with Jev score

While AlloyDB's AI functions like ai.analyze_sentiment return categorical buckets (positive, negative, neutral), TypeSafe AI's score primitive evaluates nuance across an ordered rubric and returns a continuous, expectation-weighted rating from 0 to 100.

Because score returns numeric metadata rather than a text classification label, you can register a custom generic model endpoint (jev-systemone) and invoke it using google_ml.predict_row.

1. Register the Generic Endpoint

In AlloyDB Studio, register jev-systemone with model_type => 'generic':

DO $$
BEGIN
    IF EXISTS (SELECT 1 FROM google_ml.model_info_view WHERE model_id = 'jev-systemone') THEN
        CALL google_ml.drop_model('jev-systemone');
    END IF;
EXCEPTION WHEN OTHERS THEN
    NULL;
END $$;

CALL google_ml.create_model(
    model_id => 'jev-systemone',
    model_request_url => 'https://api.typesafe.ai/v1/systemone',
    model_provider => 'custom',
    model_type => 'generic',
    model_qualified_name => 'jev-latest',
    model_auth_type => 'secret_manager',
    model_auth_id => 'jev_api_key'
);

2. Run a Multi-Task Evaluation (Boolean + Continuous Score)

Evaluate both a defect boolean check (noul) and continuous sentiment intensity (score) simultaneously in a single HTTP call:

SELECT jsonb_pretty(google_ml.predict_row(
    model_id => 'jev-systemone',
    request_body => json_build_object(
        'model', 'jev-latest',
        'state', 'Review for North Face Parka: "The zipper broke on day 3 and customer service refused an exchange, though the fabric itself is very soft."',
        'questions', json_build_object(
            'has_defect', json_build_object(
                'type', 'noul',
                'instructions', 'Does this review report a physical product defect or failure?'
            ),
            'satisfaction_score', json_build_object(
                'type', 'score',
                'instructions', 'Rate the customer sentiment and satisfaction on a 1-5 scale.',
                'criteria', json_build_array(
                    '1: Extremely dissatisfied / defect failure',
                    '2: Dissatisfied',
                    '3: Mixed or neutral',
                    '4: Satisfied',
                    '5: Delighted'
                )
            )
        )
    )
)::jsonb) AS jev_analysis;

You should see output similar to (your results may vary):

{
  "model": "jev-1.13.0",
  "usage": {
    "input_tokens": 388,
    "output_tokens": 39
  },
  "answers": {
    "has_defect": {
      "noul": 0.98,
      "type": "noul"
    },
    "satisfaction_score": {
      "type": "score",
      "score": 0.11,
      "legend": {
        "0": "1: Extremely dissatisfied / defect failure",
        "1": "2: Dissatisfied",
        "2": "3: Mixed or neutral",
        "3": "4: Satisfied",
        "4": "5: Delighted"
      },
      "confidence": 0.91,
      "probabilities": {
        "0": 0.9,
        "1": 0.08,
        "2": 0.02,
        "3": 0.0,
        "4": 0.0
      }
    }
  }
}

Notice that satisfaction_score evaluated to 11 / 100, accurately capturing that the customer had a mostly negative experience despite acknowledging that the fabric was soft!

10. Clean up

To avoid incurring ongoing charges for resources created during this codelab, clean up the secret and database objects, and delete the AlloyDB cluster.

1. Delete Secret Manager Resources

In Cloud Shell, delete the secret created for the TypeSafe AI API key:

gcloud secrets delete typesafe-jev-api-key --quiet

2. Delete the AlloyDB Primary Instance and Cluster

Delete the primary instance first:

gcloud alloydb instances delete $ADBINSTANCE \
  --cluster=$ADBCLUSTER \
  --region=$REGION \
  --quiet

Once the instance deletion completes, delete the cluster:

gcloud alloydb clusters delete $ADBCLUSTER \
  --region=$REGION \
  --quiet

11. Congratulations

Congratulations! You have successfully provisioned a Google Cloud AlloyDB cluster and integrated it with TypeSafe AI System One (Jev).

What you've learned

  • How to establish Private Service Access (PSA) and provision an AlloyDB for PostgreSQL cluster and primary instance.
  • How to enable --outbound-public-ip so AlloyDB can reach external HTTPS SaaS APIs.
  • How to store third-party AI provider credentials securely in Google Secret Manager and bind them to AlloyDB.
  • How to write PL/pgSQL transformation functions that bridge standard PostgreSQL AI functions (ai.if and ai.analyze_sentiment) to external REST APIs.
  • How to register custom LLM endpoints in AlloyDB using google_ml.create_model.
  • How to execute both scalar and high-throughput batched semantic SQL queries directly in PostgreSQL.
  • How to analyze real customer product reviews for categorical sentiment.
  • How to register a generic model endpoint and evaluate continuous customer satisfaction scores (0..100) via google_ml.predict_row.

Next steps

Reference docs