Cloud Run에서 반려동물 여권 에이전트 빌드 및 배포

1. 개요

이 코드 랩에서는 모델 컨텍스트 프로토콜 (MCP)을 사용하여 데이터 분석과 위치 서비스를 결합하는 AI 에이전트인 반려동물 여권 앱을 배포하는 방법을 알아봅니다.

이 앱은 뉴욕시의 견종 인기를 기반으로 사용자가 반려견과 함께 완벽한 하루를 계획할 수 있도록 지원합니다. 상담사는 '매크로-마이크로' 추론 체인을 사용합니다.

  1. 전략적 검색 (BigQuery): 특정 품종의 인구가 가장 많은 뉴욕시 우편번호를 식별합니다.
  2. 지역 실행 (지도): 해당 우편번호를 위치 편향으로 사용하여 '반려동물 동반 카페'와 '애견 공원'을 찾습니다.
  3. 여행 일정 생성: 데이터를 결합하여 클릭 가능한 링크와 이미지가 포함된 '반려동물 여권' 여행 일정을 만듭니다.

에이전트는 google-adk 프레임워크를 사용하여 빌드되며 Gemini로 구동됩니다.

참고: 프런트엔드 UI를 포함한 전체 프로젝트 코드는 GitHub에서 확인할 수 있습니다. 이 Codelab에서는 핵심 에이전트 로직과 인프라 설정에 중점을 둡니다.

2. 설정 및 요건

먼저 개발 환경이 올바르게 설정되어 있는지 확인합니다.

1. Google Cloud로 인증

활성 Google Cloud 프로젝트를 설정하고 인증합니다. 에이전트가 BigQuery 및 기타 서비스에 액세스하려면 이 권한이 필요합니다.

gcloud config set project [YOUR-PROJECT-ID]
gcloud auth application-default login --project [YOUR-PROJECT-ID]

참고: 인증 중에 다른 프로젝트에 관한 오류가 발생하면 할당량 프로젝트를 사용 중지하고 수동으로 설정하여 이 문제를 해결할 수 있습니다.

gcloud auth application-default login --disable-quota-project
gcloud auth application-default set-quota-project [YOUR-PROJECT-ID]

2. 소프트웨어 요구사항

로컬 머신에 다음 소프트웨어가 설치되어 있어야 합니다.

  • Python (버전 3.13 이상 필요)
  • Git (저장소 다운로드)

저장소 다운로드

이 프로젝트의 코드는 Google MCP 저장소에서 확인할 수 있습니다. 저장소를 클론하고 프로젝트 폴더로 이동합니다.

git clone https://github.com/google/mcp.git
cd examples/petpassport

3. 설치

이제 파일이 있으므로 Python 환경을 설정해 보겠습니다.

  1. 가상 환경 만들기: 종속 항목을 격리합니다.
    python3 -m venv .venv
    
  2. 가상 환경을 활성화합니다.
    • Linux/macOS:
      source .venv/bin/activate
      
    • Windows:
      .venv\Scripts\activate
      
  3. 종속 항목 설치:
    pip install google-adk==1.28.0 python-dotenv google-genai pillow uvicorn
    

Cloud API 사용 설정

프로젝트에 다음 API 사용 설정:

gcloud services enable \
  bigquery.googleapis.com \
  aiplatform.googleapis.com \
  artifactregistry.googleapis.com \
  cloudbuild.googleapis.com \
  run.googleapis.com \
  storage.googleapis.com

지역 선택

셸에서 리전을 환경 변수로 설정합니다.

export REGION=us-central1

4. API 키 획득

지도 및 Gemini 서비스를 사용하려면 API 키를 획득하여 프로젝트 루트의 .env 파일에 저장해야 합니다.

1. Google 지도 API 키

  1. Google Cloud Console로 이동합니다.
  2. API 및 서비스 > 사용자 인증 정보로 이동합니다.
  3. 사용자 인증 정보 만들기 > API 키를 클릭합니다.
  4. 생성된 키를 복사하여 .env 파일에 MAPS_API_KEY=[YOUR_KEY]로 추가합니다.
  5. (권장) MCP 서버에서 사용하는 지도 API만 허용하도록 키를 제한합니다.

