고속 AI 쿼리를 위해 AlloyDB AI 함수와 함께 TypeSafe AI의 Jev 모델 사용

1. 소개

이 Codelab에서는 AlloyDB의 기본 AI 함수 (ai.if, ai.analyze_sentiment, ml_predict_row)를 사용하여 TypeSafe AI의 Jev 모델을 AlloyDB AI에 연결합니다. 이렇게 하면 데이터를 이동하거나 복잡한 애플리케이션 측 코드를 작성하지 않고도 AlloyDB의 운영 데이터에서 직접 Jev 모델을 사용하여 효율적인 일괄 추론을 실행할 수 있습니다.

실습할 내용

  • 비공개 서비스 액세스 (PSA), PostgreSQL용 AlloyDB 클러스터, 기본 인스턴스 프로비저닝
  • Cloudshell 및 내부 연결을 허용하는 VPC 방화벽 규칙 만들기
  • gcloud alloydb clusters import을 사용하여 이커머스 샘플 데이터 세트 로드
  • Google Secret Manager 및 AlloyDB의 google_ml_integration 확장 프로그램 사용 설정
  • Secret Manager에 TypeSafe AI API 키를 저장하고 액세스 권한 부여
  • Jev의 noul (불리언) 및 choice (감정) 기본 요소에 대한 PL/pgSQL 입력 및 출력 변환 함수 만들기
  • AlloyDB에 맞춤 jev-model 모델 엔드포인트 등록
  • SQL에서 직접 스칼라 및 배열 일괄 처리된 시맨틱 쿼리 (ai.if 및 ai.analyze_sentiment) 실행
  • ai.analyze_sentiment를 사용하여 실제 고객 제품 리뷰에서 감정 분류를 쿼리합니다.
  • (보너스) 일반 엔드포인트 (jev-systemone)를 등록하고 Jev의 score 기본 요소를 사용하여 지속적인 고객 만족도 강도 평가

필요한 항목

  • 웹브라우저(예: Chrome)
  • 결제가 사용 설정된 Google Cloud 프로젝트
  • TypeSafe AI API 키 (TypeSafe AI에서 가져옴)

2. 시작하기 전에

Google Cloud 프로젝트 선택

Google Cloud 콘솔에서 사용할 Google Cloud 프로젝트를 선택하거나 만듭니다.

Cloud Shell 시작

Google Cloud 콘솔의 오른쪽 상단에서 Cloud Shell 활성화를 클릭합니다.

Cloud Shell 세션이 인증되었는지 확인합니다.

gcloud auth list

프로젝트 ID와 환경 변수를 설정합니다.

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"

필수 Google Cloud API 사용 설정

다음 명령어를 실행하여 필요한 모든 Google Cloud 서비스 API를 사용 설정합니다.

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

3. AlloyDB 클러스터 및 기본 인스턴스 프로비저닝

이 단계에서는 VPC 네트워크에 비공개 서비스 액세스 IP 범위를 할당하고, 비공개 피어링을 설정하고, 아웃바운드 인터넷 액세스를 사용하여 AlloyDB 클러스터와 기본 인스턴스를 프로비저닝합니다.

1. 비공개 서비스 액세스 (PSA) IP 범위 만들기

AlloyDB에는 가상 프라이빗 클라우드 (VPC) 네트워크의 비공개 IP 범위 할당이 필요합니다. default VPC 네트워크를 사용한다고 가정합니다.

비공개 IP 범위 할당을 만듭니다.

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

비공개 VPC 피어링 연결을 설정합니다.

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

2. AlloyDB 클러스터 만들기

초기 postgres 관리 권한 사용자의 보안 데이터베이스 비밀번호를 생성합니다.

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

AlloyDB 무료 체험판 클러스터를 만듭니다.

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

3. AI 플래그 및 아웃바운드 연결로 기본 인스턴스 만들기

