ThunderPhone 2.0을 출시했습니다.별도 문의 없이 분당 2¢부터.출시 소식 보기

Function Tools

함수 도구

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

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

작동 방식

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

도구 스키마

각 도구는 다음 구조를 따릅니다.

{
  "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-SignatureContent-Type + 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: abc123...
X-ThunderPhone-Call-ID: 987654321
X-Api-Key: your-api-key

endpoint.headers의 커스텀 헤더는 항상 원문 그대로 포함되며, ThunderPhone 네임스페이스에 속하는 헤더 두 개도 추가됩니다.

  • X-ThunderPhone-Signature조직 웹훅 시크릿을 키로 사용하여 정확한 요청 본문 바이트에 대해 생성한 HMAC-SHA256
  • X-ThunderPhone-Call-ID — 현재 통화 ID

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

요청 본문

POST / PUT / PATCH의 경우 본문에는 래퍼 없이 도구 인수만 표준 형식(키 정렬, 압축 구분 기호)으로 직렬화되어 포함됩니다.

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

GET / DELETE의 경우 인수는 쿼리 매개변수로 전송되고 본문은 비어 있습니다. 이 경우 서명은 빈 바이트 문자열에 대해 계산됩니다. 웹훅 서명 확인을 참조하세요.

응답

도구 결과가 포함된 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으로 응답합니다. 응답 계약은 직접 엔드포인트 호출과 동일합니다. 요청은 다른 모든 웹훅과 마찬가지로 원시 본문에 대해 조직 웹훅 시크릿으로 서명됩니다.


서명 검증

직접 도구 호출은 웹훅과 동일한 방식으로 서명됩니다.

  • 정확한 요청 본문 바이트(정규 JSON — 키 정렬, 추가 공백 없음)에 대한 HMAC-SHA256
  • 조직의 웹훅 시크릿으로 키 지정
  • GET / DELETE 도구는 빈 바이트 문자열에 서명
Python
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}
Node.js
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 });
});

빈 본문 사례와 시크릿이 없는 경우의 주의 사항을 포함한 전체 예시는 웹훅 서명 검증에서 확인할 수 있습니다.


예시: 전체 예약 흐름

다음은 완전한 예약 시스템을 위한 도구 세트입니다.

{
  "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는 도구를 호출하기 전에 사용자에게 필수 정보를 요청합니다.


관련 항목