---
title: "함수 도구"
description: "에이전트에 대화 중 외부 API를 호출하는 함수 도구를 제공하세요. 유형이 지정된 매개변수로 고객 데이터를 조회하고, 예약을 등록하고, 기록을 업데이트할 수 있습니다."
---

함수 도구를 사용하면 AI 에이전트가 통화 중에 외부 API를 호출할 수 있습니다. 이를 사용하여 고객 데이터를 조회하고, 예약 가능 여부를 확인하고, 약속을 예약하거나, 백엔드가 지원하는 모든 작업을 수행할 수 있습니다.

## 작동 방식

1. 스키마(도구가 허용하는 인수)를 사용하여 도구를 정의합니다.
2. `endpoint` 구성(ThunderPhone이 API를 호출하는 위치)을 제공합니다. 또는 이를 생략하여 조직 웹훅에서 도구 호출을 수신합니다.
3. 통화 중 AI는 대화 내용을 바탕으로 도구를 사용할 시점을 결정합니다.
4. ThunderPhone은 도구 인수와 함께 엔드포인트를 호출합니다.
5. API 응답은 대화를 계속하기 위해 AI에 다시 전달됩니다.

| 기능 | 실행 위치 | 설정 |
| --- | --- | --- |
| [내장 도구](/ko/guides/built-in-tools) | ThunderPhone | 프롬프트 지침, 일부 도구에는 에이전트 설정도 필요 |
| [앱 연결](/ko/guides/connect-apps) | ThunderPhone 및 연결된 제공업체 | 계정을 연결하고 승인된 작업을 연결 |
| [API 연결](/ko/guides/api-connections) 및 함수 도구 | HTTP API | 엔드포인트와 스키마를 정의하거나 웹훅을 통해 함수 호출 수신 |
| [MCP 서버](/ko/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` | 문자열 | 예 | 이 도구를 사용해야 하는 시점을 AI에 설명 |
| `parameters` | 객체 | 예 | 도구 인수를 위한 JSON 스키마 |

### 엔드포인트 구성

| 필드 | 유형 | 필수 | 설명 |
|-------|------|----------|-------------|
| `url` | 문자열 | 예 | API 엔드포인트 URL |
| `method` | 문자열 | 아니요 | HTTP 메서드(기본값: `POST`) |
| `headers` | 객체 | 아니요 | 포함할 사용자 지정 헤더 |

<Note>
  `endpoint` 구성은 AI 모델에 전송되지 않습니다. 이 구성은 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` |
| 서명 키 | 조직 웹훅 시크릿 | 조직 웹훅 시크릿 |

두 경로 모두 **차단 방식**으로 작동합니다. AI는 문장 도중 결과를
기다립니다. 기본 시간 제한은 **20초**입니다. 더 긴 실행 시간을 허용하려면 도구 최상위 수준의
`timeout`을 설정하세요. 플랫폼 최대값인 **180초**까지 설정할 수
있습니다. 핸들러는 빠르게 유지하세요. 혼합하여 사용해도 됩니다.
조직에 웹훅 URL이 있는 통화에서는 `endpoint`가 있는 도구가
직접 호출되고, 나머지는 웹훅으로 대체됩니다.

## 직접 엔드포인트 호출

AI가 `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분이 지난 요청은 거부합니다. 자세한 내용은
  [웹훅 서명 검증](/ko/guides/verify-webhook-signatures)을 참조하세요.
- `X-ThunderPhone-Signature` — 정확한 요청 본문 바이트에 대한 레거시
  HMAC-SHA256이며, 동일한 키를 사용하고 기존 검증기에도 계속 전송됩니다.
- `X-ThunderPhone-Call-ID` — 현재 통화 ID

`endpoint.headers`에서 재정의하지 않는 한 `Content-Type: application/json`이
설정됩니다. 사용자 지정 `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`는 Vapi가 문서화한 형태로 Vapi의 [`tool-calls` 서버 메시지](https://docs.vapi.ai/tools/custom-tools)를
전송합니다. `call.id`는 ThunderPhone 통화 ID입니다. 도구 호출 `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" }
          }
        }
      }
    ]
  }
}
```

`assistant`, `artifact`, 전체 `call` 객체와 같은 Vapi 전용 필드는
전송되지 않습니다.

일치하는 도구 호출 ID와 함께 Vapi의 응답 엔벌로프를 반환합니다.

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

ThunderPhone은 래핑 해제된 `result` 문자열을 AI에 제공합니다. 이미 JSON 값을 반환하는
엔드포인트와의 호환성을 위해 JSON 값도 허용합니다. `result` 대신 `error`가 있으면
도구 오류로 AI에 반환됩니다. 엔드포인트가 잘못되었거나 일치하지 않는 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은 디스패치 시점에
정확히 제공할 수 없는 `agent_id`, `transcript`, `transcript_object`, `metadata` 또는 동적
변수와 같은 Retell 전용 필드를 포함하지 않습니다. Retell은 모든 응답 본문을 허용하며 ThunderPhone은
표준 응답 처리를 사용하여 이를 그대로 전달합니다.

`GET` / `DELETE`의 경우 인수는 **쿼리 매개변수**로 전송되며
`request_format`과 관계없이 본문은 비어 있습니다. V2 서명은 전송된 쿼리 문자열을
포함하며, 레거시 서명은 빈 바이트 문자열에 대해 계산됩니다. 자세한 내용은
[웹훅 서명 검증](/ko/guides/verify-webhook-signatures)을 참조하세요.

### 응답

도구 결과가 포함된 JSON 응답을 반환하십시오.

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

응답은 형식화되어 대화를 계속할 수 있도록 AI에 제공됩니다.
JSON이 아닌 응답은 `{"data": "<text>"}`로 래핑됩니다.
시간 초과 및 연결 실패는 AI에 오류로 보고되므로 에이전트가 멈추는 대신
사과하고 다음 단계로 진행할 수 있습니다.

## 웹훅 모드 디스패치

`endpoint`가 **없는** 도구는 조직의 레거시 웹훅 URL로 서명된
`telephony.tool`(전화 통화) 또는 `web.tool`(웹 통화) 요청으로 디스패치됩니다.
실행 후 웹훅 엔드포인트에 전달되는 [감사 알림](/ko/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`은 `from_number` / `to_number` 대신 `origin_domain`을 전달합니다. 도구 결과를 JSON으로 응답합니다 — 직접 엔드포인트 호출과 동일한 응답 계약입니다. 이 요청은 다른 모든 웹훅과 마찬가지로 두 서명 헤더 모두를 사용하여 조직의 웹훅 시크릿으로 서명됩니다.

