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

# Công cụ Function

> Cho phép tác nhân AI của bạn gọi API bên ngoài trong cuộc trò chuyện

Công cụ hàm cho phép tác nhân AI của bạn gọi API bên ngoài trong các cuộc gọi điện thoại. Dùng chúng để tra cứu dữ liệu khách hàng, kiểm tra tình trạng còn chỗ, đặt lịch hẹn hoặc thực hiện bất kỳ hành động nào mà backend của bạn hỗ trợ.

## Cách hoạt động

1. Bạn định nghĩa công cụ bằng một schema (các đối số mà công cụ chấp nhận)
2. Bạn cung cấp cấu hình `endpoint` (nơi ThunderPhone gọi API của bạn) — hoặc bỏ qua để nhận lệnh gọi công cụ trên webhook của tổ chức
3. Trong cuộc gọi, tác nhân AI quyết định thời điểm sử dụng công cụ dựa trên cuộc trò chuyện
4. ThunderPhone gọi endpoint của bạn với các đối số của công cụ
5. Phản hồi API của bạn được gửi lại cho tác nhân AI để tiếp tục cuộc trò chuyện

<Note>
  Công cụ hàm là phương thức tự cung cấp API. ThunderPhone cũng
  cung cấp các công cụ do nền tảng quản lý, không cần endpoint:
  [kết nối ứng dụng](/vi/guides/connect-apps) (HubSpot, Salesforce, Slack,
  Google Calendar, Google Sheets, Cal.com),
  [kết nối API](/vi/guides/api-connections) và
  [máy chủ MCP](/vi/guides/mcp-servers).
</Note>

***

## Schema công cụ

Mỗi công cụ tuân theo cấu trúc này:

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

### Định nghĩa hàm

| Trường        | Loại   | Bắt buộc | Mô tả                                                    |
| ------------- | ------ | -------- | -------------------------------------------------------- |
| `name`        | string | Có       | Giá trị nhận dạng duy nhất của công cụ                   |
| `description` | string | Có       | Giải thích cho tác nhân AI thời điểm sử dụng công cụ này |
| `parameters`  | object | Có       | JSON Schema cho các đối số của công cụ                   |

### Cấu hình endpoint

| Trường    | Loại   | Bắt buộc | Mô tả                               |
| --------- | ------ | -------- | ----------------------------------- |
| `url`     | string | Có       | URL endpoint API của bạn            |
| `method`  | string | Không    | Phương thức HTTP (mặc định: `POST`) |
| `headers` | object | Không    | Header tùy chỉnh cần đưa vào        |

<Note>
  Cấu hình `endpoint` **không** được gửi đến mô hình AI — cấu hình này chỉ được ThunderPhone sử dụng để thực thi lệnh gọi công cụ.
</Note>

***

## Hai phương thức gọi

Yêu cầu mà máy chủ của bạn nhận được phụ thuộc vào việc công cụ có
`endpoint` hay không:

