ThunderPhone 2.0、提供開始。セルフサービスで、1分あたり2¢から。発表内容を見る

Function Tools

関数ツール

会話中に外部APIを呼び出す関数ツールをAIエージェントに付与します。型付きパラメータで、顧客データの取得、予約の登録、レコードの更新を行えます。

関数ツールを使用すると、AIエージェントは通話中に外部APIを呼び出せます。顧客データの検索、空き状況の確認、予約の登録、またはバックエンドが対応する任意のアクションの実行に使用できます。

仕組み

  1. スキーマ(ツールが受け付ける引数)を使用してツールを定義します
  2. endpoint 設定(ThunderPhoneがAPIを呼び出す場所)を指定します。指定しない場合、組織Webhookでツール呼び出しを受信します
  3. 通話中、AIは会話に基づいてツールを使用するタイミングを判断します
  4. ThunderPhoneはツール引数とともにエンドポイントを呼び出します
  5. APIレスポンスは会話を継続するためにAIへ返されます

ツールスキーマ

各ツールは次の構造に従います。

{
  "type": "function",
  "function": {
    "name": "search_appointments",
    "description": "Find available appointment slots for a given date",
    "parameters": {
      "type": "object",
      "properties": {
        "date": {
          "type": "string",
          "description": "Date in YYYY-MM-DD format"
        },
        "service": {
          "type": "string",
          "description": "Type of service (e.g., 'consultation', 'follow-up')"
        }
      },
      "required": ["date"]
    }
  },
  "endpoint": {
    "url": "https://api.example.com/appointments/search",
    "method": "POST",
    "headers": {
      "X-Api-Key": "your-api-key"
    }
  },
  "timeout": 120
}

ツール設定

フィールド必須説明
timeout数値いいえ最大実行時間(秒)。デフォルト: 20、最大: 180

関数定義

フィールド必須説明
name文字列はいツールの一意の識別子
description文字列はいこのツールを使用するタイミングをAIに説明します
parametersオブジェクトはいツール引数用のJSONスキーマ

エンドポイント設定

