VertexAI Google Maps Groundingを用いて作る週末お出かけプランナー

[この記事は、Insight Edge Advent Calendar 2025 5日目の記事です。]

こんにちは。アジャイル開発チームの中根です。

週末に子どもとのお出かけ先を探すとき、「神奈川県 子ども お出かけ」などと検索していますが、いわゆるまとめサイトが中心に表示されることが多くないでしょうか?結局、自分が子どもと一緒に行ってみたい場所とは違うものが多く、どこがいいのか分からずじまいであんまり意味がないなと感じていました。

また、移動時間や交通手段も考えられておらず、「ここ良さそう!」と思っても、遠すぎたり、子どもの年齢に合わないようなところも多い印象です。いわゆるアミューズメントパークのような場所でなく、広い公園や公営の科学館などそういった地域に根付いた施設をまず第一に紹介してほしい気持ちがありました。

そこで何か作れないかと思い、Vertex AIのGoogle Maps Grounding機能を用いて、これを利用して今回のお出かけプランナーを作成することを考えました。この記事では、Google Maps Groundingの実装方法から、うまくいかなかった点まで、開発を経て得た知見を共有します。

なお今回作成したアプリケーションの全体像は以下の画像のものになっています。

※現状のアプリの全体像

使用した技術スタック

今回のプロジェクトで使用した主な技術スタックは以下の通りです。

フロントエンド

  • React+TypeScript
  • Vite
  • Tailwind CSS
  • Google Maps JavaScript API

バックエンド

  • Python
  • FastAPI
  • Vertex AI (Gemini 2.5 Pro)
  • Google Maps Platform APIs

基本的な構成としては、React + TypeScriptで作成したフロントエンドからFastAPIバックエンドにリクエストを送ります。そこからバックエンドがVertex AIとGoogle Maps APIを呼び出して結果を返す、というシンプルな構成です。プロダクトを作成するというよりはVertex AIのGoogle Maps Groundingの検証をすることが目的だったため簡易的な実装にしています。そのため、今回の記事では具体的なReactやPythonの環境構築やディレクトリ構成、その他テストなどについては言及しません。

Google Maps Groundingとは

まず、Groundingという概念について説明します。「Grounding」は日本語で「根拠づけ」や「接地」という意味で、LLM(大規模言語モデル)の応答に信頼できる情報源を結びつける技術です。ChatGPTやGeminiなどのLLMは、膨大な学習データを元に応答を生成します。しかし、学習されてないデータをはじめとして、営業時間や電話番号などの詳細な情報は不正確なことが多く、すべての情報を正確に返させるには限界があります。そのため以下のような問題が表出することがあります。

  • 「渋谷周辺の子ども向け博物館を教えて」と聞いたとき、実在しない「渋谷こども科学館」のような施設を提案される。
  • 新しくオープンした施設や、逆に閉店してしまった施設について、LLMは知らない可能性がある。
  • 「上野動物園は良い場所です」という一般的な情報は提供できても、「現在の営業時間は9:30-17:00、月曜休園」といった具体的な情報がない可能性がある。

Groundingは、これらの課題を解決する仕組みです。今回のケースでいうとGeminiを通して、Google Mapsのリアルタイムデータへのアクセスを許可することで、以下が可能になります。

  • Googleが検証した実在の場所だけが候補になる
  • 新規オープンや閉店情報がリアルタイムで反映される
  • Place ID、座標、カテゴリなどの構造化データが得られる
  • どのGoogle Mapsデータを参照したかが記録される(Grounding Metadata)

これらの確実な情報を取得することにより、情報の信頼性が高まり、ハルシネーションを防ぐことが可能になります。なお、Vertex AIではGoogle Maps Grounding以外にも、いくつかのGroundingオプションが提供されており、Google検索やGCSをはじめとするデータストアもGroundingに使用できます。