|                 | Công cụ **có** `endpoint`                                                          | Công cụ **không có** `endpoint`                                                          |
| --------------- | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| Nơi gửi yêu cầu | Trực tiếp đến `endpoint.url`                                                       | [URL webhook cũ](/api-reference/organizations#legacy-single-url-webhook) của tổ chức bạn |
| Nội dung        | **Chỉ đối số công cụ**                                                             | Bao bọc `telephony.tool` / `web.tool`                                                    |
| Header          | `endpoint.headers` của bạn + `X-ThunderPhone-Call-ID` + `X-ThunderPhone-Signature` | `Content-Type` + `X-ThunderPhone-Signature`                                              |
| Khóa ký         | Bí mật webhook của tổ chức                                                         | Bí mật webhook của tổ chức                                                               |

Cả hai phương thức đều **chặn** — tác nhân AI đang chờ kết quả giữa
câu nói — với thời gian chờ **20 giây**. Giữ handler xử lý nhanh. Bạn có thể
kết hợp cả hai: trong cuộc gọi mà tổ chức có URL webhook, các công cụ có `endpoint`
được gọi trực tiếp và các công cụ còn lại sẽ dự phòng về webhook.

## Lệnh gọi endpoint trực tiếp

Khi tác nhân AI gọi một công cụ có `endpoint`, ThunderPhone sẽ gửi
một yêu cầu đến URL của bạn:

### Header yêu cầu

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

Các header tùy chỉnh từ `endpoint.headers` của bạn luôn được bao gồm
nguyên văn, cùng với hai header trong không gian tên ThunderPhone:

* `X-ThunderPhone-Signature` — HMAC-SHA256 của chính xác các byte trong
  phần thân yêu cầu, sử dụng **secret webhook của tổ chức** làm khóa
* `X-ThunderPhone-Call-ID` — ID cuộc gọi hiện tại

`Content-Type: application/json` được đặt trừ khi `endpoint.headers`
của bạn ghi đè — `Content-Type` tùy chỉnh sẽ được ưu tiên.

<Warning>
  Chữ ký sử dụng secret webhook cấp tổ chức từ
  [`GET /v1/webhook`](/api-reference/organizations#legacy-single-url-webhook).
  Nếu tổ chức của bạn chưa từng cấu hình webhook cũ, sẽ không có
  secret và các lệnh gọi công cụ chỉ mang `X-ThunderPhone-Call-ID` —
  một trình xử lý báo lỗi nghiêm ngặt khi thiếu chữ ký sẽ từ chối chúng.
  Hãy cấu hình webhook cũ để có secret, hoặc đặt secret dùng chung của
  riêng bạn trong `endpoint.headers`.
</Warning>

### Phần thân yêu cầu

Với `POST` / `PUT` / `PATCH`, phần thân chỉ chứa **các đối số** của
công cụ (không có wrapper), được tuần tự hóa theo chuẩn (khóa được sắp
xếp, dấu phân cách gọn):

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

Với `GET` / `DELETE`, các đối số được gửi dưới dạng **tham số truy vấn**
và phần thân để trống — khi đó chữ ký được tính trên chuỗi byte trống.
Xem
[Xác minh chữ ký webhook](/vi/guides/verify-webhook-signatures).

### Phản hồi

Trả về phản hồi JSON chứa kết quả công cụ:

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

Phản hồi được định dạng và cung cấp cho AI để tiếp tục cuộc trò chuyện.
Các phản hồi không phải JSON được bọc dưới dạng `{"data": "<text>"}`;
hết thời gian chờ và lỗi kết nối được báo cho AI dưới dạng lỗi, để
tác nhân có thể xin lỗi và tiếp tục thay vì bị treo.

## Điều phối ở chế độ webhook

Các công cụ **không có** `endpoint` được điều phối đến URL webhook cũ
của tổ chức bạn dưới dạng yêu cầu `telephony.tool` (cuộc gọi điện thoại)
hoặc `web.tool` (cuộc gọi web) có chữ ký. Không giống các [thông báo kiểm
toán](/vi/webhooks/events) được gửi đến endpoint webhook sau khi thực thi,
yêu cầu này **chính là** quá trình thực thi — phản hồi HTTP của bạn là
kết quả công cụ.

```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` mang `origin_domain` thay cho `from_number` /
`to_number`. Phản hồi bằng kết quả công cụ dưới dạng JSON — cùng hợp
đồng phản hồi như các lệnh gọi endpoint trực tiếp. Yêu cầu được ký bằng
secret webhook của tổ chức trên phần thân thô, như mọi webhook khác.

<Note>
  Các [endpoint webhook](/vi/webhooks/endpoints) đã đăng ký cũng nhận được
  một **thông báo** `telephony.tool` / `web.tool` không chặn sau khi
  mỗi công cụ thực thi (bất kể đường dẫn nào đã chạy công cụ), bao gồm
  phản hồi của công cụ — hữu ích cho nhật ký kiểm toán. Xem
  [danh mục sự kiện](/vi/webhooks/events).
</Note>

***

## Xác minh chữ ký

Lệnh gọi công cụ trực tiếp được ký giống như webhook:

* HMAC-SHA256 trên chính xác các byte của phần thân yêu cầu (JSON chuẩn —
  khóa được sắp xếp, không có khoảng trắng thừa)
* Dùng secret webhook của tổ chức bạn làm khóa
* Công cụ `GET` / `DELETE` ký chuỗi byte rỗng

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

Các hướng dẫn đầy đủ — bao gồm trường hợp phần thân rỗng và lưu ý khi không có secret —
có trong [Xác minh chữ ký webhook](/vi/guides/verify-webhook-signatures).

***

## Ví dụ: Quy trình đặt lịch hoàn chỉnh

Dưới đây là bộ công cụ cho hệ thống đặt lịch hẹn hoàn chỉnh:

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

***

## Thực hành tốt nhất

<AccordionGroup>
  <Accordion title="Viết mô tả rõ ràng">
    Trường `description` giúp tác nhân AI hiểu **khi nào** nên sử dụng công cụ. Hãy nêu cụ thể công cụ làm gì và khi nào phù hợp để sử dụng.
  </Accordion>

  <Accordion title="Xử lý lỗi hợp lý">
    Trả về thông báo lỗi mà tác nhân AI có thể hiểu: `{"error": "No slots available for that date"}` thay vì lỗi 500 chung chung.
  </Accordion>

  <Accordion title="Giữ phản hồi ngắn gọn">
    Chỉ trả về những gì tác nhân AI cần để tiếp tục cuộc hội thoại. Payload lớn làm chậm thời gian phản hồi.
  </Accordion>

  <Accordion title="Sử dụng trường bắt buộc một cách hợp lý">
    Chỉ đánh dấu trường là `required` khi thực sự cần thiết. Tác nhân AI sẽ hỏi người dùng thông tin bắt buộc trước khi gọi công cụ.
  </Accordion>
</AccordionGroup>

***

## Liên quan

<CardGroup cols={2}>
  <Card title="Kết nối ứng dụng" icon="plug" href="/vi/guides/connect-apps">
    Công cụ do nền tảng quản lý dành cho HubSpot, Salesforce, Slack, Google
    Calendar, Google Sheets và Cal.com — không cần endpoint.
  </Card>

  <Card title="Máy chủ MCP" icon="server" href="/vi/guides/mcp-servers">
    Gắn máy chủ MCP và để tác nhân AI gọi các công cụ của máy chủ.
  </Card>

  <Card title="Kết nối API" icon="code" href="/vi/guides/api-connections">
    Tích hợp REST có thể tái sử dụng mà bạn có thể gắn vào tác nhân AI.
  </Card>

  <Card title="Xác minh chữ ký webhook" icon="shield-check" href="/vi/guides/verify-webhook-signatures">
    Một trình hỗ trợ xác minh cho webhook và lệnh gọi công cụ.
  </Card>
</CardGroup>
