📞

ClaudeのTool(function calling)を使う

2024/04/06に公開

はじめに

2024年4月、ClaudeのTool(function calling)がパブリックベータ版となりました。
「GPT-4を超えた」とも噂されているClaude 3 Opusを使ってfunction callingも使えるようなので、その使い方を紹介します。

本記事は公式ドキュメントを噛み砕いてまとめた内容となります。

対象読者

タイトルの通り、ドキュメントを読むのがマストですが「その前にちょっとかじっておきたい」という方に向けて書いています。

Tool(function calling)とは

https://docs.anthropic.com/claude/docs/tool-use

Tool(関数名、引数、それらの説明などを定義したオブジェクト)とユーザープロンプトを与えると、Claudeはユーザーのプロンプトを評価し、そのツールを使用すべきか判断します。使用すべきと判断したときは、どのような入力値(引数)で実行すべきかという情報も返します。

※あくまで関数を実行するのはクライアント側(プログラム側)です。Claudeはその処理を実行すべきか、実行するとしたらどのようなinputをすべきかということを教えてくれます。

使ってみる

Pythonを使って、指定した日付の天気を取得するサンプルコードを書いてみましょう。
今回は公式ドキュメントに沿ってMessaging APIを使用します。

anthropic-toolsをcloneしてtoolを使用する方法もあるので、以下のリポジトリをご確認ください。
https://github.com/anthropics/anthropic-tools

準備1: Anthropic(Claude)のAPIキーを取得

以下のURLからAPIキーを発行し、.envファイルに追加します。

https://console.anthropic.com/settings/keys

.env
ANTHROPIC_API_KEY=sk-xxxxxxxxxxxxx

準備2: 天気を取得する関数を定義

まずは、天気を取得する関数を定義します。
今回は練習のため、無料でAPIキーも必要なく使えるこちらのAPIを使用します。
https://weather.tsukumijima.net/

from dotenv import load_dotenv
import requests

load_dotenv()

def get_weather(date) -> str:
    # 130010: 東京
    url = "https://weather.tsukumijima.net/api/forecast/city/130010"

    try:
        response = requests.get(url)
        response.raise_for_status()
        data = response.json()

        for forecast in data["forecasts"]:
            if forecast["date"] == date:
                return forecast["telop"]
        return "該当する日付の天気情報が見つかりませんでした。"

    except requests.RequestException as e:
        return f"エラーが発生しました: {e}"

Toolを使う

今日、明日、明後日の東京の天気を取得してみます。

import anthropic

client = anthropic.Anthropic()

messages = [{
    "role": "user",
    "content": "今日と明日と明後日の天気を教えてください。今日の日付は2024-04-06です。",
}]

response = client.beta.tools.messages.create(
    model="claude-3-opus-20240229",
    max_tokens=1024,
    tools=[{
        "name": "get_weather",
        "description": "与えられた日付の天気を取得します。",
        "input_schema": {
            "type": "object",
            "properties": {
                "date": {
                    "type": "string",
                    "description": "取得したい日付を指定します。形式はYYYY-MM-DDです。例: 2022-01-01",
                }
            },
            "required": ["date"],
        },
    }],
    messages=messages,
)

# レスポンスの内容から、get_weather関数をコールします
for content in response.content:
    if content.type == "tool_use":
        res = get_weather(content.input["date"])
        print(res)

3日間の天気が取得できました。

曇り
曇一時雨
曇のち一時雨

一旦動くことが確認できたので、もう少し細かくみていきます。

ツールの指定

先ほど、以下のようなtoolsを指定しました。

    [{
        "name": "get_weather",
        "description": "与えられた日付の天気を取得します。",
        "input_schema": {
            "type": "object",
            "properties": {
                "date": {
                    "type": "string",
                    "description": "取得したい日付を指定します。形式はYYYY-MM-DDです。例: 2022-01-01",
                }
            },
            "required": ["date"],
        },
    }],

指定するパラメータは以下の通りです。

  • name:
    • ツールの名前です。主に関数名と一致する場合が多いかと思います。
    • ^[a-zA-Z0-9_-]{1,64}$この正規表現に従う必要があります。
  • description:
    • ツールの機能、使用時期、動作方法についての詳細なプレーンテキストの説明です。
  • input_schema:
    • パラメータを定義するオブジェクトです。
    • 関数の引数となるものを与えます。