2. Gemini API 키 (AI Studio)

  1. Google AI Studio로 이동합니다.
  2. API 키 가져오기를 클릭하거나 API 키 섹션으로 이동합니다.
  3. API 키 만들기를 클릭합니다.
  4. 키를 복사하여 .env 파일에 GEMINI_API_KEY=[YOUR_KEY]로 추가합니다.

5. 종속 항목 설치

petpassport/ 폴더에 requirements.txt 파일을 만듭니다.

google-adk==1.28.0
python-dotenv
google-genai
pillow

6. MCP 서버 인증

이 애플리케이션은 모델 컨텍스트 프로토콜 (MCP) 서버를 사용하여 Google 지도 및 BigQuery와 상호작용합니다. 이러한 서버를 인증하려면 적절한 환경 변수와 헤더를 구성해야 합니다.

  1. Google 지도 MCP: X-Goog-Api-Key 헤더에 전달된 유효한 지도 API 키가 필요합니다.
  2. BigQuery MCP: BigQuery 서비스에 액세스할 수 있는 OAuth 사용자 인증 정보가 필요합니다. 에이전트는 Cloud Run에서 실행될 때는 기본 컴퓨팅 서비스 계정을 사용하고, 로컬에서 실행될 때는 로컬 사용자 인증 정보를 사용합니다.

저장소에는 .env 파일에서 이러한 변수를 구성하는 데 도움이 되는 설정 스크립트 setup/setup_env.sh가 제공됩니다.

7. BigQuery 테이블 만들기

에이전트가 반려견 면허 데이터를 쿼리하려면 BigQuery에서 데이터 세트와 테이블을 만들고 데이터를 로드해야 합니다.

다음 단계를 실행하는 설정 스크립트 setup/setup_bigquery.sh가 제공됩니다.

  1. 원시 데이터를 저장하기 위해 pet-passport-data-[PROJECT_ID]라는 Cloud Storage 버킷을 만듭니다.
  2. 공개 NYC Dog Licensing 데이터 세트 (CSV)를 다운로드합니다.
  3. CSV를 버킷에 업로드합니다.
  4. nyc_dogs라는 BigQuery 데이터 세트를 만듭니다.
  5. 버킷의 데이터를 데이터 세트의 licenses이라는 테이블에 로드합니다.

설정 스크립트를 실행하려면 터미널에서 다음 명령어를 실행합니다.

bash setup/setup_bigquery.sh

8. MCP 서버에 연결

이 앱의 핵심 부분은 MCP를 사용하여 데이터 및 서비스에 연결하는 것입니다. 이 섹션에서는 petpassport/tools.py라는 파일에서 BigQuery 및 Google 지도의 MCP 도구 모음을 구성합니다.

tools.py 코드 완성

다음은 이미지 및 데이터 지속성을 위한 MCP 도구 세트와 맞춤 도구를 포함한 tools.py의 전체 구현입니다. 버킷 확인을 모듈 수준으로 이동하여 중복을 줄이도록 이 코드를 최적화했습니다.

import os
import dotenv
import google.auth
import time
import datetime
from google.cloud import storage
from PIL import Image
from google import genai
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams 

MAPS_MCP_URL = "https://mapstools.googleapis.com/mcp" 
BIGQUERY_MCP_URL = "https://bigquery.googleapis.com/mcp" 

PROJECT_ID = os.getenv('GOOGLE_CLOUD_PROJECT', 'project_not_set')
BUCKET_NAME = f"pet-passport-data-{PROJECT_ID}" 

def get_maps_mcp_toolset():
    dotenv.load_dotenv()
    maps_api_key = os.getenv('MAPS_API_KEY', 'no_api_found')
    
    tools = MCPToolset(
        connection_params=StreamableHTTPConnectionParams(
            url=MAPS_MCP_URL,
            headers={    
                "X-Goog-Api-Key": maps_api_key
            },
            timeout=30.0,          
            sse_read_timeout=300.0
        )
    )
    print("Maps MCP Toolset configured.")
    return tools


