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

Developer cookbook

도구 통합(API) 구축

대화 중간에 에이전트가 API를 호출하여 데이터베이스를 검색하고, 티켓을 생성하고, 주문을 조회하도록 설정합니다.

도구 통합은 에이전트가 통화 중에 호출할 수 있는 재사용 가능한 HTTP 엔드포인트입니다. ThunderPhone에 도구의 JSON 스키마 설명과 엔드포인트 URL을 제공하면, 에이전트가 대화를 바탕으로 호출 시점을 결정하고 ThunderPhone이 자체 서버에서 외부 HTTP 요청을 수행한 뒤 응답을 에이전트에 반환합니다.

이 가이드에서는 날씨 조회 도구를 처음부터 끝까지 구축하는 과정을 안내합니다.

도구의 구성

두 가지 요소가 있습니다.

  1. 스키마 — 도구의 기능과 수락하는 인수를 LLM에 알려 주는 OpenAI 스타일 함수 정의 ({type: "function", function: {name, description, parameters}})입니다.
  2. 엔드포인트 — LLM이 도구 사용을 결정할 때 ThunderPhone 서버가 호출하는 URL입니다. 요청은 LLM이 선택한 인수를 본문으로 포함하는 JSON POST 요청입니다.

1. 편집기 선택

대시보드

연결 → API를 열고 API 연결을 생성하거나 편집한 다음, 매개변수 편집기를 JSON으로 전환하고 해당 위치에 형식을 추가합니다.

통합 API

POST /v1/integrations로 사양을 생성하거나 PATCH /v1/integrations/{id}로 업데이트합니다.

두 경로 모두 저장된 통합을 생성합니다. 저장한 후 해당 통합을 에이전트에 연결합니다. Agents API에는 쓰기 가능한 인라인 tools 필드가 없습니다. 이 가이드에서는 통합 API 경로를 사용합니다.

2. 통합을 생성합니다

curl -X POST https://api.thunderphone.com/v1/integrations \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "display_name": "Weather API",
    "spec": {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Return the current weather for a zip code.",
        "parameters": {
          "type": "object",
          "properties": {
            "zip": { "type": "string", "description": "5-digit US ZIP code" }
          },
          "required": ["zip"]
        }
      }
    },
    "endpoint_url":    "https://api.example.com/weather",
    "endpoint_method": "GET",
    "headers": [
      { "key": "X-Api-Key", "value": "your-provider-key" }
    ]
  }'

반환된 id(UUID)를 저장합니다. 헤더 값은 쓰기 전용입니다. 응답에는 헤더 이름만 나열되며, 이후 PATCH에서는 value 없이 헤더를 전송하여 저장된 값을 유지할 수 있습니다.

주소 매개변수에 format: "email"을 선언합니다

이메일 주소를 받는 매개변수는 스키마에 이를 명시해야 합니다.

"email": { "type": "string", "format": "email", "description": "The caller's email address" }

format은 단순한 힌트가 아닙니다. 확인된 이메일 스키마의 경우, 엔드포인트를 호출하기 전에 ThunderPhone은 값을 앞뒤 공백 제거하고, 도메인을 소문자로 변환하며, 독립적으로 사용된 영어 단어 at, dot, underscore, dash, hyphen을 해당 문자로 변환하고, @, ., _, - 바로 주변의 공백을 제거합니다. 기록에 리터럴 @가 이미 포함되어 있는지와 관계없이 이 단어들의 의미는 동일합니다. "john dot smith at gmail dot com"은 john.smith@gmail.com으로 변환됩니다.

그 밖의 내부 공백은 자동으로 연결하지 않고 거부됩니다. 구분자를 나타내는 발화 단어는 영어로만 지원되며, 영어가 아니거나 인식할 수 없는 공백 포함 형식은 안전하게 실패합니다. 유효한 국제화 도메인과 SMTPUTF8 로컬 파트는 허용됩니다. Punycode 입력은 Punycode로 유지되고 Unicode 도메인 입력은 파서 정규화 후에도 Unicode로 유지되므로, API는 발신자가 제공한 일반적인 표현을 받습니다. 최종 값이 유효하지 않으면 도구는 호출되지 않습니다. 에이전트는 발신자와 철자를 확인하고 리터럴 주소를 다시 전송하도록 지시하는 invalid_email_argument를 받습니다.

생략된 선택적 이메일은 변경되지 않습니다. 속성이 선택 사항이거나 null 허용인 경우 null, 빈 문자열 또는 공백만 포함된 문자열도 변경되지 않습니다. 동일한 값은 필수이고 null을 허용하지 않는 이메일에 대해서는 거부됩니다.

#/$defs/email, #/definitions/email 같은 로컬 스키마 참조와 anyOf, oneOf, allOf는 순환 및 깊이 제한 내에서 검사됩니다. 비로컬 또는 확인할 수 없는 $ref는 알려진 적용 제한 사항으로, 사용 가능한 스키마가 없는 도구 스냅샷의 호출과 마찬가지로 변경 없이 통과됩니다. 이 검증 규칙을 적용해야 하는 경우 이메일 스키마를 로컬로 유지합니다.

적용되는 이메일 형식이 없는 매개변수는 모델이 생성한 그대로 전달됩니다.

