음성봇을 스텁부터 만든 이유 — 테스트로 설계를 먼저 굳히다

시리즈 · 음성봇 직접 만들기 — 실시간 음성봇의 뼈대를 바닥부터 만든 8편 기록입니다. 1. 끼어들기(barge-in) — 재생 중인 TTS를 실시간으로 끊는 법 2. "말이 끝났다"는 판단 — VAD 엔드포인터 3. 엔진을 코드 수정 없이 바꾸는 인터페이스 설계 4. 로컬 LLM 대화 두뇌와 도구 호출 루프 5. 매 턴 지연시간을 재는 이유 6. 브라우저 마이크에서 서버까지 — 오디오 프로토콜 7. 로컬 Whisper STT 연결하기 8. 스텁부터 만든 이유 — 테스트 우선 설계 (완결) — 지금 보는 글 돌아보며 시작하는 마지막 편 이 시리즈를 시작할 때부터 정해 둔 규칙이 하나 있었습니다. 진짜 음성 인식 모델과 진짜 합성 모델이 준비되기 전까지는, 그 둘을 흉내만 내는 가짜 엔진으로 전체 구조를 먼저 돌려본다는 규칙입니다. 여덟 편을 거치며 끼어들기 처리, 상태 흐름, 프로토콜, 엔진 인터페이스를 하나씩 다뤘는데, 그 모든 것이 사실 이 가짜 엔진 위에서 검증됐습니다. 지금 코드를 다시 열어 보면 그때는 별생각 없이 세운 규칙이 결과적으로 시리즈 전체의 진행 속도를 결정했다는 게 눈에 들어옵니다. 이번 편에서는 왜 굳이 가짜부터 만들었는지, 그리고 그 선택이 시리즈 전체에 어떤 영향을 남겼는지 정리하면서 이 시리즈를 마무리하려 합니다. 실제 모델을 기다리지 않기로 한 이유 음성봇의 핵심 로직은 사실 음성 인식이나 음성 합성 자체가 아닙니다. 사용자가 말을 시작했는지 끝냈는지 판단하는 상태머신, 합성 중에 끼어들기가 들어왔을 때 스트림을 멈추는 동시성 처리, 이벤트가 순서대로 오가는 흐름 제어 같은 것들이 훨씬 까다롭습니다. 반면 실제 인식·합성 모델은 무겁고 설치가 까다롭고 실행 속도도 느립니다. 이 두 가지를 한꺼번에 붙잡고 있으면 어려운 동시성 버그를 실제 모델의 로딩 시간과 뒤섞어 디버깅하게 됩니다. 모델이 느려서 반응이 늦은 건지, 상태머신이 잘못 짜여서 늦은 건지 구분이 안 되는 상황을 미...

음성인식 주소 오류, 국토부 API로 자동 보정하기 — STT가 틀린 한국 주소 고치는 법

음성으로 "테헤란로 이백팔십"이라고 말하면, STT는 그 소리를 들은 그대로 텍스트로 옮깁니다. 사람에게는 "280번지"라는 게 뻔하지만, STT에게는 그냥 "이백팔십"이라는 글자입니다. 여기에 도로명이 살짝 뭉개지거나 비슷한 발음의 다른 동네 이름이 섞이면, 결과 문자열은 실제로 존재하지 않는 주소가 됩니다.

주소를 텍스트로 입력받을 때는 안 겪던 문제입니다. 키보드로 치면 자동완성이 실존 주소를 골라 주니까요. 그런데 음성으로 받는 순간, STT가 뱉은 문자열이 진짜 주소인지 아닌지를 확인하고 고쳐 주는 단계가 통째로 비어 있습니다. 이 글은 그 빈 자리를 왜, 어떻게 메우는지에 대한 시리즈의 시작입니다.

시리즈 · 한국 주소 STT 보정 — 음성으로 받은 한국 주소의 인식 오류를 단계별로 고쳐 가는 6편 기록입니다.

  1. 1. 왜 자꾸 틀리나 — 문제 정의와 파이프라인 개요 — 지금 보는 글
  2. 2. 국토부 도로명주소 API를 정답지로 (JSONP→서버)
  3. 3. 자모 유사도 매칭 — 한글 분해 + 레벤슈타인
  4. 4. "이백팔십"을 280으로 — 한글 숫자 되돌리기
  5. 5. 규칙으로 안 되는 것만 LLM에게 (api vs cli)
  6. 6. 브라우저 프로토타입 → Vercel 서버리스

1. 주소는 일반 문장과 다르다

STT 오류 보정이라고 하면 보통 맞춤법 교정기를 떠올립니다. 그런데 주소에는 일반 문장에 없는 제약이 하나 있습니다. 실제로 존재해야 한다는 것입니다.

일반 문장은 조금 어색해도 뜻이 통하면 됩니다. 하지만 주소는 "그럴듯한" 걸로는 부족합니다. "서울 강남구 테헤란로 280"은 실존하고, "서울 강남구 테헤란로 281"은 없을 수 있습니다. 한 글자, 숫자 하나가 틀리면 배송이 안 가고 좌표가 안 찍힙니다. 그래서 주소 보정은 문장을 다듬는 문제가 아니라, 후보를 실존 주소 목록과 대조해 맞는 걸 찾는 문제에 가깝습니다.

