Next.js, Supabase, WebSockets를 활용한 자체 호스팅 음성 AI 면접 플랫폼 구축하기
요약
Next.js, Supabase, WebSockets를 활용하여 실시간 음성 AI 면접 플랫폼인 Aural을 구축하는 과정을 다룹니다. 지연 시간 최적화, 음성 데이터 정규화, 문맥을 유지하는 후속 질문 생성 등 실시간 인터랙티브 시스템 구현을 위한 핵심 엔지니어링 결정 사항을 설명합니다.
핵심 포인트
- 실시간 음성 인터랙션을 위한 스트리밍 기반 지연 시간 최적화
- 불완전한 음성 전사 데이터를 정규화하여 신뢰할 수 있는 상태 관리
- 대화의 흐름과 심도를 조절하는 상태 기반(Stateful) 워크플로우 설계
- Next.js와 WebSockets를 활용한 자체 호스팅 가능한 아키텍처 구축
Aural은 단순한 한계점에서 시작되었습니다. 정적인 질문 은행과 챗봇은 실제 면접의 리듬을 재현하지 못합니다. 유용한 면접 시스템은 경청하고, 언제 심층 질문을 던질지 결정하며, 문맥(Context)을 유지하고, 실행 가능한 수준으로 충분히 일관된 피드백을 생성할 수 있어야 합니다.
저는 음성, 채팅, 비디오를 통해 면접을 진행할 수 있는 오픈 소스 플랫폼인 Aural을 구축했습니다. 팀은 면접을 설계하고, 링크를 공유하며, AI가 문맥에 맞는 후속 질문을 던지게 하고, 구조화된 보고서를 받을 수 있습니다. 지원자 또한 연습을 위해 동일한 핵심 워크플로우를 사용할 수 있습니다.
이 포스트에서는 채팅 프로토타입에서 자체 호스팅이 가능한 실시간 제품으로 전환할 때 가장 중요했던 엔지니어링 결정 사항들을 다룹니다.
플랫폼이 수행해야 할 작업
단일 면접은 여러 서브시스템(Subsystems)을 접촉합니다:
- 면접 설정 및 질문 순서 지정
- 실시간 음성, 채팅 또는 비디오 상호작용
- 적응형 후속 질문 생성
- 코딩 및 화이트보드 연습
- 전사(Transcript) 저장 및 정리
- 구조화된 점수 산정 및 보고서 생성
- 조직, 프로젝트 및 액세스 제어
중요한 설계 결정은 면접을 일련의 독립적인 프롬프트(Prompts)가 아닌, 상태가 있는 워크플로우(Stateful workflow)로 취급하는 것이었습니다. 모든 답변은 시스템이 알고 있는 내용과 다음에 무엇을 물어야 할지를 변화시킵니다.
음성이 채팅보다 어려운 이유
채팅은 사용자에게 '전송' 버튼을 누르는 명확한 턴 경계(Turn boundary)를 제공합니다. 음성은 그렇지 않습니다. 실시간 면접은 지원자가 언제 말을 마쳤는지 추론해야 하고, 일시 정지와 채움말(Filler words)을 처리해야 하며, 아직 전개 중인 생각을 방해하지 않아야 합니다.
구현 과정에서 세 가지 문제가 지배적이었습니다.
1. 지연 시간(Latency)은 사용자 경험의 일부입니다
모든 단계에서 지연이 발생합니다: 오디오 캡처, 전송, 전사(Transcription), 후속 질문 생성, 음성 합성(Speech synthesis), 그리고 재생까지 말입니다. 모델 호출만을 최적화하는 것으로는 부족합니다. 전체 경로가 가능한 곳마다 스트리밍(Streaming)되어야 하며, 각 단계(Hop)마다 타이밍 데이터가 필요하여 성능 저하(Regressions)를 확인할 수 있어야 합니다.
2. 원시 전사 데이터(Raw transcripts)는 신뢰할 수 있는 애플리케이션 상태가 아닙니다
음성 인식(Speech recognition) 출력에는 부분적인 구절, 중복, 잘못된 시작(False starts), 구두점 변화 등이 포함될 수 있습니다. Aural은 라이브 전사(Live transcript)를 인터페이스용으로 유용하게 유지하는 동시에, 최종 답변이 영구적인 인터뷰 컨텍스트(Context)가 되기 전에 이를 정규화(Normalizing)합니다. 이러한 분리를 통해 일시적인 전사 아티팩트(Transcription artifacts)가 이후의 질문이나 보고서를 오염시키는 것을 방지합니다.
3. 후속 질문(Follow-ups)은 정당성을 느껴야 합니다
단순히 내용을 더 자세히 설명해 달라는 일반적인 요청은 마치 대본을 읽는 것처럼 들립니다. 후속 질문 생성기(Follow-up generator)는 인터뷰 목표, 현재 질문, 정규화된 답변, 이전 대화(Previous turns), 그리고 남은 심도 예산(Depth budget)을 전달받습니다. 생성기는 집중된 후속 질문을 던지거나, 아니면 명시적으로 다음 단계로 넘어가야 합니다. 설정 가능한 심도 제한(Configurable depth limit)을 통해, 하나의 답변이 전체 세션을 모두 소비하지 않도록 하면서 대화의 유용성을 유지합니다.
개념적으로 루프(Loop)는 다음과 같습니다:
- 지원자의 응답을 캡처하고 정규화합니다.
- 해당 턴(Turn)을 저장하고 인터뷰 상태를 업데이트합니다.
- 답변에 후속 질문이 필요한지 결정합니다.
- 설정된 심도 및 시간 제한 내에서 다음 프롬프트(Prompt)를 생성합니다.
- 활성화된 통신 채널을 통해 프롬프트를 다시 스트리밍(Stream)합니다.
- 대화 이력(Turn history)이 안정된 후에만 점수 산정(Scoring)을 완료합니다.
아키텍처
Aural은 애플리케이션을 위해 Next.js와 TypeScript를, 영구 데이터(Durable data)를 위해 Supabase/PostgreSQL을, 타입이 지정된 애플리케이션 API를 위해 tRPC를, 그리고 실시간 음성 이벤트를 위해 WebSocket 릴레이(Relays)를 사용합니다. LLM 제공업체들은 어댑터(Adapters) 뒤에 위치하므로, 자체 호스팅(Self-hosted) 배포가 특정 벤더에 종속되지 않습니다.
데이터 플레인(Data plane)과 AI 플레인(AI plane)은 의도적으로 분리되어 있습니다. 조직, 인터뷰, 세션, 질문, 권한, 보고서와 같은 제품 상태(Product state)는 PostgreSQL에 저장됩니다. 모델 호출(Model calls)은 현재 작업에 필요한 범위 내의 컨텍스트(Scoped context)만 전달받습니다. 이를 통해 권한 부여(Authorization)를 더 쉽게 추론할 수 있으며, 운영자가 데이터 보존 정책(Retention policies)을 적용할 수 있는 명확한 위치를 제공합니다.
장시간 실행되는 작업(Long-running operations) 또한 실시간 대화 경로(live turn path)와 분리되어 있습니다. 보고서 생성은 인터뷰를 다시 재생할 필요 없이 재시도할 수 있는 반면, 실시간 경로는 응답성(responsiveness)에 집중된 상태를 유지합니다.
하나의 불투명한 점수 대신 구조화된 평가
전체 점수를 하나의 숫자로 표시하는 것은 쉽지만 신뢰하기는 어렵습니다. Aural은 명시적인 차원(dimensions)에 따라 답변을 평가하며, 그 근거를 전사 데이터(transcript)와 연결된 상태로 유지합니다. 따라서 보고서는 정체불명의 점수를 반환하는 대신, 무엇이 잘되었는지, 무엇이 누락되었는지, 그리고 다음에 무엇을 개선해야 하는지를 보여줄 수 있습니다.
동일한 구조가 지원자의 연습 기능에도 적용됩니다. 시스템은 단순히 보고서만 제공하고 끝내는 대신, 약점을 수정 프롬프트(revision prompt), 제안된 답변 개요, 또는 다른 연습 시도로 전환할 수 있습니다.
자체 호스팅(Self-hosting) 및 데이터 소유권
인터뷰 데이터에는 이력서, 직무 기술서(job descriptions), 녹음 파일, 전사 데이터(transcripts), 그리고 내부 채용 기준이 포함될 수 있습니다. 많은 팀에게 해당 데이터가 어디에 저장되는지는 사소한 배포 세부 사항이 아닙니다.
오픈 소스 릴리스에는 Docker 기반의 자체 호스팅(self-hosting)과 문서화된 환경 설정(environment configuration)이 포함되어 있습니다. 운영자는 데이터베이스, 스토리지, 지원되는 모델 제공업체(model provider)를 선택한 다음, 배포 환경을 자체 네트워크 및 데이터 보존 정책(retention controls) 내에 배치할 수 있습니다. 호스팅 서비스(hosted service)는 스택을 직접 운영하고 싶지 않은 팀에게 여전히 유용하지만, 자체 호스팅은 단순한 내보내기(export) 기능이 아닌 일급 시민(first-class) 경로로 제공됩니다.
내가 배운 점
가장 큰 교훈은 믿을 만한 AI 인터뷰는 시스템의 문제라는 것입니다. 프롬프트(Prompt)의 품질도 중요하지만, 발화 감지(turn detection), 스트리밍(streaming), 전사 데이터의 청결도(transcript hygiene), 상태 전이(state transitions), 권한 부여(authorization), 관찰 가능성(observability), 그리고 장애 복구(failure recovery)도 마찬가지로 중요합니다. 전체 대화가 여전히 느리거나 일관성이 없다면, 단일 구성 요소를 고립시켜 개선하는 것은 가치가 제한적입니다.
두 번째 교훈은 모델의 출력을 제품 상태(product state)에 따라 제한해야 한다는 것입니다. 후속 질문의 깊이, 남은 시간, 질문의 목표, 평가 루브릭(evaluation rubrics), 그리고 허용된 도구(permitted tools)는 명시적인 입력값이어야 합니다. 모델이 워크플로우를 조용히 스스로 만들어내게 해서는 안 됩니다.
Aural 사용해보기
- 가입 없는 라이브 데모 (Live demo without signup): aural-ai.com
- 소스 코드 (Source code): github.com/1146345502/aural-oss
- 라이선스 (License): MIT
특히 자체 호스팅 (self-hosting) 워크플로우, 실시간 음성 경험 (real-time voice experience), 그리고 면접 데이터에 대해 기대하는 개인정보 보호 경계 (privacy boundaries)에 대한 피드백을 적극 환영합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기