0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

HEREでエンジニアをしておりますkekishidaと申します。世間のAIエージェントブームからは、少し遅れ気味ですが、本記事ではHERE APIをAIエージェントへ組み込むことにチャレンジしてみたいと思います。

ユースケースとしては、ユーザーの趣味嗜好を理解したAIエージェントが、ユーザーの位置情報に従って最も好ましい行き先と経路を自動的に提供するシナリオにしたいと思います。

image.png

実は、このユースケースは、投稿者本人が十数年前に某日系メーカーの研究企画部門に所属していた際に、研究テーマとして取り組んでいた携帯電話に組み込むエージェントのデモと近いイメージになります。当時は、Androidが発表されたばかりの頃で、当然LLMという概念は存在していなかったので、いわゆるWeb APIのマッシュアップと信頼する友人のSNS情報およびPKIの信頼関係をベースとしたアプローチをとっておりました。

今回はそれをHERE APIとLLMの力のみで実現しようという試みです。

以下は、そもそもHERE APIをご存知ない皆様のために、HEREという会社の紹介になります。

そういういうわけで、せっかく
The world's #1 location platformと標榜するHERE APIをAIエージェントに組み込むからには、最終的に地図へと情報を反映させたく、以下の3つのロケーションサービスを組み合わせてAIエージェントを構築してみました。

  • HERE Geocoding and Search v7 API (以下GS7)
  • HERE Routing v8 API (以下Routing)
  • HERE Maps API for Javascript

https://www.here.com/docs/category/geocoding-search-v7
https://www.here.com/docs/category/routing-api-v8
https://www.here.com/docs/category/here-sdk-for-js

また、気軽にプロトタイピングするという意味でJupyter lab/Jupyter notebookを使用します。その際、HERE Maps API for JavascriptをJupyter widgetであるhere-map-widget-for-jupyter経由で実装します。

実現すること

ユーザーの趣味嗜好を理解したAIエージェントが、ユーザーの位置情報に従って最も好ましい行き先と経路を自動的に提供するシナリオを実現したいと思います。

1. 趣味嗜好をAIエージェントに、事前に理解してもらう(RAGを利用します。)

ユーザープロファイル
favorite = "\
# 趣味嗜好\
## 動物好きで猫カフェなど好きです。\
## 好きな食べ物は焼肉、ジンギスカン。お寿司、イタリアンです。\
## たまに友達と飲みに行きます。\
## 休みの日は近所のジムで筋トレしたり、家事をしたりして過ごします。\
## プロ野球観戦も好きです。\
## 音楽イベントなども積極的に参加します。"

※ちなみに、上記は架空の人物です。(投稿者本人ではございません。)

2. 現在の位置情報をAIエージェントに入力する (本番環境では、位置が変わる度にイベントトリガーする)

image.png
3. AIエージェントが自動的に行き先と経路を提案
image.png

'焼肉 デ・ニーロは福岡県福岡市中央区薬院に位置する焼肉レストランです。
具体的には、〒810-0022 福岡県福岡市中央区薬院1丁目10-10にあります。
このエリアは福岡市内でも人気のある地域で、アクセスが良好です。
焼肉 デ・ニーロは、その名前からも推察される通り、こだわりのある焼肉料理を提供するお店です。
上質な肉を使用しており、多くのフードレビューサイトで高評価を得ています。特に、個室が利用できるため、
プライベートな空間で食事を楽しむことができると評判です。
また、薬院駅から徒歩圏内にあるため、アクセスも非常に便利です。
駅周辺には他の飲食店やショップも多数あるので、
焼肉 デ・ニーロを訪れた際には周囲の散策も楽しむことができます。
営業時間や定休日については変更がある可能性があるため、
訪問前にお店の公式ウェブサイトや直接問い合わせて確認することをおすすめします。'

つまり、ユーザーのことをよく理解しているAIエージェントが、ユーザーが移動するごとに、ユーザーに囁くように行きたいところを提案するという内容です。(今回実装する内容はプロトタイプです。)
こうして見ますと、単純にChatGPTを使用しただけのRecomendationシステムのように見えますが、内部的にはHERE Geocoding&Search v7 APIで提供されるデータを元に構築しているため、LLM単体と比較するよりは情報の確度が高くなるところが味噌となります。

