본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 22. 22:22

프로덕션 Voice AI 운영 6개월 — 무엇이 변했고, 무엇이 고장 났으며, 무엇을 다시 구축할 것인가

요약

의료용 Voice AI 서비스 Loquent의 프로덕션 운영 6개월간의 기술적 시행착오를 다룹니다. 모놀리식 프롬프트를 모듈형 구조로 전환하여 유지보수성과 지연 시간을 개선하고, 언어 독립적 의도 계층을 구축한 경험을 공유합니다.

핵심 포인트

  • 거대 단일 프롬프트를 모듈형 프롬프트 시스템으로 전환하여 유지보수성 확보
  • 구조화된 데이터 주입을 통해 컨텍스트를 줄이고 응답 지연 시간 200ms 개선
  • 언어에 구애받지 않는 의도 계층 구축으로 다국어 지원 효율화
  • 외부 벤더(Deepgram)의 모델 업데이트가 시스템 정확도에 미치는 영향 확인

6개월 전, 우리는 의료 및 치과 클리닉을 위한 Voice AI 접수원인 Loquent를 프로덕션(Production) 환경에 배포했습니다. 현재 Loquent는 여러 클리닉에서 영어와 프랑스어로 매월 수천 건의 자동 전화를 24시간 내내 처리하고 있습니다. 실제 환자들이 AI와 대화를 시작하면서 실제로 어떤 일들이 일어났는지 그 모든 것을 공유합니다.

이 글은 출시 기념 포스트가 아닙니다. 그런 글은 이미 썼습니다. 이것은 화려하지 않은 속편입니다. 우리의 가정이 틀렸던 부분, 벤더(Vendor)들이 우리 모르게 설정을 변경했던 부분, 그리고 우리 자신의 아키텍처(Architecture)를 보며 "왜 그렇게 만들었지?"라고 생각했던 부분들에 대한 이야기입니다.

0개월 차의 시스템

우리가 배포한 기술 스택(Stack)에 대해 간단히 설명하겠습니다. Loquent는 전화 통신을 위해 Twilio를, 음성-텍스트 변환(Speech-to-Text)을 위해 Deepgram을, 대화 로직(Conversation Logic)을 위해 Anthropic Claude를, 그리고 텍스트-음성 변환(Text-to-Speech)을 위해 ElevenLabs를 사용합니다. 백엔드(Backend)는 NestJS와 PostgreSQL 및 Prisma로 구성되어 있으며, Docker를 사용하여 AWS에 배포되었습니다. 우리는 이 모든 것을 8주 만에 구축했습니다.

출시 당시에는 주당 약 400건의 전화를 처리하는 단일 의료 클라이언트가 있었습니다. 시스템은 예약, 취소, 보험 확인 라우팅(Routing), 그리고 환자가 상담원과 직접 통화해야 하는지 아니면 자동으로 처리될 수 있는지를 결정하는 기본적인 트리아지(Triage, 분류)를 수행했습니다.

우리의 목표는 85%의 완전 자동화율이었습니다. 출시 첫 주에 82%를 달성했으며, 이는 배포하기에 충분히 근접한 수치라고 느꼈습니다.

무엇이 변했는가

프롬프트 아키텍처(Prompt Architecture)를 두 번이나 다시 작성했습니다. 우리의 초기 접근 방식은 모든 시나리오를 다루는 약 4,000 토큰(Tokens) 분량의 거대한 단일 시스템 프롬프트(System Prompt)였습니다. 이는 하나의 전문 분야를 가진 하나의 클리닉에는 효과적이었습니다. 하지만 2개월 차에 접어들면서 예약 규칙, 보험 요구 사항, 운영 시간이 각기 다른 세 개의 클리닉을 관리하게 되었습니다. 이 모놀리식(Monolithic) 프롬프트는 유지보수가 불가능해졌습니다.

우리는 각 클리닉이 기본 대화 스캐폴드 (Scaffold)를 할당받고, 클리닉별 규칙(영업시간, 절차, 보험 로직)이 산문 형태가 아닌 구조화된 데이터 (Structured data)로 주입되는 모듈형 프롬프트 시스템 (Modular prompt system)으로 전환했습니다. 프롬프트는 핵심 로직 약 1,200 토큰과 클리닉 설정 300~800 토큰 수준으로 줄어들었습니다. Claude가 처리해야 할 컨텍스트 (Context)가 줄어들면서 첫 응답 지연 시간 (Latency)이 약 200ms 개선되었습니다.

