---
title: "Công cụ hàm"
description: "Trang bị cho tác nhân AI của bạn các công cụ hàm để gọi API bên ngoài trong cuộc trò chuyện — truy xuất dữ liệu khách hàng, đặt lịch hẹn, cập nhật hồ sơ — với tham số có kiểu."
---

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 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 lịch trống, đặ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 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, AI quyết định thời điểm 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 AI để tiếp tục cuộc trò chuyện

| Khả năng | Nơi chạy | Thiết lập |
| --- | --- | --- |
| [Công cụ tích hợp sẵn](/vi/guides/built-in-tools) | ThunderPhone | Hướng dẫn trong prompt; một số công cụ cũng cần cài đặt tác nhân |
| [Kết nối ứng dụng](/vi/guides/connect-apps) | ThunderPhone và nhà cung cấp đã kết nối | Kết nối tài khoản và gắn hành động đã phê duyệt |
| [Kết nối API](/vi/guides/api-connections) và công cụ hàm | API HTTP của bạn | Định nghĩa endpoint và schema, hoặc nhận lệnh gọi hàm qua webhook |
| [Máy chủ MCP](/vi/guides/mcp-servers) | Máy chủ MCP từ xa | Thêm máy chủ, khám phá công cụ của máy chủ và gắn vào tác nhân |

---

## Schema công cụ

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

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

### Cấu hình công cụ

| Trường | Loại | Bắt buộc | Mô tả |
|-------|------|----------|-------------|
| `timeout` | number | Không | Thời gian thực thi tối đa tính bằng giây (mặc định: `20`, tối đa: `180`) |

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

| Trường | Loại | Bắt buộc | Mô tả |
|-------|------|----------|-------------|
| `name` | string | Có | Mã định danh duy nhất của công cụ |
| `description` | string | Có | Giải thích cho AI khi nào nên 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 bao gồm |

<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 đường dẫn 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:

| | Tool **có** `endpoint` | Tool **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 | Định dạng yêu cầu đã chọn (mặc định là đối số không bọc) | Envelope `telephony.tool` / `web.tool` |
| Header | `endpoint.headers` của bạn + `X-ThunderPhone-Call-ID` + `X-ThunderPhone-Signature-V2` + `X-ThunderPhone-Signature` | `Content-Type` + `X-ThunderPhone-Signature-V2` + `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 đường dẫn đều **chặn** — AI đang chờ kết quả giữa câu nói.
Thời gian chờ mặc định là **20 giây**; đặt `timeout` cấp cao nhất của công cụ
để cho phép thời gian thực thi dài hơn, tối đa **180 giây** theo giới hạn
nền tảng. Giữ handler xử lý nhanh. Có thể kết hợp:
trong một cuộc gọi mà tổ chức có URL webhook, công cụ có `endpoint` sẽ được
gọi trực tiếp và các công cụ còn lại sẽ dùng 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
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
```

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

- `X-ThunderPhone-Signature-V2` — HMAC-SHA256 có dấu thời gian trên
  phương thức, đường dẫn, truy vấn và phần thân, được tạo khóa bằng **bí mật webhook của tổ chức**.
  Xác minh header này và từ chối các yêu cầu cũ hơn 5 phút; xem
  [Xác minh chữ ký webhook](/vi/guides/verify-webhook-signatures)
- `X-ThunderPhone-Signature` — HMAC-SHA256 cũ của các byte chính xác trong
  phần thân yêu cầu, dùng cùng khóa, vẫn được gửi cho các trình xác minh hiện có
- `X-ThunderPhone-Call-ID` — ID cuộc gọi hiện tại

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

<Warning>
  Chữ ký được khóa bằ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à lệnh gọi công cụ chỉ mang `X-ThunderPhone-Call-ID` — một
  handler báo lỗi cứng 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>

### Định dạng yêu cầu

Mỗi kết nối API có một cài đặt `request_format`. Chọn `vapi` hoặc
`retell` khi một công cụ được nhập cần tiếp tục gọi một endpoint được xây dựng cho
nền tảng đó. Kết nối mới và hiện có mặc định dùng `standard`.

Với `POST` / `PUT` / `PATCH`, ThunderPhone tuần tự hóa khung dữ liệu đã chọn
theo quy tắc chuẩn (khóa được sắp xếp, dấu phân tách gọn). Cả hai chữ ký
đều bao phủ chính xác các byte này.

#### Tiêu chuẩn