ここで、HERE Geocoding&Search v7 APIについて補足しますと、施設情報 (POI)、周辺のジオコードアドレスなどを検索することが可能なAPIです。地理座標、POI、アドレスを正確に変換して特定することで、位置情報の精度と内容を強化することが可能となります。つまり、LLMアプリケーションに位置情報のリアルを補完することが可能となります。

以下の記事(リンク)に、HERE Geocoding&Search v7 APIの各エンドポイントの要点についてわかりやすくまとめてあります。

実装方針

Langchain/Langgraphを利用する

Langchain/Langgraphを使用して、制御可能な形でAIエージェントを作成します。

Jupyterエコシステムを利用する

簡易的なプロトタイプでかつ、簡単に地図描画を実装するためにJupyterを使用します。

とりあえず動くものを作る(プロトタイプなので)

  • 一部古いライブラリを使用しております(Pydanticv1)が、とりあえず動くものを作ることを目的とします
  • プロンプトエンジニアリングは完璧でないかもしれませんが、大きく逸脱するようなハルシネーションが起きないような、とりあえず動くものを作ることを目的とします

必要なもの

それではプロトタイピングを開始したいと思いますが、その前に必要なものがいくつか存在します。

Jupyter notebookまたはJupyter lab

インストール方法についてはQiita内にいくつか有用な記事が存在します。

HEREのアカウント取得

以下の記事が大変参考になります。

HERE Map Widget for Jupyterのインストール

冒頭で説明した通り、HERE Maps API for JavascriptをHERE Map Widget for Jupyterを経由して使用します。HERE Map Widget for Jupyterを使用することで、Javascriptを使用して自身でWebアプリケーションを作成することなく、簡単に地図描画のシミュレーションを行うことが可能になります。

pip install
pip install here-map-widget-for-jupyter

現行のhere-map-widget-for-jupyterは最新のJupyter lab4には対応しておりませんが、近々に最新版に対応したものをリリースする予定です。それまでは旧Jupyter lab3系をインストールしている方のみインストール可能です。

その他ライブラリのインストール

pip install langchain_openai
pip install langchain
pip install pysqlite3
pip install chromadb
pip install langchain_chroma
pip install langgraph
pip install --extra-index-url https://repo.platform.here.com/artifactory/api/pypi/analytics-pypi/simple/ here-platform==2.30.1 here-geotiles==2.30.1 here-geopandas-adapter==2.30.1 here-content==2.30.1 here-inspector==2.30.1
pip install flexpolyline

コード説明 (step by step)

ライブラリのimport

ちょこまかと、いろいろimportしております。。。

jupyter
import urllib.request
from urllib.parse import quote
import os
import json
from langchain_openai import AzureChatOpenAI
from langchain_openai import AzureOpenAIEmbeddings
from langchain.schema.document import Document
from langchain.text_splitter import CharacterTextSplitter
# Workaround for Chroma DB
__import__('pysqlite3')
import sys
sys.modules['sqlite3'] = sys.modules.pop('pysqlite3')
import chromadb
from chromadb.config import Settings
from langchain_chroma import Chroma
from langchain_core.runnables import RunnablePassthrough
from langchain.schema import HumanMessage, SystemMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_core.pydantic_v1 import BaseModel, Field, validator
from langchain_core.prompts import ChatPromptTemplate
from langgraph.graph import StateGraph, END
from typing import Annotated, Any
from typing import Union
import operator

from here.platform import Platform
import pandas as pd
from IPython.display import display, FileLink
from here_map_widget import GeoData
from here_map_widget import Map, Platform,ServiceNames, OMV, OMVUrl,Style, TileLayer, MapSettingsControl, Circle, Point, InfoBubble
import geopandas as gpd
import flexpolyline as fp

import here_map_widget as H

APIキーなどの設定

HERE API KEYは事前に取得したものを設定します。
このケースでは、Azure OpenAIを使用しておりますので、以下の設定が必要になります。

