見出し画像

位置情報と連動した会話AIを作る!miibo✖️Google Places APIの繋ぎ方

この記事では、miiboとGoogle Places APIを繋ぐことで、ユーザの現在地に即した情報を提供する、GPTベースのチャットボットを作成する方法を紹介します。

また、今回は通常のチャットではなく、サービスを模したプロトタイプツールまで作ります。そこで、ツールを作るにあたって重要になるmiibo APIについてもご紹介いたします。


※本記事は、会話型AI構築プラットフォームmiiboを開発する株式会社miiboの提供です

何が作れるの?

まずは、2分ほどの簡易的なプロトタイプの動画をご覧ください!

デモ動画では、会話AIとチャットをしていく中で、必要に応じて返答内容を地図上に表示しています。これにより、チャットだけでは難しかったユーザ体験を提供することが出来ます

GoogleMapsPlatformを使うと、場所の情報だけでなく、レビューやルートなど様々な情報を同様に表現することができます

この様々な情報を使うことで、例えば、
観光客と会話をしながら、地図を使いながら観光ルートを提案する
過去の移動履歴と現在地・歩行速度から、最適なお店を提案する
といった、内容も実現できそうです。

皆さんのお仕事やアイデアの中にも、使い所があるのではないでしょうか?
ぜひ、ご自身のシチュエーションにあった使い道を考えてみてください!

どうやって作るの?

では、どのようにこのプロトタイプを作るかを解説していきます。
プロトタイプは以下の構成で作成されています。

構成のイメージ

それぞれのポイントでは、以下のような処理の流れになっています。
① Webツールから、miibo APIを介してmiiboと対話する
②③ Function Callingが発火したタイミングで、lambda経由でPlaces APIにリクエストする
④ 結果をmiiboのプロンプトに埋め込み、成形してからWebツールに返す。Webツールでは、返ってきた内容を地図上に表示する。

具体的に、個々の要素についてみていきましょう。

Google Places API

まずは公式ドキュメントに従って、Places APIをセットアップしましょう。

今回のプロトタイプでは、テキスト検索(新規)を使います。
パラメーターとしては、FieldMask, textQuery, locationBiasを設定します。

  • FieldMask:displayName.text (店名), location (位置), types (店舗タイプ)

  • textQuery:miiboで自動抽出される検索クエリー

  • locationBias:現在地を中心とした800m圏内

{
    "languageCode":"ja",
    "locationBias":{
        "circle":{
            "center":{
                "latitude":"35.681236",
                "longitude":"139.767125"
            },
            "radius":800
        }
    },
    "textQuery":"美味しい ランチ"
}

ここで取得した API KEY は、後述のブラウザApp側でも使用するため控えておきます。

AWS Lambda

詳細な設定方法に関しては、下記の記事をご参照ください。

メインスクリプトは、シンプルな通信の仲介を行っています。

import requests
import json

URL = "https://places.googleapis.com/v1/places:searchText"
HEADERS = {
    "X-Goog-Api-Key": "<API_KEY>",
    "X-Goog-FieldMask": "places.displayName.text,places.location,places.types"
}

def lambda_handler(event, context):
    body = json.loads(event["body"])
    params = {
      "languageCode":"ja",
      "locationBias": {
        "circle": {
          "center": {
            "latitude": body["latitude"],
            "longitude": body["longitude"]
          },
          "radius": 800
        }
      },
      "textQuery": body["query"]
    }

    response = requests.post(URL, json=params, headers=HEADERS)
    
    return {
        "statusCode": 200,
        "body": response.text
    }

miibo

プロンプトの設定

プロンプトエディタで、ユーザから現在地の緯度経度を受け取った時に、miiboのステートという記憶機能に入れるようにしています。

miiboでは、メッセージに #{key:value} という形式の文字列が含まれる場合、ステートにkeyとvalueの形式で保管されます。

プロンプトエディタの設定例

レポートのユーザ一覧では、ユーザ毎に記憶したステートが何かを確認することができます。

Google Place APIの呼び出しにはFunctionCallingを使い、適切なタイミングで発火するようにします。ステートに記憶した現在地と、NotionAPIの記事でも紹介した検索クエリーをパラメーターに入れてPOSTしています。

Webhookの設定例

また、プロンプト挿入時のプレフィックスを見ていただくと分かるように、APIの返答内容をフォーマットしています
JSON形式の場合、すべてが出力されるまで表示することができません。
TSVやjsonlなどの細かい単位で扱える形式に変換することで、ユーザを待ちぼうけにさせず、UXを損なわないツールを作ることに繋がります

miibo APIの設定

