🌏

個人で地図にAIをマッピングしてみた #5 コンテキストプロンプト活用 & 地図アプリ連携

に公開

1. はじめに

前回の記事(個人で地図にAIをマッピングしてみた #4 AWS API 構築編)では、地図AI向けに大規模なWikipediaデータを元に構築したナレッジベースを自然言語で検索できるAPIとして公開する方法を紹介しました。

今回はその続きとして、

  • 生成AIにナレッジデータを文脈として投入する方法
  • Google Maps APIと連携して、地図上からクエリ検索を実現する方法
    の2つを軸に、よりユーザー体験に近づいた応用例を解説します。

このロジックによって、次のような体験が実現できます。

📱「このあたりの有名な歴史的建造物って何かある?」

ユーザーの現在地に基づいて、Wikipediaベースのナレッジから関連情報を抽出し、生成AIでわかりやすく整形して応答。

つまり、「自然言語クエリ」 × 「地理情報」 × 「分散ナレッジベース」 を組み合わせることで、地図アプリやチャットアシスタントなどに応用可能な検索体験を実現します。

このAPIは以下のようなAWS構成で構築しました。

  • API Gateway + Lambda によるサーバレスなAPI設計
  • Bedrock による自然言語Embedding変換
  • S3に分散保存されたナレッジデータのベクトル類似検索
  • コスト効率を意識したスケーラブルな構成

この記事では、これらの仕組みを実装ベースで具体的に紹介しながら、個人開発でも本番運用可能な検索APIを構築するまでのプロセスを詳しく解説していきます。


2. AWS上の全体構成と処理フロー

本章では、地図AIにおけるWikipediaデータの処理をAWS上でどのように構成しているかを紹介します。

2.1 構成図

以下の図は、地図アプリからデータを取得することも想定し、ローカルで開発したバッチ処理ロジックをAWSに展開し、実際にスケーラブルに動かすための全体構成を示しています。

今回は図の下半分、API連携の構成が中心となります。
上半分のバッチ処理のデプロイと実行に関する構成については、前回の記事(#3 AWSバッチ処理編)で詳しく解説しています。

構成図


2.2 クエリ応答APIのざっくりとした構成

地図アプリなどのクライアントが位置情報と自然言語クエリを送信すると、以下のような流れで応答が返ります。

  1. ユーザーのGETクエリは API Gateway を通じて、LambdaBedrock(Embedding / ChatAI) に連携
  2. 前回のバッチ処理で構築した「地理情報付きナレッジベース分散データ(S3)」を参照
  3. このナレッジベースは、位置情報のハッシュ値(H3インデックス)をキーとしているため、クエリの位置情報から対応する大域的データを取得
  4. 対応する大域的データに対して Bedrock Embedding を実施し、入力クエリのEmbeddingと近傍探索(ベクトル類似検索)を行う
  5. 最も類似したデータを抽出し、APIのレスポンスとして返却

この仕組みによって、自然言語クエリ × 位置情報 に応じた、文脈的に関連性の高い情報検索が可能になります。


2.3 処理全体の流れ

  1. クライアントから送信された 緯度 (lat)、経度 (lon)、検索半径 (radius)、自然言語クエリ (query) を受け取る
  2. LocationModel を使って、指定範囲内にある地理ナレッジデータ(Wikipedia)をS3から取得
  3. EmbedModel によって、自然言語クエリと地理ナレッジをベクトル化し、類似度を計算
  4. 最も類似する記事を抽出して、JSON形式で応答する

3. 生成AIプロンプトへの組み込み

検索APIによって、ユーザーの位置情報と自然言語クエリに応じたWikipediaベースのナレッジを取得できるようになりました。
しかし生成AI(大規模言語モデル)は、緯度・経度などの数値的な位置情報に基づいて学習されているわけではありません。
そのため、「この場所に関する情報はこれです」といった地理的な文脈(=コンテキスト)を明示的に与える必要があります。
GPTなどの生成AIは「緯度 35.6, 経度 139.7」などの座標から直接意味を導き出せるわけではなく、
その座標に関連付けられた**文書情報(例:地名、歴史、施設など)**をあらかじめプロンプトに与える必要があります。

つまり、位置にひもづくナレッジをプロンプトとして渡し、その上でユーザーのクエリに応じて応答を生成させるのが最適な解法です。

このプロンプト設計を柔軟に管理するために、LangChainを活用します。
以下は、LangChain の ChatPromptTemplate を使って、「地理情報に基づく応答」を生成する一連の流れです。


3.1 LangChainでプロンプトを構築する

LangChainを使うことで、以下のようなメリットがあります。

  • システムメッセージとユーザークエリを構造的に分離して扱える
  • テンプレートによって再利用性が高いプロンプト設計が可能
  • 複数のプロンプト(チャット形式)やツール呼び出しにも拡張しやすい

ベースとなる構成は以下のような流れです。

  1. 検索APIで取得したWikipediaナレッジ
  2. LangChainでプロンプトを組み立て
  3. BedrockなどのLLMに問い合わせ
  4. 自然文で要約された応答を取得

以下は、LangChainの ChatPromptTemplate を使って、位置に基づいたWikipedia情報をプロンプトに含め、自然文での応答を生成する例です。

from langchain_core.prompts import (
    SystemMessagePromptTemplate,
    HumanMessagePromptTemplate,
    ChatPromptTemplate,
)
from langchain_community.chat_models import BedrockChat  # Bedrock利用時の例

# LLMクライアントの初期化(Bedrock Chatモデルを使用)
chat_llm = BedrockChat(
    model_id="amazon.titan-text-lite-v1",
    region_name="ap-northeast-1"
)

# プロンプトテンプレート定義
chat_prompt = ChatPromptTemplate.from_messages([
    SystemMessagePromptTemplate.from_template(
        "あなたは地図アプリのナビゲーションAIです。"
        "ユーザーの位置と周辺の情報をもとに、自然な日本語で簡潔に説明してください。"
    ),
    HumanMessagePromptTemplate.from_template(
        "以下は、現在地周辺に関するWikipediaの情報です:\n\n"
        "---\n{context}\n---\n\n"
        "この情報を参考にして、「{query}」という質問に対して、"
        "200文字以内でわかりやすく答えてください。"
    )
])

# 検索APIから取得したWikipedia本文とユーザーの自然言語クエリ
context = retrieved_text  # 例: "◯◯博物館は1923年に..."
query = user_query        # 例: "この辺で有名な建築物は?"

# プロンプトを整形
prompt_message = chat_prompt.format_prompt(context=context, query=query)

# LLMに問い合わせて自然文を生成
chat_response = chat_llm.invoke(prompt_message.to_messages())

# 応答結果
result_text = chat_response.content

  • chat_llmは、他の LLM(OpenAI、Claudeなど)に差し替え可能です。
  • プロンプトでの回答文の指示言語を変更することで、別の言語でも可能です。

3.2 生成結果の表示例

下記ように、文脈に応じた簡潔でユーザーにとって意味のある応答が得られます。

この地域には「日本のあかり博物館」があります。和ろうそくや行灯など、伝統的な照明器具の展示が特徴です。

4. 地図アプリから検索APIを呼び出す

生成AIによる文脈応答の準備が整ったところで、次は実際の地図UIとの連携に進みます。
今回は、Google Maps JavaScript API を使って、マップ上のPOI(Point of Interest)をクリックしたときに緯度経度を取得し、その位置に対する検索APIを呼び出す仕組みを実装します。


4.1 地図クリック時に緯度・経度を取得する

Google Maps API では、map.addListener("click", ...) を使うことで、ユーザーが地図上をクリックしたときの位置情報を取得できます。

以下は、マップ上の任意の地点をクリックしたときに、検索APIを呼び出して情報を取得する流れの抜粋です。

import { GoogleMap, useJsApiLoader, Marker, InfoWindow } from "@react-google-maps/api";

const param = {...}
const { isLoaded } = useJsApiLoader(param);

const onLoadGoogleMap = useCallback((map) => {
  map.addListener("click", (element) => onClickPoi(element));
}, []);

const onClickPoi = (element) => {
  const query = "歴史的建造物"; 
  const apiUrl = `https://your-api-url/query?lat=${element.lat}&lon=${element.lng}&radius=1500&query=${encodeURIComponent(query)}`;

  try {
    const response = await fetch(apiUrl);
    const result = await response.json();

  } catch (error) {
    console.error("検索API呼び出しに失敗しました:", error);
  }
}

return (
      <GoogleMap
        onLoad={onLoadGoogleMap}
      />
);

4.3 フローのイメージ

ユーザーが地図上の任意の地点をクリックすると、緯度経度が取得できて、その位置情報に基づくナレッジを取得できます。

  1. 緯度・経度が取得される
  2. その座標をもとに検索APIが呼び出される
  3. 該当地点に関連するWikipediaナレッジ(+生成AIによる自然文)が地図上に表示される

この機能を利用することで、次のようなUXになります。

  1. マップ上で温泉街エリアをクリック
  2. 「この地域には江戸時代から続く◯◯温泉があります。現在も日帰り入浴が可能です。」

5. まとめ 〜地図 × 生成AI の個人開発を振り返って〜

この「個人で地図にAIをマッピングしてみた」シリーズでは、地理情報と生成AIを組み合わせて、自然言語で検索できるインタラクティブな体験をゼロから構築してきました。
最終回となる今回は、シリーズ全体を振り返りながら、学べたこと・やれたことをまとめます。

5.1 ここまでやってきたこと

  1. Wikipediaデータをベースにしたナレッジ構築

    • 緯度・経度付きのデータをH3で空間分割
    • AWSバッチ処理で並列変換してS3に保存
  2. 自然言語 × 位置情報 の検索APIを構築

    • Lambda + API Gateway のサーバレス構成
    • BedrockによるEmbedding検索で類似ナレッジを抽出
  3. 生成AIで文脈に応じた応答を生成

    • LangChainを使ってプロンプトを構築
    • ナレッジを「位置にひもづくコンテキスト」として渡す
  4. Google Maps APIとの統合

    • 地図上のPOIをクリック → 緯度経度を取得
    • その座標から検索APIを呼び出し、AIが自然文で解説

5.2 この構成で実現できたこと

  • 緯度・経度の意味を自然言語で説明してくれる地図AI
  • 「この近くの歴史的建物ってなに?」に応える体験
  • バックエンドもサーバレス、しかも個人開発レベルで構築可能

5.3 ひとまずここで一区切りです

今回でこのシリーズはいったん完結としますが、「生成AIと地図情報を組み合わせた応用」は、まだまだ広がる余地があります。

たとえば

  • スマホアプリへの組み込み
  • 対話型の観光案内チャットUI
  • 音声アシスタントと組み合わせた“その場で答える地図体験”

など、次のフェーズへつなげるヒントは無限です。


5.4 本シリーズを通して

「生成AIを活用した検索体験って、自分でもここまで作れるんだ」と感じてもらえたなら、開発者としてこれ以上ない喜びです。
ここまで読んでくださり、本当にありがとうございました!
このシリーズが、皆さんのプロジェクトにとっても何かのヒントになれば幸いです。

Discussion