---
title: "Xây dựng tích hợp công cụ (API)"
description: "Cho phép tác nhân AI của bạn gọi API trong khi trò chuyện — tìm kiếm cơ sở dữ liệu, tạo phiếu yêu cầu, tra cứu đơn hàng."
---

Một **tích hợp công cụ** là điểm cuối HTTP có thể tái sử dụng mà tác nhân AI có thể
gọi trong cuộc gọi. Bạn cung cấp cho ThunderPhone mô tả lược đồ JSON
của công cụ cùng URL điểm cuối; tác nhân AI quyết định khi nào gọi công cụ
dựa trên cuộc trò chuyện, và ThunderPhone thực hiện yêu cầu HTTP đi
từ máy chủ của mình rồi trả phản hồi về cho tác nhân AI.

<Note>
  Bảng điều khiển đáp ứng hầu hết nhu cầu về công cụ mà không cần API này: **Kết nối
  → Ứng dụng** kết nối Slack, HubSpot, Salesforce, Google Calendar,
  Google Sheets và Cal.com chỉ với vài thao tác OAuth; **Kết nối →
  API** biến bất kỳ HTTP API nào thành hành động của tác nhân AI (dán lệnh cURL
  và trình hướng dẫn AI sẽ phác thảo công cụ, kèm tính năng Kiểm tra yêu cầu tích hợp sẵn); và
  **Kết nối → MCP** thêm máy chủ MCP. Xem
  [Kết nối](/vi/guides/concepts). Hướng dẫn này trình bày API
  cấp thấp bên dưới giao diện API.
</Note>

Hướng dẫn này trình bày toàn bộ quy trình xây dựng công cụ tra cứu thời tiết.

## Cấu trúc của một công cụ

Gồm hai phần:

1. **Lược đồ** — định nghĩa hàm theo kiểu OpenAI
   (`{type: "function", function: {name, description, parameters}}`)
   cho LLM biết công cụ làm gì và nhận những đối số nào.
2. **Điểm cuối** — URL mà máy chủ ThunderPhone gọi khi
   LLM quyết định sử dụng công cụ. Yêu cầu là JSON POST với
   các đối số do LLM chọn làm phần thân.

## 1. Chọn trình chỉnh sửa

<CardGroup cols={2}>
  <Card title="Bảng điều khiển" icon="window-maximize">
    Mở **Kết nối → API**, tạo hoặc chỉnh sửa kết nối API,
    chuyển trình chỉnh sửa tham số sang **JSON**, rồi thêm định dạng tại đó.
  </Card>
  <Card title="API tích hợp" icon="plug">
    Tạo đặc tả bằng `POST /v1/integrations`, hoặc cập nhật bằng
    `PATCH /v1/integrations/{id}`.
  </Card>
</CardGroup>

Cả hai cách đều tạo một tích hợp đã lưu. Gắn tích hợp đó vào
tác nhân AI sau khi lưu. Agents API không có trường `tools` nội tuyến có thể ghi.
Hướng dẫn này sử dụng cách thức API tích hợp.

## 2. Tạo integration

```bash
curl -X POST https://api.thunderphone.com/v1/integrations \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "display_name": "Weather API",
    "spec": {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Return the current weather for a zip code.",
        "parameters": {
          "type": "object",
          "properties": {
            "zip": { "type": "string", "description": "5-digit US ZIP code" }
          },
          "required": ["zip"]
        }
      }
    },
    "endpoint_url":    "https://api.example.com/weather",
    "endpoint_method": "GET",
    "headers": [
      { "key": "X-Api-Key", "value": "your-provider-key" }
    ]
  }'
```

Lưu `id` được trả về (một UUID). Giá trị header chỉ ghi: phản hồi chỉ liệt kê tên header, và một `PATCH` sau đó có thể gửi header không có `value` để giữ lại giá trị đã lưu.

<Tip>
  Hãy đầu tư công sức thực sự vào `description` của công cụ và từng
  tham số. LLM sử dụng các chuỗi này khi chạy để quyết định có nên
  gọi công cụ hay không và gọi như thế nào. Mô tả mơ hồ → lệnh gọi công cụ mơ hồ.
</Tip>

### Khai báo `format: "email"` trên tham số địa chỉ email

Một tham số nhận địa chỉ email cần nêu rõ điều đó trong
lược đồ của nó:

```json
"email": { "type": "string", "format": "email", "description": "The caller's email address" }
```

`format` không chỉ là một gợi ý. Với một lược đồ email đã được phân giải, trước khi
endpoint của bạn được gọi, ThunderPhone sẽ cắt khoảng trắng ở đầu cuối giá trị, chuyển
tên miền thành chữ thường, chuyển các từ tiếng Anh đứng riêng lẻ `at`, `dot`, `underscore`,
`dash` và `hyphen` thành ký tự tương ứng, đồng thời xóa khoảng trắng
ngay sát `@`, `.`, `_` và `-`. Các từ này có cùng ý nghĩa
dù bản ghi lời thoại đã chứa ký tự `@` theo nghĩa đen hay chưa:
`"john dot smith at gmail dot com"` trở thành
`john.smith@gmail.com`.

