VAPI를 이용한 고객 지원용 AI 음성 에이전트 배포 방법
Source: Dev.to
(번역을 진행하려면 번역하고자 하는 본문 텍스트를 제공해 주시겠어요?)
TL;DR
대부분의 음성 에이전트는 고객이 중간에 말을 끊거나 통화량이 급증할 때 작동이 중단됩니다. 이 가이드는 VAPI의 기본 음성 인프라와 Twilio의 통신사 수준 전화 시스템을 활용하여 두 상황을 모두 처리할 수 있는 프로덕션 급 AI 음성 에이전트를 구축하는 방법을 보여줍니다. 500 ms 미만의 응답 시간, 적절한 바지인 처리, 그리고 API가 타임아웃될 때 자동 페일오버를 기대할 수 있습니다.
Stack
- VAPI – 음성 AI
- Twilio – 전화 라우팅
- Webhook server – 비즈니스 로직 통합
API 액세스 및 인증
- VAPI API 키 –
dashboard.vapi.ai에서 획득 - Twilio 계정 SID 및 Auth 토큰 –
console.twilio.com에서 확인 - 음성 기능이 활성화된 Twilio 전화번호
- OpenAI API 키 – GPT‑4 모델 접근용
개발 환경
- Node.js 18+ 또는 Python 3.9+
- ngrok (또는 유사한 터널링 도구) 웹훅 테스트용
- 버전 관리를 위한 Git
기술 요구 사항
- 웹훅 핸들러용 퍼블릭 HTTPS 엔드포인트 (프로덕션)
- SSL 인증서 (Let’s Encrypt 사용 가능)
- 서버 메모리 ≥ 512 MB (권장 1 GB)
- 안정적인 인터넷 연결 (실시간 오디오를 위한 업로드 ≥ 10 Mbps)
지식 가정
- REST API 통합 경험
- Webhook 이벤트 처리 패턴
- 음성 프로토콜에 대한 기본 이해 (SIP, WebRTC)
- JSON 구성 관리
비용 인식
| 서비스 | 대략적인 비용 |
|---|---|
| VAPI (model + voice synthesis) | $0.05 – $0.10 per minute |
| Twilio (voice minutes) | $0.0085 per minute |
| Twilio phone number | $1 / month |
Architecture Overview
flowchart LR
A[Customer Calls] --> B[Twilio Number]
B --> C[VAPI Assistant]
C --> D[Your Webhook Server]
D --> E[CRM/Database]
E --> D
D --> C
C --> B
B --> A
Twilio는 전화 라우팅을 담당하고, VAPI는 음성 AI를 담당합니다. 서버는 웹훅을 통해 두 시스템을 연결합니다. 이러한 책임을 분리하여 phantom‑audio 문제를 방지하십시오.
Assistant Configuration
const assistantConfig = {
name: "Support Agent",
model: {
provider: "openai",
model: "gpt-4",
temperature: 0.7,
systemPrompt: "You are a customer support agent. Extract: customer name, issue type, account number. If caller interrupts, acknowledge immediately and adjust."
},
voice: {
provider: "11labs",
voiceId: "21m00Tcm4TlvDq8ikWAM",
stability: 0.5,
similarityBoost: 0.75
},
transcriber: {
provider: "deepgram",
model: "nova-2",
language: "en",
endpointing: 255 // ms silence before considering speech ended
},
recordingEnabled: true,
serverUrl: process.env.WEBHOOK_URL,
serverUrlSecret: process.env.WEBHOOK_SECRET
};
Tip: endpointing = 255 ms는 공격적이며 빠른 지원 상호작용에 잘 작동합니다. 불안정한 모바일 네트워크에서 잘못된 중단이 발생하면 400 ms로 늘리세요.
웹훅 서버 (Node.js + Express)
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
// Validate webhook signatures – production requirement
function validateSignature(req) {
const signature = req.headers['x-vapi-signature'];
const payload = JSON.stringify(req.body);
const hash = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(payload)
.digest('hex');
return signature === hash;
}
app.post('/webhook/vapi', async (req, res) => {
if (!validateSignature(req)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const { message } = req.body;
try {
switch (message.type) {
case 'function-call':
// Example: fetch customer data from CRM
const customerData = await fetchCustomerData(message.functionCall.parameters.accountNumber);
res.json({ result: customerData });
break;
case 'end-of-call-report':
// Log call metrics
await logCallMetrics({
callId: message.call.id,
duration: message.call.endedAt - message.call.startedAt,
cost: message.call.cost,
transcript: message.transcript
});
res.sendStatus(200);
break;
case 'speech-update':
// Real‑time transcript for live‑agent handoff
if (message.status === 'in-progress') {
await updateLiveTranscript(message.call.id, message.transcript);
}
res.sendStatus(200);
break;
default:
res.sendStatus(200);
}
} catch (error) {
console.error('Webhook error:', error);
res.status(5xx).json({ error: 'Processing failed' });
}
});
app.listen(3000);
Important: VAPI는 5초 이내에 응답을 받아야 합니다. 외부 호출이 오래 걸리는 경우, 즉시 res.sendStatus(202) 로 응답하고 작업을 비동기적으로 처리한 뒤, 결과를 VAPI API를 통해 나중에 전송하십시오.
Twilio를 VAPI에 연결하기
- VAPI 대시보드에서 어시스턴트의 전화 설정을 찾습니다.
- Twilio Console → Phone Numbers → Buy Number → Configure Webhook.
- 인바운드 웹훅 URL을 VAPI 어시스턴트의 전화 엔드포인트(VAPI 대시보드에 제공)로 설정합니다.
아웃바운드 호출은 지원 티켓이 생성되거나 에스컬레이션될 때 프로그래밍 방식으로 트리거합니다.
Monitoring Metrics (first 100 calls)
| 메트릭 | 목표 |
|---|---|
| Interruption accuracy | > 95 % |
| False barge‑ins | 92 % |
| Transcription accuracy (noisy) | > 85 % |
중단 정확도가 90 % 이하로 떨어지면, endpointing을 300 ms로 늘리고 음성 stability를 0.4로 낮춰 더 빠른 차단을 구현하십시오.
오디오 처리 파이프라인
graph LR
A[Microphone] --> B[Audio Buffer]
B --> C[Voice Activity Detection]
C -->|Speech Detected| D[Speech-to-Text]
C -->|No Speech| E[Error: Silence]
D --> F[Intent Detection]
F --> G[Response Generation]
G --> H[Text-to-Speech]
H --> I[Speaker]
D -->|Error: Unrecognized Speech| J[Error Handling]
J --> F
F -->|Error: No Intent| K[Fallback Response]
K --> G
ngrok을 사용한 로컬 테스트
# Start ngrok tunnel (run in terminal)
ngrok http 3000
예시 curl 테스트 (Node.js 스니펫)
const crypto = require('crypto');
const fetch = require('node-fetch');
const testPayload = {
message: {
type: "function-call",
functionCall: {
name: "getCustomerData",
parameters: { customerId: "test-123" }
}
}
};
const hash = crypto
.createHmac('sha256', process.env.VAPI_SERVER_SECRET)
.update(JSON.stringify(testPayload))
.digest('hex');
fetch('https://your-ngrok-url.ngrok.io/webhook/vapi', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-vapi-signature': hash
},
body: JSON.stringify(testPayload)
})
.then(res => res.json())
.then(data => console.log('Webhook response:', data))
.catch(err => console.error('Webhook failed:', err));
Note: 무료 티어 ngrok URL은 2시간 후에 만료됩니다. 404 오류를 방지하려면 각 재시작 후 VAPI 대시보드에서
serverUrl을 업데이트하세요.
서명 검증
validateSignature 함수는 요청 페이로드의 HMAC‑SHA256 해시를 x-vapi-signature 헤더와 비교합니다. 서명이 일치하지 않으면 401 응답이 반환되어 재생 공격과 비용이 많이 드는 API 호출의 무단 트리거를 방지합니다.
고객 호출 → Twilio → VAPI → 귀하의 웹훅 → CRM/Database → (루프) → VAPI → Twilio → 고객.