두 번째 재작성은 4개월 차에 퀘벡 (Quebec) 클리닉을 위한 프랑스어 지원을 추가할 때 이루어졌습니다. 프롬프트를 복제하는 대신, 언어에 구애받지 않는 의도 계층 (Language-agnostic intent layer)을 구축하고 환자 대면 텍스트를 모두 템플릿 시스템 (Template system)으로 밀어 넣었습니다. 이를 통해 새로운 언어를 추가하는 작업이 프롬프트 엔지니어링 (Prompt engineering) 프로젝트가 아닌 단순한 설정 변경 (Config change)이 되었습니다.

Deepgram의 모델 업데이트가 하룻밤 사이에 우리의 정확도 수치를 바꿔 놓았습니다. 6개월 동안 두 번, Deepgram이 모델 업데이트를 배포하면서 우리의 전사 (Transcription) 정확도가 변했습니다. 첫 번째는 개선되었습니다. 치과 전문 용어 인식률이 약 71%에서 83%로 급증했습니다. 하지만 3주 후 진행된 두 번째 업데이트에서는 퀘벡 프랑스어 악센트에 대한 회귀 (Regressions) 현상이 발생했습니다. 몬트리올 (Montreal)에서의 자동화율이 단 하루 만에 9포인트 하락했습니다.

이제 우리는 프로덕션 (Production) 환경에서 특정 Deepgram 모델 버전을 고정 (Pin)하며, 새로운 버전을 승격 (Promoting)하기 전에 500개의 실제 통화 녹음으로 구성된 저장된 코퍼스 (Corpus)를 대상으로 테스트를 수행합니다. 이로 인해 벤더 (Vendor) 업데이트 주기가 일주일 늘어났지만, 갑작스러운 회귀 현상은 제거되었습니다.

통화량은 세 배로 늘어났지만, 우리가 예상한 곳에서 발생하지 않았습니다. 우리는 모든 클리닉에서 꾸준한 성장이 있을 것으로 계획했습니다. 하지만 예상과 달리, 한 치과 그룹이 우리에게 알리지 않고 지역 광고 캠페인을 진행하여 일주일 만에 통화량이 세 배로 급증했습니다. 우리의 Twilio 동시 통화 제한 (Concurrent call limit)은 15건이었습니다. 그들은 어느 화요일 아침에 23건의 동시 통화를 기록했습니다.

초과된 통화들은 통화 중 신호(Busy signals)를 받았습니다. 클리닉에서 우리에게 직접 전화하기 전까지는 이런 일이 일어나고 있다는 사실조차 몰랐습니다. 이제 우리는 동시 통화 수, 대기열 깊이 (Queue depth), 그리고 Twilio 용량 여유분 (Capacity headroom)에 대한 알림 (Alerting)을 설정해 두었습니다. 또한 사용률이 70%를 넘으면 동시 제한을 높이는 오토스케일링 (Auto-scaling) 설정도 구축했습니다.

무엇이 고장 났는가

"잠시 기다릴게요" 문제. 우리는 상담원이 연결되지 않는다는 안내를 받았을 때, 환자들이 얼마나 많이 "잠시 기다릴게요" 또는 "기다릴게요"라고 말할지 예상하지 못했습니다. 우리의 대화 로직 (Conversation logic)은 8초간의 침묵을 연결 끊김 신호로 처리했습니다. 상담원을 기다리던 환자들은 조용해졌고, 연결이 끊겼으며, 다시 전화를 걸어 또다시 AI를 만나게 되었고, 이는 점점 더 큰 좌절감을 불러일으켰습니다.

우리는 전체 통화의 6%에서 이러한 패턴을 발견했습니다. 이는 우리 클리닉 전체에서 주당 약 40건의 통화에 해당합니다. 해결책은 주기적인 확인 메시지("여전히 연결되어 있습니다. 곧 담당 직원이 연결될 것입니다")를 포함하는 전용 대기 상태 (Hold state)를 구축하고, 침묵 허용 시간을 45초로 연장하는 것이었습니다. 그 결과, 상담원 전환 성공률 (Transfer-to-human success rate)이 74%에서 91%로 상승했습니다.

