본문으로 건너뛰기

© 2026 Molayo

Zenn헤드라인2026. 06. 05. 12:37

AI의 오독을 허용하지 않는다. 프롬프트를 안전한 '읽기 형식'으로 트랜스파일하는 방어 전술

요약

음성 합성(TTS) 엔진에서 발생하는 한자 오독 문제를 해결하기 위해, 프롬프트를 안전한 가타카나 형식으로 트랜스파일하는 방어적 전처리 기술을 소개합니다. 최장 일치 탐욕적 치환과 형태소 분석을 결합하여 데이터 구조의 무결성을 보장하는 구현 방법을 다룹니다.

핵심 포인트

  • 한자 오독 방지를 위한 세이프 프롬프트(Safe Prompt) 트랜스파일 전략
  • 최장 일치(Longest Match) 원칙을 통한 부분 치환 오류 방지
  • Go 언어의 map 순서 불확실성을 해결하기 위한 키 정렬 로직
  • 사전 기반 매핑과 형태소 분석의 하이브리드 접근 방식

1. 【과제】 생 텍스트(Raw Text)가 초래하는 「예측 불가능한 오독」과 음성 AI의 한계

lyria-3-pro-preview

음성 합성 (TTS, Text-to-Speech) 엔진에 사용자가 입력한 일본어 프롬프트를 그대로 던져버리면, 다음과 같은 「현실의 벽」에 부딪힙니다.

한자 오독으로 인한 세계관의 붕괴: 「刃(칼)」를 「ハ(하)」라고 읽어 긴박감이 깎이거나, 「運命(운명)」을 「ウンメイ(운메이)」라고 읽어 에모셔널(Emotional)함이 상실됨.

컨텍스트(Context, 배경) 혼동으로 인한 파싱 에러 (Parse Error): 프롬프트 배경에 「疎結合(소결합/Loose Coupling)」, 「冪等性(멱등성/Idempotency)」와 같은 시스템 운영 메타데이터나 시스템 프롬프트의 파편이 섞여 들어갔을 때, 모델이 한자의 음독/훈독이나 구분에 혼란을 느껴 표현의 정확도가 현저히 저하됨.

이를 해결하기 위해서는 AI의 진화를 기다리는 것이 아니라, 입력 단계에서 「절대로 오독할 수 없는 클린한 가타카나 표기 (세이프 프롬프트, Safe Prompt)」로 트랜스파일 (Transpile, 전처리)하는 접근 방식이 매우 유효합니다.

2. 【데이터 구조】 에모셔널과 테크를 망라하는 「최종 방어 사전」

AI가 해석에 혼란을 느끼지 않도록, 미리 「표층형 (한자 등)」과 「기대하는 읽기 (가타카나)」를 엄격하게 정의한 매핑 (Mapping, 사전)을 준비합니다.

특히 음성 생성 현장에서 중요하게 작용하는 것은, 「刃(ヤイバ, 야이바)", 「運命(サダメ, 사다메)", 「永遠(トワ, 토와)」와 같은 애니송·판타지적인 특수 표현, 그리고 컨텍스트를 파괴할 수 있는 「疎結合(ソケツゴウ, 소케츠고우)", 「冪等性(ベキトウセイ, 베키토우세이)」와 같은 테크 용어를 확실하게 고정하는 것입니다.

이것들을 커버하는 최강의 진용을 go:embed로 읽어들이는 reading_overrides.json으로 정의합니다.

