> ## 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.

# 函数工具

> 让您的 AI 智能体在对话过程中调用外部 API

函数工具允许您的 AI 智能体在通话期间调用外部 API。您可以使用它们查询客户数据、检查可用性、预约，或执行您的后端支持的任何操作。

## 工作原理

1. 使用架构定义工具（工具接受哪些参数）
2. 提供 `endpoint` 配置（ThunderPhone 在哪里调用您的 API）——或者省略该配置，以便通过您的组织 webhook 接收工具调用
3. 通话期间，AI 会根据对话决定何时使用工具
4. ThunderPhone 使用工具参数调用您的端点
5. 您的 API 响应会反馈给 AI，以继续对话

<Note>
  函数工具支持自带 API。ThunderPhone 还提供无需端点的
  平台托管工具：
  [应用连接](/zh/guides/connect-apps)（HubSpot、Salesforce、Slack、
  Google Calendar、Google Sheets、Cal.com）、
  [API 连接](/zh/guides/api-connections)和
  [MCP 服务器](/zh/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`        | string | 是  | 工具的唯一标识符          |
| `description` | string | 是  | 向 AI 说明何时使用此工具    |
| `parameters`  | object | 是  | 工具参数的 JSON Schema |

### 端点配置

| 字段        | 类型     | 必填 | 说明                  |
| --------- | ------ | -- | ------------------- |
| `url`     | string | 是  | 您的 API 端点 URL       |
| `method`  | string | 否  | HTTP 方法（默认值：`POST`） |
| `headers` | object | 否  | 要包含的自定义标头           |

<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 秒**。请确保处理程序快速响应。可以混合使用：
对于组织具有 webhook URL 的通话，具有 `endpoint` 的工具会
被直接调用，其余工具则回退到 webhook。

## 直接端点调用

当 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/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/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/webhooks/endpoints)还会在每个工具执行**后**额外收到一条非阻塞的
  `telephony.tool` / `web.tool` **通知**（无论通过哪种路径执行），其中包含工具的响应——适用于审计跟踪。请参阅
  [事件目录](/zh/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/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` 字段可帮助 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="plug" href="/zh/guides/connect-apps">
    由平台管理的 HubSpot、Salesforce、Slack、Google
    Calendar、Google Sheets 和 Cal.com 工具，无需端点。
  </Card>

  <Card title="MCP 服务器" icon="server" href="/zh/guides/mcp-servers">
    连接 MCP 服务器，让智能体调用其工具。
  </Card>

  <Card title="API 连接" icon="code" href="/zh/guides/api-connections">
    可附加到智能体的可复用 REST 集成。
  </Card>

  <Card title="验证 Webhook 签名" icon="shield-check" href="/zh/guides/verify-webhook-signatures">
    用于 Webhook 和工具调用的验证辅助工具。
  </Card>
</CardGroup>