피크 시간대 ElevenLabs의 지연 시간 (Latency) 급증. 동부 표준시 기준 오전 9시에서 11시 사이 — 예약이 가장 활발한 시간대 — ElevenLabs의 응답 시간이 우리의 기준치인 180ms에서 가끔 600~900ms까지 급증하곤 했습니다. 환자들은 이를 대화 도중 AI가 "멈추는" 현상으로 경험했으며, 이는 신뢰를 떨어뜨렸습니다.

우리는 일반적인 문구(인사, 확인, 대기 메시지)를 위한 TTS 응답 캐시 (TTS response cache)를 구축하여 전체 음성 응답의 약 35%에서 지연 시간을 제거했습니다. 나머지 동적 응답의 경우, 전체 오디오가 생성되기 전에 말을 시작하는 스트리밍 재생 파이프라인 (Streaming playback pipeline)을 추가했습니다. 이 두 가지를 결합하여 최악의 경우 체감되는 지연 시간을 약 300ms까지 낮추었습니다.

보험 확인의 늪. 우리의 원래 보험 확인 방식은 간단했습니다. 환자에게 보험사 및 증권 번호를 묻고, 그것이 클리닉의 허용 목록에 있는지 확인하는 것이었습니다. 그러다 클리닉 측에서 실시간 자격 확인 (Real-time eligibility checks)을 수행해 달라는 요청을 하기 시작했습니다. 우리는 클리어링하우스 (Clearinghouse) API와의 연동을 구축했고, 이는 잘 작동했습니다 — 문제가 발생하기 전까지는 말이죠.

클리어링하우스 (Clearinghouse)의 평균 응답 시간은 4초였습니다. 전화 통화 중 4초간의 침묵은 영겁의 시간처럼 느껴집니다. 우리는 "확인해 보겠습니다"라는 멘트나 대기 음악 조각들로 그 공백을 채우려 시도했지만, 사용자 경험 (UX)은 최악이었습니다. 결국 우리는 보험 확인 절차를 비동기 (async) 흐름으로 옮겼습니다. 즉, AI가 정보를 수집하고 예약 전에 확인될 것임을 안내한 뒤, 실제 확인은 통화가 끝난 후에 이루어지도록 한 것입니다. 환자 만족도 점수는 올라갔고, 클리닉 직원의 업무량은 줄어들었습니다.

우리가 다시 구축할 것들

대화 상태 머신 (Conversation state machine). 우리는 상태 관리 (state management)를 인사 $\rightarrow$ 의도 탐지 (intent detection) $\rightarrow$ 정보 수집 $\rightarrow$ 실행 $\rightarrow$ 확인 $\rightarrow$ 작별 인사라는 단순한 선형 흐름으로 구축했습니다. 실제 대화는 선형적이지 않습니다. 환자들은 말을 끊기도 하고, 이전 내용으로 되돌아가기도 하며, 예약 도중 관련 없는 질문을 하거나 마음을 바꾸기도 합니다.

우리는 점점 더 복잡해지는 분기 로직 (branching logic)으로 이를 패치했고, 작동은 하지만 취약합니다. 만약 처음부터 다시 구축한다면, 우리는 그래프 기반 대화 모델 (graph-based conversation model)을 사용할 것입니다. 이 모델에서는 각 노드 (node)가 정의된 진입/진출 조건(entry/exit conditions)을 가진 의도 (intent)이며, 환자가 말하는 내용에 따라 어떤 노드든 다른 노드로 전환될 수 있습니다. 현재 이 재구축 작업은 약 60% 정도 진행되었습니다.

모니터링 스택 (Monitoring stack). 우리는 기본적인 CloudWatch 로깅과 Slack 알림 채널로 시작했습니다. 주당 400건의 통화 규모에서는 괜찮았습니다. 하지만 현재의 통화량에서는 클리닉별 자동화율, 평균 통화 시간, 전환 사유, 전사 (transcription) 신뢰도 점수, 그리고 TTS 지연 시간 (latency)을 시간대 및 언어별로 세분화하여 보여주는 실시간 대시보드가 필요합니다.

