GeminiのFunction Callingと、Grounding

2024/04/22に公開されました。
2024/04/22に更新されました。

Google CloudのGeminiでFunction Callingを試してみました。おまけで、Google Cloud Next24で発表されたGoogle検索とのGroundingなども少し触れています。


author: Shintaro

はじめに

SIチーム4の森本慎太郎です。
(私のチームはマネージャーも森本さんなので、あえてフルネームです)

Function Callingが気になって調べてみると
OpenAIのAPIを使った解説ばかり出てきて、
Google CloudのGeminiは日本語記事が少なかったので対抗がてら検証した結果を紹介します。

そのおまけとして、Google Cloud Next24のタイミングで
プレビューになったGeminiでのGroundingと、
Google検索とのGroundingに軽く触れます。

Function Callingとは(超ざっくり解説)

自然言語のテキストをLLMに渡すと、
準備しておいた関数の中から適したものを選んでくれて、
さらに、その関数に渡すための引数をテキストの中から抽出してくれる優れものです。

すごく簡単な具体例を挙げます。

  • 自然言語テキスト:東京の天気を教えて
  • 準備した関数2つ:
    • 天気を取得する
    • 為替レートを取得する
def get_weather(location):
    # 中身はテキトーです。
    if location == "dokoka":
        api_response = requests.get("https://hogehoge")
    return api_response.json()

def get_exchange_rate(currency_from, currency_to):
    # 中身はテキトーです。
    params = {}
    for key, velue in (...)
    api_response = requests.get("https://hogehoge", params=params)
    return api_response.json()

↑の自然言語テキストと「関数宣言」というもの(のちほど解説します)を
Function Callingすると、以下のように返してくれます。

  • 適した関数:get_weather
  • 引数:東京

Google Cloudの公式ドキュメントからの抜粋ですが、
Function Callingは以下のようなユースケースを想定できるとのことです。

  • テキストから情報を抽出して、その情報を使用してカレンダーの予定を作成する。
  • コードベースでif-thenステートメントを置換する。
  • 音声コマンドを車両システムのタスクに変換する。
  • 自然言語を使用してSQLデータベースを操作する。

私は2番目に近く、自然言語による処理の分岐を試してみようと思い、検証してみました。

Dialogflowにおけるintentの検出に近いことができればと思っていましたが、
この記事を書いている途中に
Agent Builder(旧Vertex AI Search and Conversation)のAgentにも
Function Callingが実装され、
(とくにDialogflowのAPIになじみのある方なら)より簡単に実践できるようになったようです。

ちなみに、Function Callingは日本語で「関数呼び出し」と訳されることが多いです。

実例

ここからは実際のコードを見ていきます。 以下の順番で解説します。

  1. 関数&関数宣言を作成
  2. 関数宣言をモデル呼び出しの際に使えるTool形式で指定
  3. Geminiを呼び出し、Function Callingのコンテンツを作成
  4. 関数実行

関数&関数宣言を作成

繰り返しになりますが、Function Callingは
プロンプトに対する適した関数と引数を取得するだけです。
では、適した関数と引数はどんな情報をもとに取得してくるのか?という話で、
そこで使われるのが関数宣言です。

今回は例として、以下を前提とします。

  1. Vertex AI Agent Builderの検索アプリ(旧Vertex AI Search)を作成
    1. データストアとして就業規則や社内規定のPDFが入ったCloud Storageを設定
  2. ユーザーからのプロンプトを受け取り、Geminiから回答を返す関数を作成
    1. 根拠づけ(Grounding)として上記のデータストアを使用
  3. ユーザーからの入力を受けとって、それが社内情報に関するものだった場合は上記の関数を呼び出す

Vertex AI Agent Builderの検索アプリを作成

Vertex AI Agent Builderに関する解説は今回は割愛しますが、
データストアとして設定したCloud Storageには、
厚生労働省が公開しているモデル就業規則を入れました。
具体的には、以下の部分のみ1~2ページだけ抜粋しました。

厚生労働省のモデル就業規則から服務規律を抜粋したもの 厚生労働省のモデル就業規則から有給の箇所を抜粋したもの 厚生労働省のモデル就業規則から有給の箇所を抜粋したもの