jupyter
HERE_API_KEY=<HERE API KEY>
os.environ["AZURE_OPENAI_ENDPOINT"] = <YOUR AZURE OPENAI ENDPOINT>
os.environ["AZURE_OPENAI_API_KEY"] = <YOUR AZURE OPENAI APIKEY>

OpenAI model設定

Azure OpenAI modeを指定するために、以下の設定が必要になります。

jupyter
model = AzureChatOpenAI(
    openai_api_version=<YOUR OPENAI VERSION>,
    azure_deployment=<YOUR AZURE DEPLOYMENT>,
)

Pydaticによる型定義

langgraphによるステートレスな処理と、LLMからの回答の型を固定するために以下の定義を行います。

jupyter
class Favorite(BaseModel):
    favorite_list: list[str] = Field(
        default=[], description="ユーザーの趣味、嗜好"
    )
class Category(BaseModel):
    category: str = Field(
        default=[], description="HERE GS7 APIのためのカテゴリー"
    )
class Message(BaseModel):
    message: str = Field(
        default="", description="回答案"
    )
    title: str = Field(
        default="", description="その場所の名前"
    )
    address: str = Field(
        default="", description="その場所の住所"
    )
    latitude: float = Field(default=0.0, description="緯度")
    longitude: float = Field(default=0.0, description="経度")
    category: str = Field(
        default="", description="その場所のカテゴリー"
    )
class Judgement(BaseModel):
    reason: str = Field(default="", description="判定理由")
    judge: bool = Field(default=False, description="判定結果")
jupyter
class State(BaseModel):
    latitude: float = Field(
        default=0.0,description="経度"
    )
    longitude: float = Field(
        default=0.0,description="緯度"
    )
    favorites: list[str] = Field(
        default=["restaurant"], description="ユーザーの好み"
    )
    messages: Annotated[list[dict],operator.add] = Field(
        default=[], description="回答履歴"
    )
    favorite_message: str = Field(
        default="", descriptiom="最も適切な回答"
    )
    favorite_list: Favorite = Field(
        default=None, description="ユーザーの趣味、嗜好"
    )
    category_list: list = Field(
        default=[], description="HERE GS7 APIのためのカテゴリーリスト"
    )
    retriever: object = Field(
        default=None, description="ユーザープロファイルのRAG Retriever"
    )
    current_judge: bool = Field(
        default=False, description="品質チェックの結果"
    )
    judgement_reason: str = Field(
        default="", description="品質チェックの判定理由"
    )

サポート関数の定義

jupyter
def get_text_chunks_langchain(text):
    text_splitter = CharacterTextSplitter(chunk_size=500, chunk_overlap=50)
    docs = [Document(page_content=x) for x in text_splitter.split_text(text)]
    return docs
    
def remove_duplicates_dict(lst):
    return list(dict.fromkeys(lst))

def to_geojson(res):
    from geojson import Feature, FeatureCollection, LineString, Point, Polygon
    """Return API response as GeoJSON."""
    feature_collection = FeatureCollection([])
    for route in res["routes"]:
        for section in route["sections"]:
            polyline = section["polyline"]
            lstring = fp.decode(polyline)
            lstring = [
                (coord[1], coord[0], coord[2]) if len(coord) > 2 else (coord[1], coord[0], 0)
                for coord in lstring
            ]
            f = Feature(geometry=LineString(lstring), properties=section)
            feature_collection.features.append(f)
    return feature_collection

langgraphのノード定義

ユーザープロファイルのRAG retriever作成

jupyter
def create_retriever_node(state: State):
    persist_directory = './'
    client = chromadb.PersistentClient(path=persist_directory)
    embeddings = AzureOpenAIEmbeddings(model="text-embedding-3-small")
    db = Chroma(
        collection_name="User_Profile",
        embedding_function=embeddings,
        client=client,
    )
    retriever = db.as_retriever()
    return {"retriever": retriever}    

ユーザープロファイルから、趣味嗜好をピックアップ

