---
title: "函式工具"
description: "為你的 AI 智慧體提供函式工具，可在對話進行中呼叫外部 API——擷取客戶資料、預約、更新紀錄——並使用具型別的參數。"
---

函式工具可讓你的語音智慧體在通話期間呼叫外部 API。使用它們查詢客戶資料、確認可用時段、預約，或執行你的後端支援的任何動作。

## 運作方式

1. 使用結構描述定義工具（工具接受哪些參數）
2. 提供 `endpoint` 設定（ThunderPhone 在何處呼叫你的 API）——或省略它，以便在你的組織 webhook 上接收工具呼叫
3. 通話期間，人工智慧會根據對話決定何時使用工具
4. ThunderPhone 使用工具參數呼叫你的端點
5. 你的 API 回應會回傳給人工智慧，以繼續對話

| 功能 | 執行位置 | 設定方式 |
| --- | --- | --- |
| [內建工具](/zh-Hant/guides/built-in-tools) | ThunderPhone | 提示指令；部分工具也需要設定智慧體 |
| [應用程式連線](/zh-Hant/guides/connect-apps) | ThunderPhone 與已連線的服務供應商 | 連接帳號並附加已核准的動作 |
| [API 連線](/zh-Hant/guides/api-connections)與函式工具 | 你的 HTTP API | 定義端點與結構描述，或透過 webhook 接收函式呼叫 |
| [MCP 伺服器](/zh-Hant/guides/mcp-servers) | 遠端 MCP 伺服器 | 新增伺服器、探索其工具，並將其附加至智慧體 |

---

## 工具結構描述

每個工具都遵循以下結構：

```json
{
  "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` | 字串 | 是 | 向人工智慧說明何時使用此工具 |
| `parameters` | 物件 | 是 | 工具參數的 JSON Schema |

### 端點設定

| 欄位 | 類型 | 必填 | 說明 |
|-------|------|----------|-------------|
| `url` | 字串 | 是 | 你的 API 端點 URL |
| `method` | 字串 | 否 | HTTP 方法（預設值：`POST`） |
| `headers` | 物件 | 否 | 要包含的自訂標頭 |

<Note>
  `endpoint` 設定**不會**傳送至人工智慧模型——它僅供 ThunderPhone 執行工具呼叫時使用。
</Note>

---

## 兩種呼叫路徑

你的伺服器收到哪種請求，取決於工具是否具有
`endpoint`：