最後にAPIを使えるようにしましょう。
外部サービス連携 > APIを利用して会話やデータの入稿を行う から設定できます。

APIの設定項目

APIを有効にすると、API KEYエージェントIDが発行されます。Webツールから呼び出す際に使うため、こちらも控えておきます。

APIの詳細な仕様に関しては、ドキュメントをご確認ください。

Webツール

最後に、ユーザが触るWebツールを簡易的に作成しましょう
今回、Webツールの大部分の開発はGPTで行いました

ここでは、重要な要素である、miiboAPIとの通信地図の処理に関して解説します。

miiboAPIとの通信

下記の例では、miiboAPIとstream通信を行う処理になっています。
地図上に表示する内容には、返答の1行目に特定文字列を入れるようにしています。(前述のWebhookの設定参照)
この文字列が入っている場合に、地図上の表示に切り替えるような処理になっています。
※ 実際のツールでは、文字列での場合分けは事故が発生する可能性があるため、別の方法を検討してください。

// AIの返答取得(stream)
async function getAIResponse(message) {
    const params = {
        api_key: "<MIIBO_API_KEY>",
        agent_id: "<MIIBO_AGENT_ID>",
        uid: "user001",
        stream: true,
        utterance: message
    };

    try {
        const res = await fetch("https://api-mebo.dev/api", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(params),
        });

        const reader = res.body.getReader();
        const decoder = new TextDecoder();

        const read = async () => {
            const { done, value } = await reader.read();
            if (done) return;

            let dataString = decoder.decode(value).split("\n").filter(x => x != "");

            try {
                let responseData = JSON.parse(dataString[dataString.length - 1]);
                let responseLines = responseData.bestResponse.utterance.split("\n").filter(x => x.trim() != "");

                if (responseLines[0] === "placedata:") {
                    // 1行目は指定文字を入れているため、2行目から処理
                    if (responseLines.length>=2) {
                        if (markers.length != responseLines.length-1) {
                        	// Mapのマーカー作成
                            addPlacesToMap(responseLines[responseLines.length-1]);
                        } else {
                        	// マーカーの内容を都度更新
                            updateMarkerInfo(responseLines[responseLines.length-1]); 
                        }
                    }
                } else { 
                    // update chat message
                }
            } catch(e) {
                console.log(e);
            }
            return read();
        };

        await read();
        reader.releaseLock();
    } catch(error) {
        console.log("Error fetching AI response: ", error);
    }
}

地図の処理

Maps JavaScript APIを使った実装をしています。

マーカーを作成し、miiboからのStreamでの返答が来るたびに、マーカー内のポップアップの内容を修正しています。

/* 地図の処理 */
let markers = [];
let infoWindows = [];

// 初期化
function initMap() {
    map = new google.maps.Map(document.getElementById("map"), {
        center: { lat: 35.681236, lng: 139.767125 },
        zoom: 15,
        mapTypeId: "roadmap",
        gestureHandling: "greedy",
    });
}
window.initMap = initMap;

// マーカーの追加
function addPlacesToMap(dataString) {
        // TSVのパース
    const dataParts = dataString.split("\t")
    if (dataParts.length != 2) return;

        // マーカーの作成
    const latlng = dataParts[0].split(",")
    const position = {lat: parseFloat(latlng[0]), lng: parseFloat(latlng[1])};
    const marker = new google.maps.Marker({
        position: position,
        map: map
    });

        // ポップアップの作成
    const infoContent = `<div class="marker-title">${dataParts[1]}</div><div></div>`;
    const infoWindow = new google.maps.InfoWindow({content: infoContent});

    infoWindow.open(map, marker);
    markers.push(marker);
    infoWindows.push(infoWindow);
}

// マーカーの追記
function updateMarkerInfo(dataString) {
    const index = markers.indexOf(markers[markers.length-1]);
    if (index !== -1) {
        const dataParts = dataString.split("\t");
        const infoContent = `
            <div class="marker-title">${dataParts[1]}</div>
            <div>${dataParts[2] || ""}</div>
        `;
        infoWindows[index].setContent(infoContent);
    }
}

// マーカーの削除        
function clearMarkers() {
    markers.forEach(marker => marker.setMap(null));
    markers = [];
    infoWindows = [];
}

おわりに

この記事では、miiboGoogleMapsPlatformを繋ぐ方法と、位置情報のデータを効果的に見せるWebツールの例について紹介しました。

miibo APIを使うことで、非常にリッチな対話をユーザに提供することができます。ぜひ使ってみてください。

参考にしてみてください!

この記事が気に入ったらサポートをしてみませんか?