본문으로 건너뛰기Hwanseok Sim

작성일

Gem3Park 개발기 7 — 두 번째 귀와 모니터링 웹

Google STT 옆에 Gemini 3.5 Transcribe Live를 PoC에서 shadow, 병렬 보조로 단계 승격한 과정과, 그 결과를 사람이 확인할 수 있게 9월 12일 하루에 다시 만든 운영 모니터링 웹(통화 검색·녹음 재생·검수·SSE)을 다뤘다.

Gem3Park 개발기 시리즈 7/8편. 전체 목록은 gem3park 태그에서 볼 수 있다.

Gemini Live를 옆에 붙여 보기

지금까지 Gemini 전사는 발화가 끝난 뒤 한 턴씩 따로 부르는 방식이었다. 8월 28일에 다른 방식을 시험했다. Google STT를 바꾸지 않고 실시간 Gemini 3.5 Transcribe Live를 병렬로 붙이면 어떨까.

PoC를 해 보고 내린 결론은 "바꿀 수는 있지만 아직 주 경로로 쓰지 않는다"였다. 오프라인 재생과 카나리를 거치기 전까지는 선택 기능으로 둔다. 가장 큰 이유는 N-best였다. Live 모델은 최종 전사 하나만 주고 후보 목록이나 신뢰도가 없다. 6편의 위험 쌍 탐지는 Google의 둘째, 셋째 후보를 쓰기 때문에 Live를 주 경로로 쓰면 그 증거가 사라진다. PoC에서는 Live 결과를 후보 목록 첫 자리에 confidence 없이 넣어 기존 이벤트 형식에 맞췄다. 연결이 끊겼을 때 Google로 자동 전환하는 기능, 10분 세션 제한 대응, 전사 내용 없이 쓰는 지표도 아직 없어서 카나리 전에 해결할 공백으로 남겼다.

그래서 주 경로는 바꾸지 않았다. 순서는 다음과 같다.

  1. 감사 통화 몇 건으로 사전 replay(8월 28일).
  2. 통화 결과에 영향이 없는 관측 전용 shadow(8월 28일).
  3. 주소가 어긋나는 경우를 분류해 동·호수 보조로 승격(9월 1일).
  4. 모든 청취 창에서 Google과 병렬 실행(9월 2일). 방문 목적 복구로 확장(9월 12일).

병렬 보조의 규칙

9월 2일에 정한 규칙은 이렇다. Google 전사에서 목적 키워드가 나오면 Google이 우선이다. 없고 지금이 목적 수집 창이면 Gemini 결과를 기다린다. 이때 기다리는 시간은 통화 시작이 아니라 공유 VAD가 발화 종료를 판단한 시점부터 최대 3초다.

STT_SHADOW_TIMEOUT_MS=3000

Gemini에서만 목적이 확인되면 Gemini 원문을 가져오지 않고, 검증된 표준 라벨만 Google 전사에 보충한다.

수치는 작은 표본뿐이다. 사전 replay(감사 통화 3건, 발화 창 5개)에서 문장 일치 4/5, 동·호수 3/3, 목적 3/3이었고 재생 종료 뒤 지연은 122~225ms였다. 운영 반영 직후(통화 5건, 청취 창 8개)는 성공 7/8, timeout 1건, 목적 일치 7/7, 성공 결과 평균 지연 1.02초였다. 통화 5건이라 정확도를 말하기엔 적다. 다음 합격 단계(오프라인 300턴, shadow 1,000창·7일)는 기록이 아직 없다.

결과를 볼 화면이 없었다

두 전사가 엇갈린 통화를 직접 들어 보고 싶었는데, 2월의 1차 버전에서 이어진 모니터링 웹으로는 할 수 없었다. 9월 12일에 현황을 따져 보니 이랬다.

확인 사항문제
서버가 한 파일 1,424줄설정, 수집, 집계, API, SSE, 파일 저장이 섞임
종료된 통화는 메모리에서 5분 뒤 제거과거 통화를 볼 수 없음
UUID 없는 로그를 유일한 활성 통화로 추정병렬 통화에서 잘못 귀속될 수 있음
전체 상태를 SSE로 반복 전송조사 시점 snapshot 한 번이 370,786바이트
요약 파일 전체를 5초마다 재탐색요약 파일이 18,810개까지 쌓임
녹음 전송과 접근 제어 없음사람이 들을 방법이 없음

먼저 될 것을 "통화 한 건을 골라 녹음을 들으며 프로그램의 전사와 비교하는 일"로 정하고 나머지를 거기에 맞췄다.

만든 것

통화 한 건을 골라 녹음을 듣고 전사와 비교한 뒤 검수 결과를 남기는 흐름대로 만들었다. 통화는 기간, 결과, 녹음 유무, 검수 상태, 전사 내용으로 찾는다. 날짜별 영속 인덱스와 커서 페이지네이션을 쓴다. 녹음은 HTTP Range로 받아 ±5초 이동, 배속, A-B 반복을 지원하고, 청취 창 단위로 "이 구간 듣기"를 누를 수 있다. 이를 위해 릴레이에도 녹음 샘플 구간을 기록하는 변경을 넣었다.

전사를 비교하는 화면에서는 프로그램이 채택한 전사와 공급자별 원문을 나란히 놓는다. 녹음과 전사 쌍의 해시가 맞을 때만 보여 주는데, 다른 통화의 전사가 붙어 보이는 사고를 막으려는 것이다. 검수 결과는 수정할 때마다 revision을 별도 파일에 원자적으로 저장하고, 동시 수정은 409로 막는다. 실시간 갱신은 처음 한 번만 전체를 조회하고 이후는 증분 이벤트를 SSE로 보낸다.

통화 녹음에는 방문객의 목소리가 들어 있어서 외부에 열지 않았다. 계정과 역할별 권한, 쓰기 요청의 CSRF 검사를 두고, 운영자의 사설망 안에서만 HTTPS로 연다. 처음 시도한 사설망 노출은 계정 불일치로 중단했고, 모니터 전용 노드를 따로 등록하는 방식으로 바꿔 실제 HTTPS 브라우저 접속을 확인했다.

검증과 한계

모니터 단위 테스트 21개, Playwright E2E 5개(axe로 WCAG 2.2 AA 자동 검사 포함), HTTPS 통합 1개를 통과시킨 뒤 반영했다. 합성 인덱스 10만 건에서 목록 API p95는 14.45ms였는데, warm 캐시와 단일 클라이언트에서 잰 값이라 부하 성능은 아니다. 운영 반영 시점에 녹음 12,002건이 인덱싱됐다.

다른 기기에서 접속하는 것은 그때 확인하지 못했다. 이 화면이 생겨서 5편의 24.2%나 이번 편의 7/8 같은 숫자를 사람이 들은 결과와 맞춰 볼 수 있게 됐다. 검수 기록은 이제 쌓기 시작한 단계다.

다음 글

Gem3Park 개발기 8 — timeout, 자기 정정 복구, 그리고 남은 과제

이전 글: Gem3Park 개발기 6 — 0 하나 차이 호수의 오승인을 막기까지