ツール定義のベストプラクティス

以下のルールに従って記述することが推奨されています。

  • ツールの説明には次の4つを含める
    • ツールの機能
    • いつ使用すべきか(いつ使用すべきでないか)
    • 各パラメータの意味とそれがツールの動作にどのように影響するか
    • ツール名が不明な場合にツールが返さない情報など、重要な注意事項や制限事項
  • 少なくとも3~4文の説明を記述する
    • ツールが複雑な場合は、それ以上の文量を記述
  • 例よりも説明を優先
    • 例を含めることができるが、それよりもツールの説明の方が大切
    • 説明を完全に具体化した後でのみ、例を追加する

レスポンスの内容

先ほどのコードでは、Claudeから以下のレスポンスが返ってきています。

{
  "id": "msg_014m3KBnQqi7v9KRZSE8WsTA",
  "content": [
    {
      "text": "<thinking>\nツールの選択:\nユーザーは複数の日付の天気を聞いているので、get_weather関数を使うのが適切だと思います。\n\nパラメータの確認:\nget_weather関数には必須パラメータとしてdateがあります。\n今日の日付は2024-04-06だと与えられています。\n明日は2024-04-07、明後日は2024-04-08になります。\nこれで3日分の日付が揃ったので、関数を3回呼び出せばユーザーの質問に答えられそうです。\n</thinking>",
      "type": "text"
    },
    {
      "id": "toolu_01RuCF8aZ56eewqb4jpYi4YR",
      "input": {
        "date": "2024-04-06"
      },
      "name": "get_weather",
      "type": "tool_use"
    },
    {
      "id": "toolu_01XsL9YfnZUqQGWZnKFxoZQd",
      "input": {
        "date": "2024-04-07"
      },
      "name": "get_weather",
      "type": "tool_use"
    },
    {
      "id": "toolu_01DBvW7Raq5x7gP1v8HWW41L",
      "input": {
        "date": "2024-04-08"
      },
      "name": "get_weather",
      "type": "tool_use"
    }
  ],
  "model": "claude-3-opus-20240229",
  "role": "assistant",
  "stop_reason": "tool_use",
  "stop_sequence": null,
  "type": "message",
  "usage": {
    "input_tokens": 675,
    "output_tokens": 309
  }
}

ClaudeがToolを実行すべきと判断した場合は、"stop_reason": "tool_use"を返し、content[n].type = "tool_use"となるオブジェクトをリストに格納します。

Toolを使用する必要がないと判断された場合

今回定義したToolは天気を取得するもので、プロンプトも

"今日と明日と明後日の天気を教えてください。今日の日付は2024-04-06です。"

と明確なものでした。

全く関係のないプロンプトを入力して、Toolを実行する必要がないと判断させてみましょう。

messages = [{
    "role": "user",
    # "content": "今日と明日と明後日の天気を教えてください。今日の日付は2024-04-06です。",
    "content": "こんにちは!",
}]

今度は"stop_reason": "end_turn",というレスポンスが返り、contentには実行すべきオブジェクトが入っていません。

ユーザーは単に「こんにちは!」と挨拶しただけで、特に質問や要求はありません。

実行すべきではないと決めた理由もtextにしっかり入っています。

response
{
  "id": "msg_01WcjzoLGXrGeMpCL8SnqaZT",
  "content": [
    {
      "text": "<thinking>\nユーザーは単に「こんにちは!」と挨拶しただけで、特に質問や要求はありません。天気を聞かれたわけでもないので、現時点では get_weather 関数を呼び出す必要はないと考えられます。\n</thinking>\n\nこんにちは!ご挨拶ありがとうございます。私にできることがあればお手伝いしますので、気軽に聞いてくださいね。天気予報や日程のご相談など、どんなことでもお気軽にどうぞ!",
      "type": "text"
    }
  ],
  "model": "claude-3-opus-20240229",
  "role": "assistant",
  "stop_reason": "end_turn",
  "stop_sequence": null,
  "type": "message",
  "usage": {
    "input_tokens": 648,
    "output_tokens": 166
  }
}