フィールド必須説明
url文字列はいAPIエンドポイントURL
method文字列いいえHTTPメソッド(デフォルト: POST
headersオブジェクトいいえ含めるカスタムヘッダー

2つの呼び出し経路

サーバーが受信するリクエストは、ツールに endpoint があるかどうかによって異なります。

endpoint ありのツールendpoint なしのツール
リクエストの送信先endpoint.url へ直接送信組織の旧Webhook URL
本文ツール引数のみtelephony.tool / web.tool エンベロープ
ヘッダーendpoint.headers + X-ThunderPhone-Call-ID + X-ThunderPhone-SignatureContent-Type + X-ThunderPhone-Signature
署名キー組織Webhookシークレット組織Webhookシークレット

どちらの経路もブロッキングです。AIは文の途中で結果を待機します。 デフォルトのタイムアウトは20 秒です。ツールの最上位レベルにある timeout を設定すると、プラットフォームの最大値である180 秒まで、 より長い実行時間を許可できます。ハンドラーは高速に保ってください。混在も可能です。 組織にWebhook URLが設定された通話では、endpoint を持つツールは 直接呼び出され、残りはWebhookにフォールバックします。

直接エンドポイント呼び出し

AI が endpoint を持つツールを呼び出すと、ThunderPhone は 設定した URL にリクエストを送信します。

リクエストヘッダー

POST /appointments/search HTTP/1.1
Host: api.example.com
Content-Type: application/json
X-ThunderPhone-Signature: abc123...
X-ThunderPhone-Call-ID: 987654321
X-Api-Key: your-api-key

endpoint.headers のカスタムヘッダーは常にそのまま含まれ、 さらに ThunderPhone 名前空間のヘッダーが 2 つ追加されます。

  • X-ThunderPhone-Signature組織の webhook シークレットをキーとして使用した、正確なリクエスト本文バイト列の HMAC-SHA256
  • X-ThunderPhone-Call-ID — 現在の通話 ID

endpoint.headers で上書きしない限り、Content-Type: application/json が設定されます。カスタムの Content-Type が優先されます。

リクエスト本文

POST / PUT / PATCH では、本文にはツール引数のみが 含まれます(ラッパーなし)。正規形式(キーのソート、コンパクトな 区切り文字)でシリアル化されます。

{"date":"2025-01-02","service":"consultation"}

GET / DELETE では、引数はクエリパラメーターとして送信され、 本文は空です。この場合、署名は空のバイト文字列に対して計算されます。 webhook 署名の検証を参照してください。

レスポンス

ツール結果を含む JSON レスポンスを返します。

{
  "available_slots": ["9:00 AM", "2:00 PM", "4:30 PM"],
  "timezone": "America/Los_Angeles"
}

レスポンスは整形され、会話を継続するために AI に提供されます。 JSON 以外のレスポンスは {"data": "<text>"} としてラップされます。 タイムアウトと接続障害はエラーとして AI に報告されるため、 エージェントは停止することなく謝罪して次に進めます。

webhook モードのディスパッチ

endpoint持たないツールは、組織の従来の webhook URL に、 署名付きの telephony.tool(電話通話)または web.tool (ウェブ通話)リクエストとしてディスパッチされます。実行後に webhook エンドポイントへ配信される監査通知とは異なり、このリクエスト自体が 実行です。HTTP レスポンスがツール結果になります。

{
  "type": "telephony.tool",
  "data": {
    "call_id": 987654321,
    "tool_name": "search_appointments",
    "arguments": { "date": "2026-04-21" },
    "from_number": "+14155550199",
    "to_number": "+15551234567"
  }
}

web.tool には from_number / to_number の代わりに origin_domain が含まれます。ツール結果を JSON として返してください。 レスポンスの契約は直接エンドポイント呼び出しと同じです。リクエストは、 他のすべての webhook と同様に、生の本文に対して組織の webhook シークレットで署名されます。


署名の検証

直接ツール呼び出しは、Webhook と同じ方法で署名されます。

  • 正確なリクエスト本文のバイト列(正規化された JSON、キーはソート済み、余分な空白なし)に対する HMAC-SHA256
  • 組織の Webhook シークレットをキーとして使用
  • GET / DELETE ツールは空のバイト列に署名
Python
import hmac
import hashlib
 
def verify_tool_call(body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)
 
@app.post("/appointments/search")
async def search_appointments(request: Request):
    body = await request.body()
    signature = request.headers.get("X-ThunderPhone-Signature", "")
 
    if not verify_tool_call(body, signature, WEBHOOK_SECRET):
        raise HTTPException(status_code=401)
 
    data = json.loads(body)
    date = data["date"]
 
    # Look up availability
    slots = await get_available_slots(date)
 
    return {"available_slots": slots}
Node.js
app.post('/appointments/search', express.raw({type: 'application/json'}), (req, res) => {
  const signature = req.headers['x-thunderphone-signature'] || '';
  const expected = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(req.body)
    .digest('hex');
 
  if (!signature ||
      signature.length !== expected.length ||
      !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
    return res.status(401).send('Invalid signature');
  }
 
  const { date, service } = JSON.parse(req.body);
 
  // Look up availability
  const slots = getAvailableSlots(date, service);
 
  res.json({ available_slots: slots });
});

空の本文の場合やシークレットがない場合の注意事項を含む完全な手順は、Webhook 署名を検証を参照してください。


例: 完全な予約フロー

完全な予約システム向けのツールセットを以下に示します。

{
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "search_appointments",
        "description": "Find available appointment slots",
        "parameters": {
          "type": "object",
          "properties": {
            "date": { "type": "string", "description": "YYYY-MM-DD" },
            "service": { "type": "string" }
          },
          "required": ["date"]
        }
      },
      "endpoint": {
        "url": "https://api.example.com/appointments/search",
        "method": "POST",
        "headers": { "X-Api-Key": "key" }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "book_appointment",
        "description": "Book an appointment at a specific time",
        "parameters": {
          "type": "object",
          "properties": {
            "date": { "type": "string", "description": "YYYY-MM-DD" },
            "time": { "type": "string", "description": "HH:MM format" },
            "customer_name": { "type": "string" },
            "customer_phone": { "type": "string" }
          },
          "required": ["date", "time", "customer_name"]
        }
      },
      "endpoint": {
        "url": "https://api.example.com/appointments/book",
        "method": "POST",
        "headers": { "X-Api-Key": "key" }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "cancel_appointment",
        "description": "Cancel an existing appointment",
        "parameters": {
          "type": "object",
          "properties": {
            "confirmation_number": { "type": "string" }
          },
          "required": ["confirmation_number"]
        }
      },
      "endpoint": {
        "url": "https://api.example.com/appointments/cancel",
        "method": "POST",
        "headers": { "X-Api-Key": "key" }
      }
    }
  ]
}

ベストプラクティス

明確な説明を書く

description フィールドは、AI がツールを使用するタイミングを理解するのに役立ちます。ツールの機能と適切な使用タイミングを具体的に記述してください。

エラーを適切に処理する

汎用的な 500 エラーではなく、AI が理解できるエラーメッセージを返してください。例: {"error": "No slots available for that date"}

応答を簡潔に保つ

AI が会話を続けるために必要な情報だけを返してください。大きなペイロードは応答時間を遅くします。

必須フィールドを適切に使用する

本当に必要な場合にのみフィールドを required として指定してください。AI はツールを呼び出す前に、必要な情報をユーザーに尋ねます。


関連