ThunderPhone 2.0 đã chính thức ra mắt.Tự đăng ký và sử dụng, chỉ từ 2¢/phút.Xem thông báo ra mắt

Function Tools

Công cụ hàm

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ăngNơi chạyThiết lập
Công cụ tích hợp sẵnThunderPhoneHướ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ụngThunderPhone và nhà cung cấp đã kết nốiKết nối tài khoản và gắn hành động đã phê duyệt
Kết nối API và công cụ hàmAPI 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ủ MCPMáy chủ MCP từ xaThê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:

{
  "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ườngLoạiBắt buộcMô tả
timeoutnumberKhôngThờ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ườngLoạiBắt buộcMô tả
namestringCóMã định danh duy nhất của công cụ
descriptionstringCóGiải thích cho AI khi nào nên dùng công cụ này
parametersobjectCóJSON Schema cho các đối số của công cụ

Cấu hình endpoint

TrườngLoạiBắt buộcMô tả
urlstringCóURL endpoint API của bạn
methodstringKhôngPhương thức HTTP (mặc định: POST)
headersobjectKhôngHeader tùy chỉnh cần bao gồm

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ó endpointTool không có endpoint
Nơi gửi yêu cầuTrực tiếp đến endpoint.urlURL webhook cũ 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
Headerendpoint.headers của bạn + X-ThunderPhone-Call-ID + X-ThunderPhone-Signature-V2 + X-ThunderPhone-SignatureContent-Type + X-ThunderPhone-Signature-V2 + X-ThunderPhone-Signature
Khóa kýBí mật webhook của tổ chứcBí 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

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

Đị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:

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

Vapi

vapi gửi thông báo máy chủ tool-calls 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:

{
  "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:

{
  "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:

{
  "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.

Phản hồi

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

{
  "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 đượ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ụ.

{
  "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.


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:

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}
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 });
});

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.


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:

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

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.

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.

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.

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


Liên quan