今回のGoogle MapsのGroundingはユーザーからのインプットを受け取った後、必要に応じてGoogle Mapsで検索をします。そしてその結果に応じて内容やメタデータをレスポンスとして返します。これにより、「なぜその施設を提案したのか」という根拠が明確になり、信頼性が向上します。

下準備 - Google Cloud APIキーとサービスアカウントの設定

まずGoogle CloudにてAPIキーを取得します。これはフロントエンド、バックエンドともにGoogle Mapsを利用するためです。なお、サービスアカウントの取得に関しては省略しますが、最低限のロールとして Vertex AI User を付与しています。

APIキーの取得

フロントエンド用ではウェブサイトを選択し、許可するホストに制限をかけます。今回はまだローカルだけのため、localhost しか指定しておりません。あとはMaps JavaScript APIとDirections APIを選択します。ただし、Directions APIはルートをアプリケーション上で表示するために指定したのですが、その機能について実装を取りやめたため結局Maps JavaScript APIしか使ってないのが現状です。

バックエンド用ではアプリケーションの制限は特にしていません。バックエンドでは5つを選択しましたが、開発の途中でいろいろな判断をしましたが、最終的に使用しているのは「Geocoding API」と「Places API(New)」2つです。

  • Geocoding API
  • Places API(New)

取得したAPIキーは環境変数としてそれぞれの環境で使用しています。

Pythonでの使用方法

つづいてPythonでの使用方法に移っていきます。実装は非常にシンプルで、Groundingに関してはVertex AIのクライアント設定でGoogle Mapsツールを有効化するだけです。

if use_grounding:
    config_params["tools"] = [
        Tool(google_maps=GoogleMaps(enable_widget=False))
    ]
    logger.debug("Google Maps grounding enabled")

    # 位置情報バイアスの設定
    if latitude is not None and longitude is not None:
        config_params["tool_config"] = types.ToolConfig(
            retrieval_config=types.RetrievalConfig(
                lat_lng=types.LatLng(
                    latitude=latitude,
                    longitude=longitude,
                ),
                language_code="ja_JP",
            ),
        )

Tool とは、LLMが外部のデータソースや機能にアクセスするための仕組みです。通常、LLMは学習データに基づいて応答を生成しますが、Tool を使うことでリアルタイムのデータに直接的にアクセスができ、関数を実行できたり(Function Calling)、外部APIを呼び出すことができます。

今回使用している Tool(google_maps=GoogleMaps(enable_widget=False)) は、Google Mapsをデータソースとして指定しています。これにより、Geminiは応答生成時にGoogle Mapsの最新データを参照し、実在する施設のみを提案できるようになります。

enable_widget は今回Falseにしていますが、Trueにした場合はLLMのレスポンスに埋め込み可能な地図ウィジェット(iframe等)が含まれます。今回のアプリケーションでは、React + Google Maps JavaScript APIで地図表示のコンポーネントを実装しており、バックエンドからは施設情報のデータのみを受け取れば十分です。そのため、ウィジェットは不要で、むしろレスポンスサイズが増えるだけなので False に設定しています。

また、位置情報(緯度・経度)を lat_lng として渡すことで、検索結果をその場所の近くに絞り込むことができます。例えば、「公園を探して」というリクエストに対して、東京駅周辺なのか横浜駅周辺なのかで全く異なる結果が返ってきます。

実装内容 - 週末お出かけプランナー

今回作成したのは、チャット形式で会話しながら週末のお出かけプランを作成するWebアプリケーションです。

ユーザーは自由な形式で要望を入力できます。例えば、現在地を取得して30分くらいで行ける場所、熱海から1時間以内でいける施設など自然言語による入力が可能です。また入力の内容に関わらず、受け取った情報が不足しているものがあれば出発地、移動時間、室内or屋外、子どもの年齢、交通手段などを段階的に質問し、最適なプランを提案する形にしています。

システムアーキテクチャと会話状態の管理

システム全体の構成は以下の図の通りです。