Mọi khoảng trắng nội bộ khác sẽ bị từ chối thay vì được tự động nối lại.
Các từ phân cách được nói chỉ hỗ trợ tiếng Anh; các dạng có khoảng trắng không phải tiếng Anh hoặc không được nhận diện
sẽ bị từ chối. Tên miền quốc tế hóa hợp lệ và phần cục bộ SMTPUTF8 được chấp nhận. Dữ liệu đầu vào Punycode vẫn là Punycode và dữ liệu đầu vào tên miền Unicode vẫn là Unicode sau khi trình phân tích chuẩn hóa, vì vậy API của bạn nhận được biểu diễn thông dụng do người gọi cung cấp. Nếu giá trị cuối cùng không hợp lệ, công cụ sẽ **không được gọi**. Tác nhân AI nhận
`invalid_email_argument`, yêu cầu tác nhân AI
xác nhận cách viết với người gọi và gửi lại địa chỉ theo nghĩa đen.

Email tùy chọn bị bỏ qua sẽ không thay đổi. `null`, chuỗi rỗng hoặc
chuỗi chỉ có khoảng trắng cũng không thay đổi khi thuộc tính là tùy chọn
hoặc cho phép giá trị rỗng; các giá trị tương tự sẽ bị từ chối đối với email bắt buộc, không cho phép giá trị rỗng.

Các tham chiếu lược đồ cục bộ như `#/$defs/email` và
`#/definitions/email`, cùng với `anyOf`, `oneOf` và `allOf`, được kiểm tra
với giới hạn chu trình và độ sâu. `$ref` không cục bộ hoặc không thể phân giải là
một giới hạn thực thi đã biết và được chuyển tiếp không thay đổi, tương tự một lệnh gọi
có ảnh chụp công cụ không có lược đồ có thể sử dụng. Giữ lược đồ email ở dạng cục bộ khi
bạn cần áp dụng lớp kiểm tra này.

Các tham số không có định dạng email được thực thi sẽ được chuyển tiếp chính xác
như cách mô hình tạo ra.

Các định dạng được hỗ trợ là `date-time`, `time`, `date`, `duration`,
`email`, `hostname`, `ipv4`, `ipv6` và `uuid`; hiện tại chỉ `email` được
chuẩn hóa và thực thi.

## 3. Kiểm thử endpoint trong sandbox

Trước khi liên kết integration với một tác nhân AI, hãy gửi một yêu cầu đã ký
từ máy chủ của ThunderPhone để xác nhận kết nối:

```bash
curl -X POST https://api.thunderphone.com/v1/integrations/test-request \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url":    "https://api.example.com/weather?zip=94110",
    "method": "GET",
    "headers": { "X-Api-Key": "your-provider-key" }
  }'
```

```json Response
{
  "ok": true,
  "status": 200,
  "elapsed_ms": 187,
  "response_headers": { "content-type": "application/json" },
  "response_preview": "{\"temperature_f\": 64, ...}"
}
```

Kiểm thử này cũng tăng cường các biện pháp bảo vệ SSRF của ThunderPhone — các yêu cầu đến
localhost hoặc dải IP riêng tư sẽ trả về `400 code=url_not_allowed`.

## 4. Liên kết integration với tác nhân AI

Đính kèm qua `integration_ids` khi bạn tạo hoặc cập nhật tác nhân AI:

```bash
curl -X PATCH https://api.thunderphone.com/v1/agents/12 \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "integration_ids": ["f9b5a1a4-..."]
  }'
```

Bạn có thể liên kết nhiều integration với một tác nhân AI. Prompt của tác nhân AI có thể
tham chiếu chúng theo tên — "dùng `get_weather` khi người gọi hỏi
về điều kiện thời tiết" — hoặc có thể ngầm nhận diện chúng từ phần mô tả
schema.

## 5. Triển khai endpoint

Khi tác nhân AI gọi công cụ, ThunderPhone gửi một POST đã ký đến
`endpoint_url` của bạn:

```
POST /weather HTTP/1.1
Host: api.example.com
X-Api-Key: your-provider-key
X-ThunderPhone-Signature-V2: t=<unix seconds>,v1=<HMAC-SHA256 hex>
X-ThunderPhone-Signature: <HMAC-SHA256 hex>
X-ThunderPhone-Call-ID: 987654321
Content-Type: application/json

{"zip": "94110"}
```

Máy chủ của bạn phản hồi bằng JSON, dữ liệu này được chuyển lại cho LLM:

```json
{"temperature_f": 64, "condition": "Partly cloudy", "wind_mph": 8}
```