jupyter
def take_favorite_node(state: State) -> dict[list, Any]:
    retriever = state.retriever
    prompt = ChatPromptTemplate.from_template('''\
    以下の文脈だけ踏まえて質問に回答してください。
    文脈:"""
    {context}
    """
    質問: {question}
    ''')
    chain = (
        {"context": retriever, "question": RunnablePassthrough()}
        | prompt
        | model.with_structured_output(Favorite)
    )
    output= chain.invoke("趣味、嗜好について教えてください。")
    print(output)
    return {"favorite_list": output}

このnodeが実行されると趣味嗜好がリスト形式で返ってきます。

jupyter
favorite_list=['動物好き', '猫カフェ', '横浜ベイスターズを応援', '焼肉', 'ジンギスカン', 'お寿司', 'イタリアン', '友達と飲みに行く', '近所のジムで筋トレ', '家事', 'プロ野球観戦', '音楽イベントに積極的に参加する']

ピックアップした趣味嗜好からHERE GS7 placesカテゴリに変換

ここでは、上記の趣味嗜好リストをHERE GS7 placesカテゴリに変換します。実行するAPIはdiscoverエンドポイントを使用します。discoverエンドポイントを使用することにより、テキストベースで簡易的にカテゴリ検索をすることが可能になります。
https://www.here.com/docs/bundle/geocoding-and-search-api-developer-guide/page/topics-api/code-discover-category.html

ここでのポイントとしては、指定するカテゴリ名をどのように変換するかということになります。こちらについては基本的に以下のページのPlacesカテゴリから参照します。
https://www.here.com/docs/bundle/geocoding-and-search-api-developer-guide/page/topics-places/places-category-system-full.html
こちらのPlacesカテゴリは以下のリンクにあるbrowseエンドポイントで使用されるIDですが、ここに記載されているカテゴリについても基本的にdiscoverエンドポイントで使用するカテゴリテキストとして対応可能という前提のロジックとしました。
https://www.here.com/docs/bundle/geocoding-and-search-api-developer-guide/page/topics/endpoint-browse-brief.html

なぜ、discoverエンドポイントをあえて使用しているかは自然言語と相性が良いからという理由になります。
より精度を上げるには、上記のPlacesカテゴリページをロードし、RAG RetriverにしてIDに変換するという手もありそうです。

こちらで紹介しておりますアプローチは、HERE公式の推奨アプローチではありません。ひとつのアイデアとして捉えていただければ幸いです。

jupyter
def map_category_node(state: State) -> dict[list, Any]:
    favorite_list = state.favorite_list
    prompt = ChatPromptTemplate.from_template('''\
    以下の英語のカテゴリのうち、{favorite}に最も近いものをひとつだけ選択してください。
    # Restaurant
    # Karaoke
    # Cinema
    # Music
    # Casino
    # Gallery
    # Hot spring
    # Musium
    # Mountain
    # Lake
    # Temple
    # Comedy
    # Sports
    # Entertainment                  
    ''')

    outputList = []
    for favorites in favorite_list:
        for favorite in favorites[1]:    
            chain =(
                prompt
                | model.with_structured_output(Category)
            )
            output = chain.invoke({"favorite": favorite})
            outputList.append(output)
    print(outputList)
    return {"category_list": outputList}

このnodeが実行されるとHERE GS7 discoverエンドポイント向けのカテゴリーが以下のようにリスト形式で返ってきます。

jupyter
[Category(category='Mountain'), Category(category='Entertainment'), Category(category='Sports'), Category(category='Restaurant'), Category(category='Restaurant'), Category(category='Restaurant'), Category(category='Restaurant'), Category(category='Entertainment'), Category(category='Sports'), Category(category='Entertainment'), Category(category='Sports'), Category(category='Music')]

HERE GS7API実行、その結果をベースにLLMにより情報の補完

取得したカテゴリーリストをベースに、HERE GS7 discoverエンドポイントを呼び出し、その近辺の目的地候補を取得し、その目的地候補はLLMにお願いして、情報を補完します。

