ThunderPhone 2.0을 출시했습니다.셀프서비스로, 분당 2¢부터.출시 소식 보기

Function Tools

함수 도구

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

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

작동 방식

  1. 스키마(도구가 허용하는 인수)를 사용하여 도구를 정의합니다.
  2. endpoint 구성(ThunderPhone이 API를 호출하는 위치)을 제공합니다. 또는 이를 생략하여 조직 웹훅에서 도구 호출을 수신합니다.
  3. 통화 중 AI는 대화 내용을 바탕으로 도구를 사용할 시점을 결정합니다.
  4. ThunderPhone은 도구 인수와 함께 엔드포인트를 호출합니다.
  5. API 응답은 대화를 계속하기 위해 AI에 다시 전달됩니다.
기능실행 위치설정
내장 도구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문자열예이 도구를 사용해야 하는 시점을 AI에 설명
parameters객체예도구 인수를 위한 JSON 스키마

엔드포인트 구성

필드유형필수설명
url문자열예API 엔드포인트 URL
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
서명 키조직 웹훅 시크릿조직 웹훅 시크릿

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

직접 엔드포인트 호출

AI가 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 — 현재 통화 ID

endpoint.headers에서 재정의하지 않는 한 Content-Type: application/json이 설정됩니다. 사용자 지정 Content-Type이 우선합니다.

요청 형식

각 API 연결에는 request_format 설정이 있습니다. 가져온 도구가 해당 플랫폼용으로 구축된 엔드포인트를 계속 호출해야 하는 경우 vapi 또는 retell을 선택합니다. 새 연결과 기존 연결의 기본값은 standard입니다.

POST / PUT / PATCH의 경우 ThunderPhone은 선택한 엔벌로프를 표준 방식으로 직렬화합니다(정렬된 키, 압축 구분자). 두 서명은 이 정확한 바이트를 포함합니다.

Standard

standard는 래퍼 없이 도구 인수만 전송합니다. 이것이 기본값이며 ThunderPhone의 원래 요청 본문을 바이트 단위로 그대로 유지합니다:

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

Vapi

vapi는 Vapi가 문서화한 형태로 Vapi의 tool-calls 서버 메시지를 전송합니다. call.id는 ThunderPhone 통화 ID입니다. 도구 호출 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" }
          }
        }
      }
    ]
  }
}

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

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

{
  "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 사용자 지정 함수에 문서화된 요청을 전송합니다.

{
  "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 서명은 전송된 쿼리 문자열을 포함하며, 레거시 서명은 빈 바이트 문자열에 대해 계산됩니다. 자세한 내용은 웹훅 서명 검증을 참조하세요.

응답

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


서명 검증

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

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

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

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 필드는 AI가 도구를 언제 사용해야 하는지 이해하도록 돕습니다. 도구의 기능과 적절한 사용 시점을 구체적으로 설명합니다.

오류를 적절히 처리

일반적인 500 오류 대신 AI가 이해할 수 있는 오류 메시지를 반환합니다. 예: {"error": "No slots available for that date"}

응답을 간결하게 유지

AI가 대화를 계속하는 데 필요한 정보만 반환합니다. 큰 페이로드는 응답 시간을 늦춥니다.

필수 필드를 신중하게 사용

반드시 필요한 경우에만 필드를 required로 표시합니다. AI는 도구를 호출하기 전에 사용자에게 필수 정보를 요청합니다.


관련 항목