まずフロントエンドからのリクエストをバックエンドで受け取り、その後Vertex AIでプランを生成します。Google Maps APIで詳細情報を取得してフロントエンドに返す、というシンプルな構成です。今回、ユーザーの入力から始まり、質問や提案などフェーズが移り変わって行くため、その会話の流れを管理するためのステートを用意しています。以下の7つの状態で会話を管理しています。

class ConversationState(str, Enum):
    INITIAL = "INITIAL"
    FREE_INPUT = "FREE_INPUT"
    GATHERING_PREFERENCES = "GATHERING_PREFERENCES"
    GENERATING_PLAN = "GENERATING_PLAN"
    PRESENTING_PLAN = "PRESENTING_PLAN"
    REFINING = "REFINING"
    COMPLETED = "COMPLETED"

状態の遷移は以下の図のようになります。

例えば、ユーザーが最初に「子どもと遊びたい」とだけ入力した場合、INITIAL 状態から FREE_INPUT 状態に遷移し、出発地や移動時間などの詳細を聞いていきます。必要な情報が揃ったら GENERATING_PLAN 状態に移り、プランを生成する形となっています。

初回入力と情報収集フェーズ

まずプロンプトの紹介です。抜粋ではありますが、初回には以下のような情報を渡しています。

"""あなたは日本の家族向け週末お出かけプランを提案するアシスタントです。

重要な役割:
- 実在する場所のみを提案する(Google Mapsのデータを使用)
- 家族で楽しめる安全な場所を優先
- 移動時間と交通手段を考慮
- 子供の年齢に適した提案をする
- 具体的で実用的な情報を提供

回答のスタイル:
- 親しみやすく、わかりやすい日本語
- 具体的な施設名、住所、アクセス方法を記載
- 簡潔だが必要な情報は漏らさない
"""

あくまで私の好みで地域の博物館や科学館を積極的に提案してもらえるようにし、商業施設はあまり提案しないようにしています。これは商業施設が悪いというわけではなく、商業施設は既知のものが多いため、わざわざ検索する必要がないためです。

"""
- 観光名所だけでなく、地域の博物館、科学館、公園、図書館なども積極的に提案
- 子供が学べる施設や体験型の場所を優先
- 有名な場所と地元の人が利用する場所をバランスよく含める
- 市立・県立などの公共施設も検討対象に含める
"""

実際に入力する際、チャットベースのUIでは、すべてを自然言語入力に頼ると、ユーザーの入力負担が大きくなります。そこで、determine_missing_info というプロンプトを返す関数を作成し、ユーザーの入力からLLMを通して不足している情報を判定する仕組みを実装しました。

def determine_missing_info(
    user_message: str,
    extracted_prefs: dict
) -> list[str]:
    """
    ユーザーの入力から、プラン生成に必要な情報で
    何が不足しているかをLLMに判断させる

    Returns: ["location", "child_age", "transportation"] など
    """

この仕組みにより、文脈を理解した判断が可能になります。例えば「新宿駅から1時間で行ける動物園」という入力があれば、出発地と移動時間は含まれていると判断し、他の必要情報(室内/屋外、交通手段など)について質問を返します。すべての必要情報が揃ったら、GENERATING_PLAN 状態に遷移し、プラン生成プロンプトを使ってVertex AIを呼び出します。このプロンプトには、収集したすべての情報と移動時間の制約が含まれます。

prompt = f"""
## 条件
- 出発地: {location}
- 移動時間: 片道 {travel_time} 分以内
- アクティビティタイプ: {activity_type}

## 必須要件
1. 実在する場所のみ提案(Google Mapsで確認可能な施設)
2. 家族で楽しめる安全な場所
3. {travel_time}分以内で到達可能な場所
...
"""

このように、プロンプト内で「30分以内で到達可能な場所」と明示し、位置情報バイアス(lat_lng)と組み合わせることで、Google Maps Groundingが出発地からの適切な距離圏内の施設を提案してくれます。