ユーザーからのプロンプトを受け取り、Geminiから回答を返す関数を作成

これまでPaLM2でのみ可能だった根拠づけ(Grounding)が
Geminiでできるようになったので、試してみました。
※2024/4/18時点でパブリックプレビューの機能です。

根拠づけによって、モデルと特定の情報を簡単に紐づけできます。
設定の詳しい手順については記事の最後に参照リンクを貼っておきますのでご確認ください。

def get_internal_info(query: str):
    vertexai.init(project=project_id, location=location)

    model = GenerativeModel("gemini-1.0-pro-001")
    data_store_path = "projects/project/locations/location/collections/default_collection/dataStores/data_store_id"

    # データストアだけではなく、Agent Builderの検索アプリを作る必要あり。
    tool = Tool.from_retrieval(
        grounding.Retrieval(grounding.VertexAISearch(datastore=data_store_path))
    )

    response = model.generate_content(query, tools=[tool])

    return response

なお、モデルの宣言でgemini-1.0-proとすると、
2024/4/19時点でgemini-1.0-pro-002が適用されます。
今回紹介しているレスポンスから変化するため注意です。

上記の関数に対する関数宣言は、以下です。

get_internal_info_func = FunctionDeclaration(
    # 関数名
    name="get_internal_info",
    # 関数の説明
    description="ジーアイクラウド株式会社の社内情報を取得する。具体的には「就業規則」や「社内規定」などに載っている「休暇」や「人事評価」、「給与」についてなど。",
    # 引数の型と説明。OpenAPIのスキーマに準拠する必要あり。
    parameters={
        "type": "object",
        "properties": {
            "query_text": {
                "type": "string",
                "description": "ユーザーから受け取った「質問」をそのまま使う。",
            },
        },
    },
)

関数宣言をモデル呼び出しの際に使える形式で指定

Toolクラスを使用して、上記の関数宣言をGeminiに渡せる形式にします。

tool = Tool(
    function_declarations=[get_internal_info_func],
)

list型の形式なので、複数の関数宣言を渡せます。

Geminiを呼び出し、Function Callingのコンテンツを作成

あとはGeminiに渡すだけです。

model = GenerativeModel("gemini-1.0-pro-001")

user_query = "服務規定には職場でのパワーハラスメントにはどのようなことが該当すると書いてありますか?"

prompt = f"""
    質問に回答をお願いします。/n
    社内情報に関係ない質問に対しては、ツールを使わずに回答してください。/n
    質問:{user_query}
    """

# プロンプトと指定したToolを使ってモデルを呼び出し、Function Callingのコンテンツを生成
response = model.generate_content(
        prompt,
        generation_config={"temperature": 0},
        tools=[tool],
    )

# 適切な関数名と、それに合う引数をプロンプトから抽出
response_function_call_content = response.candidates[0].content

print(response_function_call_content)
# 適切な関数が見つかった場合
# role: "model"
# parts {
#     function_call {
#         name: "get_internal_info"
#         args {
#             fields {
#                 key: "query_text"
#                 value {
#                     string_value: "社内情報に関係ない質問に対しては、ツールを使わずに回答してください。\\n質問:###服務規定には職場でのパワーハラスメントにはどのようなことが該当すると書いてありますか?###"
#                 }
#             }
#         }
#     }
# }

この場合で例えばprompt = "日本の天気を教えてください"とすると、
response.candidates[0].contentは以下のようになります。

# 適切な関数が見つからなかった場合
# role: "model"
# parts {
#     text: "申し訳ありませんが、そのご要望にはお応えできません。私は社内情報にアクセスするツールしか持っていません。"
# }

上記のように、回答できない場合は回答できないと毎回ハッキリしてくれれば良いのですが、
promptをうまいこと調整しないと、一般的な質問に対する回答も
関数宣言で与えられている関数を使ってくれと言われてしまうケースが多いです。
例えば、prompt = "アメリカの大統領は誰ですか?"のみだと、
「適した関数はget_internal_infoだよ。アメリカの大統領は誰ですか?を引数に使ってね」
と返ってきてしまいます。