def get_bigquery_mcp_toolset():   
    credentials, project_id = google.auth.default(
            scopes=["https://www.googleapis.com/auth/bigquery"]
    )

    credentials.refresh(google.auth.transport.requests.Request())
    oauth_token = credentials.token
        
    HEADERS_WITH_OAUTH = {
        "Authorization": f"Bearer {oauth_token}",
        "x-goog-user-project": project_id
    }

    tools = MCPToolset(
        connection_params=StreamableHTTPConnectionParams(
            url=BIGQUERY_MCP_URL,
            headers=HEADERS_WITH_OAUTH,
            timeout=30.0,          
            sse_read_timeout=300.0
        )
    )
    print("BigQuery MCP Toolset configured.")
    return tools

def generate_pet_passport_photo(prompt: str, image_path: str = None) -> str:
    """Generates an image using gemini-3.1-flash-image-preview based on a prompt and a reference image."""
    client = genai.Client()
    output_path = f"/tmp/pet_passport_{int(time.time())}.png"
    
    try:
        image = Image.open(image_path)
        response = client.models.generate_content(
            model="gemini-3.1-flash-image-preview",
            contents=[prompt, image],
        )
        
        for part in response.parts:
            if part.inline_data is not None:
                generated_image = part.as_image()
                generated_image.save(output_path)
                
                # Upload to GCS and generate signed URL
                try:
                    storage_client = storage.Client()
                    bucket = storage_client.bucket(BUCKET_NAME)
                    blob_name = os.path.basename(output_path)
                    blob = bucket.blob(blob_name)
                    
                    blob.upload_from_filename(output_path)
                    
                    url = blob.generate_signed_url(
                        version="v4",
                        expiration=datetime.timedelta(hours=24),
                        method="GET",
                    )
                    return url
                except Exception as e:
                    print(f"Error uploading image to GCS: {e}")
                    return output_path
                
        raise ValueError("No image was returned by the model.")
    except Exception as e:
        print(f"Error generating image: {e}")
        raise

def save_pet_passport(user_id: str, breed: str, postal_code: str, route_details: str, image_paths: list[str] = None) -> str:
    """Appends the generated itinerary to the user's history in GCS."""
    try:
        storage_client = storage.Client()
        bucket = storage_client.bucket(BUCKET_NAME)
        blob = bucket.blob(f"user-{user_id}.json")
        
        # Download existing or start fresh
        # ... (Implementation details hidden for brevity) ...
        return "Success"
    except Exception as e:
        print(f"Error saving path: {e}")
        raise

코드 설명: tools.py

  • get_maps_mcp_toolsetget_bigquery_mcp_toolset는 올바른 엔드포인트와 인증 헤더로 MCP 클라이언트를 구성합니다.
  • generate_pet_passport_photo는 Gemini를 사용하여 장면을 만들고 결과를 Google Cloud Storage에 업로드하여 서버 재시작을 견딜 수 있는 서명된 URL을 프런트엔드에 반환합니다.

9. 에이전트 만들기

도구가 구성되었으므로 이제 에이전트의 '두뇌'를 빌드할 차례입니다. 에이전트 개발 키트 (ADK)를 사용하여 petpassport/agent.py이라는 파일에 에이전트를 만듭니다.

agent.py 코드 완성

다음은 에이전트와 그 명령어를 정의하는 agent.py의 전체 구현입니다.

import os
import dotenv
import tools
from google.adk.agents import LlmAgent

dotenv.load_dotenv()

PROJECT_ID = os.getenv('GOOGLE_CLOUD_PROJECT', 'project_not_set')

maps_toolset = tools.get_maps_mcp_toolset()
bigquery_toolset = tools.get_bigquery_mcp_toolset()