ClaudeでToolを使用すべきではないと判断され、レスポンスも期待した状態となっています。

実行結果を元に、再度会話を進める

現時点では結果が以下のようにしか表示されていません。

曇り
曇一時雨
曇のち一時雨

これをさらにClaudeにリクエストすることで、より自然な回答とすることができます。

今回は実装しませんが、以下のリクエストをmessageに追加して送信します。

{
  "role": "user",
  "content": [
    {
      "type": "tool_result",
      "tool_use_id": "toolu_01RuCF8aZ56eewqb4jpYi4YR",
      "content": "曇り"
    }
  ]
}

エラー発生時の処理

get_weatherでエラーが発生した場合、"is_error": trueとし、contentにエラーメッセージを含めることでエラーであることを表現できます。

{
  "role": "user",
  "content": [
    {
      "type": "tool_result",
      "tool_use_id": "toolu_01RuCF8aZ56eewqb4jpYi4YR",
      "content": "ConnectionError: the weather service API is not available (HTTP 500)",
      "is_error": true
    }
  ]
}

また、エラーは主に次の2つに分けられます。

  • リクエストは問題なかったが、内部でエラーが発生
  • Claudeからのリクエストが不正な値であった

これらの違いをcontentのメッセージに詳しく記述することでClaudeが次のアクションを返します。
もしClaudeからのリクエストが不正であった場合は再度ツールの使用を試みますが、このエラーはツールを正しく使用するための十分な情報がなかったことを意味します。

もし開発段階で分かっている場合は、ツールの説明を見直して再度リクエストすることが望ましいです。

内部エラーの場合は、基本的にはその内容をレスポンスとして返してくれます。

{
  "role": "user", 
  "content": [
    {
      "type": "tool_result",
      "tool_use_id": "toolu_01RuCF8aZ56eewqb4jpYi4YR",
      "content": "ConnectionError: the weather service API is not available (HTTP 500)",
      "is_error": true
    }
  ]
}

この場合は、以下のように返します。(公式ドキュメントのサンプルを使用しています)

「申し訳ありませんが、天気予報サービス API が利用できないため、現在の天気を取得できませんでした。後でもう一度お試しください。」

ツールの使用に関するベストプラクティス

※以下で頻出する「複雑なツール」とは、多数のパラメータを持つツール、またはネストされたオブジェクトのパラメータが含まれているツールのことを指します。

  • 複雑なツールを使用するには Claude 3 Opusを使用
    • (当然ですね。)
  • ツールの数は250以上でもOK
    • 説明が詳細に書かれており、パラメータに必要な情報が全て含まれている場合は90%を超える精度で処理できる
  • 複雑で深くネストされたツールを避ける
    • よりシンプルなインターフェイスの方が適切に処理できる
  • 並行処理を避ける
    • 一度に1つのツールを使用し、そのツールの出力を使用して次のアクションを通知すること好む
    • プロンプトの工夫で複数ツールを並行して処理させることは可能だが、パラメーターにダミー値を入力する可能性がある
  • 再試行は2-3回
    • Claudeが必要なパラメータを欠落した場合にエラーを返すことで再試行できるが、2-3回失敗すると再試行ではなく謝罪を返す
    • ※先述の通り、この場合は説明かパラメータのどちらかの情報が欠落していることが多い

トークンと料金

モデルに送信された入力トークンの総数(toolsパラメータ)と生成された出力トークンの数が課金対象となるトークン数となります。(1リクエストあたり)

また、モデルによってシステムプロンプトが異なり、これはデフォルトで追加されるトークン数となります。

  • システムプロンプト(変更不可)
    • Claude 3 Opus: 395トークン
    • Claude 3 Sonnet: 159トークン
    • Claude 3 Haiku: 264トークン

エラー時に再実行させる場合は、その回数分のトークンが消費されることになるため料金には注意してください。

詳しくは以下のページをご確認ください。
https://docs.anthropic.com/claude/docs/tool-use-pricing-and-tokens

さいごに

最初にも記載しましたが、本記事の情報は常に更新されるため、公式ドキュメントをご確認ください。

Discussion