STT는 이 대조를 하지 않습니다. 발음을 글자로 옮기는 것까지가 STT의 일이고, 그 글자가 지도에 있는 주소인지는 STT의 관심 밖입니다. 보정 단계가 따로 필요한 이유가 여기 있습니다.

2. STT가 주소를 틀리는 대표 유형

직접 데이터를 보며 정리한 오류는 크게 네 갈래였습니다.

숫자를 한글로 풀어 씀. 가장 흔합니다. "280"을 "이백팔십"으로, 혹은 번지를 한 자리씩 불러 "오팔구"(5·8·9)처럼 인식합니다. 사람은 바로 숫자로 알아듣지만 문자열로는 한글 덩어리입니다.

비슷한 발음의 다른 지명. "역삼동"과 "역산동"처럼 자음·모음 한둘 차이로 다른(혹은 존재하지 않는) 동네가 됩니다. 발음이 가까울수록 STT가 헷갈립니다.

도로명·지번·번지의 혼동. "고산로 오백삼십구번길"처럼 도로명과 번길 숫자가 떨어져 인식되거나, 지번의 본번-부번(예: 445-2)이 뭉개집니다. 붙여야 실존 도로명이 되는데 STT는 띄어서 뱉습니다.

건물명 오인식. "엘리엇"이라고만 들렸는데 실제로는 "현대엘리엇호텔앤드리조트"의 일부인 경우가 있습니다. 짧게 들린 조각을 긴 정식 건물명과 이어 줘야 합니다.

이 넷은 성격이 다릅니다. 앞의 둘(숫자·유사 발음)은 규칙과 유사도 계산으로 상당 부분 풀리고, 뒤의 둘(도로명 조합·건물명)은 실존 데이터 조회와 판단이 필요합니다. 그래서 보정도 한 방식이 아니라 여러 단계를 겹쳐야 합니다.

3. 맞춤법 교정으로는 안 되는 이유 — 정답지가 필요하다

여기서 자연스럽게 나오는 생각이 "LLM한테 고쳐 달라고 하면 되지 않나"입니다. 그런데 LLM 단독으로는 위험합니다. LLM은 실존 주소 데이터베이스를 들고 있지 않아서, 그럴듯하지만 존재하지 않는 주소를 자신 있게 만들어 낼 수 있습니다. 주소에서 이건 치명적입니다.

그래서 이 파이프라인의 중심은 LLM이 아니라 정답지입니다. 한국에는 국토교통부가 제공하는 도로명주소 API가 있어서, 어떤 문자열이 실제 주소인지 조회할 수 있습니다. 보정의 뼈대는 "STT 결과에서 검색어를 만들어 국토부에 물어보고, 돌아온 실존 후보 중 가장 가까운 걸 고르는" 것입니다. LLM은 규칙으로도 정답지 조회로도 안 풀리는 마지막 순간에만 폴백으로 씁니다.

4. 이 시리즈에서 만들 파이프라인

전체 흐름은 이렇게 잡았습니다.

STT 결과 문자열
   │
   ▼
① 토큰화        시도 / 시군구 / 읍면동 / 도로명 / 번지 / 건물명 으로 쪼갬
   │            ("이백팔십" 같은 한글 숫자는 여기서 280으로 변환)
   ▼
② 자모 유사도    비슷한 발음 후보를 자모 단위로 비교해 순위 매김
   │
   ▼
③ 국토부 API     실존하는 도로명주소인지 조회 · 실제 후보를 받아옴
   │
   ▼
④ LLM 폴백       규칙으로 안 풀리는 건물명·검색어만 마지막에 처리
   │
   ▼
보정된 실존 주소

각 단계를 한 편씩 풀어 갈 예정입니다. 다음 편은 이 중 뼈대인 ③ 국토부 도로명주소 API를 정답지로 쓰는 법입니다. 브라우저 프로토타입에서 CORS 때문에 우회하던 걸 서버에서 어떻게 정식으로 부르는지, 그리고 "5초에 10건"이라는 호출 제한을 어떻게 다루는지를 다룹니다.

자주 묻는 질문

Q. LLM에게 "이 주소 고쳐 줘"라고 통째로 맡기는 것과 지금 방식 중 뭐가 나은가요? LLM은 실존 주소 목록을 갖고 있지 않아, 그럴듯하지만 존재하지 않는 주소를 만들어 낼 수 있습니다(환각). 그래서 국토부 API로 실존 여부를 확인하는 게 먼저고, LLM은 규칙·조회로 안 되는 경우의 마지막 폴백으로만 씁니다. 순서를 반대로 하면 틀린 주소를 자신 있게 내놓는 시스템이 됩니다.

Q. "이백팔십"을 280으로 바꾸는 것도 LLM이 하나요? 아니요. 한글 숫자 → 아라비아 숫자 변환은 규칙으로 결정론적으로 처리할 수 있어서 코드로 합니다. LLM을 부르지 않으니 빠르고, 결과가 항상 같고, 비용도 들지 않습니다. LLM은 애매한 판단이 필요한 곳에만 아껴 씁니다.

댓글

이 블로그의 인기 게시물

디스코드 구글캘린더 연동 봇 만들기: 카톡 캡처 OCR 자동 등록 시스템 구축

클로드 코드 비용 줄이는 법 — ollama 로컬 LLM 연동 (M3 맥북)

유튜브 노션 자동 저장 봇 만들기 — Claude CLI MCP + Discord 233일 운영기