ThunderPhone 2.0 уже доступен.Самостоятельный запуск — от 2 ¢/мин.Читать анонс

Function Tools

Инструменты функций

Предоставьте своим ИИ-агентам инструменты функций, которые вызывают внешние API во время разговора — получают данные клиентов, записывают на приём, обновляют записи — с типизированными параметрами.

Инструменты-функции позволяют вашим ИИ-агентам вызывать внешние API во время телефонных разговоров. Используйте их для поиска данных клиентов, проверки доступности, записи на приём или выполнения любых действий, поддерживаемых вашим бэкендом.

Как это работает

  1. Определите инструменты с помощью схемы (какие аргументы принимает инструмент)
  2. Укажите конфигурацию endpoint (куда ThunderPhone вызывает ваш API) или не указывайте её, чтобы получать вызовы инструментов на вебхук вашей организации
  3. Во время звонка ИИ решает, когда использовать инструмент, на основе разговора
  4. ThunderPhone вызывает ваш эндпоинт с аргументами инструмента
  5. Ответ вашего API передаётся обратно ИИ для продолжения разговора
ВозможностьГде выполняетсяНастройка
Встроенные инструментыThunderPhoneИнструкции в промпте; для некоторых инструментов также требуется настройка агента
Подключения приложенийThunderPhone и подключённый провайдерПодключите аккаунт и добавьте одобренные действия
Подключения API и инструменты-функцииВаш HTTP APIОпределите эндпоинт и схему или получайте вызовы функций через вебхук
Серверы MCPУдалённый сервер MCPДобавьте сервер, найдите его инструменты и подключите его к агенту

Схема инструмента

Каждый инструмент имеет следующую структуру:

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

Конфигурация инструмента

ПолеТипОбязательноОписание
timeoutчислоНетМаксимальное время выполнения в секундах (по умолчанию: 20, максимум: 180)

Определение функции

ПолеТипОбязательноОписание
nameстрокаДаУникальный идентификатор инструмента
descriptionстрокаДаОбъясняет ИИ, когда следует использовать этот инструмент
parametersобъектДаJSON Schema для аргументов инструмента

Конфигурация эндпоинта

ПолеТипОбязательноОписание
urlстрокаДаURL эндпоинта вашего API
methodстрокаНетHTTP-метод (по умолчанию: POST)
headersобъектНетПользовательские заголовки для включения

Два пути вызова

Какой запрос получит ваш сервер, зависит от того, есть ли у инструмента endpoint:

Инструмент с endpointИнструмент без endpoint
Куда отправляется запросНапрямую в endpoint.urlВ устаревший URL вебхука вашей организации
ТелоВыбранный формат запроса (по умолчанию — только аргументы)Обёртка telephony.tool / web.tool
ЗаголовкиВаши endpoint.headers + X-ThunderPhone-Call-ID + X-ThunderPhone-Signature-V2 + X-ThunderPhone-SignatureContent-Type + X-ThunderPhone-Signature-V2 + X-ThunderPhone-Signature
Ключ подписиСекрет вебхука организацииСекрет вебхука организации

Оба пути являются блокирующими — ИИ ждёт результат в середине фразы. Время ожидания по умолчанию — 20 с; задайте timeout на верхнем уровне инструмента, чтобы разрешить более длительное выполнение, вплоть до максимума платформы 180 с. Обработчики должны работать быстро. Допускается смешанный вариант: при звонке, когда у организации есть URL вебхука, инструменты с endpoint вызываются напрямую, а остальные возвращаются к вебхуку.

Прямые вызовы endpoint

Когда ИИ вызывает инструмент с endpoint, ThunderPhone отправляет запрос на ваш URL:

Заголовки запроса

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

Пользовательские заголовки из вашего endpoint.headers всегда включаются дословно, а также добавляются следующие заголовки в пространстве имён ThunderPhone:

  • X-ThunderPhone-Signature-V2 — HMAC-SHA256 с меткой времени для метода, пути, запроса и тела, подписанный вашим секретом вебхука организации. Проверяйте эту подпись и отклоняйте запросы старше 5 минут; см. Проверка подписей вебхуков
  • X-ThunderPhone-Signature — устаревший HMAC-SHA256 точных байтов тела запроса с тем же ключом, который по-прежнему отправляется для существующих средств проверки
  • X-ThunderPhone-Call-ID — идентификатор текущего звонка

Content-Type: application/json устанавливается, если только ваш endpoint.headers не переопределяет его — пользовательский Content-Type имеет приоритет.

Формат запроса

У каждого API-подключения есть настройка request_format. Выберите vapi или retell, если импортированный инструмент должен продолжать вызывать конечную точку, созданную для этой платформы. Для новых и существующих подключений по умолчанию используется standard.

