---
title: "函數工具"
description: "為你的 AI 智能體提供函數工具，可在對話期間呼叫外部 API——擷取客戶資料、預約、更新記錄——並支援具類型的參數。"
---

函數工具讓你的 AI 智能體可在通話期間調用外部 API。你可用它們查詢客戶資料、檢查可用時段、預約服務，或執行後端支援的任何操作。

## 運作方式

1. 以綱要定義工具（工具可接受哪些參數）
2. 提供 `endpoint` 設定（ThunderPhone 在何處調用你的 API）——或不設定，以便透過組織 webhook 接收工具調用
3. 通話期間，AI 會根據對話決定何時使用工具
4. ThunderPhone 會連同工具參數調用你的端點
5. 你的 API 回應會傳回 AI，以繼續對話

| 功能 | 執行位置 | 設定方式 |
| --- | --- | --- |
| [內建工具](/yue/guides/built-in-tools) | ThunderPhone | 提示指示；部分工具亦需要設定智能體 |
| [應用程式連接](/yue/guides/connect-apps) | ThunderPhone 及已連接的服務供應商 | 連接帳戶並附加已核准操作 |
| [API 連接](/yue/guides/api-connections)及函數工具 | 你的 HTTP API | 定義端點及綱要，或透過 webhook 接收函數調用 |
| [MCP 伺服器](/yue/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` | 字串 | 是 | 向 AI 說明何時使用此工具 |
| `parameters` | 物件 | 是 | 工具參數的 JSON 綱要 |

### 端點設定

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

<Note>
  `endpoint` 設定**不會**傳送至 AI 模型——僅供 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` | `Content-Type` + `X-ThunderPhone-Signature` |
| 簽署金鑰 | 組織 webhook 密鑰 | 組織 webhook 密鑰 |

兩種路徑均為**阻塞式**——AI 會在句子中途等待
結果。預設逾時時間為 **20 s**；設定工具頂層的
`timeout` 可允許較長執行時間，最高為平台的 **180 s**
上限。請保持處理程式快速執行。可混合使用：
在組織設有 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: abc123...
X-ThunderPhone-Call-ID: 987654321
X-Api-Key: your-api-key
```

你的 `endpoint.headers` 中的自訂標頭一律會原樣包含，
另外亦會加入兩個 ThunderPhone 命名空間標頭：

- `X-ThunderPhone-Signature` —— 使用你的**機構 webhook 密鑰**作為金鑰，對完全一致的請求主體
  位元組計算的 HMAC-SHA256
- `X-ThunderPhone-Call-ID` —— 目前的通話 ID

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

<Warning>
  簽名使用機構層級的 webhook 密鑰作為金鑰，該密鑰來自
  [`GET /v1/webhook`](/api-reference/organizations#legacy-single-url-webhook)。
  如你的機構從未設定舊版 webhook，便不會有
  密鑰，工具呼叫只會帶有 `X-ThunderPhone-Call-ID` —— 對缺少簽名
  而直接失敗的處理程式會拒絕這些呼叫。
  你可設定舊版 webhook 以取得密鑰，或在 `endpoint.headers` 中加入自己的
  共用密鑰。
</Warning>

### 請求主體

對於 `POST` / `PUT` / `PATCH`，主體只包含工具
引數（不含包裝層），並會以標準格式序列化（鍵已排序，分隔符號精簡）：

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

對於 `GET` / `DELETE`，引數會以**查詢參數**傳送，
而主體為空 —— 簽名會根據空位元組字串計算。請參閱
[驗證 webhook 簽名](/yue/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 端點的[稽核通知](/yue/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 端點](/yue/webhooks/endpoints)亦會在每次工具執行後
  額外收到非阻塞式 `telephony.tool` / `web.tool` **通知**
  （不論採用哪種路徑執行），當中包括工具的回應 —— 適合用於建立
  稽核記錄。請參閱
  [事件目錄](/yue/webhooks/events)。
</Note>

---

## 簽名驗證

直接工具呼叫的簽名方式與 Webhook 相同：

- 對完全相同的請求主體位元組計算 HMAC-SHA256（規範 JSON——鍵值已排序，沒有額外空白字元）
- 使用你的機構 Webhook 密鑰作為金鑰
- `GET` / `DELETE` 工具會對空位元組字串簽名

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

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

完整範例——包括空主體情況及沒有密鑰時的注意事項——請參閱[驗證 Webhook 簽名](/yue/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="/yue/guides/built-in-tools">
    無需定義端點，即可觸發由平台管理的通話操作。
  </Card>
  <Card title="應用程式連接" icon="plug" href="/yue/guides/connect-apps">
    適用於 HubSpot、Salesforce、Slack、Google
    Calendar、Google Sheets 及 Cal.com 的平台管理工具——無需端點。
  </Card>
  <Card title="MCP 伺服器" icon="server" href="/yue/guides/mcp-servers">
    連接 MCP 伺服器，讓智能體呼叫其工具。
  </Card>
  <Card title="API 連接" icon="code" href="/yue/guides/api-connections">
    可重複使用並可連接至智能體的 REST 整合。
  </Card>
  <Card title="驗證 webhook 簽章" icon="shield-check" href="/yue/guides/verify-webhook-signatures">
    適用於 webhook 及工具呼叫的單一驗證輔助程式。
  </Card>
</CardGroup>
