Cloud Run에 배포하기 전에 Google 시트에 쓰는 ADK 에이전트를 로컬에서 테스트하는 방법

1. 소개

개요

이 게시물에서는 Google 에이전트 개발 키트 (ADK)와 FastAPI를 사용하여 Gemini 에이전트를 빌드합니다. 배포하기 전에 Cloud Run 서비스 ID와 동일한 서비스 계정을 사용하여 로컬에서 테스트하도록 애플리케이션 기본 사용자 인증 정보 (ADC)를 구성합니다.

Google Sheets와 통신하려면 스프레드시트에 액세스할 수 있는 적절한 범위의 OAuth 액세스 토큰이 애플리케이션에 필요합니다. ADC를 사용하여 로컬 및 Cloud Run에서 실행할 때 이 액세스 토큰을 가져오는 방법을 알아봅니다.

용어 참고사항:

'ID 가정' 또는 '동일한 권한 가정'이라는 용어가 더 익숙할 수도 있습니다. Google Cloud에서 서비스 계정을 가장하면 인증된 주 구성원이 서비스 계정이 액세스할 수 있는 무엇이든 액세스할 수 있습니다. 적절한 권한을 사용해서 인증된 주 구성원만 서비스 계정을 가장할 수 있습니다. 자세한 내용은 https://docs.cloud.google.com/iam/docs/service-account-overview#impersonation을 참고하세요.

학습할 내용

  • 로컬 환경에 애플리케이션 기본 사용자 인증 정보 (ADC)를 구성하는 방법
  • Google 시트를 읽고 쓸 수 있는 ADK 에이전트를 만드는 방법
  • Cloud Run에 에이전트를 배포하는 방법

2. 설정 및 요구사항

기본 요건

  • Cloud 콘솔에 로그인되어 있습니다.
  • 이전에 Cloud Run 서비스를 배포한 적이 있습니다. 예를 들어 Cloud Run 서비스 배포를 따라 시작할 수 있습니다.

환경 변수 설정

이 Codelab 전체에서 사용할 환경 변수를 설정할 수 있습니다.

공유 링크를 복사할 때 Google 시트의 URL에서 시트 ID를 확인할 수 있습니다(예: https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID/edit?usp=sharing).

PROJECT_ID=<YOUR-PROJECT-ID>
REGION=<YOUR_REGION>
SPREADSHEET_ID=<YOUR_SPREADSHEET_ID>
SA_NAME=sheet-agent-sa
SERVICE_ACCOUNT_EMAIL=$SA_NAME@$PROJECT_ID.iam.gserviceaccount.com

필수 Google Cloud API 사용 설정

gcloud services enable \
  sheets.googleapis.com \
  aiplatform.googleapis.com \
  artifactregistry.googleapis.com \
  cloudbuild.googleapis.com \
  run.googleapis.com \
  logging.googleapis.com \
  --project=$PROJECT_ID

서비스 계정 만들기

먼저 다음 명령어를 사용하여 서비스 계정을 만듭니다.

gcloud iam service-accounts create $SA_NAME \
    --description="Service account for spreadsheet agent codelab" \
    --display-name="Spreadsheet Agent Service Account" \
    --project=$PROJECT_ID

다음으로 서비스 계정에 Vertex AI 사용자 역할을 부여합니다 (Vertex AI의 Gemini 모델에 필요).

gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member="serviceAccount:$SERVICE_ACCOUNT_EMAIL" \
    --role="roles/aiplatform.user"

gcloud ID에 서비스 계정 가장 권한을 부여합니다 (로컬 개발에 필요). gcloud auth list를 실행하여 gcloud ID를 확인할 수 있습니다.

USER_EMAIL=$(gcloud config get-value account)

gcloud iam service-accounts add-iam-policy-binding $SERVICE_ACCOUNT_EMAIL \
    --member="user:$USER_EMAIL" \
    --role="roles/iam.serviceAccountTokenCreator" \
    --project=$PROJECT_ID
  1. 브라우저에서 Google 시트를 엽니다.
  2. 공유 버튼을 클릭합니다.
  3. 서비스 계정 이메일 $SERVICE_ACCOUNT_EMAIL을 붙여넣고 편집자 액세스 권한을 부여합니다.

3. 앱 만들기

먼저 소스 코드 디렉터리를 만들고 해당 디렉터리로 cd합니다.

mkdir local-adk-sheets-codelab && cd $_

그런 다음 다음 콘텐츠로 main.py 파일을 만듭니다.

import os
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from google.auth import default
from googleapiclient.discovery import build
from google.adk.agents.llm_agent import Agent
from google.adk.runners import InMemoryRunner

# 1. Environment and Global Google Sheets Client Setup (ADC)
SPREADSHEET_ID = os.environ.get("SPREADSHEET_ID")
if not SPREADSHEET_ID:
    raise ValueError("SPREADSHEET_ID environment variable is missing. Please set it before running.")

credentials, _ = default(scopes=['https://www.googleapis.com/auth/spreadsheets'])
sheets_api = build('sheets', 'v4', credentials=credentials).spreadsheets()

# 2. Tools
def read_spreadsheet(range_name: str) -> dict:
    """Reads values from the Google Spreadsheet for a given range.
   
    Args:
        range_name: The A1 notation or R1C1 notation of the range to retrieve, e.g., 'Sheet1!A1:D10'.
    """
    return sheets_api.values().get(spreadsheetId=SPREADSHEET_ID, range=range_name).execute()