root_agent = LlmAgent(
    model='gemini-2.5-pro',
    name='root_agent',
    instruction=f"""
        You are the Pet Passport Agent. Your goal is to help users find a fun walking route for their dog in NYC.
        
        When given a breed and a postal code, follow this flow:
        1. **Strategic Discovery:** Use BigQuery to find the most popular neighborhood for that breed in NYC.
        2. **Local Execution:** Use Maps to build a walking route with specific places (parks, cafes) in that area.
        
        **NO DIRECTIONS LINKS:** You must NOT include a Google Maps directions link (e.g., `https://www.google.com/maps/dir/...`) in your final response. Only provide links to individual places.
        
        After generating the itinerary, you MUST call the `save_pet_passport` tool to save this path to the user's profile. Pass a clean summary of the itinerary as `route_details`. The summary should include details (like rating, description from maps).
    """,
    tools=[maps_toolset, bigquery_toolset, tools.generate_pet_passport_photo, tools.save_pet_passport]
)

코드 설명: agent.py

  • 컨테이너 환경을 지원하기 위해 tools를 직접 가져옵니다 (평면 구조).
  • 에이전트는 gemini-2.5-pro로 초기화됩니다.
  • 이 지침에서는 엄격한 다단계 생각의 사슬 (BigQuery 먼저, 다음으로 지도)을 정의하고, 혼란을 야기하는 도보 경로의 할루시네이션 또는 렌더링을 엄격하게 금지합니다.

10. 애플리케이션을 로컬로 실행

Cloud Run에 배포하기 전에 애플리케이션을 로컬로 테스트하는 것이 좋습니다.

  1. 프로젝트 디렉터리에 있는지 확인합니다.
    cd examples/petpassport
    
  2. FastAPI 서버 시작: uvicorn를 사용하여 앱을 실행합니다. 진입점은 petpassport 폴더 내의 main.py입니다.
    uvicorn petpassport.main:app --reload
    
  3. UI 열기: 브라우저에서 http://127.0.0.1:8000/ui/로 이동하여 반려동물 여권 인터페이스와 상호작용합니다.

11. Cloud Run에 배포

에이전트가 준비되었으므로 이제 Cloud Run에 배포할 차례입니다. 컨테이너 환경을 엄격하게 관리하기 위해 표준 gcloud 명령어를 직접 사용합니다.

프로젝트 디렉터리에서 다음 명령어를 실행합니다.

gcloud run deploy petpassport \
  --source petpassport \
  --region $REGION \
  --allow-unauthenticated \
  --labels dev-tutorial=google-mcp

환경 변수 구성

배포 후 Google Cloud 콘솔에서 Cloud Run 서비스로 이동하여 변수 및 보안 비밀 탭에서 다음 환경 변수를 설정합니다.

  • MAPS_API_KEY: Google 지도 API 키입니다.
  • GOOGLE_CLOUD_PROJECT: 프로젝트 ID입니다.
  • PROJECT_ID: 프로젝트 ID입니다 (기존 모듈의 경우 중복 지원).

12. 샘플 프롬프트

다음 프롬프트를 사용하여 배포된 에이전트와 상호작용해 보세요.

  1. 스탠더드: "10021 근처 뉴욕에서 골든 리트리버와 산책하고 싶어. 카페가 있는 경로를 찾아 줘."
  2. 다른 견종: '프렌치 불독을 키우고 어퍼 웨스트 사이드 (10024 근처)에 살고 있습니다. 인기 있는 반려견 공원에 들르는 짧은 산책을 추천해 줘.
  3. 이미지 사용: (강아지 사진 업로드) '우리 코기 사진입니다. 10013 근처에 있습니다. 나를 위해 완벽한 하루를 계획해 줘.'

13. 삭제

이 튜토리얼에서 사용한 리소스 비용이 청구되지 않도록 하려면 다음 안내를 따르세요.

  • Cloud Run 서비스를 삭제합니다. gcloud run services delete petpassport --region=$REGION
  • GCS 버킷을 삭제합니다. gcloud storage rm -r gs://pet-passport-data-$PROJECT_ID