{
"刃": "ヤイバ",
"閃光": "ヒカリ",
...

3. 【로직 해설】 「최장 일치 탐욕적 치환」과 「형태소 분석」의 하이브리드 방어선

본 설계의 가장 아름다운 부분은, 「사전 기반의 최장 일치 스캔」과 「Kagome에 의한 일반적인 폴백(Fallback) 분석」을 문자열의 바이트 인덱스(Byte Index)를 컨트롤하면서 1패스(1-pass)로 스마트하게 처리하고 있다는 점입니다.

먼저, audio/phonetic 패키지의 코어(Core)가 되는 소스 코드의 전체상을 제시합니다.

package phonetic
import (
_ "embed"
...

이 구현의 핵심이 되는 중요 포인트를 심층 분석합니다.

map 이터레이션(Iteration) 순서를 신뢰해서는 안 되는 이유

왜 Go의 사양상 map[string]string의 루프 순서는 실행할 때마다 랜덤인가요?

만약 사전에 「結合(결합)」과 「密結合(밀결합)」이 혼재되어 있을 경우, 순서의 불확실성 때문에 「密結合」의 「結合」 부분만 먼저 잘못 치환되어 파싱이 파괴될 리스크가 있습니다.

본 코드에서는 Option 적용 후에 rebuildOverrideKeys를 단 한 번 호출하여, 명확하게 「글자 수가 긴 순서」로 정렬한 슬라이스 (overrideKeys)를 정적으로 구축함으로써 이 문제를 완전히 봉쇄하고 있습니다.

func (c *Converter) rebuildOverrideKeys() {
    c.overrideKeys = make([]string, 0, len(c.readingOverrides))
    for surface := range c.readingOverrides {
        ...

참고로, 이 구현의 matchOverride는 정렬된 overrideKeys를 맨 앞부터 차례대로 살펴보는 선형 탐색(Linear Search)입니다. 사전이 수십에서 수백 건 정도라면 사용 편의성을 우선시한 이 설계로 충분하지만, 커스텀 사전이 수천에서 수만 건 규모로 늘어나는 경우에는 트라이 트리(Trie Tree)나 아호-코라식(Aho-Corasick) 알고리즘과 같은 다중 문자열 탐색 알고리즘으로 교체할 여지가 있습니다.

한 글자씩 진행하며 사전과 형태소 버퍼를 제어하는 알고리즘

ConvertToReading 메서드의 내부 루프는 문자열 조작의 퍼포먼스(Performance)와 정확성을 양립시킨 구현입니다.

for i := 0; i < len(input); {
// 1. 먼저 최장 일치로 등록된 사전에 히트하는지 확인
if surface, reading, ok := c.matchOverride(input[i:]); ok {
...

이 부분이 철저한 기본기(凡事徹底) 포인트: 사전에 히트하는 순간, 그때까지의 pending

문자열을 한꺼번에 Kagome (convertTokenized)로 전달함으로써, 형태소 분석기 (Morphological Analyzer) 호출 횟수를 최소화하고, 할당 (Allocation)과 CPU 부하를 극적으로 줄이고 있습니다.

Kagome의 토큰 분석과 「가창용 조사 보정」

사전을 통과한 일반적인 일본어는 Kagome (IPA 사전)에서 안전하게 분해되지만, 음성·가창에 있어 치명적인 약점이 되는 「は (HA)」「へ (HE)」「を (WO)」의 읽기 문제를, tokenReading 내의 피처 (Feature, POS 정보) 판정을 통해 깔끔하게 「ワ (WA)」「エ (E)」「オ (O)」로 보정하고 있습니다.

if len(features) > posIndex && features[posIndex] == "助詞" {
switch token.Surface {
case "は": reading = "ワ"
...

4. 【실례 트레이스】 「二重螺旋のタクト (이중나선의 타크트)」를 흘려 넣었을 때의 내부 동작

구축한 로직의 아름다움을 증명하기 위해, 실제 악곡 레시피 타이틀인 「二重螺旋のタクト」를 흘려 넣었을 때의 내부 동작을 뇌내 디버깅 (Trace) 해보겠습니다.

만약 당신이 「『二重螺旋』을 그대로 평범하게 읽게 하는 것이 아니라, 판타지 극중 음악처럼 『ダブルヘリックス (Double Helix)』라는 특수한 읽기를 AI에게 강제하고 싶다」고 생각하여, Option으로 사전을 확장했다고 가정해 봅시다.

converter, _ := phonetic.NewConverter(
phonetic.WithReadingOverrides(map[string]string{
"二重螺旋": "ダブルヘリックス",
...

🔄 처리 단계

  • 단계 1: 사전에 의한 최장 일치 (Longest Match) 가로채기

루프 시작 직후, 인덱스 0 ("二重螺旋のタクト"의 선두) 시점에서, 정렬된 overrideKeys (등록 사전)에 대해 c.matchOverride가 실행됩니다.

여기서 "二重螺旋"이 멋지게 히트하기 때문에, pending의 폴백 (Fallback)은 실행되지 않고, 최종 출력 (sb)에 직접적으로 쓰여집니다. 인덱스 ilen("二重螺旋") 바이트만큼 한꺼번에 점프합니다.

  • 단계 2: 남은 문자열의 보호

남은 문자열은 "のタクト"가 됩니다. 다시 사전 스캔이 수행되지만 히트하지 않으므로, 폴백 처리로 전환됩니다. utf8.DecodeRuneInString에 의해 「の」, 「タ」, 「ク」, 「ト」를 한 글자씩 안전하게 분해하면서, pending 버퍼로 쌓아 나갑니다.

  • 단계 3: Kagome에 의한 파싱과 조사 보정

문자열의 끝에 도달했기 때문에, pending에 쌓인 "のタクト"가 일괄적으로 convertTokenized (Kagome)로 전달됩니다.

Kagome에 의해 「の (조사)」와 「タクト (명사)」로 분해되고, 조사 보정 방어선을 통과한 가타카나 ノタクト가 최종 출력으로 결합됩니다.

  • 최종 Output: ダブルヘリックスノタクト

형태소 분석기가 성실하게 「二重 / 螺旋」이라고 분해해 버리기 전에, 전단의 정렬된 슬라이스 (Sorted Slice)가 최장 일치로 인터셉트(Intercept)하여 독자적인 도메인 지식을 주입한다. 이 대비야말로 이 하이브리드 구조의 진수입니다.

5. 요약 (README-first와 방어선의 사상)

AI와 대치함에 있어 가장 중요한 것은, 「AI의 출력을 컨트롤하기 위한 경계선을 시스템 측에서 얼마나 견고하게 정의할 것인가」입니다.

이번에 소개한 phonetic.Converter는 바로 그 경계선을 지탱하는 견고한 방벽입니다.

🔗 참고 링크

Discussion

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0