jupyter
def answering_node(state: State) -> dict[list, Any]:
    retriever = state.retriever
    category_list = state.category_list
    revised_category_list = []
    for categories in category_list:
        revised_category_list.append(categories.dict()['category'])
    revised_category_list = remove_duplicates_dict(revised_category_list)
    print(revised_category_list)
    outputList = []
    for text in revised_category_list:
        arguments=f'apiKey={HERE_API_KEY}&q={text}&in=circle:{latitude},{longitude};r=100'
        arguments=quote(arguments,safe='=&:;')
        url = f'https://discover.search.hereapi.com/v1/discover?{arguments}'
        req = urllib.request.Request(url)
        with urllib.request.urlopen(req) as res:
            body = res.read()
        result =json.loads(body)
        if len(result['items']) > 0:
            prompt = ChatPromptTemplate.from_messages(
                [
                    {"role": "system", "content": "You are a helpful coordinator"},
                    {"role": "human", "content": "{request}"},
                ]
            )
            output_parser = StrOutputParser()
            chain = (
                prompt
                | model
                | output_parser
            )
            for m in result["items"]:
                result = {}
                output = chain.invoke({"request": f"{m['address']['label']}周辺にある{m['title']}について教えてください。"})
                result["title"] = m["title"]
                result["address"] = m["address"]["label"]
                result["position"] = m["position"]
                result["categories"] = m["categories"]
                result["message"] = output
                outputList.append(result)
    return {"messages": outputList}

LLM回答案からユーザープロファイルに基づいて最適な回答をピックアップ

次にユーザープロファイルを保持しているRAG retrieverを利用して、目的地候補リストから最適な目的地候補を選択します。

jupyter
def priority_check_node(state: State) -> dict[str, Any]:
    retriever = state.retriever
    messageList = []
    prompt = ChatPromptTemplate.from_template('''\
    以下の文脈内の趣味嗜好を踏まえて、質問の回答をしてください。
    文脈:"""
    {context}
    """
    質問: {question}
    ''')
    output_parser = StrOutputParser()
    chain =(
        {"context": retriever, "question": RunnablePassthrough()}
        | prompt
        | model.with_structured_output(Message)
    )
    output = chain.invoke(f'回答案の配列{state.messages}の中で最もその趣味嗜好を捉えた回答をmessageフィールドから選択してください。またその回答の各属性値は該当するフィールドに保存してください')
    return {"favorite_message": output}

LLM回答をユーザープロファイルに基づいて改めてレビュー

典型的なAIエージェントの作法に則って、誤った回答になっていないかレビューします。

jupyter
def validate_node(state: State) -> dict[str, Any]:
    retriever = state.retriever
    favorite_message = state.favorite_message
    prompt = ChatPromptTemplate.from_template('''\
    以下の文脈の趣味嗜好を踏まえて、推薦する目的地が適切な場合は'True'、適切でない場合は'False'を回答してください。また、その判断理由を説明してください。
    文脈:"""
    {context}
    """
    レコメンド: {message}
    ''')
    chain =(
        {"context": retriever, "message": RunnablePassthrough()}
        | prompt
        | model.with_structured_output(Judgement)
    )
    
    result: Judgement = chain.invoke(favorite_message['message'])
    print(result.judge)
    if result.judge == False:
        print(result.reason)
    return {"current_judge": result.judge, "judgement_reason": result.reason}

グラフのコンパイル

上記のnode達を接続したグラフを作成し、コンパイルします。

jupyter
workflow = StateGraph(State)
workflow.add_node("create retriever for user profile",create_retriever_node)
workflow.add_node("capture favorite from user profile",take_favorite_node)
workflow.add_node("map GS7 category from favorite",map_category_node)
workflow.add_node("executing GS7 API and LLM processing",answering_node)
workflow.add_node("chosing appropriate recommendation",priority_check_node)
workflow.add_node("validating recommendation",validate_node)
workflow.add_edge("create retriever for user profile","capture favorite from user profile")
workflow.add_edge("capture favorite from user profile","map GS7 category from favorite")
workflow.add_edge("map GS7 category from favorite","executing GS7 API and LLM processing")
workflow.add_edge("executing GS7 API and LLM processing","chosing appropriate recommendation")
workflow.add_edge("chosing appropriate recommendation","validating recommendation")
workflow.add_conditional_edges("validating recommendation", lambda state: state.current_judge, {True: END, False: "chosing appropriate recommendation"})
workflow.set_entry_point("create retriever for user profile")
compiled = workflow.compile()

