Open in
함수 도구
에이전트에 대화 중 외부 API를 호출하는 함수 도구를 제공하세요. 유형이 지정된 매개변수로 고객 데이터를 조회하고, 예약을 등록하고, 기록을 업데이트할 수 있습니다.
함수 도구를 사용하면 AI 에이전트가 통화 중에 외부 API를 호출할 수 있습니다. 이를 사용하여 고객 데이터를 조회하고, 예약 가능 여부를 확인하고, 약속을 예약하거나, 백엔드가 지원하는 모든 작업을 수행할 수 있습니다.
작동 방식
- 스키마(도구가 허용하는 인수)를 사용하여 도구를 정의합니다.
endpoint구성(ThunderPhone이 API를 호출하는 위치)을 제공합니다. 또는 이를 생략하여 조직 웹훅에서 도구 호출을 수신합니다.- 통화 중 AI는 대화 내용을 바탕으로 도구를 사용할 시점을 결정합니다.
- ThunderPhone은 도구 인수와 함께 엔드포인트를 호출합니다.
- 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-Signature | Content-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-keyendpoint.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 헤더를 확인합니다.
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}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는 도구를 호출하기 전에 사용자에게 필수 정보를 요청합니다.