diff --git a/infrastructure/00-authcheck.sh b/infrastructure/00-authcheck.sh new file mode 100644 index 0000000..0500ee7 --- /dev/null +++ b/infrastructure/00-authcheck.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# 00-authcheck.sh — Verify correct gcloud identity and project before any action +# Run in: local VS Code terminal OR Cloud Shell (identical behavior) + +set -euo pipefail + +PROJECT_ID="${PROJECT_ID:-propane-will-491900-m5}" +REGION="${REGION:-us-central1}" + +echo "=== OSVauco-NMTMD-GCOS :: Auth Check ===" + +# 1. Confirm gcloud is installed +command -v gcloud >/dev/null 2>&1 || { echo "ERROR: gcloud CLI not found. Install from https://cloud.google.com/sdk/docs/install"; exit 1; } + +# 2. Check active account +ACTIVE_ACCOUNT=$(gcloud config get-value account 2>/dev/null) +echo "Active account : $ACTIVE_ACCOUNT" +[ -z "$ACTIVE_ACCOUNT" ] && { echo "ERROR: No active account. Run: gcloud auth login"; exit 1; } + +# 3. Set and confirm project +gcloud config set project "$PROJECT_ID" --quiet +ACTIVE_PROJECT=$(gcloud config get-value project) +echo "Active project : $ACTIVE_PROJECT" +[ "$ACTIVE_PROJECT" != "$PROJECT_ID" ] && { echo "ERROR: Project mismatch. Expected $PROJECT_ID, got $ACTIVE_PROJECT"; exit 1; } + +# 4. Check Application Default Credentials +if ! gcloud auth application-default print-access-token >/dev/null 2>&1; then + echo "WARNING: No ADC found. Run: gcloud auth application-default login" +fi + +echo "" +echo "Auth check PASSED. Project: $PROJECT_ID | Region: $REGION" +echo "========================================" diff --git a/infrastructure/01-setupenv.sh b/infrastructure/01-setupenv.sh new file mode 100644 index 0000000..8b29867 --- /dev/null +++ b/infrastructure/01-setupenv.sh @@ -0,0 +1,96 @@ +#!/bin/bash +# 01-setupenv.sh — Enable APIs, create staging bucket with lifecycle, SA, billing budget +# Run in: local VS Code terminal OR Cloud Shell +# Source .env before running: source .env + +set -euo pipefail + +: "${PROJECT_ID:?Set PROJECT_ID in .env}" +: "${REGION:?Set REGION in .env}" +: "${BILLING_ACCOUNT_ID:?Set BILLING_ACCOUNT_ID in .env}" +: "${AGENT_SA:?Set AGENT_SA in .env}" + +BUCKET_NAME="gs://${PROJECT_ID}-agent-staging" +BUDGET_NAME="OSVauco-Agent-Budget-500USD" + +echo "=== 01: Environment Setup for ${PROJECT_ID} ===" + +# 0. Auth check +bash "$(dirname "$0")/00-authcheck.sh" + +# 1. Enable required APIs (idempotent) +echo "Enabling required APIs..." +gcloud services enable \ + aiplatform.googleapis.com \ + storage.googleapis.com \ + cloudbilling.googleapis.com \ + cloudresourcemanager.googleapis.com \ + iam.googleapis.com \ + run.googleapis.com \ + artifactregistry.googleapis.com \ + cloudbuild.googleapis.com \ + secretmanager.googleapis.com \ + monitoring.googleapis.com \ + logging.googleapis.com \ + cloudtrace.googleapis.com \ + --project="$PROJECT_ID" --quiet +echo "✓ APIs enabled." + +# 2. Create staging bucket (idempotent) +if gcloud storage ls "$BUCKET_NAME" >/dev/null 2>&1; then + echo "✓ Staging bucket $BUCKET_NAME already exists." +else + gcloud storage buckets create "$BUCKET_NAME" \ + --location="$REGION" --project="$PROJECT_ID" --quiet + echo "✓ Bucket created: $BUCKET_NAME" +fi + +# 3. Apply lifecycle rule (auto-delete objects >7 days) +cat > /tmp/lifecycle.json << 'EOF' +{"rule":[{"action":{"type":"Delete"},"condition":{"age":7}}]} +EOF +gcloud storage buckets update "$BUCKET_NAME" --lifecycle-file=/tmp/lifecycle.json --quiet +echo "✓ Lifecycle rule applied (delete after 7 days)." + +# 4. Service account (idempotent) +SA_NAME=$(echo "$AGENT_SA" | cut -d'@' -f1) +if ! gcloud iam service-accounts describe "$AGENT_SA" --project="$PROJECT_ID" >/dev/null 2>&1; then + gcloud iam service-accounts create "$SA_NAME" \ + --display-name="OSVauco Agent Runner SA" --project="$PROJECT_ID" --quiet + echo "✓ Service account created: $AGENT_SA" +fi + +for ROLE in \ + roles/aiplatform.user \ + roles/storage.objectAdmin \ + roles/logging.logWriter \ + roles/cloudtrace.agent \ + roles/monitoring.metricWriter \ + roles/secretmanager.secretAccessor \ + roles/run.invoker; do + gcloud projects add-iam-policy-binding "$PROJECT_ID" \ + --member="serviceAccount:${AGENT_SA}" --role="$ROLE" --quiet +done +echo "✓ IAM bindings configured." + +# 5. Billing budget alert (idempotent check by display name) +EXISTING=$(gcloud billing budgets list \ + --billing-account="${BILLING_ACCOUNT_ID}" \ + --filter="displayName=${BUDGET_NAME}" \ + --format="value(name)" 2>/dev/null | head -1 || true) + +if [[ -z "${EXISTING}" ]]; then + gcloud billing budgets create \ + --billing-account="${BILLING_ACCOUNT_ID}" \ + --display-name="${BUDGET_NAME}" \ + --budget-amount=500USD \ + --threshold-rule=percent=0.5,basis=CURRENT_SPEND \ + --threshold-rule=percent=0.8,basis=CURRENT_SPEND \ + --threshold-rule=percent=1.0,basis=CURRENT_SPEND + echo "✓ Budget alert created" +else + echo "✓ Budget alert already exists" +fi + +echo "" +echo "=== 01: Environment setup COMPLETE ===" diff --git a/infrastructure/02-deploy.sh b/infrastructure/02-deploy.sh new file mode 100644 index 0000000..c526216 --- /dev/null +++ b/infrastructure/02-deploy.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# 02-deploy.sh — Build and deploy orchestrator agent to Cloud Run +# Idempotent: updates existing service if present +# Source .env before running: source .env +set -euo pipefail + +: "${PROJECT_ID:?Set PROJECT_ID}" +: "${REGION:?Set REGION}" +: "${AGENT_SA:?Set AGENT_SA}" +: "${ARTIFACT_REPO:?Set ARTIFACT_REPO}" +: "${CLOUD_RUN_SERVICE:?Set CLOUD_RUN_SERVICE}" + +IMAGE="${REGION}-docker.pkg.dev/${PROJECT_ID}/${ARTIFACT_REPO}/${CLOUD_RUN_SERVICE}:latest" + +echo "=== 02: Building and deploying ${CLOUD_RUN_SERVICE} ===" + +bash "$(dirname "$0")/00-authcheck.sh" + +# 1. Ensure Artifact Registry repo exists +if ! gcloud artifacts repositories describe "${ARTIFACT_REPO}" \ + --location="${REGION}" --project="${PROJECT_ID}" >/dev/null 2>&1; then + gcloud artifacts repositories create "${ARTIFACT_REPO}" \ + --repository-format=docker \ + --location="${REGION}" \ + --project="${PROJECT_ID}" --quiet + echo "✓ Artifact Registry repo created: ${ARTIFACT_REPO}" +fi + +# 2. Configure Docker auth +gcloud auth configure-docker "${REGION}-docker.pkg.dev" --quiet + +# 3. Build image via Cloud Build (no local Docker required) +gcloud builds submit agents/core-logic \ + --tag="${IMAGE}" \ + --project="${PROJECT_ID}" \ + --quiet +echo "✓ Image built: ${IMAGE}" + +# 4. Deploy to Cloud Run +gcloud run deploy "${CLOUD_RUN_SERVICE}" \ + --image="${IMAGE}" \ + --platform=managed \ + --region="${REGION}" \ + --service-account="${AGENT_SA}" \ + --no-allow-unauthenticated \ + --min-instances=1 \ + --max-instances=10 \ + --concurrency=80 \ + --timeout=300s \ + --memory=1Gi \ + --cpu=1 \ + --set-env-vars="PROJECT_ID=${PROJECT_ID},REGION=${REGION}" \ + --labels="env=prod,team=osvaucoe,agent=orchestrator" \ + --quiet + +SERVICE_URL=$(gcloud run services describe "${CLOUD_RUN_SERVICE}" \ + --region="${REGION}" \ + --format="value(status.url)") + +echo "" +echo "=== 02: Deploy COMPLETE ===" +echo " Service URL: ${SERVICE_URL}" +echo " REMINDER: Run 03-teardown.sh at end of workday to stop billing." diff --git a/infrastructure/03-teardown.sh b/infrastructure/03-teardown.sh new file mode 100644 index 0000000..b9db7e4 --- /dev/null +++ b/infrastructure/03-teardown.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# 03-teardown.sh — Safely remove all deployed resources +# WARNING: Destructive. Requires explicit confirmation. +# Source .env before running: source .env +set -euo pipefail + +: "${PROJECT_ID:?Set PROJECT_ID}" +: "${REGION:?Set REGION}" +: "${CLOUD_RUN_SERVICE:?Set CLOUD_RUN_SERVICE}" +: "${ARTIFACT_REPO:?Set ARTIFACT_REPO}" +: "${AGENT_SA:?Set AGENT_SA}" + +echo "=== 03: TEARDOWN for project ${PROJECT_ID} ===" +echo "" +echo "WARNING: This will delete Cloud Run services, Artifact Registry images," +echo " and Service Account IAM bindings." +echo " GCS buckets and RAG corpora will NOT be deleted (to prevent data loss)." +echo "" +read -rp "Type 'DELETE' to confirm teardown: " CONFIRM +if [[ "${CONFIRM}" != "DELETE" ]]; then + echo "Teardown cancelled." + exit 0 +fi + +bash "$(dirname "$0")/00-authcheck.sh" + +# 1. Delete Cloud Run service +if gcloud run services describe "${CLOUD_RUN_SERVICE}" \ + --region="${REGION}" &>/dev/null; then + gcloud run services delete "${CLOUD_RUN_SERVICE}" \ + --region="${REGION}" --quiet + echo "✓ Deleted Cloud Run service: ${CLOUD_RUN_SERVICE}" +fi + +# 2. Delete Agent Runtime instances (if any) +AGENTS=$(gcloud ai agent-engines list \ + --project="${PROJECT_ID}" --region="${REGION}" \ + --format="value(name)" 2>/dev/null || echo "") +if [ -n "$AGENTS" ]; then + while IFS= read -r AGENT_NAME; do + gcloud ai agent-engines delete "$AGENT_NAME" \ + --project="${PROJECT_ID}" --region="${REGION}" --quiet + echo "✓ Deleted agent runtime: $AGENT_NAME" + done <<< "$AGENTS" +fi + +# 3. Delete container images from Artifact Registry +gcloud artifacts docker images delete \ + "${REGION}-docker.pkg.dev/${PROJECT_ID}/${ARTIFACT_REPO}/${CLOUD_RUN_SERVICE}" \ + --delete-tags --quiet 2>/dev/null || echo " (no images found)" +echo "✓ Artifact Registry images cleaned" + +# 4. Remove IAM bindings +ROLES=( + roles/aiplatform.user + roles/run.invoker + roles/secretmanager.secretAccessor + roles/cloudtrace.agent + roles/logging.logWriter + roles/monitoring.metricWriter + roles/storage.objectAdmin +) +for ROLE in "${ROLES[@]}"; do + gcloud projects remove-iam-policy-binding "${PROJECT_ID}" \ + --member="serviceAccount:${AGENT_SA}" \ + --role="${ROLE}" --quiet 2>/dev/null || true +done +echo "✓ IAM bindings removed" + +echo "" +echo "=== 03: Teardown COMPLETE ===" +echo "NOTE: GCS buckets and RAG corpora were NOT deleted. Remove manually if needed." diff --git a/infrastructure/04-observability-setup.sh b/infrastructure/04-observability-setup.sh new file mode 100644 index 0000000..906fef1 --- /dev/null +++ b/infrastructure/04-observability-setup.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +# 04-observability-setup.sh — Configure Cloud Monitoring, alerting, uptime checks +# Idempotent +# Source .env before running: source .env +set -euo pipefail + +: "${PROJECT_ID:?Set PROJECT_ID}" +: "${REGION:?Set REGION}" +: "${CLOUD_RUN_SERVICE:?Set CLOUD_RUN_SERVICE}" +: "${ALERT_EMAIL:?Set ALERT_EMAIL}" + +echo "=== 04: Setting up observability for ${PROJECT_ID} ===" + +bash "$(dirname "$0")/00-authcheck.sh" + +gcloud services enable \ + monitoring.googleapis.com \ + logging.googleapis.com \ + cloudtrace.googleapis.com \ + --project="${PROJECT_ID}" --quiet +echo "✓ Monitoring APIs enabled" + +# 1. Notification channel (email) +CHANNEL_FILE=$(gcloud alpha monitoring channels list \ + --filter="type=email AND labels.email_address=${ALERT_EMAIL}" \ + --format="value(name)" --project="${PROJECT_ID}" 2>/dev/null | head -1 || true) + +if [[ -z "${CHANNEL_FILE}" ]]; then + cat > /tmp/channel.json << EOF +{ + "type": "email", + "displayName": "OSVauco Alert Email", + "labels": { "email_address": "${ALERT_EMAIL}" } +} +EOF + NOTIFICATION_CHANNEL=$(gcloud alpha monitoring channels create \ + --channel-content-from-file=/tmp/channel.json \ + --project="${PROJECT_ID}" \ + --format="value(name)") + echo "✓ Notification channel created: ${NOTIFICATION_CHANNEL}" +else + NOTIFICATION_CHANNEL="${CHANNEL_FILE}" + echo "✓ Notification channel already exists: ${NOTIFICATION_CHANNEL}" +fi + +# 2. Create log-based metric for agent errors +if ! gcloud logging metrics describe agent-error-count \ + --project="${PROJECT_ID}" >/dev/null 2>&1; then + gcloud logging metrics create agent-error-count \ + --description="Count of ERROR severity logs from agent" \ + --log-filter="resource.type=\"cloud_run_revision\" severity=ERROR" \ + --project="${PROJECT_ID}" + echo "✓ Log-based metric 'agent-error-count' created" +else + echo "✓ Log-based metric already exists" +fi + +# 3. Uptime check for Cloud Run service +SERVICE_URL=$(gcloud run services describe "${CLOUD_RUN_SERVICE}" \ + --region="${REGION}" \ + --format="value(status.url)" 2>/dev/null || echo "") + +if [[ -n "${SERVICE_URL}" ]]; then + HOST=$(echo "${SERVICE_URL}" | sed 's|https://||') + gcloud monitoring uptime create \ + --display-name="${CLOUD_RUN_SERVICE}-uptime" \ + --protocol=HTTPS \ + --request-path="/health" \ + --port=443 \ + --monitored-resource-type=uptime_url \ + --project="${PROJECT_ID}" \ + --hostname="${HOST}" 2>/dev/null || true + echo "✓ Uptime check configured for ${SERVICE_URL}" +fi + +echo "" +echo "=== 04: Observability setup COMPLETE ===" +echo " Dashboard: https://console.cloud.google.com/monitoring?project=${PROJECT_ID}" diff --git a/infrastructure/05-cloudrun-deploy.sh b/infrastructure/05-cloudrun-deploy.sh new file mode 100644 index 0000000..b7553f0 --- /dev/null +++ b/infrastructure/05-cloudrun-deploy.sh @@ -0,0 +1,68 @@ +#!/bin/bash +# 05-cloudrun-deploy.sh — Deploy ADK agent to Cloud Run using `adk deploy cloud_run` +# Run in: local VS Code terminal OR Cloud Shell +# Requires: ADK installed (pip install google-adk), gcloud CLI +# Source .env before running: source .env + +set -euo pipefail + +: "${PROJECT_ID:?Set PROJECT_ID}" +: "${REGION:?Set REGION}" +: "${CLOUD_RUN_SERVICE:?Set CLOUD_RUN_SERVICE}" +: "${AGENT_SA:?Set AGENT_SA}" + +APP_NAME="oavauco_root" +AGENT_PATH="agents/core-logic" + +echo "=== 05: Cloud Run Deploy via ADK CLI ===" + +bash "$(dirname "$0")/00-authcheck.sh" + +gcloud services enable \ + run.googleapis.com \ + artifactregistry.googleapis.com \ + cloudbuild.googleapis.com \ + --project="${PROJECT_ID}" --quiet + +# Grant compute SA permission to use Cloud Build +PROJECT_NUMBER=$(gcloud projects describe "${PROJECT_ID}" --format="value(projectNumber)") +COMPUTE_SA="${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" + +gcloud projects add-iam-policy-binding "${PROJECT_ID}" \ + --member="serviceAccount:${COMPUTE_SA}" \ + --role="roles/cloudbuild.builds.builder" --quiet +gcloud projects add-iam-policy-binding "${PROJECT_ID}" \ + --member="serviceAccount:${COMPUTE_SA}" \ + --role="roles/secretmanager.secretAccessor" --quiet +echo "✓ IAM bindings for Cloud Build SA applied" + +export GOOGLE_CLOUD_PROJECT="${PROJECT_ID}" +export GOOGLE_CLOUD_LOCATION="${REGION}" +export GOOGLE_GENAI_USE_VERTEXAI="True" + +# Deploy via ADK CLI +# --with_ui includes the ADK dev UI (remove for API-only production deployments) +# --no-allow-unauthenticated requires an identity token to call the service +echo "Deploying ${CLOUD_RUN_SERVICE} to Cloud Run in ${REGION}..." +adk deploy cloud_run \ + --project="${PROJECT_ID}" \ + --region="${REGION}" \ + --service_name="${CLOUD_RUN_SERVICE}" \ + --app_name="${APP_NAME}" \ + "${AGENT_PATH}" \ + -- --no-allow-unauthenticated + +echo "" +echo "=== 05: Cloud Run Deploy COMPLETE ===" +SERVICE_URL=$(gcloud run services describe "${CLOUD_RUN_SERVICE}" \ + --region="${REGION}" --project="${PROJECT_ID}" \ + --format="value(status.url)" 2>/dev/null || echo "(pending)") +echo " Service URL: ${SERVICE_URL}" +echo "" +echo " To call (authenticated):" +echo " TOKEN=\$(gcloud auth print-identity-token)" +echo " curl -H \"Authorization: Bearer \$TOKEN\" -H 'Content-Type: application/json' \\" +echo " -d '{\"message\": \"Hello\"}' ${SERVICE_URL}/run" +echo "" +echo " COST NOTE: Cloud Run scales to 0. No idle cost." +echo " Delete with: gcloud run services delete ${CLOUD_RUN_SERVICE} --region=${REGION} --quiet" diff --git a/infrastructure/06-cicd-setup.sh b/infrastructure/06-cicd-setup.sh new file mode 100644 index 0000000..8ec0844 --- /dev/null +++ b/infrastructure/06-cicd-setup.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# 06-cicd-setup.sh — Set up Cloud Build trigger + Artifact Registry for CI/CD +# Prerequisites: GitHub repo connected to Cloud Build via GCP Console FIRST: +# https://console.cloud.google.com/cloud-build/triggers/connect +# Source .env before running: source .env +set -euo pipefail + +: "${PROJECT_ID:?Set PROJECT_ID}" +: "${REGION:?Set REGION}" +: "${GITHUB_OWNER:?Set GITHUB_OWNER}" +: "${GITHUB_REPO:?Set GITHUB_REPO}" +: "${ARTIFACT_REPO:?Set ARTIFACT_REPO}" +: "${CLOUD_RUN_SERVICE:?Set CLOUD_RUN_SERVICE}" + +BRANCH_PATTERN="^main$" + +echo "=== 06: CI/CD Setup for ${PROJECT_ID} ===" + +bash "$(dirname "$0")/00-authcheck.sh" + +gcloud services enable \ + cloudbuild.googleapis.com \ + artifactregistry.googleapis.com \ + run.googleapis.com \ + --project="${PROJECT_ID}" --quiet + +# Create Artifact Registry repo (idempotent) +if ! gcloud artifacts repositories describe "${ARTIFACT_REPO}" \ + --location="${REGION}" --project="${PROJECT_ID}" >/dev/null 2>&1; then + gcloud artifacts repositories create "${ARTIFACT_REPO}" \ + --repository-format=docker \ + --location="${REGION}" \ + --project="${PROJECT_ID}" --quiet + echo "✓ Artifact Registry repo created: ${ARTIFACT_REPO}" +else + echo "✓ Artifact Registry repo already exists" +fi + +gcloud auth configure-docker "${REGION}-docker.pkg.dev" --quiet + +# Grant Cloud Build SA permissions +PROJECT_NUMBER=$(gcloud projects describe "${PROJECT_ID}" --format="value(projectNumber)") +CB_SA="${PROJECT_NUMBER}@cloudbuild.gserviceaccount.com" + +for ROLE in \ + roles/run.admin \ + roles/iam.serviceAccountUser \ + roles/artifactregistry.writer \ + roles/secretmanager.secretAccessor; do + gcloud projects add-iam-policy-binding "${PROJECT_ID}" \ + --member="serviceAccount:${CB_SA}" --role="${ROLE}" --quiet +done +echo "✓ Cloud Build SA IAM bindings applied" + +# Create Cloud Build trigger +TRIGGER_NAME="${CLOUD_RUN_SERVICE}-main-trigger" +EXISTING=$(gcloud builds triggers list \ + --filter="name=${TRIGGER_NAME}" \ + --format="value(name)" 2>/dev/null | head -1 || true) + +if [[ -z "${EXISTING}" ]]; then + gcloud builds triggers create github \ + --name="${TRIGGER_NAME}" \ + --repo-owner="${GITHUB_OWNER}" \ + --repo-name="${GITHUB_REPO}" \ + --branch-pattern="${BRANCH_PATTERN}" \ + --build-config="cloudbuild.yaml" \ + --project="${PROJECT_ID}" \ + --quiet + echo "✓ Cloud Build trigger created: ${TRIGGER_NAME}" +else + echo "✓ Trigger already exists: ${TRIGGER_NAME}" +fi + +echo "" +echo "=== 06: CI/CD Setup COMPLETE ===" +echo " NOTE: GitHub connection must be pre-authorized at:" +echo " https://console.cloud.google.com/cloud-build/triggers/connect?project=${PROJECT_ID}" +echo " After first deploy, switch traffic manually:" +echo " gcloud run services update-traffic ${CLOUD_RUN_SERVICE} --to-latest --region=${REGION}" diff --git a/infrastructure/07-rag-setup.sh b/infrastructure/07-rag-setup.sh new file mode 100644 index 0000000..3949a09 --- /dev/null +++ b/infrastructure/07-rag-setup.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +# 07-rag-setup.sh — Create Vertex AI RAG Engine corpus and upload initial documents +# Idempotent +# Source: Vertex AI RAG Engine SDK (google-cloud-aiplatform >= 1.87) +# NOTE: RAG Engine in us-central1 requires allowlist access. +# Contact: vertex-ai-rag-engine-support@google.com +# Alternative: set REGION=us-east1 or us-east4 for immediate access. +# Source .env before running: source .env +set -euo pipefail + +: "${PROJECT_ID:?Set PROJECT_ID}" +: "${REGION:?Set REGION}" +: "${RAG_CORPUS_DISPLAY_NAME:?Set RAG_CORPUS_DISPLAY_NAME}" + +echo "=== 07: Setting up Vertex AI RAG Engine ===" + +bash "$(dirname "$0")/00-authcheck.sh" +gcloud services enable aiplatform.googleapis.com --quiet + +# Create GCS bucket for corpus source documents (idempotent) +CORPUS_BUCKET="${PROJECT_ID}-agent-corpus" +if ! gsutil ls -b "gs://${CORPUS_BUCKET}" &>/dev/null; then + gsutil mb -l "${REGION}" -b on "gs://${CORPUS_BUCKET}" + echo "✓ GCS corpus bucket created: gs://${CORPUS_BUCKET}" +else + echo "✓ GCS corpus bucket exists: gs://${CORPUS_BUCKET}" +fi + +# Upload seed documents if present +if [[ -d "docs/corpus-seed" ]]; then + gsutil -m cp docs/corpus-seed/*.md "gs://${CORPUS_BUCKET}/seed/" 2>/dev/null || true + echo "✓ Seed documents uploaded to gs://${CORPUS_BUCKET}/seed/" +fi + +# Run Python to create/update corpus +python3 - << PYEOF +import os, sys +try: + import vertexai + from vertexai.preview import rag +except ImportError: + print("ERROR: google-cloud-aiplatform not installed. Run: pip install google-cloud-aiplatform>=1.87.0") + sys.exit(1) + +PROJECT_ID = os.environ["PROJECT_ID"] +REGION = os.environ["REGION"] +CORPUS_DISPLAY_NAME = os.environ["RAG_CORPUS_DISPLAY_NAME"] +CORPUS_BUCKET = f"{PROJECT_ID}-agent-corpus" + +vertexai.init(project=PROJECT_ID, location=REGION) + +corpus = None +for c in rag.list_corpora(): + if c.display_name == CORPUS_DISPLAY_NAME: + corpus = c + print(f"✓ RAG corpus already exists: {c.name}") + break + +if corpus is None: + embedding_config = rag.EmbeddingModelConfig( + publisher_model="publishers/google/models/text-embedding-005" + ) + corpus = rag.create_corpus( + display_name=CORPUS_DISPLAY_NAME, + embedding_model_config=embedding_config, + ) + print(f"✓ RAG corpus created: {corpus.name}") + +gcs_uri = f"gs://{CORPUS_BUCKET}/seed/" +try: + rag.import_files( + corpus_name=corpus.name, + paths=[gcs_uri], + chunk_size=512, + chunk_overlap=50, + max_embedding_requests_per_min=900, + ) + print(f"✓ Documents imported from {gcs_uri}") +except Exception as e: + print(f" WARNING: Document import skipped or failed: {e}") + print(" Import manually via: https://console.cloud.google.com/vertex-ai/rag") + +with open("/tmp/rag_corpus_name.txt", "w") as f: + f.write(corpus.name) +print(f"\n Corpus resource name: {corpus.name}") +print(f" Add to .env: RAG_CORPUS_NAME={corpus.name}") +PYEOF + +echo "" +echo "=== 07: RAG Engine setup COMPLETE ===" +echo " View corpus: https://console.cloud.google.com/vertex-ai/rag?project=${PROJECT_ID}" diff --git a/infrastructure/08-memorybank-setup.sh b/infrastructure/08-memorybank-setup.sh new file mode 100644 index 0000000..d7caf56 --- /dev/null +++ b/infrastructure/08-memorybank-setup.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# 08-memorybank-setup.sh — Create Vertex AI Agent Platform Memory Bank instance +# Idempotent +# Requires: google-cloud-aiplatform >= 1.111.0 +# UNKNOWN: gcloud CLI equivalent for client.agent_engines.create() — SDK-only (OQ-01) +# Source .env before running: source .env +set -euo pipefail + +: "${PROJECT_ID:?Set PROJECT_ID}" +: "${REGION:?Set REGION}" +: "${AGENT_SA:?Set AGENT_SA}" + +MEMORY_INSTANCE_DISPLAY_NAME="${MEMORY_INSTANCE_DISPLAY_NAME:-${PROJECT_ID}-memory-bank}" + +echo "=== 08: Setting up Vertex AI Memory Bank ===" + +bash "$(dirname "$0")/00-authcheck.sh" +gcloud services enable aiplatform.googleapis.com --quiet + +# Run Python to create/update Agent Engine instance with Memory Bank +python3 - << PYEOF +import os, sys +try: + import vertexai + from vertexai.preview import agent_engines +except ImportError: + print("ERROR: google-cloud-aiplatform >= 1.111.0 required.") + print(" Run: pip install 'google-cloud-aiplatform>=1.111.0'") + sys.exit(1) + +PROJECT_ID = os.environ["PROJECT_ID"] +REGION = os.environ["REGION"] +DISPLAY_NAME = os.environ.get("MEMORY_INSTANCE_DISPLAY_NAME", f"{PROJECT_ID}-memory-bank") + +vertexai.init(project=PROJECT_ID, location=REGION) +client = vertexai.Client(project=PROJECT_ID, location=REGION) + +existing = list(client.agent_engines.list()) +instance = None +for eng in existing: + if hasattr(eng, "display_name") and eng.display_name == DISPLAY_NAME: + instance = eng + print(f"✓ Memory Bank instance already exists: {eng.api_resource.name}") + break + +if instance is None: + memory_bank_config = { + "memories_ttl_days": 30, + "generate_memory_config": { + "trigger_config": {"trigger_type": "ON_SESSION_END"} + }, + "similarity_search_config": {"top_k": 5} + } + instance = client.agent_engines.create( + display_name=DISPLAY_NAME, + spec={"context_spec": {"memory_bank_config": memory_bank_config}} + ) + print(f"✓ Memory Bank instance created: {instance.api_resource.name}") + +instance_name = instance.api_resource.name +with open("/tmp/memory_bank_instance.txt", "w") as f: + f.write(instance_name) +print(f"\n Instance resource name: {instance_name}") +print(f" Add to .env: MEMORY_BANK_INSTANCE={instance_name}") +PYEOF + +# Grant agent SA memory permissions +gcloud projects add-iam-policy-binding "${PROJECT_ID}" \ + --member="serviceAccount:${AGENT_SA}" \ + --role="roles/aiplatform.user" \ + --quiet 2>/dev/null || true +echo "✓ roles/aiplatform.user granted to ${AGENT_SA}" + +echo "" +echo "=== 08: Memory Bank setup COMPLETE ===" +echo " View instances: https://console.cloud.google.com/vertex-ai/agents?project=${PROJECT_ID}"