> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thunderphone.com/llms.txt
> Use this file to discover all available pages before exploring further.

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

> Позвольте вашим ИИ-агентам вызывать внешние API во время разговоров

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

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

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

<Note>
  Инструменты функций — это вариант с использованием собственного API. ThunderPhone также
  предоставляет управляемые платформой инструменты, которым не нужен эндпоинт:
  [подключения приложений](/ru/guides/connect-apps) (HubSpot, Salesforce, Slack,
  Google Calendar, Google Sheets, Cal.com),
  [подключения API](/ru/guides/api-connections) и
  [серверы MCP](/ru/guides/mcp-servers).
</Note>

***

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

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

```json theme={null}
{
  "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"
    }
  }
}
```

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

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

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

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

<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` | `Content-Type` + `X-ThunderPhone-Signature`                                                        |
| Ключ подписи             | Секрет вебхука организации                                                      | Секрет вебхука организации                                                                         |

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

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

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

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

```http theme={null}
POST /appointments/search HTTP/1.1
Host: api.example.com
Content-Type: application/json
X-ThunderPhone-Signature: abc123...
X-ThunderPhone-Call-ID: 987654321
X-Api-Key: your-api-key
```

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

* `X-ThunderPhone-Signature` — HMAC-SHA256 точных байтов тела запроса
  с ключом в виде вашего **секрета webhook организации**
* `X-ThunderPhone-Call-ID` — идентификатор текущего звонка

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

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

### Тело запроса

Для `POST` / `PUT` / `PATCH` тело содержит **только** аргументы инструмента
(без обёртки), сериализованные канонически (отсортированные ключи, компактные
разделители):

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

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

### Ответ

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

```json theme={null}
{
  "available_slots": ["9:00 AM", "2:00 PM", "4:30 PM"],
  "timezone": "America/Los_Angeles"
}
```

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

## Маршрутизация в режиме webhook

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

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

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

***

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

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

* HMAC-SHA256 по точным байтам тела запроса (канонический JSON — ключи отсортированы, без лишних пробелов)
* С использованием секрета вебхука вашей организации
* Инструменты `GET` / `DELETE` подписывают пустую строку байтов

<CodeGroup>
  ```python Python theme={null}
  import hmac
  import hashlib

  def verify_tool_call(body: bytes, signature: str, secret: str) -> bool:
      expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
      return hmac.compare_digest(expected, signature)

  @app.post("/appointments/search")
  async def search_appointments(request: Request):
      body = await request.body()
      signature = request.headers.get("X-ThunderPhone-Signature", "")

      if not verify_tool_call(body, signature, WEBHOOK_SECRET):
          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 theme={null}
  app.post('/appointments/search', express.raw({type: 'application/json'}), (req, res) => {
    const signature = req.headers['x-thunderphone-signature'] || '';
    const expected = crypto
      .createHmac('sha256', WEBHOOK_SECRET)
      .update(req.body)
      .digest('hex');

    if (!signature ||
        signature.length !== expected.length ||
        !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
      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>

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

***

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

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

```json theme={null}
{
  "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="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>