LLM tiếp nhận phản hồi đó và nói bản tóm tắt tự nhiên cho người gọi.

<Warning>
  Chữ ký được tính trên phần thân yêu cầu thô bằng cùng `secret`
  với endpoint webhook của bạn. **Xác minh chữ ký** — endpoint công cụ
  được công khai trên internet và có cùng các rủi ro giả mạo như
  webhook. Xem
  [Xác minh chữ ký webhook](/vi/guides/verify-webhook-signatures).
</Warning>

## 6. Kiểm thử vòng lặp

Chạy một [mic session](/api-reference/mic-sessions) với tác nhân AI
và đặt câu hỏi mà công cụ của bạn xử lý ("Thời tiết ở
94110 thế nào?"). Bản chép lời của cuộc gọi hiển thị toàn bộ quy trình:

```json
{
  "call_id": 987654321,
  "transcripts": [
    { "role": "user",
      "content": "What's the weather in 94110?" },
    { "role": "tool_call",
      "content": "{\"tool_call\": \"get_weather\", \"arguments\": {\"zip\": \"94110\"}}" },
    { "role": "tool_response",
      "content": "{\"tool_name\": \"get_weather\", \"response\": {\"temperature_f\": 64, \"condition\": \"Partly cloudy\"}}" },
    { "role": "agent",
      "content": "It's 64 degrees and partly cloudy." }
  ]
}
```

Bạn có thể lấy dữ liệu này qua
[`GET /v1/calls/{call_id}/transcript`](/api-reference/calls#get-transcript);
luồng sự kiện thô (có thời gian cho từng mục và độ lệch âm thanh) nằm tại
[`GET /v1/calls/{call_id}/history`](/api-reference/calls#get-history).

Trong các giao diện danh sách cuộc gọi và phân tích, ThunderPhone hiện báo cáo riêng cuộc gọi thử nghiệm với lưu lượng thực tế. Kiểm thử Browser Talk và mô phỏng có thể dùng lại số điện thoại thật và có thể trông như cuộc gọi đến, nhưng không được tính vào tổng số cuộc gọi production, điểm đánh giá hoặc chỉ số sự cố.

## Các lỗi thường gặp

<AccordionGroup>
  <Accordion title="Tác nhân AI không bao giờ gọi công cụ">
    LLM quyết định dựa trên mô tả của công cụ. Nếu câu hỏi của người gọi
    không khớp với mô tả, mô hình sẽ không gọi
    công cụ. Hãy làm rõ mô tả hơn (thêm các từ đồng nghĩa và
    cách diễn đạt phổ biến) hoặc đề cập rõ trong prompt của tác nhân AI ("Khi
    người gọi hỏi về thời tiết, hãy dùng `get_weather`.").
  </Accordion>

  <Accordion title="Công cụ trả về quá nhiều dữ liệu">
    Phản hồi lớn hơn 6 kB sẽ bị cắt ngắn trong phần xem trước bản chép lời. Chỉ trả về
    các trường mà LLM cần — không phải toàn bộ hàng dữ liệu của bạn.
  </Accordion>

  <Accordion title="Hết thời gian chờ">
    Endpoint công cụ có thời gian chờ mặc định là 10 giây. Nếu bạn cần lâu hơn,
    hãy xử lý bất đồng bộ: trả về `{"status": "pending", "request_id": "..."}`
    và hiển thị kết quả thông qua một lệnh gọi công cụ riêng.
  </Accordion>

  <Accordion title="Quản lý phiên bản">
    Mỗi lần `PATCH` integration sẽ tạo một bản sửa đổi mới. Kiểm tra
    [`GET /v1/integrations/{id}/versions`](/api-reference/integrations#version-history)
    để xem ai đã thay đổi nội dung nào. Nếu bạn làm hỏng schema của một công cụ, bạn có thể
    khôi phục thủ công bằng cách PATCH lại một ảnh chụp cũ.
  </Accordion>
</AccordionGroup>

---

## Các bước tiếp theo

<CardGroup cols={2}>
  <Card title="Tài liệu tham khảo về tích hợp" icon="plug" href="/api-reference/integrations">
    CRUD, chuyển giao, lịch sử phiên bản.
  </Card>
  <Card title="Đặc tả Function Tools" icon="screwdriver-wrench" href="/vi/tools/overview">
    Toàn bộ ngữ pháp schema JSON và hợp đồng endpoint có chữ ký.
  </Card>
  <Card title="Xác minh chữ ký" icon="shield-check" href="/vi/guides/verify-webhook-signatures">
    Áp dụng mẫu chữ ký webhook cho các endpoint công cụ.
  </Card>
  <Card title="API bản chép lời + lịch sử" icon="phone" href="/api-reference/calls">
    Kiểm tra toàn bộ vòng lặp của một lệnh gọi công cụ.
  </Card>
</CardGroup>