지원되는 형식은 date-time, time, date, duration, email, hostname, ipv4, ipv6, uuid이며, 현재 정규화 및 적용되는 형식은 email뿐입니다.

3. 샌드박스에서 엔드포인트를 테스트합니다

통합을 에이전트에 연결하기 전에 ThunderPhone 서버에서 서명된 요청을 전송하여 연결을 확인합니다.

curl -X POST https://api.thunderphone.com/v1/integrations/test-request \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url":    "https://api.example.com/weather?zip=94110",
    "method": "GET",
    "headers": { "X-Api-Key": "your-provider-key" }
  }'
Response
{
  "ok": true,
  "status": 200,
  "elapsed_ms": 187,
  "response_headers": { "content-type": "application/json" },
  "response_preview": "{\"temperature_f\": 64, ...}"
}

이 테스트는 ThunderPhone의 SSRF 방어도 강화합니다. localhost 또는 사설 IP 범위에 대한 요청은 400 code=url_not_allowed를 반환합니다.

4. 통합을 에이전트에 연결합니다

에이전트를 생성하거나 업데이트할 때 integration_ids를 통해 연결합니다.

curl -X PATCH https://api.thunderphone.com/v1/agents/12 \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "integration_ids": ["f9b5a1a4-..."]
  }'

하나의 에이전트에 여러 통합을 연결할 수 있습니다. 에이전트의 프롬프트에서 이름으로 참조할 수 있습니다. 예: "발신자가 날씨 조건에 관해 물으면 get_weather를 사용합니다." 또는 스키마 설명을 기반으로 암시적으로 탐색하도록 할 수 있습니다.

5. 엔드포인트를 구현합니다

에이전트가 도구를 호출하면 ThunderPhone은 서명된 POST 요청을 endpoint_url로 전송합니다.

POST /weather HTTP/1.1
Host: api.example.com
X-Api-Key: your-provider-key
X-ThunderPhone-Signature-V2: t=<unix seconds>,v1=<HMAC-SHA256 hex>
X-ThunderPhone-Signature: <HMAC-SHA256 hex>
X-ThunderPhone-Call-ID: 987654321
Content-Type: application/json

{"zip": "94110"}

서버는 LLM에 다시 전달되는 JSON으로 응답합니다.

{"temperature_f": 64, "condition": "Partly cloudy", "wind_mph": 8}

LLM은 해당 응답을 처리하고 발신자에게 사람이 이해하기 쉬운 요약을 말합니다.

6. 전체 흐름을 테스트합니다

에이전트를 대상으로 마이크 세션을 실행하고 도구가 처리하는 질문을 합니다("94110의 날씨는 어떤가요?"). 통화 기록에는 전체 왕복 과정이 표시됩니다.

{
  "call_id": 987654321,
  "transcripts": [
    { "role": "user",
      "content": "What's the weather in 94110?" },
    { "role": "tool_call",
      "content": "{\"tool_call\": \"get_weather\", \"arguments\": {\"zip\": \"94110\"}}" },
    { "role": "tool_response",
      "content": "{\"tool_name\": \"get_weather\", \"response\": {\"temperature_f\": 64, \"condition\": \"Partly cloudy\"}}" },
    { "role": "agent",
      "content": "It's 64 degrees and partly cloudy." }
  ]
}

다음 엔드포인트를 통해 이를 조회할 수 있습니다. GET /v1/calls/{call_id}/transcript 항목별 타이밍 및 오디오 오프셋이 포함된 원시 이벤트 스트림은 다음에 있습니다. GET /v1/calls/{call_id}/history.

통화 목록 및 분석 화면에서 ThunderPhone은 이제 테스트 통화를 실제 트래픽과 구분하여 보고합니다. Browser Talk 테스트와 시뮬레이션은 실제 전화번호를 재사용할 수 있으며 수신 통화처럼 보일 수 있지만, 프로덕션 통화 총계, 등급 또는 이슈 지표에는 포함되지 않습니다.

일반적인 주의 사항

에이전트가 도구를 호출하지 않음

LLM은 도구 설명을 기반으로 결정합니다. 발신자의 질문이 설명과 일치하지 않으면 모델은 도구를 호출하지 않습니다. 설명을 더 구체화하여 일반적인 동의어와 표현을 추가하거나 에이전트 프롬프트에 명시적으로 언급합니다("발신자가 날씨에 관해 물으면 get_weather를 사용합니다.").

도구가 너무 많은 데이터를 반환함

6 kB를 초과하는 응답은 기록 미리보기에서 잘립니다. 전체 행이 아니라 LLM에 필요한 필드만 반환합니다.

시간 초과

도구 엔드포인트의 기본 시간 초과는 10초입니다. 더 긴 시간이 필요하면 비동기적으로 처리합니다. {"status": "pending", "request_id": "..."} 를 반환하고 별도의 도구 호출을 통해 결과를 표시합니다.

버전 관리

모든 통합 PATCH는 새 리비전을 생성합니다. GET /v1/integrations/{id}/versions 를 확인하여 누가 무엇을 변경했는지 살펴봅니다. 도구의 스키마를 손상한 경우 이전 스냅샷을 다시 PATCH하여 수동으로 롤백할 수 있습니다.


다음 단계