なお、より厳密に移動時間を確認したい場合は、Distance Matrix APIを使用して事後的にフィルタリングする方法もありますが、今回はプロンプトベースの絞り込みで十分な精度が得られたため、APIコストを削減する観点からこの方式を採用しています。

プロンプトベースでの絞り込みを採用した理由としては、Google Maps Groundingが最初から条件に合った場所を提案してくれており、事後のフィルタリングが不要なためです。また、lat_lng と組み合わせることで十分な精度が得られています。Distance Matrix APIは、より厳密な移動時間チェックが必要な場合(例えば、交通手段による所要時間の差が大きい場合)に有効ですが、今回のケースでは十分な結果が得られてるため使用の取りやめを判断しました。

LLMからの詳細化質問

前述した不足している情報が判明した場合、それぞれの項目について選択式の質問を表示します。

質問項目 選択肢の例
室内/屋外 「屋外(公園・遊び場など)」「室内(博物館・科学館など)」「どちらでもよい」
移動時間 「30分以内」「1時間以内」「2時間以内」
交通手段 「車」「電車・バス」
子どもの年齢 「0-2歳」「3-5歳」「6-8歳」「9-12歳」「その他」

バックエンドは質問と一緒に選択肢としての配列も返す仕組みをとっています。ユーザーが「子どもと遊びたい」のような情報の少ない入力をした場合、バックエンドはLLMを使って入力内容を解析し、前述の関数を用いて不足している情報を判定します。

# LLMで不足情報を判定
missing_info = determine_missing_info(user_message, extracted_prefs)
# 例: ["activity_type", "transportation", "child_age"]

不足情報がある場合、優先順位に従って1つずつ質問します。レスポンスには先ほどのmissing_infoがもっている配列も quick_replies として返されます

{
  "response": "天候も考慮して、室内と屋外どちらがよいですか?",
  "state": "FREE_INPUT",
  "quick_replies": [
    "屋外(公園・遊び場など)",
    "室内(博物館・科学館など)",
    "どちらでもよい"
  ],
  "enriched_places": null,
  "origin_location": null
}

そしてフロントエンドは受け取った quick_replies を元に選択式のボタンとしてチャット上に描画します。

これにより、 ユーザーは選択するだけで回答することが可能です。完全な自然言語チャットは一見スマートに見えますが、実際には「何をどう答えればいいか分からない」「毎回文章を入力するのが面倒」といった問題があると私は感じています。特にモバイルでは入力負担が大きくなります。そのため、自然言語の柔軟性と、選択式の使いやすさを組み合わせたハイブリッドなアプローチを採用しました。これにより、ユーザーは自由に入力することもできますし、サクサクとボタンで答えることもできます。

ユーザーはボタンをクリックするか、自由入力で回答できます。どちらの方法でも、回答内容はバックエンドに送信され、次の質問または最終的なプラン生成に進みます。

すべての必要情報が揃うと、バックエンドは状態を GENERATING_PLAN に遷移し、Vertex AIを使ってプラン生成を開始します。

プランの生成

プラン生成フェーズではおでかけ先の候補調査の開始をします。この時、Google Maps Groundingが有効化されており、LLMはGoogle Mapsのリアルタイムデータを参照して実際に存在している施設のみを提案します。Vertex AIからのレスポンスには、自然言語の説明文とともに grounding_metadata が含まれています。このメタデータには、提案された施設のPlace IDや座標などの構造化データが含まれています。

施設情報の充実化

Google Maps Groundingは実在する施設を提案してくれますが、それだけでは情報が不足しています。施設の写真、ユーザーレビュー、営業時間、電話番号、詳細な住所といった情報は含まれていないためです。そのため、Groundingで施設名を取得した後、Google Maps Platform APIを使って詳細情報を取得する必要があります。今回のプロジェクトで実際に使用しているのは、以下の2つのAPIです。

Geocoding APIは住所を座標(緯度・経度)に変換するために使用します。