| | **含有** `endpoint` 的工具 | **不含** `endpoint` 的工具 |
|---|---|---|
| 請求傳送位置 | 直接傳送至 `endpoint.url` | 你組織的[舊版 Webhook URL](/api-reference/organizations#legacy-single-url-webhook) |
| 內文 | 所選的請求格式（預設為純引數） | `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 密鑰 |

兩種路徑都是**阻塞式**——人工智慧會在句子中途等待
結果。預設逾時時間為**20 秒**；請設定工具頂層的
`timeout` 以允許更長的執行時間，最長可達平台的**180 秒**
上限。請讓處理常式保持快速。混用沒有問題：
在組織具有 webhook URL 的通話中，具有 `endpoint` 的工具會
直接呼叫，其餘工具則會改由 webhook 處理。

## 直接端點呼叫

當 AI 呼叫具有 `endpoint` 的工具時，ThunderPhone 會向你的 URL 傳送
請求：

### 請求標頭

```http
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-key
```

你在 `endpoint.headers` 中設定的自訂標頭一律會原樣包含，
此外還會加入以下 ThunderPhone 命名空間標頭：

- `X-ThunderPhone-Signature-V2` ——涵蓋方法、路徑、查詢參數與
  內文的時間戳記 HMAC-SHA256，並以你的**組織 webhook 密鑰**作為金鑰。
  請驗證此簽章並拒絕超過 5 分鐘的請求；請參閱
  [驗證 webhook 簽章](/zh-Hant/guides/verify-webhook-signatures)
- `X-ThunderPhone-Signature` ——針對完整請求內文位元組的舊版 HMAC-SHA256，
  使用相同金鑰，仍會傳送給既有的驗證工具
- `X-ThunderPhone-Call-ID` ——目前的通話 ID

除非你的 `endpoint.headers` 覆寫它，否則會設定
`Content-Type: application/json` —— 自訂 `Content-Type` 優先。

<Warning>
  簽名使用來自組織層級
  [`GET /v1/webhook`](/api-reference/organizations#legacy-single-url-webhook)
  的 Webhook 密鑰作為金鑰。
  如果你的組織從未設定舊版 Webhook，就不會有
  密鑰，且工具呼叫將**僅**包含 `X-ThunderPhone-Call-ID` —— 對缺少簽名
  直接失敗的處理常式會拒絕這些呼叫。
  請設定舊版 Webhook 以取得密鑰，或在 `endpoint.headers` 中放入你自己的
  共用密鑰。
</Warning>

### 請求格式

每個 API 連線都有 `request_format` 設定。當匯入的工具應持續呼叫為該平台建立的端點時，請選擇 `vapi` 或
`retell`。新的和既有的連線預設為 `standard`。

對於 `POST` / `PUT` / `PATCH`，ThunderPhone 會以標準形式序列化所選的
封裝格式（排序鍵值、精簡分隔符）。兩種簽章都涵蓋這些完全相同的位元組。

#### Standard

`standard` 僅傳送工具引數，不含任何包裝。這是預設行為，並會逐位元組保留 ThunderPhone 原始的請求主體：

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

#### Vapi

`vapi` 會以 Vapi 文件記載的格式傳送 Vapi 的 [`tool-calls` 伺服器訊息](https://docs.vapi.ai/tools/custom-tools)。
`call.id` 是 ThunderPhone 的通話 ID。工具呼叫 `id` 對每次叫用皆為唯一，並會重複使用以比對端點的結果。該呼叫會同時出現在 `toolCallList` 和
`toolWithToolCallList` 中，其中 `function.arguments` 為 JSON 物件：

```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" }
          }
        }
      }
    ]
  }
}
```

不會傳送僅限 Vapi 的欄位，例如 `assistant`、`artifact` 和完整的 `call` 物件。

請傳回包含相符工具呼叫 ID 的 Vapi 回應封裝：

```json
{
  "results": [
    {
      "toolCallId": "call_00112233445566778899aabbccddeeff",
      "result": "{\"available_slots\":[\"9:00 AM\",\"2:00 PM\"]}"
    }
  ]
}
```

ThunderPhone 會將未封裝的 `result` 字串提供給 AI。為了相容於已經傳回 JSON 值的端點，也接受 JSON 值。以 `error` 取代 `result` 時，會以工具錯誤的形式傳回給 AI。如果端點傳回格式錯誤或不相符的 Vapi 封裝，ThunderPhone 會記錄警告，並改用其標準回應剖析，而不會使通話失敗。

#### Retell

`retell` 會傳送 [Retell 自訂函式](https://docs.retellai.com/build/single-multi-prompt/custom-function)文件中記載的請求：

```json
{
  "name": "search_appointments",
  "call": { "call_id": "987654321" },
  "args": { "date": "2025-01-02", "service": "consultation" }
}
```

`call` 物件僅包含 `call_id`。ThunderPhone 不會包含在派送時無法如實提供的 Retell 專用欄位，例如
`agent_id`、`transcript`、`transcript_object`、`metadata` 或動態變數。Retell 接受任何回應主體；ThunderPhone 會使用標準回應處理將其直接傳遞。

對於 `GET` / `DELETE`，引數會作為**查詢參數**傳送，
且無論 `request_format` 為何，主體皆為空。V2 簽章涵蓋傳送的查詢字串；舊版簽章則根據空位元組字串計算。請參閱
[驗證 webhook 簽章](/zh-Hant/guides/verify-webhook-signatures)。

### 回應

傳回包含工具結果的 JSON 回應：

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

系統會格式化回應並提供給 AI，以繼續進行
對話。非 JSON 回應會包裝為 `{"data": "<text>"}`；
逾時與連線失敗會以錯誤形式回報給 AI，讓
智慧體可以致歉並繼續處理，而不會停滯。

## Webhook 模式分派

**沒有** `endpoint` 的工具會作為已簽名的 `telephony.tool`（電話通話）或 `web.tool`
（網頁通話）請求，分派至你的組織舊版 Webhook URL。不同於在執行後
傳送至 Webhook 端點的[稽核通知](/zh-Hant/webhooks/events)，此請求**就是**
執行本身 —— 你的 HTTP 回應即為工具結果。

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

`web.tool` 會帶有 `origin_domain`，而非 `from_number` /
`to_number`。請以 JSON 回傳工具結果——其回應契約與直接呼叫端點時相同。此請求會使用組織的 webhook 密鑰簽署，並包含兩個簽章標頭，如同其他所有 webhook。

<Note>
  已訂閱的 [Webhook 端點](/zh-Hant/webhooks/endpoints)會在每次工具執行後
  額外收到非阻塞的 `telephony.tool` / `web.tool` **通知**
  （無論工具經由哪個路徑執行），其中包含工具的回應 —— 適合用於
  稽核軌跡。請參閱
  [事件目錄](/zh-Hant/webhooks/events)。
</Note>

---

## 簽章驗證

直接工具呼叫的簽署方式與 Webhook 相同，使用你組織的 Webhook 密鑰：

- `X-ThunderPhone-Signature-V2` 會簽署傳送時的時間戳記、方法、路徑和查詢字串，以及完全相同的請求主體位元組。若請求的時間戳記與你的時鐘相差超過 5 分鐘，請拒絕該請求。
- `X-ThunderPhone-Signature`（舊版）僅簽署請求主體位元組；對於 `GET` / `DELETE`，則簽署空白位元組字串。此標頭仍會照原樣傳送。

SDK 輔助函式會驗證 V2 標頭，包括 5 分鐘的有效時間範圍：

<CodeGroup>
```python Python
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}
```

```javascript Node.js
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 });
});
```
</CodeGroup>

完整指南——簽署字串、實作範例、不使用 SDK 的程式碼、`GET` / `DELETE` 情境，以及未設定密鑰時的注意事項——請參閱[驗證 Webhook 簽章](/zh-Hant/guides/verify-webhook-signatures)。

---

## 範例：完整預約流程

以下是一組可用於完整預約系統的工具：

```json
{
  "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" }
      }
    }
  ]
}
```

---

## 最佳實務

<AccordionGroup>
  <Accordion title="撰寫清楚的說明">
    `description` 欄位可協助 AI 理解**何時**使用工具。請明確說明工具的功能及適用時機。
  </Accordion>

  <Accordion title="妥善處理錯誤">
    回傳 AI 能理解的錯誤訊息：`{"error": "No slots available for that date"}`，而非通用的 500 錯誤。
  </Accordion>

  <Accordion title="保持回應精簡">
    僅回傳 AI 繼續對話所需的資訊。大型承載資料會拖慢回應時間。
  </Accordion>

  <Accordion title="審慎使用必填欄位">
    僅在確實必要時才將欄位標記為 `required`。AI 會在呼叫工具前向使用者詢問必填資訊。
  </Accordion>
</AccordionGroup>

---

## 相關內容

<CardGroup cols={2}>
  <Card title="內建工具" icon="wrench" href="/zh-Hant/guides/built-in-tools">
    無須定義端點，即可提示由平台管理的通話動作。
  </Card>
  <Card title="應用程式連線" icon="plug" href="/zh-Hant/guides/connect-apps">
    適用於 HubSpot、Salesforce、Slack、Google
    Calendar、Google Sheets 和 Cal.com 的平台管理工具——無須端點。
  </Card>
  <Card title="MCP 伺服器" icon="server" href="/zh-Hant/guides/mcp-servers">
    連接 MCP 伺服器，讓智慧體呼叫其工具。
  </Card>
  <Card title="API 連線" icon="code" href="/zh-Hant/guides/api-connections">
    可重複使用且可連接至智慧體的 REST 整合。
  </Card>
  <Card title="驗證 webhook 簽章" icon="shield-check" href="/zh-Hant/guides/verify-webhook-signatures">
    適用於 webhook 與工具呼叫的單一驗證輔助工具。
  </Card>
</CardGroup>