Для POST / PUT / PATCH ThunderPhone канонически сериализует выбранную оболочку (отсортированные ключи, компактные разделители). Обе подписи охватывают эти точные байты.

Standard

standard отправляет только аргументы инструмента, без обёртки. Это значение по умолчанию, которое сохраняет исходное тело запроса ThunderPhone байт в байт:

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

Vapi

vapi отправляет серверное сообщение tool-calls Vapi в формате, описанном в документации Vapi. call.id — это идентификатор звонка ThunderPhone. Идентификатор вызова инструмента id уникален для вызова и повторно используется для сопоставления результата конечной точки. Вызов отображается и в toolCallList, и в toolWithToolCallList, а function.arguments представлен в виде 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" }
          }
        }
      }
    ]
  }
}

Поля, специфичные для Vapi, такие как assistant, artifact и полный объект call, не отправляются.

Верните оболочку ответа Vapi с соответствующим идентификатором вызова инструмента:

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

ThunderPhone передаёт ИИ строку result без оболочки. Для совместимости с конечными точками, которые уже возвращают JSON-значение, также допускается JSON-значение. Значение error вместо result возвращается ИИ как ошибка инструмента. Если конечная точка возвращает некорректную или несоответствующую оболочку Vapi, ThunderPhone записывает предупреждение в журнал и использует стандартный разбор ответа вместо сбоя звонка.

Retell

retell отправляет запрос, описанный для пользовательской функции Retell:

{
  "name": "search_appointments",
  "call": { "call_id": "987654321" },
  "args": { "date": "2025-01-02", "service": "consultation" }
}

Объект call содержит только call_id. ThunderPhone не включает специфичные для Retell поля, которые невозможно корректно предоставить при отправке, например agent_id, transcript, transcript_object, metadata или динамические переменные. Retell принимает тело ответа любого типа; ThunderPhone передаёт его с использованием стандартной обработки ответа.

Для GET / DELETE аргументы отправляются как параметры запроса, а тело остаётся пустым независимо от request_format. Подпись V2 охватывает строку запроса в отправленном виде; устаревшая подпись вычисляется по пустой строке байтов. См. Проверка подписей вебхуков.

Ответ

Верните JSON-ответ с результатом инструмента:

{
  "available_slots": ["9:00 AM", "2:00 PM", "4:30 PM"],
  "timezone": "America/Los_Angeles"
}

Ответ форматируется и передаётся ИИ для продолжения разговора. Ответы не в формате JSON оборачиваются в {"data": "<text>"}; тайм-ауты и сбои подключения сообщаются ИИ как ошибки, чтобы агент мог извиниться и продолжить работу, а не зависнуть.

Диспетчеризация в режиме вебхука

Инструменты без endpoint направляются на URL устаревшего вебхука вашей организации как подписанный запрос telephony.tool (телефонные звонки) или web.tool (веб-звонки). В отличие от уведомлений аудита, доставляемых на конечные точки вебхуков после выполнения, этот запрос является выполнением — ваш HTTP-ответ становится результатом инструмента.

{
  "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 — используется тот же контракт ответа, что и при прямых вызовах конечных точек. Запрос подписывается секретом вебхука организации и содержит оба заголовка подписи, как и любой другой вебхук.


Проверка подписи

Прямые вызовы инструментов подписываются так же, как вебхуки, с использованием секрета вебхуков вашей организации:

  • X-ThunderPhone-Signature-V2 подписывает временную метку, метод, путь и параметры запроса в отправленном виде, а также точные байты тела. Отклоняйте запросы, если их временная метка отличается от времени на ваших часах более чем на 5 минут.
  • X-ThunderPhone-Signature (устаревший) подписывает только байты тела: для GET / DELETE — пустую строку байтов. Он по-прежнему отправляется без изменений.

Вспомогательные функции SDK проверяют заголовок V2, включая 5-минутное окно:

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

Полные рецепты — подписываемая строка, рабочий пример, код без SDK, случай GET / DELETE и оговорка об отсутствии секрета — см. в разделе Проверка подписей вебхуков.


Пример: полный процесс записи

Вот набор инструментов для полноценной системы записи на приём:

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

Рекомендации

Пишите понятные описания

Поле description помогает ИИ понять, когда использовать инструмент. Чётко укажите, что он делает и в каких случаях его следует применять.

Корректно обрабатывайте ошибки

Возвращайте понятные ИИ сообщения об ошибках: {"error": "No slots available for that date"} вместо общих ошибок 500.

Делайте ответы краткими

Возвращайте только то, что нужно ИИ для продолжения разговора. Большие полезные нагрузки замедляют время ответа.

Разумно используйте обязательные поля

Помечайте поля как required только при реальной необходимости. Перед вызовом инструмента ИИ запросит у пользователя обязательную информацию.


Связанные материалы