def geocode_address(self, address: str, language: str = "ja") -> dict[str, Any] | None:
    results = self.client.geocode(address, language=language)
    location = results[0]["geometry"]["location"]
    return {
        "lat": location["lat"],
        "lng": location["lng"],
        "formatted_address": results[0]["formatted_address"],
        "place_id": results[0].get("place_id"),
    }

ユーザーが「東京駅から」と入力した場合、この関数で座標に変換します。

次に、Places APIは施設の詳細情報を取得するために使用します。

fields = [
    "name", "formatted_address", "geometry",
    "rating", "user_ratings_total", "photo",
    "opening_hours", "type", "website",
    "formatted_phone_number", "review"
]

これらのフィールドを指定することで、施設の名前、住所、評価、写真、営業時間、レビューなどの情報を一度に取得できます。

これらのAPIを組み合わせて、以下のフローで施設情報を充実させています。

具体的な実装は以下の通りです。

# ステップ1: grounding_metadataからPlace IDを抽出
place_ids_from_metadata = []
if grounding_metadata and grounding_metadata.get("grounding_chunks"):
    for chunk in grounding_metadata["grounding_chunks"]:
        if "maps" in chunk and chunk["maps"].get("place_id"):
            place_ids_from_metadata.append({
                "place_id": chunk["maps"]["place_id"],
                "name": chunk["maps"].get("title", ""),
            })

# ステップ2: Place IDで直接詳細情報を取得
for place_info in place_ids_from_metadata:
    place_id = place_info["place_id"]

    details = google_maps_service.get_place_details(
        place_id=place_id,
        fields=[
            "name", "formatted_address", "geometry",
            "rating", "user_ratings_total", "photo",
            "opening_hours", "website", "formatted_phone_number",
            "type", "review"
        ]
    )

# ステップ3: 写真URLとレビューを抽出
if details.get("photos"):
    photo_reference = details["photos"][0].get("photo_reference")
    photo_url = f"https://maps.googleapis.com/maps/api/place/photo?maxwidth=400&photo_reference={photo_reference}&key={api_key}"

for review in details["reviews"][:5]:
    reviews.append({
        "author_name": review.get("author_name"),
        "rating": review.get("rating"),
        "text": review.get("text"),
    })

重要なポイントは、grounding_metadata に含まれるPlace IDを使って直接Google Mapのデータから詳細情報を取得している点です。これにより施設名での検索が不要となり、Groundingで参照した施設との完全一致の突合を行えます。そのため、同等の施設や場所が万が一あっても間違いを起こすことがありません。この処理により、AIが生成した説明文とGoogle Mapsの実データを統合した、リッチな施設情報を提供することが可能になります。

候補の提示

フロントエンドにはプランの提案時には最終的に以下の形式でデータが返されています。

{
  "response": "3件のおすすめスポットを見つけました!\n\n### 1. 上野動物園\n...",
  "state": "PRESENTING_PLAN",
  "quick_replies": null,
  "enriched_places": [
    {
      "place_id": "ChIJ...",
      "name": "上野動物園",
      "formatted_address": "東京都台東区上野公園9-83",
      "location": {
        "lat": 35.7147,
        "lng": 139.7734
      },
      "rating": 4.2,
      "user_ratings_total": 28543,
      "photo_url": "https://maps.googleapis.com/maps/api/place/photo?...",
      "opening_hours": {
        "open_now": true,
        "weekday_text": ["月曜日: 定休日", "火曜日: 9:30~17:00", ...]
      },
      "website": "https://www.tokyo-zoo.net/zoo/ueno/",
      "formatted_phone_number": "03-3828-5171",
      "reviews": [
        {
          "author_name": "山田太郎",
          "rating": 5,
          "text": "子どもが大喜びでした!..."
        }
      ]
    }
  ],
  "origin_location": {
    "lat": 35.6812,
    "lng": 139.7671,
    "address": "東京駅"
  }
}

このレスポンスには、AIが生成した自然言語の説明文(マークダウン形式)、各施設の詳細情報(写真、評価、レビュー、営業時間など)、出発地の座標と住所、次の質問の選択肢(質問フェーズの場合のみ)が含まれています。