グラフの確認

jupyter
from IPython.display import display, Image
Image(compiled.get_graph().draw_mermaid_png())

image.png

趣味嗜好の入力

最後の準備作業です。
ここには、自然言語でユーザーの趣味嗜好を入力します。こちらにAIエージェントがユーザーのことを知る知識部分を入力します。これがRAG(Retrieval-Augumented Genenation)の元になります。

jupyter
favorite = "\
# 趣味嗜好\
## 動物好きで猫カフェなど好きです。\
## 好きな食べ物は焼肉、ジンギスカン。お寿司、イタリアンです。\
## たまに友達と飲みに行きます。\
## 休みの日は近所のジムで筋トレしたり、家事をしたりして過ごします。\
## プロ野球観戦も好きです。\
## 音楽イベントなども積極的に参加します。"

RAGの構築

ローカル環境に永続化ベクターデータベースを作成します。

jupyter
persist_directory = './'
client = chromadb.PersistentClient(path=persist_directory)
docs = get_text_chunks_langchain(favorite)
embeddings = AzureOpenAIEmbeddings(model="text-embedding-3-small")
db = Chroma(
        collection_name="User_Profile",
        embedding_function=embeddings,
        client=client,
    )
db.add_documents(documents=docs, embedding=embeddings)

実行フェーズ

準備が完了しましたので、いよいよ実行フェーズです。

位置情報の設定

おそらく、本番環境では、(ある閾値をもって)位置が移動する毎にイベントドリブンでトリガーされるように実装すべきですが、簡易的にJupyterで実装しているため、単純に緯度経度を変数として決め打ちで設定しています。

jupyter
latitude=33.58102217842539
longitude=130.39799192158978

ユーザーの現在地点地図描画

福岡県中央区の薬院大通駅あたりにいることがわかります。

jupyter
basemap = H.TileLayer(
    provider=H.OMV(
        path='v2/vectortiles/core/mc',
        platform=H.Platform(
            api_key=HERE_API_KEY,
            services_config={
                H.ServiceNames.omv: {
                    H.OMVUrl.scheme: 'https',
                    H.OMVUrl.host: 'vector.hereapi.com',
                    H.OMVUrl.path: '/v2/vectortiles/core/mc',
                },
            },
        ),
        style=H.Style(
            config="https://js.api.here.com/v3/3.1/styles/harp/oslo/tko.normal.day.json",
            base_url="https://js.api.here.com/v3/3.1/styles/harp/oslo/",
        ),
    ),
    style={'max': 22},
)
jupyter
m = H.Map(api_key=HERE_API_KEY, center=[latitude,longitude], zoom=17, basemap=basemap)
markers=[]
your_marker = H.Marker(lat=latitude, lng=longitude)
markers.append(your_marker)
m.add_objects(markers)
point = H.Point(lat=latitude, lng=longitude)
circle = Circle(center=point, radius=100, style={"strokeColor": "#822", "lineWidth": 4, "fillColor": 'rgba(225,238,220,0.3)'}, draggable=False)
m.add_object(circle)
m

image.png

langgraphの実行

ここで、いよいよAIエージェントが実行されます。すなわち先ほど構築したグラフが実行されます。そして、最終的な目的地が提供されます。

jupyter
initial_state=State(latitude=latitude,longitude=longitude)
result=compiled.invoke(initial_state)

結果を地図上に描画

以下のコードは、最終的な目的地情報をベースに、here-map-widget-for-jupyterとHERE Routing APIを使用して目的地までの経路などを地図上に描画するコードです。

