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

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

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

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

| Возможность | Где выполняется | Настройка |
| --- | --- | --- |
| [Встроенные инструменты](/ru/guides/built-in-tools) | ThunderPhone | Инструкции в промпте; для некоторых инструментов также требуется настройка агента |
| [Подключения приложений](/ru/guides/connect-apps) | ThunderPhone и подключённый провайдер | Подключите аккаунт и добавьте одобренные действия |
| [Подключения API](/ru/guides/api-connections) и инструменты-функции | Ваш HTTP API | Определите эндпоинт и схему или получайте вызовы функций через вебхук |
| [Серверы MCP](/ru/guides/mcp-servers) | Удалённый сервер MCP | Добавьте сервер, найдите его инструменты и подключите его к агенту |

---

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

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

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

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

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

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

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

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

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

<Note>
  Конфигурация `endpoint` **не** отправляется модели ИИ — она используется только ThunderPhone для выполнения вызова инструмента.
</Note>

---

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

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

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

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

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

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

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

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

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

- `X-ThunderPhone-Signature-V2` — HMAC-SHA256 с меткой времени для
  метода, пути, запроса и тела, подписанный вашим **секретом вебхука организации**.
  Проверяйте эту подпись и отклоняйте запросы старше 5 минут; см.
  [Проверка подписей вебхуков](/ru/guides/verify-webhook-signatures)
- `X-ThunderPhone-Signature` — устаревший HMAC-SHA256 точных байтов
  тела запроса с тем же ключом, который по-прежнему отправляется для существующих средств проверки
- `X-ThunderPhone-Call-ID` — идентификатор текущего звонка

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

<Warning>
  Подпись вычисляется с использованием секрета вебхука на уровне организации из
  [`GET /v1/webhook`](/api-reference/organizations#legacy-single-url-webhook).
  Если в вашей организации никогда не был настроен устаревший вебхук, секрета
  нет, и вызовы инструментов содержат **только** `X-ThunderPhone-Call-ID` —
  обработчик, завершающий работу с ошибкой при отсутствии подписи, отклонит их.
  Либо настройте устаревший вебхук, чтобы получить секрет, либо добавьте свой
  общий секрет в `endpoint.headers`.
</Warning>

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

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

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

#### Standard

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

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

#### Vapi

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

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

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

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

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

#### Retell

`retell` отправляет запрос, описанный для [пользовательской
функции 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" }
}
```

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

Для `GET` / `DELETE` аргументы отправляются как **параметры запроса**,
а тело остаётся пустым независимо от `request_format`. Подпись V2
охватывает строку запроса в отправленном виде; устаревшая подпись вычисляется по
пустой строке байтов. См.
[Проверка подписей вебхуков](/ru/guides/verify-webhook-signatures).

### Ответ

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

```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`
(веб-звонки). В отличие от [уведомлений аудита](/ru/webhooks/events),
доставляемых на конечные точки вебхуков после выполнения, этот запрос **является**
выполнением — ваш HTTP-ответ становится результатом инструмента.

```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` содержит `origin_domain` вместо `from_number` /
`to_number`. Ответьте результатом выполнения инструмента в формате JSON — используется тот же контракт ответа, что и при прямых вызовах конечных точек. Запрос подписывается секретом вебхука организации и содержит оба заголовка подписи, как и любой другой вебхук.

<Note>
  Подписанные [конечные точки вебхуков](/ru/webhooks/endpoints) дополнительно
  получают неблокирующее **уведомление после** выполнения каждого инструмента
  `telephony.tool` / `web.tool` (независимо от пути его запуска), включая
  ответ инструмента — это полезно для журналов аудита. См.
  [каталог событий](/ru/webhooks/events).
</Note>

---

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

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

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

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

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

Полные рецепты — подписываемая строка, рабочий пример, код без
SDK, случай `GET` / `DELETE` и оговорка об отсутствии секрета — см. в разделе [Проверка подписей вебхуков](/ru/guides/verify-webhook-signatures).

---

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

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

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

---

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

<AccordionGroup>
  <Accordion title="Пишите понятные описания">
    Поле `description` помогает ИИ понять, **когда** использовать инструмент. Чётко укажите, что он делает и в каких случаях его следует применять.
  </Accordion>

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

  <Accordion title="Делайте ответы краткими">
    Возвращайте только то, что нужно ИИ для продолжения разговора. Большие полезные нагрузки замедляют время ответа.
  </Accordion>

  <Accordion title="Разумно используйте обязательные поля">
    Помечайте поля как `required` только при реальной необходимости. Перед вызовом инструмента ИИ запросит у пользователя обязательную информацию.
  </Accordion>
</AccordionGroup>

---

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

<CardGroup cols={2}>
  <Card title="Встроенные инструменты" icon="wrench" href="/ru/guides/built-in-tools">
    Настраивайте управляемые платформой действия для звонков без определения конечной точки.
  </Card>
  <Card title="Подключения приложений" icon="plug" href="/ru/guides/connect-apps">
    Управляемые платформой инструменты для HubSpot, Salesforce, Slack, Google
    Calendar, Google Sheets и Cal.com — конечная точка не требуется.
  </Card>
  <Card title="Серверы MCP" icon="server" href="/ru/guides/mcp-servers">
    Подключите сервер MCP и разрешите агенту вызывать его инструменты.
  </Card>
  <Card title="Подключения API" icon="code" href="/ru/guides/api-connections">
    Повторно используемые интеграции REST, которые можно подключать к агентам.
  </Card>
  <Card title="Проверка подписей вебхуков" icon="shield-check" href="/ru/guides/verify-webhook-signatures">
    Один вспомогательный инструмент проверки для вебхуков и вызовов инструментов.
  </Card>
</CardGroup>
