在 Cloud Run 上构建和部署宠物护照代理

1. 概览

在此 Codelab 中,您将学习如何部署 Pet Passport 应用,这是一款使用 Model Context Protocol (MCP) 将数据分析和位置服务相结合的 AI 智能体。

该应用可根据纽约市的犬种受欢迎程度,帮助用户规划与爱犬共度的完美一天。该智能体使用“宏观到微观”推理链:

  1. 战略发现 (BigQuery): 确定特定犬种在纽约市人口最多的邮政编码。
  2. 本地执行 (Maps): 使用该邮政编码作为位置偏差,查找“宠物友好型咖啡馆”和“遛狗公园”。
  3. 行程生成: 将数据组合在一起,创建包含可点击链接和图片的“宠物护照”行程。

该智能体使用 google-adk 框架构建,并由 Gemini 提供支持。

注意: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

选择区域

在 Shell 中将区域设置为环境变量:

export REGION=us-central1

4. 获取 API 密钥

如需使用 Maps 和 Gemini 服务,您需要获取 API 密钥并将其存储在项目根目录中的 .env 文件中。

1. Google 地图 API 密钥

  1. 前往 Google Cloud 控制台
  2. 前往 API 和服务 > 凭据
  3. 依次点击创建凭据 > API 密钥
  4. 复制生成的密钥,并将其添加到 .env 文件中,格式为 MAPS_API_KEY=[YOUR_KEY]
  5. (推荐)限制该密钥,使其仅允许访问 MCP 服务器使用的 Maps 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 服务器进行身份验证

此应用依赖于 Model Context Protocol (MCP) 服务器与 Google 地图和 BigQuery 进行交互。如需对这些服务器进行身份验证,您需要配置相应的环境变量和标头。

  1. Google 地图 MCP: 需要在 X-Goog-Api-Key 标头中传递有效的地图 API 密钥。
  2. BigQuery MCP: 需要具有 BigQuery 服务访问权限的 OAuth 凭据。该智能体在 Cloud Run 上运行时使用默认的计算服务账号,在本地运行时使用本地凭据。

我们在代码库中提供了一个设置脚本 setup/setup_env.sh,可帮助您在 .env 文件中配置这些变量。

7. 创建 BigQuery 表

在智能体查询犬只许可证数据之前,我们需要在 BigQuery 中创建数据集和表,并加载数据。

我们提供了一个设置脚本 setup/setup_bigquery.sh,用于执行以下步骤:

  1. 创建一个名为 pet-passport-data-[PROJECT_ID] 的 Cloud Storage 存储分区,用于存储原始数据。
  2. 下载公开的纽约市犬只许可数据集 (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 代码

以下是 tools.py 的完整实现,包括 MCP 工具集以及用于图片和数据持久性的自定义工具。我们对这段代码进行了优化,通过将存储分区解析移至模块级来减少冗余:

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,然后向前端返回签名网址,以便在服务器重启后继续使用。

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,然后 Maps),并严格禁止产生幻觉或渲染导致杂乱的步行路线。

10. 在本地运行应用

在部署到 Cloud Run 之前,最好先在本地测试应用。

  1. 确保您位于项目目录中
    cd examples/petpassport
    
  2. 启动 FastAPI 服务器: 我们使用 uvicorn 运行应用。入口点是 petpassport 文件夹内的 main.py
    uvicorn petpassport.main:app --reload
    
  3. 打开界面: 在浏览器中前往 http://127.0.0.1:8000/ui/,与 Pet Passport 界面互动。

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 服务,然后在变量和 Secret 标签页下设置以下环境变量:

  • 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