Voice AI에서 자연스러움을 우선시하는 방법: VAD 구현
Source: Dev.to
TL;DR
대부분의 음성 AI는 사용자가 문장 중간에 끊거나 생각하려고 잠시 멈출 때 정상적으로 동작하지 않습니다—봇이 사용자를 가로채거나 말을 끊어버립니다. 음성 활동 감지(VAD)는 실시간으로 말의 경계를 감지하여 자연스러운 턴테이킹과 바지인(barge‑in) 처리를 가능하게 합니다. VAPI의 VAD 임계값을 설정하고, 백채널 신호(예: “mm‑hmm”)를 추가하며, 중단 시 오디오 버퍼를 즉시 플러시하여 겹침을 방지하세요. 그 결과 인간처럼 자연스러운 대화가 구현됩니다.
API Access & Authentication
- VAPI API key –
dashboard.vapi.ai에서 발급받으세요 - Twilio Account SID와 Auth Token – 전화번호 프로비저닝에 사용
Technical Requirements
- 웹훅 처리를 위한 공개 HTTPS 엔드포인트(로컬 개발 시 ngrok 사용 가능)
- Node.js 18+와 npm 또는 yarn
- WebSocket 연결 및 이벤트‑드리븐 아키텍처에 대한 기본 지식
- JavaScript에서
async/await에 익숙함
Voice AI Fundamentals
- VAD 임계값과 지연 시간에 미치는 영향
- 턴테이킹 메커니즘(사용자가 말을 멈추는 시점 감지)
- 바지인 동작(봇의 문장 중간에 사용자가 끼어들 때)
- 실시간 오디오 스트리밍 제약(16 kHz PCM, μ‑law 인코딩)
Production Considerations
- Budget: STT + TTS 결합 기준 분당 약 $0.02–$0.05
Audio Processing Pipeline
graph TD
AudioCapture[Audio Capture] --> VAD[Voice Activity Detection]
VAD --> STT[Speech‑to‑Text]
STT --> LLM[Large Language Model]
LLM --> TTS[Text‑to‑Speech]
TTS --> AudioOutput[Audio Output]
STT -->|Error| ErrorHandling[Error Handling]
LLM -->|Error| ErrorHandling
TTS -->|Error| ErrorHandling
ErrorHandling -->|Retry| AudioCapture
파이프라인은 20 ms 프레임 단위로 오디오를 처리합니다:
- 사용자가 말하면 오디오가 20 ms 프레임으로 버퍼링됩니다
- VAD가 에너지 레벨을 분석합니다
- 침묵이
endpointing기간 이상 지속되면 버퍼를 STT에 플러시합니다 - 전사 결과가 LLM에 전달되고, 응답이 합성되어 스트리밍됩니다
VAD가 이전 청크의 STT 처리 중에 작동하면 중복 응답이 발생하는 레이스 컨디션이 생길 수 있습니다. 명시적인 턴‑상태 추적로 이를 방지하세요.
Step 1: Configure Twilio for Inbound Calls
// Your server receives Twilio webhook
app.post('/voice/inbound', async (req, res) => {
const twiml = `
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Gather input="speech" action="/voice/handle" method="POST">
<Say>Welcome, please tell me how I can help.</Say>
</Gather>
</Response>
`;
res.type('text/xml');
res.send(twiml);
});
Step 2: Implement Backchanneling via Prompt Engineering
const systemPrompt = `You are a natural conversationalist. Rules:
1. Use backchannels ("mm-hmm", "I see", "go on") when user pauses mid‑thought.
2. Detect incomplete sentences (trailing "and...", "so...") and wait.
3. Keep responses under 15 words unless the user asks for detail.
4. Never say "How can I help you?" – jump straight to the topic.`;
백채널은 VAD가 아니라 LLM에 의해 생성됩니다.
Step 3: Handle Barge‑in at the Audio Buffer Level
const callConfig = {
assistant: assistantConfig,
backgroundSound: "office", // Enables barge‑in detection
recordingEnabled: true
};
VAD가 TTS 재생 중에 새로운 말을 감지하면 오디오 버퍼를 즉시 플러시해야 합니다. backgroundSound를 설정하면 VAPI가 이를 자동으로 처리합니다.
Testing Guidelines
- Pause test: Twilio 번호로 전화를 걸고 한 문장을 말한 뒤 300 ms 정도 멈췄다 다시 말합니다. 봇이 끼어들지 않아야 합니다. 끼어든다면
endpointing값을 50 ms씩 늘려 조정하세요. - Barge‑in test: 봇이 말하는 도중에 사용자가 말을 시작합니다. 오디오는 약 200 ms 이내에 차단되어야 합니다.
backgroundSound가 활성화돼 있는지 확인하세요. - Noise robustness: 카페, 자동차 등 소음이 많은 환경에서 테스트합니다. 오탐이 발생하면
endpointing을 300 ms 이상으로 높이세요.
Example VAD Threshold Test
const testVADConfig = {
transcriber: {
provider: "deepgram",
model: "nova-2",
language: "en",
endpointing: 200 // aggressive start
}
};
async function testBargeIn() {
const startTime = Date.now();
console.log('Testing barge‑in at 1.2 s into TTS playback...');
if (Date.now() - startTime > 300) {
console.error('VAD latency exceeded 300 ms – adjust endpointing');
}
}
testBargeIn();
Webhook Signature Verification
// Verify VAPI webhook signature
app.post('/webhook/vapi', (req, res) => {
const signature = req.headers['x-vapi-signature'];
const secret = process.env.VAPI_SECRET;
// (Insert HMAC verification logic here)
if (!isValidSignature(signature, secret, req.body)) {
return res.status(401).send('Invalid signature');
}
// Process webhook payload...
res.sendStatus(200);
});