AlloyDB는 아웃바운드 HTTPS를 통해 TypeSafe AI의 보안 SaaS 엔드포인트 (https://api.typesafe.ai/v1/systemone)에 직접 연결됩니다. 따라서 --outbound-public-ip를 지정해야 합니다. 또한 환경에 맞게 AlloyDB AI 데이터베이스 플래그를 구성합니다.

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. 샘플 전자상거래 데이터 로드

실제와 유사한 시맨틱 쿼리를 테스트하려면 제품 카탈로그와 고객 리뷰가 포함된 샘플 이커머스 데이터 세트를 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. Secret Manager에 TypeSafe AI API 키 저장

AlloyDB의 모델 엔드포인트 관리는 데이터베이스 코드나 구성 로그에 일반 텍스트 사용자 인증 정보를 노출하지 않고 외부 API 호출을 인증하기 위해 Google Secret Manager와 기본적으로 통합됩니다.

1. 보안 비밀 만들기

Cloud Shell에서 typesafe-jev-api-key라는 새 비밀을 만듭니다.

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

TypeSafe AI API 키를 보안 비밀 버전으로 추가합니다.

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

2. AlloyDB 서비스 에이전트에 대한 액세스 권한 부여

AlloyDB는 쿼리 실행 중에 프로젝트 서비스 에이전트를 사용하여 보안 비밀을 가져옵니다. 프로젝트 번호를 찾아 보안 비밀 접근자 역할을 부여합니다.

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"

서비스 에이전트가 roles/secretmanager.secretAccessor를 수신했음을 확인하는 출력이 표시됩니다.

5. AlloyDB Studio를 통해 AlloyDB에 연결하고 데이터베이스 확장 프로그램 초기화

로컬 클라이언트 도구나 프록시 터널을 구성하지 않고 브라우저에서 직접 AlloyDB 데이터베이스와 상호작용하려면 Google Cloud 콘솔에서 AlloyDB Studio를 사용하세요.

1. AlloyDB Studio 열기

  1. Google Cloud 콘솔에서 PostgreSQL용 AlloyDB > 클러스터로 이동합니다.
  2. 클러스터 (my-alloydb-cluster)를 클릭합니다.
  3. 왼쪽 탐색 메뉴에서 AlloyDB Studio를 클릭합니다.
  4. 데이터베이스에 인증합니다.
    • Database: postgres
    • 사용자: postgres
    • 비밀번호: 클러스터 생성 중에 생성된 $PGPASSWORD을 입력합니다 (예: Cloud Shell에서 echo $PGPASSWORD을 실행하여 확인).
  5. 인증을 클릭합니다.

이제 AlloyDB Studio 대화형 SQL 쿼리 편집기가 표시됩니다.

2. 데이터베이스 확장 프로그램 및 플래그 사용 설정

AlloyDB Studio 쿼리 편집기에서 다음 문을 붙여넣고 실행하여 google_ml_integration 확장 프로그램을 사용 설정하고 런타임 설정을 구성합니다.

-- 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';

실행을 클릭하여 쿼리를 실행합니다.

3. AlloyDB MEM에 보안 비밀 등록

TypeSafe AI API 키를 찾을 위치를 AlloyDB에 알려줍니다.

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. SQL 변환 함수 만들기

TypeSafe AI의 System One API는 명확한 추론 기본 요소 (불리언 제약 조건의 경우 noul, 다중 클래스 분류의 경우 choice)가 포함된 구조화된 JSON을 허용합니다.

AlloyDB의 모델 엔드포인트 관리에서는 4가지 변환 함수 (스칼라 입력, 스칼라 출력, 배치 입력, 배치 출력)를 사용하여 표준 PostgreSQL ai.* 함수 서명을 Jev REST API 형식으로 연결합니다.

1. 스칼라 입력 변환

단일 행 ai.if 및 ai.analyze_sentiment 호출을 Jev 요청 페이로드로 변환하는 다음 함수를 추가합니다.

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. 스칼라 출력 변환

Jev의 JSON 응답을 PostgreSQL TEXT 결과로 다시 압축 해제하는 함수를 추가합니다.

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. 일괄 입력 변환

Jev는 생성 품질 저하 없이 단일 HTTP 왕복 내에서 수십 개의 질문을 동시에 평가할 수 있습니다.

PostgreSQL prompts TEXT[] 배열을 병렬 q_1 .. q_N Jev 질문으로 변환하는 배치 입력 변환을 추가합니다.

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. 일괄 출력 변환

적절한 1..N 배열 색인 순서로 답변을 추출하는 함수를 추가합니다.

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. AlloyDB에 맞춤 모델 엔드포인트 등록

이제 변환 함수, 비밀 사용자 인증 정보, TypeSafe AI REST 엔드포인트를 jev-model이라는 등록된 모델에 함께 바인딩합니다.

1. jev-model 등록

AlloyDB Studio 쿼리 편집기에서 다음을 붙여넣고 실행합니다.

-- 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. 등록 확인

모델 레지스트리 뷰를 쿼리하여 jev-model가 활성 상태이고 인식되는지 확인합니다.

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

다음과 비슷한 출력이 표시됩니다.

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

8. Jev에 대해 테스트 쿼리 실행

jev-model가 등록되면 이제 표준 PostgreSQL 쿼리 내에서 직접 시맨틱 작업을 실행할 수 있습니다.

1. 스칼라 불리언 필터링 테스트 (ai.if)

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;

다음과 비슷한 출력이 표시됩니다.

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

2. 스칼라 감정 분석 테스트 (ai.analyze_sentiment)

고객 의견을 범주형 라벨 (positive, negative 또는 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;

다음과 비슷한 출력이 표시됩니다.

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

3. 배열 일괄 불리언 필터링 테스트

프롬프트 배열을 전달하여 단일 쿼리에서 여러 항목을 평가합니다.

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;

다음과 비슷한 출력이 표시됩니다.

                             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. 배열 일괄 처리를 사용하여 테이블 쿼리 테스트

이제 클러스터 프로비저닝 중에 이전에 로드된 ecomm.products 테이블을 쿼리하여 상품 일괄에 ai.if를 사용하여 카탈로그의 제품 중 추운 날씨에 적합한 제품을 확인합니다.

테이블 데이터에 대해 처리량이 높은 의미론적 필터링 (예: 배치당 50개 행)을 실행하려면 PostgreSQL의 row_number() 윈도우 함수를 사용하여 행을 배치로 청크화하세요.

-- 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;

다음과 비슷한 출력이 표시됩니다.

  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. 고객 제품 리뷰에서 감정 쿼리

이제 Jev의 choice 기본 요소로 지원되는 ai.analyze_sentiment을 사용하여 ecomm.product_reviews 테이블의 고객 리뷰를 분석합니다.

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;

다음과 비슷한 출력이 표시됩니다.

 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. 보너스: Jev score를 사용한 지속적인 감정 점수 매기기

AlloyDB의 AI 함수(예: ai.analyze_sentiment)는 범주형 버킷(positive, negative, neutral)을 반환하는 반면, TypeSafe AI의 score 기본 요소는 정렬된 기준표 전반의 미묘한 차이를 평가하고 0~100의 연속적인 기대치 가중 평점을 반환합니다.

score은 텍스트 분류 라벨이 아닌 숫자 메타데이터를 반환하므로 커스텀 generic 모델 엔드포인트 (jev-systemone)를 등록하고 google_ml.predict_row를 사용하여 호출할 수 있습니다.

1. 일반 엔드포인트 등록

AlloyDB Studio에서 model_type => 'generic'을 사용하여 jev-systemone를 등록합니다.

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. 다중 작업 평가 실행 (불리언 + 연속 점수)

단일 HTTP 호출에서 결함 불리언 검사 (noul)와 연속 감정 강도 (score)를 동시에 평가합니다.

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;

다음과 비슷한 출력이 표시됩니다 (결과는 다를 수 있음).

{
  "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
      }
    }
  }
}

