自然言語と位置情報をつなぐ、GeoAIエージェントの作り方

BLOG

はじめに

近所を歩いていて、古い石碑の前を通り過ぎたことはありませんか。

その中には、昔その土地をおそった洪水や土砂崩れのことを刻んだ碑があります。「自然災害伝承碑」と呼ばれる碑で、国土地理院が全国の碑の位置と説明文を公開しています。「ここまで水が来た」「この斜面が崩れて、人が亡くなった」。その土地で実際に起きたことの記録です。

でも、碑の教えを知っただけでは、まだ半分です。同じ災害がもう一度起きたら、今いる場所からどこへ逃げればいいのか。そこまでつながって、はじめて自分の身を守る情報になります。

そこで、この 2 つを 1 回の質問でつなぐ AI エージェントを作ってみました。AI エージェントは、質問に答えるために、必要な検索を自分で選んで実行するチャットボットです。日本語で、たとえばこう聞きます。

大雨で斜面が崩れた教訓を伝える碑はどこにある? その災害のとき、近くのどこへ逃げればいい?

エージェントは、まず教訓の内容から碑を探します。次に、見つけた碑のデータから災害の種類を読み取ります。そして、その種類に対応した避難場所を、現在地の近くから探して答えます。

このように、場所の情報と AI を組み合わせる考え方を GeoAI(ジオ AI)と呼びます。「近く」は距離を測れば分かります。でも「斜面が崩れた教訓」は、文章の意味を読まないと分かりません。GeoAI は、この 2 つを一緒に扱います。

今回のエージェントでは、1 つ目の検索で見つけた碑の「災害の種類」が、そのまま 2 つ目の検索の条件になります。

Elastic は、Elasticsearch という検索エンジンを中心にした製品です。Elasticsearch は、大量のデータを入れて、すばやく探すための仕組みです。Web サイトの検索のほか、システムの監視(オブザーバビリティ)や、セキュリティの分析にも使われています。Kibana は、そのデータを扱うためのWebベースのユーザーインターフェースです。グラフや地図を作ったり、AI エージェントを作ったりできます。今回のエージェントも、Kibana の中で動いています。なお、今回のエージェント環境には、インフラの構築や管理が不要な Elastic Serverless を使用しています。

今回 Elastic を使ったのは、自然言語で見つけた情報の構造化された属性を、そのまま次の地理空間検索の条件にできるからです。

もう 1 つの理由は、同じ基盤をほかの業務にも使えることです。GeoAI のために用意した Elastic の上で、システムの監視やセキュリティの分析もできます。1 つの目的のためだけの道具を増やさずに済みます。

作ったもの

Kibana の中で動く、防災の AI エージェントです。利用者が日本語で質問すると、近くの「自然災害伝承碑」と「指定緊急避難場所」を探して答えます。

