Voice AI에서 자연스러움을 우선시하는 방법: VAD 구현

발행: (2025년 12월 12일 오후 03:39 GMT+9)
6 min read
원문: Dev.to

Source: Dev.to

TL;DR

대부분의 음성 AI는 사용자가 문장 중간에 끊거나 생각하려고 잠시 멈출 때 정상적으로 동작하지 않습니다—봇이 사용자를 가로채거나 말을 끊어버립니다. 음성 활동 감지(VAD)는 실시간으로 말의 경계를 감지하여 자연스러운 턴테이킹과 바지인(barge‑in) 처리를 가능하게 합니다. VAPI의 VAD 임계값을 설정하고, 백채널 신호(예: “mm‑hmm”)를 추가하며, 중단 시 오디오 버퍼를 즉시 플러시하여 겹침을 방지하세요. 그 결과 인간처럼 자연스러운 대화가 구현됩니다.

API Access & Authentication

  • VAPI API keydashboard.vapi.ai에서 발급받으세요
  • Twilio Account SIDAuth 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 프레임 단위로 오디오를 처리합니다:

  1. 사용자가 말하면 오디오가 20 ms 프레임으로 버퍼링됩니다
  2. VAD가 에너지 레벨을 분석합니다
  3. 침묵이 endpointing 기간 이상 지속되면 버퍼를 STT에 플러시합니다
  4. 전사 결과가 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);
});
Back to Blog

관련 글

더 보기 »