<Note>
  구독 중인 [웹훅 엔드포인트](/ko/webhooks/endpoints)는 각 도구 실행 후
  (실행된 경로와 관계없이) 비차단 `telephony.tool` / `web.tool` **알림도
  수신**하며, 여기에는 도구의 응답이 포함됩니다. 감사 추적에 유용합니다.
  [이벤트 카탈로그](/ko/webhooks/events)를 참조하십시오.
</Note>

---

## 서명 검증

직접 도구 호출은 조직의 웹훅 시크릿을 키로 사용하여 웹훅과 동일한 방식으로 서명됩니다.

- `X-ThunderPhone-Signature-V2`는 전송된 타임스탬프, 메서드, 경로 및 쿼리와 정확한 본문 바이트에 서명합니다. 타임스탬프가 현재 시각에서 5분 이상 차이 나는 요청은 거부합니다.
- `X-ThunderPhone-Signature`(레거시)는 본문 바이트만 서명하며, `GET` / `DELETE`의 경우 빈 바이트 문자열에 서명합니다. 이 헤더는 변경 없이 계속 전송됩니다.

SDK 도우미는 5분 허용 범위를 포함하여 V2 헤더를 확인합니다.

<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` 사례 및 시크릿이 없을 때의 주의 사항을 포함한 전체 예시는 [웹훅 서명 확인](/ko/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` 필드는 AI가 도구를 **언제** 사용해야 하는지 이해하도록 돕습니다. 도구의 기능과 적절한 사용 시점을 구체적으로 설명합니다.
  </Accordion>

  <Accordion title="오류를 적절히 처리">
    일반적인 500 오류 대신 AI가 이해할 수 있는 오류 메시지를 반환합니다. 예: `{"error": "No slots available for that date"}`
  </Accordion>

  <Accordion title="응답을 간결하게 유지">
    AI가 대화를 계속하는 데 필요한 정보만 반환합니다. 큰 페이로드는 응답 시간을 늦춥니다.
  </Accordion>

  <Accordion title="필수 필드를 신중하게 사용">
    반드시 필요한 경우에만 필드를 `required`로 표시합니다. AI는 도구를 호출하기 전에 사용자에게 필수 정보를 요청합니다.
  </Accordion>
</AccordionGroup>

---

## 관련 항목

<CardGroup cols={2}>
  <Card title="내장 도구" icon="wrench" href="/ko/guides/built-in-tools">
    엔드포인트를 정의하지 않고 플랫폼에서 관리하는 통화 작업을 프롬프트로 실행합니다.
  </Card>
  <Card title="앱 연결" icon="plug" href="/ko/guides/connect-apps">
    HubSpot, Salesforce, Slack, Google
    Calendar, Google Sheets 및 Cal.com을 위한 플랫폼 관리형 도구입니다. 엔드포인트가 필요하지 않습니다.
  </Card>
  <Card title="MCP 서버" icon="server" href="/ko/guides/mcp-servers">
    MCP 서버를 연결하고 에이전트가 해당 도구를 호출하도록 합니다.
  </Card>
  <Card title="API 연결" icon="code" href="/ko/guides/api-connections">
    에이전트에 연결할 수 있는 재사용 가능한 REST 통합입니다.
  </Card>
  <Card title="웹훅 서명 검증" icon="shield-check" href="/ko/guides/verify-webhook-signatures">
    웹훅 및 도구 호출을 위한 단일 검증 도우미입니다.
  </Card>
</CardGroup>
