VAD, Backchanneling, Sentiment Routing으로 CSAT 향상
Source: Dev.to
TL;DR
대부분의 음성 AI 에이전트는 고객의 말을 중간에 끊거나 감정 신호를 놓쳐 CSAT 점수가 크게 떨어집니다.
해결책:
- Voice Activity Detection (VAD) 은 잘못된 턴 획득을 방지합니다.
- Backchanneling (“mm‑hmm”, “I see”) 은 끊지 않고도 적극적인 경청을 표시합니다.
- Sentiment routing 은 화난 발신자를 화를 내기 전에 에스컬레이션합니다.
VAPI의 VAD 설정 + Twilio의 콜 라우팅으로 구현했습니다.
결과: 에스컬레이션 40 % 감소, CSAT 점수 25 % 상승. 불필요한 내용 없이 실제 운영에 바로 적용 가능한 패턴만 제공합니다.
Prerequisites
API Access
- VAPI API 키 (
dashboard.vapi.ai에서 발급) - Twilio Account SID + Auth Token (
console.twilio.com에서 확인) - Voice 기능이 활성화된 Twilio 전화번호
Technical Requirements
- Node.js 18+ (async/await 및 native fetch 지원)
- 웹훅용 공개 HTTPS 엔드포인트 (예: 로컬 개발 시 ngrok)
- SSL 인증서 (Twilio는 HTTP 웹훅을 거부합니다)
System Dependencies
- 동시 호출당 최소 512 MB RAM (VAD 처리 오버헤드)
flowchart TD
B[VAD Detection] --> C{Silence > 800ms?}
C -->|Yes| D[Inject Backchannel]
C -->|No| E[Continue Listening]
D --> F[Sentiment Analysis]
E --> F
F --> G{Score}
G -->|Yes| H[Route to Human]
G -->|No| I[AI Response]
VAD는 매 오디오 청크마다 트리거됩니다. 웹훅은 speech-update 이벤트와 부분 전사를 받아옵니다. 감정 분석은 완전한 발화에만 수행해야 하며, “I’m fru…”와 같은 중간 전사는 잘못된 부정 결과를 초래합니다.
Real‑Time Sentiment Routing
핵심 포인트: 대화가 흐트러지기 전에 실시간으로 감정을 분석하고 라우팅을 트리거하는 웹훅 핸들러입니다.
const express = require('express');
const app = express();
function analyzeSentiment(transcript) {
const negativeKeywords = {
'frustrated': -0.3,
'angry': -0.5,
'terrible': -0.4,
'useless': -0.6,
'cancel': -0.7,
'manager': -0.8
};
let score = 0;
const words = transcript.toLowerCase().split(' ');
words.forEach(word => {
if (negativeKeywords[word]) score += negativeKeywords[word];
});
return Math.max(score, -1.0); // Cap at -1.0
}
app.post('/webhook/vapi', async (req, res) => {
const { message } = req.body;
if (message.type === 'transcript' && message.transcriptType === 'final') {
const sentiment = analyzeSentiment(message.transcript);
// Inject backchannel if user paused mid‑sentence
if (message.silenceDuration > 800 && sentiment > -0.3) {
return res.json({
action: 'inject-message',
message: 'mm-hmm' // Non‑verbal acknowledgment
});
}
// Route to human if sentiment tanks
if (sentiment <= -0.5) {
return res.json({
action: 'transfer',
target: 'human_agent'
});
}
}
res.sendStatus(200);
});
sequenceDiagram
participant VAPI
participant User
participant Webhook
participant Server
VAPI->>User: Plays welcome message
User->>VAPI: Provides input
VAPI->>Webhook: transcript.final event
Webhook->>Server: POST /webhook/vapi with user data
alt Valid data
Server->>VAPI: Update call config with new instructions
VAPI->>User: Provides response based on input
else Invalid data
Server->>VAPI: Send error message
VAPI->>User: Error handling message
end
Note over User,VAPI: Call continues or ends based on user interaction
User->>VAPI: Ends call
VAPI->>Webhook: call.completed event
Webhook->>Server: Log call completion
Testing & Validation
Local Testing
VAPI CLI와 ngrok을 사용해 로컬에서 웹훅을 테스트합니다. 이는 프로덕션 전 통합 버그의 약 80 %를 사전에 발견할 수 있게 해줍니다.
# Terminal 1: Start your Express server
node server.js # runs on port 3000
# Terminal 2: Forward webhooks to local server
npx @vapi-ai/cli webhook forward --port 3000
// Example snippet inside server.js for local testing
app.post('/webhook/vapi', async (req, res) => {
const { message } = req.body;
if (message?.type === 'transcript') {
const sentiment = analyzeSentiment(message.transcript);
console.log(`[TEST] Transcript: "${message.transcript}"`);
console.log(`[TEST] Sentiment Score: ${sentiment}`);
// Add any additional debug actions here
}
res.sendStatus(200);
});
위 과정을 실행하고 CLI를 통해 샘플 전사를 전송한 뒤 다음을 확인합니다:
- 설정된 침묵 시간 이후에만 백채널이 삽입되는지.
- 감정 점수가 에스컬레이션 임계값 이하일 때 콜이 인간 에이전트에게 전달되는지.
- 레이스 컨디션이 발생하지 않는지 (서버 로그에 전사당 하나의 액션만 기록되어야 함).
로컬 검증이 끝나면 웹훅을 프로덕션 HTTPS 엔드포인트에 배포하고 Twilio Voice 웹훅 URL을 업데이트합니다. 이후 실시간 메트릭(에스컬레이션 비율, 평균 CSAT, 지연 시간)을 모니터링하세요.