3개월 차에 커스텀 분석 파이프라인 (analytics pipeline)을 덧붙였지만, 이는 Lambda 함수들과 Grafana 대시보드의 집합체였으며, 구축하는 것보다 유지 관리하는 데 더 많은 노력이 들었습니다. 다시 한다면 첫날부터 제대로 된 관측성 레이어 (observability layer)에 투자할 것입니다. 아마도 커스텀 메트릭 (custom metrics)을 활용한 Datadog을 사용하겠지만, 우리의 통화량에서는 비용을 신중하게 관리해야 할 것입니다.

테스트 인프라 (testing infrastructure). 우리는 대부분의 팀이 코드를 배포하는 방식과 동일하게 프롬프트 변경 사항을 배포합니다 — PR (Pull Request), 리뷰 (review), 머지 (merge), 배포 (deploy). 하지만 4개월 차가 될 때까지 대화 품질에 대한 자동 회귀 테스트 (automated regression testing)가 없었습니다. 그전에는 팀원 중 누군가가 수동으로 시스템에 전화를 걸어 시나리오를 실행해야 했습니다.

이제 우리는 프롬프트 변경 사항에 대해 200개의 실제 통화 전사 데이터 (call transcripts)를 재생하고, 의도 탐지 (intent detection), 개체 추출 (entity extraction), 그리고 작업 완료율 (task completion rate)에서의 회귀 (regressions)를 표시하는 테스트 하네스 (test harness)를 갖추고 있습니다. 이를 더 일찍 구축했더라면 각각 수백 건의 통화에 영향을 미쳤던 최소 세 번의 프로덕션 장애 (production incidents)를 방지할 수 있었을 것입니다.

6개월 차의 수치들

출시 시점과 비교한 현재 우리의 상태는 다음과 같습니다:

  • 자동화율 (Automation rate): 82% → 89% (목표는 85%였습니다)
  • 평균 통화 시간 (Average call duration): 3분 42초 → 2분 51초
  • 환자 만족도 (통화 후 설문 조사): 3.8/5 → 4.3/5
  • 상담원 연결률 (Transfer-to-human rate): 18% → 11%
  • 첫 응답 지연 시간 (First-response latency, p95): 1.4초 → 0.8초
  • 월간 통화량 (Monthly call volume): ~1,600건 → ~5,200건

개선의 가장 큰 단일 동력은 어떤 기술적 변화도 아니었습니다. 그것은 다른 클리닉을 망가뜨릴 위험 없이 각 클리닉의 AI 동작을 조정할 수 있게 해준 모듈형 프롬프트 시스템 (modular prompt system)이었습니다. 코드보다 설정 (Configuration over code)이었습니다.

오늘날 이를 구축하려는 사람에게 해주고 싶은 다섯 가지 이야기

  1. 벤더 모델 버전을 고정하세요 (Pin your vendor model versions). 모든 음성-텍스트 변환 (Speech-to-text) 및 LLM 제공업체는 예고 없이 제품의 동작을 변경할 수 있는 업데이트를 배포합니다. 변경 사항을 채택하는 시점을 직접 제어하세요.

  2. 즉시 실제 통화 데이터를 기반으로 테스트 코퍼스 (Test corpus)를 구축하세요. 첫날부터 익명화된 통화 녹음 파일을 저장하세요. 몇 달 뒤가 아니라 불과 몇 주 안에 회귀 테스트 (Regression testing)를 위해 이 데이터들이 필요하게 될 것입니다.

  3. 예외 경로 (Unhappy path)를 우선적으로 설계하세요. 상담원이 필요한 11%의 통화는 그렇지 않은 89%의 통화보다 더 중요합니다. 잘못된 상담원 연결 경험은 AI가 쌓아온 모든 신뢰를 무너뜨립니다.

  4. 2초 이상 걸리는 모든 작업은 비동기 (Async)로 처리하세요. 전화 통화 중의 침묵은 치명적입니다. 백엔드 작업에 시간이 걸린다면, 정보를 수집한 뒤 통화가 끝난 후 처리하세요.

  5. 고객별 설정 (Per-client configuration)에 조기에 투자하세요. 두 번째 고객은 첫 번째 고객과 다른 규칙을 가질 것입니다. 설정 시스템이 필요해지기 전에 미리 구축하세요. 생각보다 훨씬 빨리 필요하게 될 것입니다.

만약 비슷한 것을 구축하고 계신다면, 저희에게 알려주세요. hello@autor.ca로 연락하시거나 autor.ca를 방문해 주세요.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0