role: "model"
parts {
  function_call {
    name: "get_internal_info"
    args {
      fields {
        key: "internal_info"
        value {
          string_value: "社内情報に関係ない質問に対しては、ツールを使わずに回答してください。\\n質問:###アメリカの大統領は誰ですか?###"
        }
      }
    }
  }
}

そのため、前述のとおりですが
以下のようにプロンプトを変えてみると少し精度が良くなりました。

user_query = "有給休暇の取得に関するルールを教えてください。"
prompt = f"""
    "質問"に回答をお願いします。/n
    社内情報に関係ない"質問"に対しては、ツールを使わずに回答してください。/n
    質問:###{user_query}###
    """

function Callingを使う場合もプロンプトエンジニアリングが必要になってきそうです。

また、関数宣言の際のdescriptionを具体的に書くことなどでも精度が上がるようです。

関数実行

再掲ですが、以下を実行すると、関数名と引数が返ってきました。

model = GenerativeModel("gemini-1.0-pro-001")

user_query = "服務規定には職場でのパワーハラスメントにはどのようなことが該当すると書いてありますか?"

prompt = f"""
    質問に回答をお願いします。/n
    社内情報に関係ない質問に対しては、ツールを使わずに回答してください。/n
    質問:{user_query}
    """

# プロンプトと指定したToolを使ってモデルを呼び出し、Function Callingのコンテンツを生成
response = model.generate_content(
        prompt,
        generation_config={"temperature": 0},
        tools=[tool],
    )

# 適切な関数名と、それに合う引数をプロンプトから抽出
response_function_call_content = response.candidates[0].content

print(response_function_call_content)
# 適切な関数が見つかった場合
# role: "model"
# parts {
#     function_call {
#         name: "get_internal_info"
#         args {
#             fields {
#                 key: "query_text"
#                 value {
#                     string_value: "社内情報に関係ない質問に対しては、ツールを使わずに回答してください。\\n質問:###服務規定には職場でのパワーハラスメントにはどのようなことが該当すると書いてありますか?###"
#                 }
#             }
#         }
#     }
# }

これを例えば以下のように受け取ります。

# Function Callingで取得した関数名
function_name = response.candidates[0].content.parts[0].function_call.name

# Function Callingで取得した引数
function_argument = (
    response.candidates[0].content.parts[0].function_call.args.get("query_text")
)

そして実行します。

if function_name == "get_internal_info":
        api_response = get_internal_info(function_argument)
        print(f"api_response: {api_response}")
  # api_responseの中身
  # candidates {
  # content {
    # role: "model"
    # parts {
      # text: "職務上の地位や人間関係などの職場内の優位性を背景にした、業務の適正な範囲を超える言動により、他の労働者に精神的・身体的な苦痛を与えたり、就業環境を害する行為"
    # }
  # }
  # finish_reason: STOP
  # safety_ratings {
    # category: HARM_CATEGORY_HATE_SPEECH
    # probability: NEGLIGIBLE
    # probability_score: 0.178239584
    # severity: HARM_SEVERITY_NEGLIGIBLE
    # severity_score: 0.102304712
  # }
  # safety_ratings {...
  # ...

  # grounding_metadata {
    # retrieval_queries: "職場でのパワーハラスメントにはどのようなことが該当すると書いてありますか?"
  # }
# }
# usage_metadata {
  # prompt_token_count: 40
  # candidates_token_count: 49
  # total_token_count: 89
# }

以下のtextが返ってきた回答です。

職務上の地位や人間関係などの職場内の優位性を背景にした、業務の適正な範囲を超える言動により、他の労働者に精神的・身体的な苦痛を与えたり、就業環境を害する行為

データストアのPDFから正しく情報を取ってきてくれていますね。

服務規律

また、grounding_metadataretrieval_queriesを見ると、以下となっています。

職場でのパワーハラスメントにはどのようなことが該当すると書いてありますか?

モデルに渡したテキストである以下を
良い感じにしてAgent Builderの検索アプリに渡してくれていることもわかりました。

社内情報に関係ない質問に対しては、ツールを使わずに回答してください。\n質問:###服務規定には職場でのパワーハラスメントにはどのようなことが該当すると書いてありますか?###

おまけ Google検索とのGrounding