satisfaction_score가 11 / 100로 평가되어 고객이 천이 부드럽다고 인정했음에도 불구하고 대부분 부정적인 경험을 했다는 점을 정확하게 포착합니다.

10. 삭제

이 Codelab에서 만든 리소스에 대한 비용이 계속 청구되지 않도록 하려면 비밀과 데이터베이스 객체를 정리하고 AlloyDB 클러스터를 삭제하세요.

1. Secret Manager 리소스 삭제

Cloud Shell에서 TypeSafe AI API 키에 대해 생성된 보안 비밀을 삭제합니다.

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

2. AlloyDB 기본 인스턴스 및 클러스터 삭제

먼저 기본 인스턴스를 삭제합니다.

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

인스턴스 삭제가 완료되면 클러스터를 삭제합니다.

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

11. 마무리

수고하셨습니다 Google Cloud AlloyDB 클러스터를 프로비저닝하고 TypeSafe AI System One (Jev)과 통합했습니다.

학습한 내용

  • 비공개 서비스 액세스 (PSA)를 설정하고 PostgreSQL용 AlloyDB 클러스터와 기본 인스턴스를 프로비저닝하는 방법
  • AlloyDB가 외부 HTTPS SaaS API에 도달할 수 있도록 --outbound-public-ip를 사용 설정하는 방법
  • Google Secret Manager에 서드 파티 AI 제공업체 사용자 인증 정보를 안전하게 저장하고 AlloyDB에 바인딩하는 방법
  • 표준 PostgreSQL AI 함수 (ai.if 및 ai.analyze_sentiment)를 외부 REST API에 연결하는 PL/pgSQL 변환 함수를 작성하는 방법
  • google_ml.create_model를 사용하여 AlloyDB에 맞춤 LLM 엔드포인트를 등록하는 방법
  • PostgreSQL에서 스칼라 및 고처리량 일괄 시맨틱 SQL 쿼리를 직접 실행하는 방법
  • 실제 고객 제품 리뷰를 분석하여 범주별 감정을 파악하는 방법
  • 일반 모델 엔드포인트를 등록하고 google_ml.predict_row을 통해 지속적인 고객 만족도 점수 (0..100)를 평가하는 방법

다음 단계

  • AlloyDB AI 문서에서 AlloyDB AI 함수의 기능을 자세히 알아보세요.
  • TypeSafe AI API 참조를 읽고 Noul 제약 조건 로직 및 감정 점수에 대해 자세히 알아보세요.

참조 문서