フロントエンドはこのデータを使って、チャットメッセージとして response フィールドを表示し、enriched_places を使って各施設の詳細をカード形式で表示することが可能になりました。そして、enriched_places の座標を使って地図上にマーカーを表示します。

つまり、同じ施設情報を2つの形式で返しています。response はAIが生成した自然言語の説明(「上野動物園は子供に人気で...」など)であり、enriched_places は構造化データ(写真、評価、住所など)です。

なお、response フィールドはマークダウン形式(### 見出しや ** 太字など)で返されますが、今回のフロントエンド実装では結局のところ使用していません。マークダウンによる情報の羅列よりは構造化したデータをリッチなコンポーネントとして表示したかったのが理由です。

そして、バックエンドから受け取った enriched_places データを使って、Google Maps上に施設をマーカー(ピン)として表示します。

// enriched_placesからマーカーを生成
const newMarkers = markers.map((location, index) => {
  const markerNumber = location.index || index + 1;
  const marker = new google.maps.Marker({
    position: { lat: location.lat, lng: location.lng },
    map: map,
    title: location.name || `スポット ${markerNumber}`,
    label: {
      text: String(markerNumber),
      color: 'white',
      fontSize: '14px',
      fontWeight: 'bold',
    },
    icon: {
      path: google.maps.SymbolPath.CIRCLE,
      scale: 20,
      fillColor: '#1d4ed8', // 青色
      fillOpacity: 1,
      strokeColor: 'white',
      strokeWeight: 2,
    },
  });

  return marker;
});

このコードでは、各施設の座標(latlng)にカスタムデザインのマーカーを配置しています。マーカーには番号が振られており、提案された順番が一目で分かるようになっています。

施設の詳細確認

各施設の詳細情報パネル(Drawer)には「ここへ行く」ボタンがあり、押下すると以下の処理が実行され、Google Mapsへと遷移します。

const handleNavigate = (placeId: string, placeName: string, lat: number, lng: number) => {
  // ユーザーの出発地から目的地へのルートを含むGoogle Maps URLを構築
  let mapsUrl: string;

  if (originLocation) {
    // 会話から取得した出発地(ユーザーの開始地点)を使用
    mapsUrl = `https://www.google.com/maps/dir/?api=1&origin=${originLocation.lat},${originLocation.lng}&destination=${lat},${lng}&destination_place_id=${placeId}`;
  } else {
    // フォールバック: 出発地が設定されていない場合は目的地のみ表示
    mapsUrl = `https://www.google.com/maps/dir/?api=1&destination=${lat},${lng}&destination_place_id=${placeId}`;
  }

  // 新しいタブでGoogle Mapsを開く
  window.open(mapsUrl, '_blank');
};

この実装のポイントは、バックエンドから受け取った origin_location(ユーザーが指定した出発地)を使用し、URLパラメータに origindestination を含めることでGoogle Mapsが自動的にルートを計算し、destination_place_id を指定することで正確な施設を特定し、新しいタブで開くことでユーザーがプランナーアプリに戻りやすくなっていることです。

これにより、ユーザーは「ここへ行く」ボタンをワンクリックするだけで、自分の出発地から選択した施設までのルートが表示されたGoogle Mapsが開かれます。あとはそのまま実際のナビゲーションを開始するだけです。なお、このアプリのマップ上でルートの提案を実装しなかったのは、単純にGoogle Mapsで開いたほうが使い勝手がいいと思ったからです。

近所の子ども向けレストラン候補

最後に施設の情報が載っているドロワー内には、訪問先の近くにある子ども向けレストランを提案する機能も実装しています。パネル内の「周辺の子ども向け飲食店を見る」ボタンを押下すると、とその施設の周辺1km以内のレストランを検索する仕組みです。

ただし、そのまま表示しているわけでなく、バックエンド側では、Places APIで周辺のレストランを検索した後、複数段階のフィルタリングを行います。

# フィルタリング1: 除外タイプの設定
exclude_types = {"bar", "night_club", "casino", "liquor_store"}
exclude_keywords = ["居酒屋", "バー", "飲み屋", "立ち飲み", "スナック"]

# 除外タイプに該当する場合はスキップ
if place_types & exclude_types:
    continue

# 施設名に除外キーワードが含まれる場合はスキップ
if any(keyword in place_name for keyword in exclude_keywords):
    continue

施設名だけでは判断できない場合もあるため、ユーザーレビューの内容も分析します。

# レビューに居酒屋キーワードが含まれていないかチェック
is_izakaya = False
for review in raw_reviews[:5]:
    review_text = review.get("text", "")
    if any(keyword in review_text for keyword in exclude_keywords):
        is_izakaya = True
        logger.info(f"Excluding {restaurant['name']} - izakaya keywords found in reviews")
        break

if is_izakaya:
    continue

評価(rating)を基準に、価格帯やレストランの種類に応じてボーナススコアを付与し、家族連れに最適なレストランを優先的に表示します。

# 基本スコアは評価から開始
score = rating

# 価格帯のボーナス/ペナルティ
if price_level is not None:
    if price_level <= 2:
        score += 1.0  # 手頃な価格にボーナス
    elif price_level >= 3:
        score -= 0.5  # 高価格にペナルティ

# ファミリーレストランに大きなボーナス
family_restaurant_keywords = ["ファミレス", "ガスト", "サイゼリヤ", "ジョナサン", ...]
if any(keyword in place_name for keyword in family_restaurant_keywords):
    if child_age is not None and child_age <= 5:
        score += 2.0  # 幼児向けに大きなボーナス
    elif child_age is not None and child_age <= 10:
        score += 1.5  # 小学生向けにボーナス
    else:
        score += 1.0  # デフォルトボーナス

このスコアリングにより、子どもの年齢を考慮した最適なレストラン候補を提案してます。例えば、0-2歳の場合は設備が整ったファミリーレストランが優先され、年齢が上がるにつれて選択肢が広がります。しかし、実際にはまだまだこちらに関しては精度が低く、大人向けのレストランが表示されてしまうことが多いのが現状です。

うまくいかなかったこと・残っている課題

自分の理想通りのものはまだ作れたわけではなく、大体80%くらいの完成度です。例えば、家族向けのレストランを提案してもらう機能を実装しましたが、まだ一部居酒屋が含まれてしまったり、子どもには少し早いお店が提案されている問題がありまだ解決しきれていません。

また、もう1つの課題としてChatGPTのように、AIの応答が文字が流れるように表示される「ストリーミング表示」を実装したいと考えていました。

Vertex AIのSDKにはストリーミング用のメソッドが用意されているのですが、純粋に実装の時間が足りず現時点ではまだ未対応となっています。またチャットとして表示する場合はストリーミングの対応ができたとしても、実際におすすめのお出かけ先として提案しているリストのコンポーネントとして表出させている場所をどのように見せてあげるのがいいのかも悩んだポイントです。純粋にローディングだけでもいい気がしたのは確かですが、なるべくユーザーの体験にはこだわりたいとは考えています。

まとめ

以上です。Google Maps Groundingを使ってお出かけプランナーを作成してみました。Groundingにより架空の施設を提案される心配がなくなり、Google Maps APIとの連携でLLMと位置情報、場所の情報を用いたリッチな情報提供をすることができました。まだ課題を潰しきれてないのと実装の時間の関係でリリースには至れてませんが、個人的には便利なものが出来あがったかなと感じています。

今後は、ストリーミング対応の完成によるユーザー体験の向上、レストランフィルタリングの精度向上などを行っていきたいと考えています。複数日程での旅行への対応、プラン保存機能による後での見返しなどもあったら面白いかもしれません。この記事が、Google Maps Groundingに興味がある方の参考になれば幸いです!