jupyter
m = H.Map(api_key=HERE_API_KEY, center=[latitude,longitude], zoom=17, basemap=basemap)
info = InfoBubble(
        position=Point(lat=result['favorite_message'].dict()['latitude'], lng=result['favorite_message'].dict()['longitude']),
        content='<div><b>Recommendation:</b> '+result['favorite_message'].dict()['title'],
)
target_marker = H.Marker(lat=result['favorite_message'].dict()['latitude'], lng=result['favorite_message'].dict()['longitude'],info=info, evt_type="tap", show_bubble=True)
layers=[]
routing_url ="https://router.hereapi.com/v8/routes"
params = {}
params["transportMode"] = "car"
params["origin"] = f"{latitude},{longitude}" 
params["destination"] = f"{result['favorite_message'].dict()['latitude']},{result['favorite_message'].dict()['longitude']}" 
params["return"] = "summary,travelSummary,polyline"
arguments=""
for key,value in params.items():
    string=f"{key}={value}&"
    arguments=arguments+string
arguments=arguments+"apiKey="+HERE_API_KEY
arguments=quote(arguments,safe='=&:;')
url = f"{routing_url}?{arguments}"
req = urllib.request.Request(url)
with urllib.request.urlopen(req) as res:
    body = json.load(res)
layer = H.GeoJSON(data=to_geojson(body), style=dict(lineWidth=5, strokeColor="red"), name="Route")
layers.append(layer)
markers.append(target_marker)
m.add_objects(markers)
m.add_object(circle)
m.add_layers(layers)
m

以下に目的地と経路が示されました。
image.png

実は、現行here-map-widget-for-jupyterでは、HEREの最新Map rendering engineであるHARP engineに対応しておりません。こちらのサンプルはhere-map-widget-for-jupterの最新版を先取りして掲載しています。こちらについては、別途記事を投稿する予定です。

ちなみに、HARP engineについては、以下の記事にて紹介しているHEREの最新の地図レンダリングエンジンです。

https://qiita.com/kekishida/items/b3ba0bc1873dc0ed48be

提案メッセージの表示

jupyter
result['favorite_message'].dict()['message']

以下は提案メッセージの例です。

'焼肉 デ・ニーロは福岡県福岡市中央区薬院に位置する焼肉レストランです。
具体的には、〒810-0022 福岡県福岡市中央区薬院1丁目10-10にあります。
このエリアは福岡市内でも人気のある地域で、アクセスが良好です。
焼肉 デ・ニーロは、その名前からも推察される通り、こだわりのある焼肉料理を提供するお店です。
上質な肉を使用しており、多くのフードレビューサイトで高評価を得ています。特に、個室が利用できるため、
プライベートな空間で食事を楽しむことができると評判です。
また、薬院駅から徒歩圏内にあるため、アクセスも非常に便利です。
駅周辺には他の飲食店やショップも多数あるので、
焼肉 デ・ニーロを訪れた際には周囲の散策も楽しむことができます。
営業時間や定休日については変更がある可能性があるため、
訪問前にお店の公式ウェブサイトや直接問い合わせて確認することをおすすめします。'

AIエージェントのテスト

それでは別の趣味嗜好であるユーザーで試してみましょう。

jupyter
favorite = "\
# 趣味嗜好\
## 美味しい寿司が大好きです。\
## サッカー観戦も好きです。\
## カラオケも好きです。"

image.png
image.png
決定論的なサンプルですが、同一ロケーションで、別ユーザーに変えた場合、ユーザーの趣味嗜好に従った違う店が提案されました!
image.png
さらに、アングルを変えて3D表示にすることで、よりこのユースケースに最適な地図へと変更ができました。

おわりに

いかがでしたでしょうか? 今回非常にベーシックなAIエージェントですが、HERE APIをAIエージェントに組み込んでみて、私自身いろいろとその可能性を学ぶことができました。こういったユースケースを、LLMとAPIのみで実現できる時代なんだなあと感慨深く思います。

一方、今回実装したプロトタイプですが、まだまだチューニングしきれておりません。本当に痒いところにも手がとどくほどの、自分自身の趣味嗜好にあった目的地を提案するLLMアプリにするには、プロンプトエンジニアリング/RAGの調整がまだまだ必要なようです。これこそがLLMアプリケーション開発の肝であり、今後この分野の可能性も大いに感じます。

是非、HERE APIを使用して様々なLLMアプリケーションを開発していただければ幸いです。ここまで読んでいただいてありがとうございました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?