Open in
関数ツール
型付きパラメータを使用して、会話中に外部APIを呼び出す関数ツールをAIエージェントに提供します。顧客データの取得、予約の登録、レコードの更新が可能です。
関数ツールにより、AIエージェントは通話中に外部APIを呼び出せます。顧客データの検索、空き状況の確認、予約の登録、バックエンドでサポートされる任意のアクションの実行に使用できます。
仕組み
- スキーマ(ツールが受け付ける引数)を使用してツールを定義します
endpoint設定(ThunderPhone がAPIを呼び出す場所)を指定します。指定しない場合は、組織のWebhookでツール呼び出しを受信します- 通話中、AIは会話に基づいてツールを使用するタイミングを判断します
- ThunderPhone がツール引数とともにエンドポイントを呼び出します
- APIレスポンスがAIに返され、会話を継続します
| 機能 | 実行場所 | 設定 |
|---|---|---|
| 組み込みツール | ThunderPhone | プロンプト指示。一部のツールにはエージェント設定も必要です |
| アプリ接続 | ThunderPhone と接続済みプロバイダー | アカウントを接続し、承認済みアクションを追加します |
| API接続 と関数ツール | 独自のHTTP API | エンドポイントとスキーマを定義するか、Webhook経由で関数呼び出しを受信します |
| MCPサーバー | リモートMCPサーバー | サーバーを追加し、そのツールを検出してエージェントに追加します |
ツールスキーマ
各ツールは次の構造に従います。
{
"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-Signature-V2 + X-ThunderPhone-Signature | Content-Type + X-ThunderPhone-Signature-V2 + 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-V2: t=1767225600,v1=5f2c...
X-ThunderPhone-Signature: abc123...
X-ThunderPhone-Call-ID: 987654321
X-Api-Key: your-api-keyendpoint.headers のカスタムヘッダーは常にそのまま含まれ、
これらのThunderPhone名前空間のヘッダーも追加されます。
X-ThunderPhone-Signature-V2— メソッド、パス、クエリ、本文に対するタイムスタンプ付きHMAC-SHA256。組織のWebhookシークレットをキーとして使用します。 これを検証し、5分を超えて古いリクエストは拒否してください。Webhook署名を検証するを参照してください。X-ThunderPhone-Signature— リクエスト本文の正確なバイト列に対する従来のHMAC-SHA256。同じキーを使用し、既存の検証ツール向けに引き続き送信されます。X-ThunderPhone-Call-ID— 現在の通話ID
Content-Type: application/json は、endpoint.headers で
上書きされない限り設定されます。カスタム Content-Type が優先されます。
リクエスト形式
各 API 接続には request_format 設定があります。インポートしたツールで、そのプラットフォーム向けに構築されたエンドポイントを引き続き呼び出す場合は、vapi または
retell を選択します。新規および既存の接続のデフォルトは standard です。
POST / PUT / PATCH では、ThunderPhone は選択された
エンベロープを正規形式でシリアライズします(キーをソートし、区切り文字を圧縮)。両方の署名は、この完全に一致するバイト列を対象とします。
Standard
standard はラッパーなしで、ツール引数のみを送信します。これはデフォルトであり、ThunderPhone の元のリクエスト本文をバイト単位でそのまま保持します。
{"date":"2025-01-02","service":"consultation"}Vapi
vapi は、Vapi のドキュメントに記載された形式で Vapi の tool-calls サーバーメッセージ
を送信します。call.id は ThunderPhone の通話 ID です。ツール呼び出しの id は呼び出しごとに一意であり、エンドポイントの結果との照合に再利用されます。通話は toolCallList と
toolWithToolCallList の両方に表示され、function.arguments は JSON オブジェクトです。
{
"message": {
"type": "tool-calls",
"timestamp": 1725000000123,
"call": { "id": "987654321" },
"toolCallList": [
{
"id": "call_00112233445566778899aabbccddeeff",
"type": "function",
"function": {
"name": "search_appointments",
"arguments": { "date": "2025-01-02", "service": "consultation" }
}
}
],
"toolWithToolCallList": [
{
"type": "function",
"function": { "name": "search_appointments" },
"toolCall": {
"id": "call_00112233445566778899aabbccddeeff",
"type": "function",
"function": {
"name": "search_appointments",
"arguments": { "date": "2025-01-02", "service": "consultation" }
}
}
}
]
}
}assistant、artifact、完全な call オブジェクトなどの Vapi 専用フィールドは送信されません。
対応するツール呼び出し ID を含む Vapi のレスポンスエンベロープを返します。
{
"results": [
{
"toolCallId": "call_00112233445566778899aabbccddeeff",
"result": "{\"available_slots\":[\"9:00 AM\",\"2:00 PM\"]}"
}
]
}ThunderPhone は、ラップ解除された result 文字列を AI に渡します。すでに JSON 値を返すエンドポイントとの互換性のため、JSON 値も受け入れます。result の代わりに error がある場合は、ツールエラーとして AI に返されます。エンドポイントが不正な形式または不一致の Vapi エンベロープを返した場合、ThunderPhone は警告をログに記録し、通話を失敗させるのではなく標準のレスポンス解析にフォールバックします。
Retell
retell は、Retell カスタム関数向けにドキュメント化されたリクエストを送信します。
{
"name": "search_appointments",
"call": { "call_id": "987654321" },
"args": { "date": "2025-01-02", "service": "consultation" }
}call オブジェクトに含まれるのは call_id のみです。ThunderPhone は、ディスパッチ時に正確に提供できない agent_id、transcript、transcript_object、metadata、動的変数などの Retell 固有フィールドを含めません。Retell は任意のレスポンス本文を受け入れます。ThunderPhone は標準のレスポンス処理でそのまま渡します。
GET / DELETE では、引数はクエリパラメータとして送信され、request_format にかかわらず本文は空です。V2 署名は送信されたクエリ文字列を対象とし、レガシー署名は空のバイト文字列に対して計算されます。
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
(Web 通話)リクエストとしてディスパッチされます。実行後に 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シークレットをキーとして、Webhookと同じ方法で署名されます。
X-ThunderPhone-Signature-V2は、送信されたタイムスタンプ、メソッド、パス、クエリ、および正確な本文バイト列に署名します。タイムスタンプが現在時刻から5分を超えているリクエストは拒否してください。X-ThunderPhone-Signature(レガシー)は本文バイト列のみ、GET/DELETEでは空のバイト文字列に署名します。現在も変更なく送信されます。
SDKヘルパーは、5分の有効期間を含めてV2ヘッダーを検証します。
import json
from fastapi import FastAPI, HTTPException, Request
from thunderphone import verify_signature
app = FastAPI()
@app.post("/appointments/search")
async def search_appointments(request: Request):
body = await request.body()
query = request.scope.get("query_string", b"").decode("latin-1")
path = request.url.path + (f"?{query}" if query else "")
if not verify_signature(
request.headers.get("X-ThunderPhone-Signature-V2"),
secret=WEBHOOK_SECRET,
method=request.method,
path=path,
body=body,
):
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}import express from "express";
import { verifySignature } from "@thunderphone/sdk";
app.post('/appointments/search', express.raw({type: 'application/json'}), (req, res) => {
const ok = verifySignature({
header: req.header('X-ThunderPhone-Signature-V2'),
secret: WEBHOOK_SECRET,
method: req.method,
path: req.originalUrl,
body: req.body,
});
if (!ok) 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 });
});署名対象文字列、実例、SDKを使わないコード、GET / DELETE のケース、シークレットがない場合の注意事項を含む完全な手順は、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 はツールを呼び出す前に、発信者に必須情報を尋ねます。