Google検索とのGroundingが可能になりました。
Preview中の機能かつ、使用するためには
規約への同意をフォーム送信する必要があります。
※フォームと規約はこちらのページから確認できます。

以下は(最低限の)Pythonのコードから実行するものです。

import vertexai
from vertexai.generative_models import GenerativeModel, Tool
import vertexai.preview.generative_models as generative_models


def generate():
    vertexai.init(project="project_id", location="location")
    tools = [
        Tool.from_google_search_retrieval(
            google_search_retrieval=generative_models.grounding.GoogleSearchRetrieval(
                disable_attribution=False
            )
        ),
    ]
    model = GenerativeModel("gemini-1.0-pro-002", tools=tools)
    responses = model.generate_content(
        [
            """2024年4月に渋谷にできた韓国初のハンバーガー店の名前を教えてください。参照したURLもください。"""
        ],
        stream=True,
    )

    response_text = []
    for response in responses:
        try:
            response_text.append(response.text)
            continue
        except Exception:
            pass
    response_text = "".join(response_text)

generate()

以下のようなレスポンスが返ってきます。

韓国最大のハンバーガー & チキンブランド「マムズタッチ」が2024年4月16日に日本に本格上陸しました。

**参照 URL:**

* https://lemon8-app.com/pyontagram/%E6%B8%8B%E8%B0%B7_%E9%9F%93%E5%9B%BD_no_%E3%83%8F%E3%83%B3%E3%83%90%E3%83%BC%E3%82%AC%E3%83%BC_%E6%97%A5%E6%9C%AC_%E4%B8%8A%E9%99%B8
* https://www.youtube.com/watch?v=iLREhgSTYM
* https://inredweb.jp/column/lifestyle/28679/

**確認事項:**

情報が最新でない可能性があるため、正確な情報は公式ウェブサイトまたは直接店舗で確認することをお勧めします。

割と最近オープンした渋谷のハンバーガー屋さんの情報が返ってきました。
そしてYouTubeのリンクも返ってきてますね。 動画を観てみると、
たしかに渋谷にマムズタッチがオープンすることを告知したものでした。

なおGoogle Cloudのコンソールからプロンプトを実行すると
以下のようにGrounding Sourcesとして表示されます。

Vertex AIのコンソール画面 Python経由だとプロンプトで指定しないと、 モデルからのレスポンスとしてはURLが返ってきませんでした。

コンソールから実行したものと同様の?curlコマンドがコピーできるので
試しましたが、そちらのレスポンスも同様でした。
(PowerShellから実行したものです)

curl.exe -i -X POST  -H "Authorization: Bearer $(gcloud auth print-access-token)" -H "Content-Type: application/json" "https://{api_endpoint}/v1/projects/{project_id}/locations/{location}/publishers/google/models/gemini-1.0-pro-002:streamGenerateContent" -d '@request.json’

Gemini APIのリファレンスのサンプルレスポンスを見る感じだと
citationMetadataとして返ってくるような感じがしたのですが、
ちょっと確認できませんでした。

さらに最新の、日経平均を聞いてみると以下の答えが返ってきました。

日経平均株価は反落で始まり、前日に比べ550円ほど安い3万7500円前後で推移しています。下げ幅は600円を超える場面もありました。

聞いてみたのは4/19の13時ごろです。
朝9時ごろの市場が空いた時間くらいのデータとしては合っています。
完全にリアルタイムな情報を取ってこれるわけではなさそうですね。

2024年4月19日9時の日経平均

まとめ

LLMが適切な関数と引数を返してくれるFunction CallingをGeminiで行いました。

また、様々なデータソースとのGroundingが
簡単にできるようになってきているねという話でした。

参照

※本記事は、ジーアイクラウド株式会社の見解を述べたものであり、必要な調査・検討は行っているものの必ずしもその正確性や真実性を保証するものではありません。

※リンクを利用する際には、必ず出典がGIC dryaki-blogであることを明記してください。
リンクの利用によりトラブルが発生した場合、リンクを設置した方ご自身の責任で対応してください。
ジーアイクラウド株式会社はユーザーによるリンクの利用につき、如何なる責任を負うものではありません。