出典:国土地理院ウェブサイト (https://www.gsi.go.jp/bousaichiri/denshouhi.html)
  • 自然災害伝承碑は、はじめにで紹介した、昔の災害の様子や教訓を刻んだ石碑です。
  • 指定緊急避難場所は、市町村が災害の種類ごとに指定した避難先です。たとえば「洪水のときは使えるが、土砂災害のときは使えない」のように、施設ごとに対応する災害が決まっています。

検索の中心は、利用者の現在地です。現在地は、ブラウザの Chrome 拡張で取得して、チャットに渡します。はじめにの質問に答えるには、碑を探す検索と、避難場所を探す検索の 2 つが必要になります。そして、1 つ目の検索の結果を使って、2 つ目の検索の条件を決めます。この PoCで確かめたかったのは、このつなぎ目がうまく動くかどうかです。

誰のための記事か

GeoAI に興味がある、いろいろな業界の方に読んでいただきたいと思っています。特に、次のような方です。

  • GeoAIという言葉は知らないけれど、店舗、物件、設備、防災など、場所に関わる課題をAIでなんとかできないかと考えている方
  • GeoAI は知っているけれど、Elastic でもできることは知らなかった方

防災は実例の一つです。同じ考え方は、ほかの業界でも使えます(7 節)。Elastic を初めて知る方にも読めるように、用語はそのつど説明します。

連載は2回です。

連載テーマ
第1回(この記事)考え方と全体像。自然言語の検索結果を、地理空間検索につなぐ
第2回エージェントの中身と、現在地の渡し方。ES|QL のツール、semantic_text、意味の点数と並び順、現在地の 7 分の期限と削除

1. 完成形を先に見る

筆者は川口市内から現在地を送り、エージェントに次の質問をしました。

現在地から半径 30km 以内で、大雨で斜面が崩れて人が亡くなったという教訓を伝える碑を探してください。そのうえで、その碑の教訓に関係する災害に対応した指定緊急避難場所を、現在地から 5km 以内で教えてください。

エージェントの動きは、次の3段階でした。

  1. 意味で碑を探す。 「斜面が崩れて人が亡くなった」という言葉の意味に近い碑を探します。一番近くで見つかったのは「園部おまわりさんありがとうきねん碑」(東京都北区、現在地から約 7km)です。1958 年の狩野川台風の記録で、説明文には「大雨で区内の急斜面60数か所で土砂が崩れ、13名の命が奪われた」とあります。質問の「亡くなった」は、説明文では「命が奪われた」と書かれています。言葉が違っても、意味が見つかりました。
  2. 碑のデータから、災害の種類を読む。 この碑のデータには、災害の種類として「土砂災害」と「洪水」の 2 つが登録されています。エージェントは、質問(斜面が崩れた)に合う「土砂災害」を選びます。
  3. その種類で、避難場所を絞る。 「土砂災害」に対応する指定緊急避難場所を、現在地から 5km 以内で、近い順に探します。

回答の中には、碑のデータを使って避難場所を探したことが、そのまま書かれていました。

1件目の碑の登録災害種別「土砂災害」を使って、現在地から半径5km以内の指定緊急避難場所を検索しました。

ここで大事なのは、2 段階目です。利用者の質問には「土砂災害」という言葉が入っていません。「土砂災害」は、1 つ目の検索で見つけた碑のデータから来ています。その値が、2 つ目の検索の条件になっています。この記事では、この流れを「属性の引き継ぎ」と呼びます。仕組みは 5 節でくわしく説明します。

地名では検索できない。それは意図的です

このエージェントは、利用者の現在地だけを検索の中心にします。「赤羽付近で探して」「神戸の市役所の近くは?」のように地名を伝えても、検索はしません。地名や住所を座標(緯度・経度)に変える処理をジオコーディングと言いますが、これを意図的に入れていません。理由は 3 つです。

  1. 地名は、1 つの点に決まらないことが多いからです。 「赤羽付近」は、どこを中心にするか決められません。同じ名前の地名も全国にあります。たとえば「中央区」は、東京都にも大阪市にもさいたま市にもあります。
  2. 中心を間違えると、間違った避難場所を案内してしまうからです。 しかも、利用者はその間違いに気づきにくいです。防災の道具では、これが一番危険です。
  3. ジオコーディングの正しさを確かめること自体が、大きなテーマだからです。 筆者は以前、住所を座標に変える検証をしました(「[日本の住所の表記揺れをElasticsearchで検証しようとして、やめました](【URL を入れる】)」)。そのとき一番の壁になったのは、検索の仕組みではありませんでした。「どの座標が正しいか」を決める正解データを用意することでした。この記事の主題は属性の引き継ぎなので、ここは切り離しました。

現在地は、端末が測った緯度・経度をそのまま使います。そのため、中心がぶれず、記事の話もぶれません。

2. GIS の言葉と Elastic の言葉

地図のデータを扱う仕組みを、GIS(地理情報システム)と呼びます。GIS でよく使う言葉が、Elastic では何にあたるかを並べます。

GIS での言い方Elastic での言い方この記事での例
点のデータgeo_point 型のフィールド碑や避難場所の位置
レイヤー、データセットインデックス伝承碑のインデックス(表)、避難場所のインデックス(表)
属性フィールド(表の列)名称、災害種別、説明文
半径 n km 以内ES|QL の ST_DISTANCE現在地から 5km 以内
地図に重ねて表示Kibana Maps のレイヤー現在地、碑、避難場所の3レイヤー
(GIS にはあまりない)semantic_text(意味検索)碑の説明文を、意味の近さで探す

距離は、地球の丸みを考えた 2 点間の直線距離です。道路距離や徒歩距離ではありません。

最後の行の semantic_text は、文章を「意味を表す数字の並び(ベクトル)」に変えて保存する型です。これで、言葉が違っても意味が近い文章を探せます。Elastic では、同じ表の中に「位置」「属性」「文章の意味」を一緒に持てます。 これが、この記事の話の土台です。

3. 設計の原則:数字と事実は Elasticsearch、言葉は LLM

このエージェントは、LLMと Elasticsearch が役割を分けて動きます。

仕事担当
碑や避難場所までの距離を計算し、指定した半径内か調べるElasticsearch
避難場所に、指定した災害種別が登録されているか調べるElasticsearch
質問と意味が近い碑を、回答の候補として探すElasticsearch
候補の説明文を読み、質問に合う碑を選ぶLLM
碑に災害種別が複数あるとき、質問に合うものを選ぶLLM
足りない条件を聞き返し、検索結果を説明するLLM

たとえば「大雨で斜面が崩れた教訓」を探すと、碑の候補に「土砂災害」と「洪水」の両方が登録されていることがあります。どちらを次の検索に使うかは、LLM が質問と碑の説明文を読んで選びます。選んだ「土砂災害」に対応する避難場所かどうか、現在地から 5km 以内かどうかは、Elasticsearch がデータを使って判定します。

LLM に自由に災害種別の名前を作らせると、データにない「がけ崩れ」などを検索条件にして、該当施設を見落とすおそれがあります。そこで、碑のデータに登録された種別から選ぶよう指示しています。碑と避難場所で名前が違う「火山災害」と「火山現象」だけは、指示文に書いた対応に従って置き換えます(5-7節)。利用者の質問や碑の説明から種別を選べない場合は、推測せずに聞き返します。

ただし、LLM が選ぶこと自体に間違いの余地は残ります。この PoC は、過去の災害の記録と、災害種別に対応して登録された避難場所を探すための実験です。避難の必要性や経路の安全を判定するものではありません。避難場所の開設状況も分からないため、その点を回答に添えています。

4. 部品の紹介

この仕組みは、4つの部品でできています。ここでは役目だけを紹介します。中身は第2回で説明します。

部品このPoCでの役目
semantic_text碑の説明文を意味で探せるようにする。インデックスの定義に書くだけで、ベクトルへの変換は Elastic 側が行う
ES|QLElasticsearch の問い合わせ言語。意味検索、属性の絞り込み、距離の計算を1本の問い合わせで書ける
Agent BuilderKibana の中で AI エージェントを作る機能。ES|QL の検索を「ツール」として持たせ、日本語の質問に答えさせる
Kibana Maps検索の結果を地図に重ねて、位置関係を目で確かめる

現在地は、Chrome 拡張でブラウザから取得し、ローカルのサーバーを通して会話に添付します。現在地は伝承碑や避難場所のデータには保存しません。現在地には 7 分の有効期限を付け、期限を過ぎたら検索に使いません。また、ローカルのサーバーが動いている間は、取得から 7 分後に会話ごと削除を試みます。サーバーを止めていた場合は、会話を手で消す必要があります。詳しくは第2回で説明します。

5. 属性の引き継ぎの仕組み

冒頭で紹介した「大雨で斜面が崩れた碑と、その災害に対応する避難場所を探す」という質問を例に、二つの検索の間で何を渡したのかを見ていきます。

5-1. まず「ツール」とは何か

この PoC では、検索の手順を ES|QL であらかじめ作り、Agent Builder にツールとして登録しました。エージェントは検索文を一から書くのではなく、質問や前の検索結果から必要な値を取り出して、ツールに渡します。

たとえば避難場所を探すツールには、次の値を渡します。

渡す値例出どころ
災害種別土砂災害先に見つけた碑の登録データ
半径5km利用者の質問
検索の中心現在地の緯度・経度会話に添付した現在地

ツールは、受け取った値を使って「災害種別が一致し、現在地から半径内にある避難場所を、近い順に最大10件返す」という、あらかじめ決めた検索を実行します。値を入れる場所を引数と呼びます。引数に何を入れるか、どのツールを使うかはLLMが決めます。距離の計算や、登録された種別との一致判定はElasticsearchが行います。

このエージェントには、自分で作った検索ツールが三つあります。ここでは、碑を意味で探すツール1と、避難場所を探すツール2を使います。碑を単純に近い順で探すツール3は、第2回で紹介します。

5-2. ツール 1:質問に近い内容の碑を探す

利用者は「現在地から半径30km以内で、大雨で斜面が崩れて人が亡くなったという教訓を伝える碑」と質問しました。ツール1には、次の値が渡されます。

引数出どころ値
query(探す内容)利用者の質問から、碑についての部分を取り出す大雨で斜面が崩れて人が亡くなったという教訓
radius_km(半径)利用者の質問30
latitude 、longitude(検索の中心)会話に添付された位置情報現在地

「そのうえで避難場所を教えてください」という後半は、碑の内容を探すための文には含めません。一方、「斜面が崩れて人が亡くなった」は「土砂災害」という一語に言い換えず、利用者の言葉をできるだけ保ちます。意味検索は、説明文に同じ単語がなくても、内容の近い碑を候補にできるからです。

ツール1は、現在地から30km以内にある碑のうち、質問に意味が近いものを最大10件返します。ここで返るのは回答の候補で、10件すべてが質問に合うという意味ではありません。今回の候補の一つは「園部おまわりさんありがとうきねん碑」でした。

データの項目この碑に登録された内容の例
名称園部おまわりさんありがとうきねん碑
説明文「急斜面60数か所で土砂が崩れ、13名の命が奪われた」など
災害種別土砂災害、洪水
現在地からの距離約7km(公開用に1km単位へ丸めた値)

質問の「人が亡くなった」に対し、説明文は「命が奪われた」と書いています。この碑が質問に合うかどうかは、候補を受け取ったLLMが説明文を読んで判断します。

5-3. 「構造化された属性」とは

上の表には、性質の違う二つの情報があります。説明文は自由に書かれた文章です。一方、災害種別には「洪水」「地震」「土砂災害」など、データで決められた値が入っています。今回の碑には「土砂災害」と「洪水」の二つが登録されています。

このように、項目名と値が決まった形で保存されている情報を、ここでは構造化された属性と呼びます。LLMが説明文から新しい災害名を作ったわけではありません。この碑に登録されていた値を、次の検索に利用します。

5-4. ツール 2:選んだ種別で避難場所を探す

質問は「斜面が崩れた教訓」です。そのため、LLMは碑に登録された二つの値から「土砂災害」を選び、避難場所を探すツール2に渡しました。

引数値出どころ
disaster_type(災害種別)土砂災害ツール1が返した碑の登録データから、LLMが選ぶ
radius_km(半径)5利用者の質問
latitude 、longitude(検索の中心)現在地会話に添付された位置情報

ツール2は、「土砂災害」が対応種別として登録されている避難場所を、現在地から5km以内で探し、近い順に最大10件返します。距離の中心は碑ではなく、利用者の現在地です。災害種別は完全一致で調べるため、碑の説明文そのものを引数に渡しても、登録された種別とは一致しません。

5-5. 全体の流れ

ここまでをまとめると、次のようになります。

利用者の質問(文章)
   │  LLM が「碑の中身」を切り出す
   ▼
ツール 1      query = 「大雨で斜面が崩れて人が亡くなったという教訓」
   │  Elasticsearch が意味で碑を探す
   ▼
碑のデータ     disaster_types = ["土砂災害", "洪水"]   ← 構造化された値
   │  LLM が質問に合う「土砂災害」を選んで書き写す
   ▼
ツール 2      disaster_type = 「土砂災害」
   │  Elasticsearch が種別の一致と距離で絞る
   ▼
現在地から 5km 以内の、土砂災害に対応した避難場所

文章で始まった質問が、途中で「土砂災害」という決まった言葉に変わり、それが地理空間検索の条件になっています。これが「属性の引き継ぎ」です。

5-6. 属性を使う理由と、その限界

碑の説明文だけを読んでLLMに災害種別を自由に作らせると、避難場所のデータと名前が合わない場合があります。登録済みの種別から選べば、次の検索に使った値と出どころを確認できます。回答には、たとえば「碑の登録災害種別『土砂災害』を使って検索しました」と書けます。

ただし、登録済みの値から選んでも、必ず正しい値を選べるわけではありません。「土砂災害」と「洪水」のどちらが利用者の質問に合うかはLLMの判断です。決められない場合は聞き返すように指示しています。また、避難場所にその種別が登録されていても、今開設されていることや、そこへ安全に移動できることは分かりません。

意味検索が返すのは、質問に近い上位10件の候補です。その中に合う碑がなくても、地域内の全碑に該当するものがないとは言えません。一方、災害種別と距離だけで登録件数を調べれば、「このデータと範囲では登録が0件」と確認できます。

ES|QLにはデータを結び付ける`LOOKUP JOIN`もあります。しかし、今回の例では、まず二つある登録種別から質問に合う一つを選ぶ必要があります。その判断をエージェントが行い、選んだ値をツール2へ渡す設計にしました。ES|QLだけでは原理的に実現できない、という意味ではありません。

5-7. 二つのデータで種別の名前が違う場合

碑と避難場所は別々に公開されたデータです。多くの種別は同じ名前ですが、すべてが一致するわけではありません。

碑に登録された種別避難場所のデータで使う種別このPoCでの扱い
洪水、地震、土砂災害、津波、高潮同じ名前登録された値をそのまま使う
火山災害火山現象指示文に書いた対応に従って置き換える
その他対応する種別がない避難場所の条件には使わない

避難場所側だけにある「大規模な火事」「内水氾濫」は、碑からは引き継ぎません。利用者が避難場所の種別として直接指定すれば、その種別で検索できます。

実際に「浅間山の噴火で亡くなった人を供養する碑」を探した例では、「信州浅間山噴火以来天災横死者供養塔」が見つかりました。登録種別は「その他」と「火山災害」です。エージェントは「火山災害」を選び、避難場所側の「火山現象」に置き換えて検索したことを回答に書きました。「その他」は対応する種別がないため使っていません。

この置き換えは、今はLLMへの指示文に書いてあります。LLMが対応を取り違える可能性は残るため、より厳密にするなら対応表をデータとして用意し、変換をプログラムで行う方法があります。

実際に、火山の碑で試しました。

現在地から半径 30km 以内で、浅間山の噴火で亡くなった人を供養する碑を探してください。そのうえで、その碑の教訓に関係する災害に対応した指定緊急避難場所を、現在地から 5km 以内で教えてください。

一番近い一致は「信州浅間山噴火以来天災横死者供養塔」(東京都墨田区)で、種別は「その他」と「火山災害」の 2 つでした。エージェントはこう答えました。

碑の登録災害種別に含まれる「火山災害」を使用し、避難場所の対応区分「火山現象」で検索しました(碑の「その他」区分には対応する避難場所種別がないため使用していません)。

5-8. 普通の RAG と何が違うか

RAG(検索拡張生成)は、検索で見つけた文章を LLM に渡して、答えを書かせる仕組みです。今回もその一種です。

違いは、検索結果の文章だけでなく、構造化された属性を次の検索の条件に使っていることです。検索の条件に使う値は、データに登録された値です。LLM が考え出した値ではありません。LLM は、その値を選び、必要なときは対応表に従って置き換えます。

6. 地図で確かめる

検索の結果は、数字だけだと位置関係が分かりにくいです。そこで Kibana Maps に3つのレイヤーを重ねました。

  • 現在地(●)
  • 自然災害伝承碑(▲)
  • 指定緊急避難場所(■)

伝承碑と避難場所のインデックスは、もともと geo_point 型の位置を持っています。そのため、そのまま地図のレイヤーにできます。

ただし、全部を描くと画面が避難場所で埋まります。そこで地図でも、災害種別という属性で絞ってから描きます。災害」で絞ると 146 件、「洪水」なら 498 件です。どの種別を引き継ぐかで、地図に出る避難場所が変わります。

ただし、この地図はチャットとは連動していません。エージェントが「土砂災害」を選んでも、地図は自動では切り替わりません。筆者が Kibana Maps の検索欄に disaster_types : “土砂災害” のように手で入れて、絞っています。また、地図に描かれるのは、エージェントが返した 10 件ではありません。インデックスの中で、その種別を持つ避難場所すべてです(画面に入る範囲)。

8. ほかの業界での使い方

防災の例を、一般的な形に直すと次のようになります。

自然言語で探す → 見つけたものの構造化属性を読む → その属性で地理空間検索をする

以下は、この形を当てはめたアイデアです。この PoC では検証していません。

業界自然言語の質問引き継ぐ属性地理空間検索
不動産子育てしやすい、静かな物件物件の種別、設備のタグ駅や学校からの距離
小売雨の日のキャンプで使えるもの商品カテゴリ在庫がある近くの店舗
設備保守ポンプから異音がするという報告故障コード対応できる近くの拠点

どの例でも、Elastic では「文章の意味」「属性」「位置」を同じインデックスに持てます。そのため、この形を1つの基盤で組めます。

9.限界と、次の回

この PoC で確認したのは、碑の登録災害種別を次の検索条件に使えることです。一方で、次の限界があります。

  • LLM の判断は間違うことがあります。 質問に合う碑や、碑に複数ある災害種別のどれを使うかは、LLM が選びます。登録済みの値を使っても、選択の正しさは保証されません。
  • チャットと地図は連動しません。 地図上の災害種別は筆者が手動で切り替えました。チャットが返した10件を地図へ自動表示する機能は、今回作っていません。
  • 回答には時間がかかります。 碑と避難場所を続けて探した例では、チャット画面の表示時間は51〜69秒でした。1回分のトレースでは、確認できた処理の中でLLMの呼び出しが最も長くなりました。処理時間の詳細は第2回で扱います。

また、検索の中心は利用者の現在地に限っています。地名や住所を指定した場所の検索は、この PoC の対象外です。

第2回では、三つの検索ツールをどう設定したか、意味検索の候補をどう扱うか、現在地を会話に渡して期限を管理する仕組みを説明します。

まとめ

この PoC では、自然言語で見つけた碑の登録災害種別を、現在地周辺の避難場所を探す次の条件に使いました。 これが、この記事でいう「属性の引き継ぎ」です。

「斜面が崩れた」という質問に合う碑を探すと、その碑には「土砂災害」と「洪水」が登録されていました。LLM は質問に合う「土砂災害」を選び、避難場所の検索ツールに渡します。Elasticsearch は、指定した種別との一致と現在地からの距離を調べ、条件に合う施設を返します。

検索に使った値の出どころを示せるのが、この方法の利点です。ただし、どの碑や種別を選ぶかというLLMの判断には誤りの余地があります。結果は過去の記録と登録された避難場所を調べるためのもので、現在の安全や避難の必要性を判定するものではありません。

出典・データの加工

本記事の検索結果と地図は、上記データをもとに筆者が災害種別の整理、検索、集計、可視化を行って作成しました。掲載した結果は取得時点のデータと、このPoCの検索条件によるものです。