`standard` chỉ gửi các đối số của công cụ, không có trình bao bọc. Đây là
mặc định và giữ nguyên phần thân yêu cầu gốc của ThunderPhone theo từng byte:

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

#### Vapi

`vapi` gửi [thông báo máy chủ `tool-calls`](https://docs.vapi.ai/tools/custom-tools)
của Vapi theo định dạng mà Vapi tài liệu hóa. `call.id` là ID cuộc gọi ThunderPhone.
`id` của lệnh gọi công cụ là duy nhất cho lần thực thi và được dùng lại để đối chiếu
kết quả của endpoint. Lệnh gọi xuất hiện trong cả `toolCallList` và
`toolWithToolCallList`, với `function.arguments` là một đối tượng 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" }
          }
        }
      }
    ]
  }
}
```

Các trường chỉ dành cho Vapi như `assistant`, `artifact` và toàn bộ đối tượng `call`
không được gửi.

Trả về khung phản hồi của Vapi với ID lệnh gọi công cụ tương ứng:

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

ThunderPhone cung cấp chuỗi `result` đã được bỏ khung cho tác nhân AI. Hệ thống cũng chấp nhận một
giá trị JSON để tương thích với các endpoint đã trả về giá trị đó. Một
`error` thay cho `result` được trả về tác nhân AI dưới dạng lỗi công cụ. Nếu
endpoint trả về một khung Vapi không hợp lệ hoặc không khớp, ThunderPhone ghi
cảnh báo và chuyển sang phân tích phản hồi tiêu chuẩn thay vì làm thất bại
cuộc gọi.

#### Retell

`retell` gửi yêu cầu được tài liệu hóa cho một [hàm tùy chỉnh
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" }
}
```

Đối tượng `call` chỉ bao gồm `call_id`. ThunderPhone không bao gồm các
trường dành riêng cho Retell mà hệ thống không thể cung cấp chính xác khi gửi đi, như
`agent_id`, `transcript`, `transcript_object`, `metadata` hoặc các biến
động. Retell chấp nhận mọi nội dung phản hồi; ThunderPhone chuyển tiếp nội dung đó
bằng cơ chế xử lý phản hồi tiêu chuẩn.

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, bất kể `request_format`. Chữ ký V2
bao phủ chuỗi truy vấn khi được gửi; chữ ký cũ được tính trên
chuỗi byte rỗng. Xem
[Xác minh chữ ký webhook](/vi/guides/verify-webhook-signatures).

### Phản hồi

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

```json
{
  "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 tác nhân AI để tiếp tục
cuộc trò chuyện. Phản hồi không phải JSON được bọc thành `{"data": "<text>"}`;
lỗi hết thời gian chờ và kết nối được báo cho tác nhân 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 [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à**
việc thực thi — phản hồi HTTP của bạn là kết quả công cụ.

```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` 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,
với cả hai header chữ ký, như mọi webhook khác.

<Note>
  Các [endpoint webhook](/vi/webhooks/endpoints) đã đăng ký cũng nhận
  một **thông báo sau** mỗi lần công cụ thực thi `telephony.tool` / `web.tool`
  không chặn (bất kể đường dẫn nào đã chạy), bao gồm cả 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 thực chữ ký

Các lệnh gọi công cụ trực tiếp được ký giống như webhook, bằng bí mật webhook của tổ chức bạn:

- `X-ThunderPhone-Signature-V2` ký dấu thời gian, phương thức, đường dẫn và
  truy vấn như đã gửi, cùng chính xác các byte của nội dung. Từ chối yêu cầu có
  dấu thời gian lệch quá 5 phút so với đồng hồ của bạn.
- `X-ThunderPhone-Signature` (phiên bản cũ) chỉ ký các byte của nội dung, hoặc
  chuỗi byte rỗng cho `GET` / `DELETE`. Header này vẫn được gửi, không thay đổi.

Các trình trợ giúp SDK kiểm tra header V2, bao gồm cả khoảng thời gian 5 phút:

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

Các hướng dẫn đầy đủ — chuỗi được ký, ví dụ minh họa, mã không dùng
SDK, trường hợp `GET` / `DELETE` và lưu ý khi không có bí mật — có tại [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à một bộ công cụ cho hệ thống đặt lịch hẹn hoàn chỉnh:

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

---

## 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 trò chuyện. 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 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="Công cụ tích hợp sẵn" icon="wrench" href="/vi/guides/built-in-tools">
    Prompt các thao tác cuộc gọi do nền tảng quản lý mà không cần xác định endpoint.
  </Card>
  <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ý 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à cho phép 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>