def update_spreadsheet(range_name: str, values: list[list[str]]) -> dict:
    """Updates the Google Spreadsheet range with the specified grid of values.
   
    Args:
        range_name: The A1 notation or R1C1 notation of the range to update, e.g., 'Sheet1!C2'.
        values: A list of lists of strings representing the grid of values to write.
    """
    return sheets_api.values().update(
        spreadsheetId=SPREADSHEET_ID,
        range=range_name,
        valueInputOption="USER_ENTERED",
        body={"values": values}
    ).execute()

# 3. Agent Definition
MODEL_NAME = os.environ.get("GEMINI_MODEL", "gemini-3.1-flash-lite")
root_agent = Agent(
    name="spreadsheet_agent",
    model=MODEL_NAME,
    instruction="""You are a helpful spreadsheet assistant.
    You can read and write to the user's Google Spreadsheet using the tools provided.
    Always use the appropriate tools when the user asks you to read or update a spreadsheet.
    When updating a range, make sure the values parameter is a list of lists (e.g. [[value]]).
    Be concise and helpful in your responses.""",
    description="An agent that can read and write to a specific Google Spreadsheet.",
    tools=[read_spreadsheet, update_spreadsheet]
)

# 4. FastAPI Application Setup
app = FastAPI()

class ChatRequest(BaseModel):
    prompt: str

@app.post("/chat")
async def chat_endpoint(request: ChatRequest):
    try:
        async with InMemoryRunner(app_name="sheets_agent_app", agent=root_agent) as runner:
            events = await runner.run_debug(request.prompt, quiet=True)
           
            # Extract the final response text
            texts = [
                "".join(p.text for p in e.content.parts if p.text)
                for e in reversed(events) if e.content and e.content.parts
            ]
            return {"response": next((t for t in texts if t), "No response from agent.")}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

그런 다음 다음 콘텐츠로 requirements.txt 파일을 만듭니다.

fastapi>=0.100.0
uvicorn>=0.22.0
google-adk>=1.27.1
google-auth
google-api-python-client

4. 로컬에서 서비스 테스트

gcloud auth application-default login --impersonate-service-account=$SERVICE_ACCOUNT_EMAIL
GOOGLE_GENAI_USE_VERTEXAI=1 GOOGLE_CLOUD_PROJECT=$PROJECT_ID SPREADSHEET_ID=$SPREADSHEET_ID uvicorn main:app --host 0.0.0.0 --port 8080

이제 다른 터미널에서 에이전트를 테스트합니다.

curl -X POST http://localhost:8080/chat \
    -H "Content-Type: application/json" \
    -d '{"prompt": "Update spreadsheet '$SPREADSHEET_ID' at range Sheet1!A1 with the value hello world"}'

서버는 {"response":"The spreadsheet at Sheet1!A1 has been updated with the value \"hello world\"."}로 응답해야 합니다.

5. Cloud Run에 배포

이제 서비스 계정에 스프레드시트에 액세스할 수 있는 적절한 권한이 있으므로 서비스 계정을 서비스 ID로 사용하여 Cloud Run에 배포합니다.

gcloud run deploy local-adk-sheets-codelab \
    --source . \
    --region=$REGION \
    --set-env-vars GOOGLE_GENAI_USE_VERTEXAI=1,GOOGLE_CLOUD_PROJECT=$PROJECT_ID,GOOGLE_CLOUD_LOCATION=global,SPREADSHEET_ID=$SPREADSHEET_ID \
    --service-account $SERVICE_ACCOUNT_EMAIL \
    --no-allow-unauthenticated \
    --project=$PROJECT_ID

배포가 완료되면 Cloud Run 서비스에 프롬프트를 보냅니다.

# Retrieve the Service URL
SERVICE_URL=$(gcloud run services describe local-adk-sheets-codelab --region $REGION --format 'value(status.url)')
curl -X POST $SERVICE_URL/chat \
    -H "Authorization: bearer $(gcloud auth print-identity-token)" \
    -H "Content-Type: application/json" \
    -d '{"prompt": "Update spreadsheet '$SPREADSHEET_ID' at range Sheet1!A1 with the value hello world"}'

{"response":"OK. I've updated cell A1 in Sheet1 with \"hello world\"."} 응답이 수신됩니다.

이제 Google 시트를 엽니다. 첫 번째 셀에 'hello world' 값이 표시됩니다.

6. 수고하셨습니다

축하합니다. Codelab을 완료했습니다.

다음 단계

다음 문서를 살펴보고 에이전트의 기능을 확장하는 것이 좋습니다.

학습한 내용

  • 로컬 환경에 애플리케이션 기본 사용자 인증 정보 (ADC)를 구성하는 방법
  • Google 시트를 읽고 쓸 수 있는 ADK 에이전트를 만드는 방법
  • Cloud Run에 에이전트를 배포하는 방법

7. 삭제

의도치 않은 요금이 청구되지 않도록 하려면(예: 이 Cloud Run 함수가 무료 등급의 월별 Cloud Run 호출 할당량보다 더 많이 호출되는 경우) Cloud Run 서비스를 삭제하거나 프로젝트를 삭제하면 됩니다.

Cloud Run 서비스를 삭제하려면 Cloud Console(https://console.cloud.google.com/run/)에서 Cloud Run으로 이동하여 이 Codelab에서 만든 서비스 local-adk-sheets-codelab를 삭제합니다.

전체 프로젝트를 삭제하려면 https://console.cloud.google.com/cloud-resource-manager으로 이동하여 2단계에서 만든 프로젝트를 선택하고 삭제를 선택합니다. 프로젝트를 삭제하면 Cloud SDK에서 프로젝트를 변경해야 합니다. gcloud projects list를 실행하여 사용 가능한 모든 프로젝트의 목록을 볼 수 있습니다.