1. 소개
Google Workspace Studio를 사용하면 Gmail, Google Chat, Google Drive, Google Sheets와 같은 Google Workspace 애플리케이션 전반에서 비즈니스 프로세스를 자동화할 수 있습니다. 워크플로를 외부 웹 애플리케이션에서 시작하거나 자체 클라우드 환경에서 맞춤 로직을 실행해야 하는 경우 Cloud Run의 Node.js에서 HTTP Google Workspace 부가기능을 사용하여 Google Workspace Studio를 확장할 수 있습니다.
- 맞춤 시작 단계 (
workflowTrigger): 고객이 내부 웹 포털에서 티켓을 제출하는 등 외부 이벤트가 발생하면 Google Workspace Studiotriggers.fireREST API를 호출하여 Google Workspace Studio 흐름을 시작합니다. - 맞춤 액션 단계 (
workflowAction): 플로우의 이전 단계에서 변수를 수신하고, Node.js 서비스에서 맞춤 로직 또는 AI 추론을 실행하고, 출력 변수를 Google Workspace Studio로 다시 반환합니다.
이 Codelab에서는 Cloud Run에서 Google Workspace Studio용 완전한 Node.js HTTP 확장 프로그램을 빌드하고 배포하고, Cloud Firestore를 사용하여 컨테이너 버전 전반에서 활성 플로우 트리거 구독과 제출된 티켓을 저장하고, 복원력 있는 키워드 일치 대체를 사용하여 Gemini Enterprise Agent Platform의 Gemini 3.8 Flash (gemini-3.8-flash)를 사용하여 맞춤 액션 단계에서 티켓 긴급도를 분류합니다.
빌드할 항목
- Cloud Run에서 Node.js, Express, Firestore로 빌드된 지원 빠른 포털 웹 앱 및 HTTP Google Workspace 부가기능
studio_triggersFirestore 컬렉션에서 활성 흐름 구독을 유지하고,support_ticketsFirestore 컬렉션에 제출된 티켓을 기록하고, 티켓이 웹 포털에 제출될 때마다 실행되어ticketTitle및ticketDescription을 Google Workspace Studio 흐름에 전달하는 새 지원 티켓 맞춤 시작 단계 (supportTicketTrigger)- Gemini Enterprise Agent Platform (
aiplatform.googleapis.com)에서 Gemini 3.8 Flash (gemini-3.8-flash)를 사용하여 티켓 긴급성을 분류하는 긴급성 감지 맞춤 액션 단계 (urgencyDetectorStep)로, 키워드 검색 유형 (urgent,blocker,critical)으로 자동 대체됩니다.
학습할 내용
workflowTrigger및workflowAction요소로 HTTP Google Workspace 부가기능 배포 매니페스트 (deployment.json)를 구성하는 방법studio_triggersFirestore 컬렉션에 Google Workspace Studio 트리거 구독 (triggerId,notifyUri,userOAuthToken)을 유지하고triggers.fireREST API를 사용하여 흐름을 실행하는 방법- 키워드 대체와 함께 Gemini Enterprise Agent Platform에서 Gemini 3.8 Flash (
gemini-3.8-flash) 구조화된 출력을 사용하여 Node.js에서workflowAction맞춤 단계를 빌드하는 방법
기본 요건
- Google Workspace Studio에 액세스할 수 있는 Google Workspace 계정
- 테스트를 위해 게시되지 않은 부가기능 배포를 설치할 수 있도록 사용자 계정과 동일한 Google Workspace 조직 내에서 생성되고 결제가 사용 설정된 Google Cloud 프로젝트
2. Google Cloud 프로젝트 및 Firestore 설정
이 단계에서는 프로젝트의 OAuth 동의 화면을 구성하고, Cloud Shell에서 필수 Google Cloud 및 Google Workspace API를 사용 설정하고, (default) Firestore 데이터베이스를 프로비저닝하고, 부가기능 승인 서비스 계정을 초기화합니다.
- Google Cloud 콘솔을 열고 Google Cloud 프로젝트를 선택한 다음 상단 탐색 메뉴에서 Cloud Shell 활성화 아이콘을 클릭합니다.
- Google Workspace에서 부가기능의 OAuth 클라이언트를 프로비저닝할 수 있도록 OAuth 동의 화면을 구성합니다.
- Google 인증 플랫폼 브랜딩 페이지를 엽니다.
- 메시지가 표시되면 시작하기를 클릭합니다. (동의 화면이 이미 구성된 경우 3단계로 건너뜁니다.)
- 앱 정보에서 앱 이름에
Support Extension을 입력하고 사용자 지원 이메일을 선택한 후 다음을 클릭합니다. - 대상에서 내부를 선택하고 다음을 클릭합니다.
- 연락처 정보에 이메일 주소를 입력하고 다음을 클릭합니다.
- 완료에서 Google API 서비스: 사용자 데이터 정책에 동의하고 계속을 클릭한 다음 만들기를 클릭합니다.
- Cloud Shell에서 활성 프로젝트를 설정하고 (
YOUR_PROJECT_ID를 Google Cloud 프로젝트 ID로 바꿈) 필요한 API를 사용 설정합니다.gcloud config set project YOUR_PROJECT_ID gcloud services enable \ aiplatform.googleapis.com \ firestore.googleapis.com \ run.googleapis.com \ cloudbuild.googleapis.com \ artifactregistry.googleapis.com \ appsmarket-component.googleapis.com \ gsuiteaddons.googleapis.com \ workspacestudio.googleapis.com us-central1에서(default)Firestore 데이터베이스를 프로비저닝하고 (아직 없는 경우) Google Workspace 부가기능 서비스 계정을 초기화합니다.gcloud firestore databases describe --database="(default)" >/dev/null 2>&1 || \ gcloud firestore databases create \ --database="(default)" \ --location=us-central1 \ --type=firestore-native \ --quiet gcloud workspace-add-ons get-authorization
3. Firestore 및 Gemini 3.8 Flash로 Node.js 앱 빌드
이 단계에서는 ~/studio-support-addon에서 Node.js 애플리케이션을 빌드합니다. 코드를 모듈화하고 읽기 쉽게 유지하기 위해 프로젝트를 다음과 같은 5개의 집중된 파일로 구성합니다.
firestore.js: Cloud Firestore 클라이언트 (studio_triggers및support_tickets컬렉션)를 초기화하고 캐시된 OAuth 토큰을 새로고침합니다.starter.js: 새 지원 티켓 맞춤 시작 단계 웹훅 (/onConfigTrigger및/onManageTrigger)을 구현합니다.action.js: 키워드 일치 대체와 함께 Gemini 3.8 Flash (gemini-3.8-flash)를 사용하여 긴급성 감지 맞춤 액션 단계 웹훅 (/onConfigUrgency및/onExecuteUrgency)을 구현합니다.portal.js: 지원 빠른 포털 웹 UI (GET /)를 제공하고 활성 Google Workspace Studio 트리거 (POST /api/submit-ticket)를 실행합니다.index.js: 모든 Express 경로를 연결하고 HTTP 서버를 시작합니다.
프로젝트 및 package.json 초기화
- 프로젝트 디렉터리를 만들고 해당 디렉터리로 이동합니다.
mkdir -p ~/studio-support-addon && cd ~/studio-support-addon express,@google-cloud/firestore,@google/genai로package.json만들기:cat << 'EOF' > package.json { "name": "studio-support-addon", "version": "1.0.0", "type": "module", "main": "index.js", "scripts": { "start": "node index.js" }, "dependencies": { "@google-cloud/firestore": "^7.11.0", "@google/genai": "^1.0.0", "express": "^4.21.2" } } EOF
firestore.js - 공유 Firestore 상태 및 OAuth 토큰 갱신
두 Firestore 컬렉션을 관리하고 캐시된 사용자 OAuth 토큰을 최신 상태로 유지하는 firestore.js를 만듭니다.
studio_triggers: Cloud Run 컨테이너 재시작 및 Scale-to-zero 이벤트가 발생해도 유지되도록 활성 Google Workspace Studio 흐름 구독 (triggerId,notifyUri,userOAuthToken)을 저장합니다.support_tickets: 포털을 통해 제출된 각 지원 티켓의 기록을 저장합니다.refreshTokenInFirestore(req): Google Workspace Studio가 웹훅 엔드포인트를 호출할 때마다 수명이 짧은authorizationEventObject.userOAuthToken가 포함됩니다. 이 도우미는 백그라운드triggers.fire호출이 승인된 상태로 유지되도록 활성 트리거 문서를 최신 토큰으로 업데이트합니다.
cat << 'EOF' > firestore.js
import { Firestore } from '@google-cloud/firestore';
export const db = new Firestore({
projectId: process.env.GOOGLE_CLOUD_PROJECT || undefined,
databaseId: '(default)',
ignoreUndefinedProperties: true,
});
export const triggersCollection = db.collection(
process.env.FIRESTORE_TRIGGERS_COLLECTION || 'studio_triggers'
);
export const ticketsCollection = db.collection(
process.env.FIRESTORE_TICKETS_COLLECTION || 'support_tickets'
);
// Keep cached userOAuthToken fresh across active trigger documents
export async function refreshTokenInFirestore(req) {
const freshToken = req.body?.authorizationEventObject?.userOAuthToken;
if (!freshToken) return;
try {
const snapshot = await triggersCollection.get();
if (snapshot.empty) return;
const batch = db.batch();
snapshot.docs.forEach((doc) => {
batch.set(
doc.ref,
{ userOAuthToken: freshToken, updatedAt: new Date().toISOString() },
{ merge: true }
);
});
await batch.commit();
} catch (err) {
console.error('Error refreshing OAuth token in Firestore:', err.message);
}
}
EOF
starter.js - 맞춤 시작 단계 (workflowTrigger)
Google Workspace Studio 맞춤 시작 단계에 필요한 두 개의 웹훅 엔드포인트를 구현하는 starter.js를 만듭니다.
onConfigTrigger(POST /onConfigTrigger): 사용자가 Google Workspace Studio에서 새 지원 티켓을 흐름의 시작으로 선택할 때 호출됩니다. 시작 프로그램을 설명하는pushCard탐색 작업을 반환합니다.onManageTrigger(POST /onManageTrigger): 사용자가 흐름을 사용 설정 (req.body.workflow.triggerCreation)하거나 사용 중지 / 삭제 (req.body.workflow.triggerDeletion)할 때 호출됩니다.studio_triggersFirestore 컬렉션에 정기 결제 문서를 저장하거나 삭제합니다.
cat << 'EOF' > starter.js
import { triggersCollection, refreshTokenInFirestore } from './firestore.js';
// Custom Starter Configuration Card
export async function onConfigTrigger(req, res) {
await refreshTokenInFirestore(req);
res.status(200).json({
action: {
navigations: [
{
pushCard: {
sections: [
{
header: 'Support Ticket Listener',
widgets: [
{
textParagraph: {
text: 'This starter listens for new tickets submitted through the Support Quick Portal and stores subscriptions in Firestore.',
},
},
],
},
],
},
},
],
},
});
}
// Custom Starter Subscription Lifecycle (Firestore studio_triggers collection)
export async function onManageTrigger(req, res) {
try {
const triggerCreation = req.body?.workflow?.triggerCreation;
const triggerDeletion = req.body?.workflow?.triggerDeletion;
const userOAuthToken =
req.body?.authorizationEventObject?.userOAuthToken || null;
if (triggerCreation?.triggerId) {
const triggerId = String(triggerCreation.triggerId);
const docId = encodeURIComponent(triggerId);
await triggersCollection.doc(docId).set(
{
triggerId,
notifyUri: triggerCreation.notifyUri || null,
...(userOAuthToken ? { userOAuthToken } : {}),
updatedAt: new Date().toISOString(),
},
{ merge: true }
);
}
if (triggerDeletion?.triggerId) {
const triggerId = String(triggerDeletion.triggerId);
const docId = encodeURIComponent(triggerId);
await triggersCollection.doc(docId).delete();
}
if (!triggerCreation && !triggerDeletion && userOAuthToken) {
await refreshTokenInFirestore(req);
}
} catch (err) {
console.error('Error in /onManageTrigger:', err.message);
}
res.status(200).json({});
}
EOF
action.js - Gemini 3.8 Flash를 사용하는 맞춤 작업 단계 (workflowAction)
Google Workspace Studio 맞춤 액션 단계에 필요한 두 개의 웹훅 엔드포인트를 구현하는 action.js를 만듭니다.
onConfigUrgency(POST /onConfigUrgency):hostAppDataSource.workflowDataSource.includeVariables: true을 설정하는textInput위젯이 있는 구성 카드를 반환하여 사용자가 시작 단계에서 출력된ticketDescription변수를 선택할 수 있도록 합니다.onExecuteUrgency(POST /onExecuteUrgency):req.body.workflow.actionInvocation.inputs에서ticketDescription입력을 읽고, Gemini Enterprise Agent Platform에서 Gemini 3.8 Flash (gemini-3.8-flash)를 사용하여 비즈니스 영향과 긴급성을 분류하고, 구조화된 출력 (responseSchemaenum["High", "Normal"])을 사용하여 모델 호출이 실패하면detectKeywordUrgency()(urgent,blocker,critical)로 대체하고,hostAppAction.workflowAction.returnOutputVariablesAction을 사용하여urgencyLevel출력 변수를 반환합니다.
cat << 'EOF' > action.js
import { GoogleGenAI, Type } from '@google/genai';
import { refreshTokenInFirestore } from './firestore.js';
// Initialize Google GenAI SDK for the Gemini Enterprise Agent Platform
const ai = new GoogleGenAI({
vertexai: true,
project: process.env.GOOGLE_CLOUD_PROJECT,
location: 'global',
});
// Fallback keyword classifier if the Gemini API call is unavailable
export function detectKeywordUrgency(description) {
const lower = (description || '').toLowerCase();
return lower.includes('urgent') ||
lower.includes('blocker') ||
lower.includes('critical')
? 'High'
: 'Normal';
}
// Custom Action Step Configuration Card
export async function onConfigUrgency(req, res) {
await refreshTokenInFirestore(req);
res.status(200).json({
action: {
navigations: [
{
pushCard: {
sections: [
{
header: 'Urgency Detector Step',
widgets: [
{
textInput: {
name: 'ticketDescription',
label: 'Source Description',
hintText:
'Select the description variable outputted by the starter',
hostAppDataSource: {
workflowDataSource: {
includeVariables: true,
},
},
},
},
],
},
],
},
},
],
},
});
}
// Custom Action Step Execution: Gemini 3.8 Flash with keyword fallback
export async function onExecuteUrgency(req, res) {
await refreshTokenInFirestore(req);
const inputs = req.body?.workflow?.actionInvocation?.inputs || {};
const description = inputs.ticketDescription?.stringValues?.[0] || '';
let urgencyLevel = detectKeywordUrgency(description);
try {
const response = await ai.models.generateContent({
model: 'gemini-3.8-flash',
contents: `Evaluate the business impact and time sensitivity of this support ticket description and classify its urgency:\n\n${description}`,
config: {
responseMimeType: 'text/x.enum',
responseSchema: {
type: Type.STRING,
enum: ['High', 'Normal'],
},
},
});
const candidate = (response.text || '').trim();
if (candidate === 'High' || candidate === 'Normal') {
urgencyLevel = candidate;
}
} catch (err) {
console.error('Gemini fallback to keyword matching:', err.message);
}
res.status(200).json({
hostAppAction: {
workflowAction: {
returnOutputVariablesAction: {
variables: {
urgencyLevel: {
stringValues: [urgencyLevel],
},
},
},
},
},
});
}
EOF
portal.js - 빠른 포털 UI 및 triggers.fire 디스패처 지원
웹 포털을 제공하고 티켓 제출을 Google Workspace Studio로 디스패치하는 portal.js를 만듭니다.
renderPortal(GET /):studio_triggersFirestore 컬렉션을 쿼리하여 등록된 활성 Google Workspace Studio 트리거 수를 표시하고 HTML 티켓 제출 양식을 제공합니다.submitTicket(POST /api/submit-ticket):studio_triggers의 모든 활성 트리거 문서를 반복하고,ticketTitle및ticketDescription출력 변수와 고유한requestId을 사용하여 Google Workspace Studiotriggers.fire엔드포인트 (https://workspacestudio.googleapis.com/v1/triggers/{triggerId}:fire)를 호출하고,support_ticketsFirestore 컬렉션에 티켓을 저장합니다.
cat << 'EOF' > portal.js
import { triggersCollection, ticketsCollection } from './firestore.js';
// Support Quick Portal UI
export async function renderPortal(req, res) {
let activeCount = 0;
try {
const snapshot = await triggersCollection.get();
activeCount = snapshot.size;
} catch (err) {
console.error('Error reading studio_triggers collection:', err.message);
}
res.status(200).send(`<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Support Quick Portal</title>
<style>
body { font-family: Arial, sans-serif; padding: 24px; max-width: 540px; margin: auto; color: #202124; }
.badge { background: #e8f0fe; color: #1967d2; padding: 8px 12px; border-radius: 6px; font-size: 14px; margin-bottom: 16px; }
label { font-weight: bold; display: block; margin-top: 16px; }
input, textarea { width: 100%; margin-top: 6px; padding: 10px; box-sizing: border-box; border: 1px solid #dadce0; border-radius: 4px; }
button { background-color: #1a73e8; color: white; border: none; padding: 10px 18px; margin-top: 16px; font-weight: bold; cursor: pointer; border-radius: 4px; }
button:hover { background-color: #1557b0; }
#status { margin-top: 16px; font-weight: bold; }
</style>
</head>
<body>
<h2>Support Quick Portal</h2>
<div class="badge">
Active Google Workspace Studio triggers in Firestore (<code>studio_triggers</code>):
<strong id="trigger-count">${activeCount}</strong>
</div>
<p>Submit a support ticket below to fire your Google Workspace Studio flow.</p>
<label for="title">Ticket Title</label>
<input type="text" id="title" placeholder="e.g. Database failure">
<label for="description">Issue Description</label>
<textarea id="description" rows="4" placeholder="Describe the issue..."></textarea>
<button onclick="submitTicket()">Submit Ticket</button>
<div id="status"></div>
<script>
async function submitTicket() {
const title = document.getElementById('title').value.trim();
const description = document.getElementById('description').value.trim();
const statusEl = document.getElementById('status');
if (!title || !description) {
statusEl.style.color = '#d93025';
statusEl.textContent = 'Please fill out both fields.';
return;
}
statusEl.style.color = '#188038';
statusEl.textContent = 'Submitting ticket...';
try {
const res = await fetch('/api/submit-ticket', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title, description }),
});
const data = await res.json();
if (data.ok) {
statusEl.textContent =
'Ticket saved to Firestore! Fired ' + data.triggersFired + ' Google Workspace Studio trigger(s).';
} else {
statusEl.style.color = '#d93025';
statusEl.textContent = 'Error: ' + (data.error || 'Unknown error');
}
} catch (err) {
statusEl.style.color = '#d93025';
statusEl.textContent = 'Request failed: ' + err.message;
}
}
</script>
</body>
</html>`);
}
// Portal Form Submission -> Fire Active Google Workspace Studio Triggers
export async function submitTicket(req, res) {
const title = req.body?.title || 'Untitled Ticket';
const description = req.body?.description || '';
const results = [];
try {
const snapshot = await triggersCollection.get();
for (const doc of snapshot.docs) {
const trigger = doc.data();
if (!trigger.triggerId || !trigger.userOAuthToken) continue;
const response = await fetch(
`https://workspacestudio.googleapis.com/v1/triggers/${encodeURIComponent(trigger.triggerId)}:fire`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${trigger.userOAuthToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'triggers/' + trigger.triggerId,
outputs: {
ticketTitle: { stringValues: [title] },
ticketDescription: { stringValues: [description] },
},
requestId:
'req_' + Date.now() + '_' + Math.floor(Math.random() * 1000),
}),
}
);
results.push({ triggerId: trigger.triggerId, status: response.status });
}
await ticketsCollection.add({
title,
description,
triggersFired: results.length,
createdAt: new Date().toISOString(),
});
res.status(200).json({ ok: true, triggersFired: results.length, results });
} catch (err) {
console.error('Error in /api/submit-ticket:', err.message);
res.status(500).json({ ok: false, error: err.message });
}
}
EOF
index.js - Express 서버 진입점
index.js를 만들어 포털 경로와 Google Workspace Studio 웹훅 엔드포인트를 PORT (Cloud Run의 기본값은 8080)에서 수신 대기하는 Express HTTP 서버에 연결합니다.
cat << 'EOF' > index.js
import express from 'express';
import { onConfigTrigger, onManageTrigger } from './starter.js';
import { onConfigUrgency, onExecuteUrgency } from './action.js';
import { renderPortal, submitTicket } from './portal.js';
const app = express();
app.use(express.json());
// Support Quick Portal routes
app.get('/', renderPortal);
app.post('/api/submit-ticket', submitTicket);
// Google Workspace Studio Custom Starter Step webhooks
app.post('/onConfigTrigger', onConfigTrigger);
app.post('/onManageTrigger', onManageTrigger);
// Google Workspace Studio Custom Action Step webhooks
app.post('/onConfigUrgency', onConfigUrgency);
app.post('/onExecuteUrgency', onExecuteUrgency);
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`Support Extension listening on port ${PORT}`);
});
EOF
4. Cloud Run에 배포하고 부가기능 설치
이 단계에서는 Node.js 서비스를 Cloud Run에 배포하고, Google Workspace 부가기능 호출자 및 Firestore와 Agent Platform 런타임 액세스에 대한 IAM 권한을 부여하고, deployment.json 매니페스트를 생성하고, 부가기능 배포를 설치합니다.
- 프로젝트 및 Firestore 컬렉션 환경 변수를 사용하여
support-extension서비스를 Cloud Run에 배포합니다.gcloud run deploy support-extension \ --source . \ --region us-central1 \ --allow-unauthenticated \ --set-env-vars="GOOGLE_CLOUD_PROJECT=$(gcloud config get-value project),FIRESTORE_TRIGGERS_COLLECTION=studio_triggers,FIRESTORE_TICKETS_COLLECTION=support_tickets" \ --quiet - Google Workspace Studio가 웹훅 엔드포인트를 호출할 수 있도록 Google Workspace 부가기능 서비스 계정에 Cloud Run 서비스에 대한
roles/run.invoker권한을 부여합니다.ADDON_SA=$(gcloud workspace-add-ons get-authorization --format="value(serviceAccountEmail)") gcloud run services add-iam-policy-binding support-extension \ --region=us-central1 \ --member="serviceAccount:${ADDON_SA}" \ --role="roles/run.invoker" \ --quiet - Cloud Run 런타임 서비스 계정에
roles/datastore.user(studio_triggers및support_ticketsFirestore 컬렉션 읽기 및 쓰기) 및roles/aiplatform.user(Gemini Enterprise Agent Platform에서 Gemini 호출) 권한을 부여합니다.PROJECT_ID=$(gcloud config get-value project) PROJECT_NUMBER=$(gcloud projects describe "${PROJECT_ID}" --format="value(projectNumber)") RUNTIME_SA=$(gcloud run services describe support-extension --region=us-central1 --format="value(spec.template.spec.serviceAccountName)") RUNTIME_SA=${RUNTIME_SA:-"${PROJECT_NUMBER}-compute@developer.gserviceaccount.com"} gcloud projects add-iam-policy-binding "${PROJECT_ID}" \ --member="serviceAccount:${RUNTIME_SA}" \ --role="roles/datastore.user" \ --condition=None \ --quiet gcloud projects add-iam-policy-binding "${PROJECT_ID}" \ --member="serviceAccount:${RUNTIME_SA}" \ --role="roles/aiplatform.user" \ --condition=None \ --quiet - 배포된 Cloud Run URL을 가져오고
supportTicketTrigger(workflowTrigger) 및urgencyDetectorStep(workflowAction) 요소를 정의하는deployment.json매니페스트를 생성합니다.SERVICE_URL=$(gcloud run services describe support-extension --region=us-central1 --format="value(status.url)") cat << EOF > deployment.json { "oauthScopes": [ "https://www.googleapis.com/auth/workspace.studio.trigger", "https://www.googleapis.com/auth/userinfo.email" ], "addOns": { "common": { "name": "Support Extension", "logoUrl": "https://www.gstatic.com/images/branding/productlogos/buganizer/v1/192px.svg" }, "studio": { "flows": { "workflowElements": [ { "id": "supportTicketTrigger", "state": "ACTIVE", "name": "New Support Ticket", "description": "Fires when someone submits a ticket in the Support Quick Portal.", "workflowTrigger": { "inputs": [], "outputs": [ { "id": "ticketTitle", "description": "Ticket Title", "cardinality": "SINGLE", "dataType": { "basicType": "STRING" } }, { "id": "ticketDescription", "description": "Ticket Description", "cardinality": "SINGLE", "dataType": { "basicType": "STRING" } } ], "onConfigFunction": "${SERVICE_URL}/onConfigTrigger", "onManageFunction": "${SERVICE_URL}/onManageTrigger" } }, { "id": "urgencyDetectorStep", "state": "ACTIVE", "name": "Detect Urgency", "description": "Evaluates the ticket description to detect High or Normal urgency.", "workflowAction": { "inputs": [ { "id": "ticketDescription", "description": "Description to scan", "cardinality": "SINGLE", "dataType": { "basicType": "STRING" } } ], "outputs": [ { "id": "urgencyLevel", "description": "Detected Urgency (High / Normal)", "cardinality": "SINGLE", "dataType": { "basicType": "STRING" } } ], "onConfigFunction": "${SERVICE_URL}/onConfigUrgency", "onExecuteFunction": "${SERVICE_URL}/onExecuteUrgency" } } ] } }, "httpOptions": { "authorizationHeader": "SYSTEM_ID_TOKEN" } } } EOF - 계정의 Google Workspace 부가기능 배포를 만들거나 교체하여 설치하고 지원 빠른 포털 URL을 인쇄합니다.
gcloud workspace-add-ons deployments create support-extension --deployment-file=deployment.json || \ gcloud workspace-add-ons deployments replace support-extension --deployment-file=deployment.json gcloud workspace-add-ons deployments install support-extension echo "Support Quick Portal URL: ${SERVICE_URL}"
5. Google Workspace Studio에서 맞춤 단계 테스트하기
- Google Workspace Studio를 열고 (이미 열려 있는 경우 브라우저 탭을 새로고침) 새 플로우를 클릭합니다.
- 시작 조건 선택 > 지원 확장 프로그램 > 새 지원 티켓을 클릭합니다.
- 단계 추가 (
+) > 지원 확장 프로그램 > 긴급성 감지를 클릭합니다. 소스 설명 필드에서 + 변수를 클릭하고 티켓 설명을 선택합니다. - 단계 추가 (
+)를 클릭하고 메시지에 티켓 제목, 긴급도, 티켓 설명 변수가 포함된 채팅 > Chat에서 알림 받기 단계를 추가합니다. - 오른쪽 상단에서 흐름을 사용으로 전환합니다. Google Workspace Studio는 Cloud Run에서
/onManageTrigger엔드포인트를 즉시 호출하여 활성 트리거 구독을 Firestore의studio_triggers컬렉션에 작성합니다. - 새 탭에서 지원 빠른 포털 URL (
https://support-extension-...run.app)을 열고 포털에 Firestore의1활성 트리거가 표시되는지 확인한 후 Gemini 3.8 Flash (gemini-3.8-flash) 분류를 테스트하기 위해 티켓 2개를 제출합니다.- 영향이 큰 티켓 (티켓 제목:
London office lockout, 문제 설명:All 500 employees in the London office are locked out of their workstations 20 minutes before the board presentation.):urgent또는blocker와 같은 하드코딩된 키워드가 없어도gemini-3.8-flash에서 이 티켓을High긴급도로 분류합니다. - 루틴 티켓 (티켓 제목:
Conference room adapters, 문제 설명:Could we order two extra HDMI adapters for the third-floor conference room next month?):gemini-3.8-flash에서 이 티켓을Normal긴급도로 분류합니다.
- 영향이 큰 티켓 (티켓 제목:
- Google Chat 및 Google Workspace Studio 활동 탭에서
Urgency Level: High및Urgency Level: Normal티켓 모두에 대해 플로우가 실행되었는지 확인합니다.
6. 삭제
Google Cloud 계정에 지속적으로 요금이 청구되지 않도록 하려면 Cloud Shell에서 배포된 리소스를 삭제하세요.
gcloud workspace-add-ons deployments uninstall support-extension
gcloud workspace-add-ons deployments delete support-extension --quiet
gcloud run services delete support-extension --region=us-central1 --quiet
gcloud firestore databases delete --database="(default)" --quiet
7. 수고하셨습니다
Gemini Enterprise Agent Platform에서 Firestore, Cloud Run, Gemini 3.8 Flash를 사용하여 Google Workspace Studio용 Node.js HTTP 확장 프로그램을 빌드, 배포, 테스트했습니다.
학습한 내용
workflowTrigger및workflowAction요소로 Google Workspace Studio HTTP 부가기능 매니페스트 (deployment.json)를 구성합니다.studio_triggersFirestore 컬렉션에 Google Workspace StudioworkflowTrigger구독을 유지하고support_tickets에 포털 제출을 로깅하여 트리거가 Cloud Run 확장 및 재배포에서 유지되도록 합니다.- Gemini Enterprise Agent Platform에서 Gemini 3.8 Flash (
gemini-3.8-flash) 구조화된 출력을 사용하여 지원 티켓을 분류하는workflowAction단계를 빌드합니다. 복원력 있는 키워드 일치 대체가 있습니다.