> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thunderphone.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 函式工具

> 讓你的人工智慧智慧體在對話期間呼叫外部 API

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

## 運作方式

1. 你使用結構描述定義工具（工具可接受的引數）
2. 你提供 `endpoint` 設定（ThunderPhone 呼叫你的 API 的位置）——或不設定，以便在組織的網路掛鉤接收工具呼叫
3. 通話期間，AI 會根據對話決定何時使用工具
4. ThunderPhone 會使用工具引數呼叫你的端點
5. 你的 API 回應會回傳給 AI，以繼續對話

<Note>
  函式工具是自備 API 的方式。ThunderPhone 也
  提供不需要端點的平台代管工具：
  [應用程式連線](/zh-Hant/guides/connect-apps)（HubSpot、Salesforce、Slack、
  Google Calendar、Google Sheets、Cal.com）、
  [API 連線](/zh-Hant/guides/api-connections)，以及
  [MCP 伺服器](/zh-Hant/guides/mcp-servers)。
</Note>

***

## 工具結構描述

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

```json theme={null}
{
  "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"
    }
  }
}
```

### 函式定義

| 欄位            | 類型 | 必填 | 說明                |
| ------------- | -- | -- | ----------------- |
| `name`        | 字串 | 是  | 工具的唯一識別碼          |
| `description` | 字串 | 是  | 向 AI 說明何時使用此工具    |
| `parameters`  | 物件 | 是  | 工具引數的 JSON Schema |

### 端點設定

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

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

***

## 兩種呼叫路徑

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

|        | **具有** `endpoint` 的工具                                                         | **不具有** `endpoint` 的工具                                                    |
| ------ | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| 請求傳送位置 | 直接傳送至 `endpoint.url`                                                          | 你的組織的[舊版網路掛鉤 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`                               |
| 簽署金鑰   | 組織網路掛鉤密鑰                                                                      | 組織網路掛鉤密鑰                                                                  |

兩種路徑皆為**阻塞式**——AI 會在說到一半時等待
結果——並設有 **20 秒**逾時限制。請讓處理常式保持快速。混用也沒問題：
在組織設有網路掛鉤 URL 的通話中，具有 `endpoint` 的工具會
直接呼叫，其餘工具則會改由網路掛鉤處理。

## 直接端點呼叫

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

### 請求標頭

```http theme={null}
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>
  簽章使用來自
  [`GET /v1/webhook`](/api-reference/organizations#legacy-single-url-webhook) 的組織層級 webhook 密鑰作為金鑰。
  如果你的組織從未設定過舊版 webhook，便不會有
  密鑰，工具呼叫將**僅**帶有 `X-ThunderPhone-Call-ID` —— 若處理常式在缺少簽章時直接失敗，
  就會拒絕這些呼叫。
  請設定舊版 webhook 以取得密鑰，或在 `endpoint.headers` 中放入你自己的
  共用密鑰。
</Warning>

### 請求主體

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

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

對於 `GET` / `DELETE`，引數會作為**查詢參數**傳送，
主體為空 —— 此時簽章會針對空位元組字串計算。請參閱
[驗證 webhook 簽章](/zh-Hant/guides/verify-webhook-signatures)。

### 回應

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

```json theme={null}
{
  "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 theme={null}
{
  "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 相同：

* 對完全一致的請求主體位元組計算 HMAC-SHA256（標準化 JSON——鍵已排序，無額外空白）
* 使用你組織的 Webhook 密鑰作為金鑰
* `GET`／`DELETE` 工具會對空位元組字串簽章

<CodeGroup>
  ```python Python theme={null}
  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 theme={null}
  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 簽章](/zh-Hant/guides/verify-webhook-signatures)。

***

## 範例：完整預約流程

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

```json theme={null}
{
  "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` 欄位可協助人工智慧理解**何時**使用此工具。請明確說明工具的功能，以及適合使用的時機。
  </Accordion>

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

  <Accordion title="保持回應簡潔">
    僅回傳人工智慧繼續對話所需的資訊。大型酬載會拖慢回應時間。
  </Accordion>

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

***

## 相關內容

<CardGroup cols={2}>
  <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>
