[WebTranslator 개발기 #14] Kaikki 및 외부 사전 API 검토와 단일 LLM 고속 사전 결정

단어 사전 기능 개발 시 Kaikki 등 오픈소스 사전 데이터 및 외부 무료 API의 인프라적 한계를 분석하고, 경량 LLM 단일 프롬프트 파이프라인으로 0.8초 고속 사전을 구축한 기술 의사결정 과정을 정리합니다.
WebTranslator Kaikki 외부 사전 검토 및 단일 LLM 고속 사전 파이프라인 아키텍처 다이어그램

앞서 6편에서 다룬 선택 영역 번역(드래그 번역) 기능을 바탕으로, 단순 번역을 넘어선 풍부한 어학 정보를 제공하여 사용자 UI/UX 경험을 한층 더 높이고 싶었습니다. 기존 단순 단어 번역에서 벗어나, 웹 서핑 중 낯선 외국어 단어를 마우스로 드래그했을 때 발음, 품사, 핵심 뜻, 예문까지 한눈에 보여주는 단어 사전 기능으로 강화하고자 했습니다.

초기에는 단어를 조회할 때마다 발생하는 LLM API 호출 비용을 줄이고 상세한 어학 데이터를 제공하기 위해 Wiktionary 기반 오픈소스 사전인 Kaikki.org 데이터베이스나 외부 무료 사전 API를 연동하는 방안을 기술적으로 검토했습니다.

그러나 인프라 구축 공수와 데이터 제약으로 인해 단일 LLM 프롬프트 방식으로 전환하게 되었습니다. 이번 글에서는 그 분석 과정과 최종 아키텍처를 정리하고자 합니다.

1. Kaikki 및 외부 무료 사전 API의 기술적 한계 분석

오픈소스 사전 데이터와 외부 API를 클라이언트 확장 프로그램에 붙이는 방안을 검토하면서 다음과 같은 현실적인 문제점들이 확인되었습니다:

  1. 방대한 사전 데이터 크기(무압축 26GB / 압축 2GB)와 클라이언트 탑재 불가: Kaikki의 전체 덤프 데이터는 무압축 기준 약 26GB에 달하며, 압축된 모델/파일조차 약 2GB 수준입니다. 수 GB에 이르는 대용량 데이터를 크롬 확장 프로그램 클라이언트에 직접 내장하는 것은 브라우저 메모리 및 확장 프로그램 배포 용량 제약상 불가능했습니다. 이를 활용하려면 별도의 검색용 DB와 자체 백엔드 API 서버를 구축하고 유지보수해야 하므로, API 호출 비용을 줄이려다 서버 호스팅 비용과 관리 공수가 더 커지는 문제가 발생했습니다.
  2. 외부 무료 API의 차단 및 다국어 부실: Free Dictionary API 같은 무료 서드파티 서비스를 호출할 경우 호출 빈도 제한(Rate Limit)으로 인한 서비스 차단 위험이 존재했고, 일본어·중국어(CJK) 등 비영어권 데이터베이스 지원이 부실했습니다. 과거 Google Translate(비공식 웹 크롤링 방식) 연동에서도 동일한 Rate Limit 문제가 있었으나, Google Translate는 무료 이용자를 위한 보조 수단이었고 실사용은 Gemini Flash 등 무료 티어 LLM API가 주로 담당하여 큰 문제가 되지 않았습니다. 반면 사전 기능은 단어를 드래그할 때마다 빈번하게 호출되는 핵심 UX이므로, 호출 차단 위험이 상존하는 불안정한 외부 무료 API에 의존할 수 없었습니다.
  3. 정적 사전 데이터의 본질적 한계: 오픈소스 사전은 데이터베이스에 미리 입력된 표제어만 정적으로 조회할 수 있어, 신조어나 다양한 어형 변화(활용형), 웹페이지 문맥에 맞는 유연한 번역을 제공하기 어려웠습니다.
⚠️ 주의: API 호출 비용을 아끼기 위해 무리하게 외부 무료 서비스를 연쇄적으로 엮거나 자체 서버를 세우려 하면, 인프라 관리 부담이 늘어나고 네트워크 지연으로 인해 팝업 표시 속도가 급격히 저하될 수 있습니다.

2. 단일 LLM JSON 파이프라인(Single LLM Pipeline) 채택

별도의 서버 인프라를 구축하지 않으면서도 다국어 커버리지와 빠른 반응 속도를 동시에 확보하기 위해, 경량 LLM(Gemini Flash / GPT-4o-mini)에 1회 프롬프트 요청으로 모든 사전 정보를 받아오는 단일 LLM 파이프라인을 구축했습니다.

// src/api/prompts.js - 단일 LLM 사전 프롬프트 빌더
export function buildDictionaryPrompt(word, langName) {
  return `Provide a concise dictionary entry for the input text "${word}" translated into ${langName}.

CRITICAL INSTRUCTIONS:
1. If the input is a single word: Provide AT MOST THE TOP 3 MOST COMMON definitions.
   - Each definition MUST have a short, concise ${langName} meaning (2 to 5 words only).
   - Provide a realistic example sentence matching the definition.
2. If the input is a PHRASE or FULL SENTENCE (not a single word):
   - Set "pos" to "구" (Phrase) or "문장" (Sentence).
   - Provide the direct, natural translation of the entire phrase/sentence in the "meaning" field.
   - DO NOT generate or invent an "example". Set the "example" field to null.
3. "pronunciation": Provide the phonetic pronunciation of the ORIGINAL SOURCE TEXT, transliterated into the characters of the target language (${langName}).
4. Return ONLY a valid JSON object matching this schema:
{"word":"${word}","pronunciation":"[Phonetic characters in ${langName}]","definitions":[{"pos":"Part of speech","meaning":"Meaning or Translation","example":{"source":"Original example sentence","target":"Translated example sentence"}}]}`;
}

이 프롬프트 설계의 핵심은 4가지 원칙에 기반합니다:

설계 원칙 동작 방식 기대 효과
핵심 뜻 3개 압축 가장 자주 쓰이는 상위 3개 뜻만 2~5단어로 제한 토큰 낭비 방지 및 팝업창 세로 늘어짐 방지
구/문장 직접 번역 전환 문장 입력 시 예문 생성을 생략(null)하고 번역문만 직결 불필요한 지연 단축 및 유연한 텍스트 처리
직관적 한글 음차 IPA 발음기호 대신 대상 언어 문자(한글)로 발음 소리 표기 외국어 단어의 실제 발음을 한눈에 파악 가능
단일 JSON 스키마 고정 마크다운 래퍼 없이 순수 JSON 객체만 반환하도록 강제 프론트엔드 UI 파싱 에러 원천 방지 및 0.8초 고속 렌더링

3. 단일 플로팅 카드 UI 통합 및 렌더링 최적화

사전 기능 개발에서 프롬프트 설계만큼 공을 들인 부분이 바로 클라이언트 팝업 UI(dictionaryPopup.js)의 구조화입니다. 초기 기획에서는 사용자가 드래그한 텍스트가 '단어'인지 '문장'인지 글자 수나 공백 기준으로 사전 분기하여 각기 다른 모달/팝업 컴포넌트를 띄우는 방식을 고려했습니다.

그러나 클라이언트 측에서 숙어나 짧은 관용구를 정확히 판별하기 어렵고, 서로 다른 팝업 컴포넌트를 이중 관리할 경우 DOM 생성 오버헤드와 화면 깜빡임이 발생하는 문제가 있었습니다. 이를 해결하기 위해 단일 플로팅 카드 아키텍처로 통합하고 세 가지 핵심 엔지니어링 요소를 적용했습니다:

  1. 스키마 기반 동적 뷰 렌더링 (Schema-driven Dynamic Rendering): 단일 팝업 컨테이너 내에서 LLM 응답의 definitions 구조를 검사합니다. pos가 '구'나 '문장'이고 example이 null이면 번역문 중심의 심플 카드로 렌더링하고, 일반 단어 품사가 들어오면 발음 음차 뱃지, 번호 매김 뜻 목록, 원문-번역 예문 박스를 정갈하게 렌더링하는 조건부 뷰 전환을 구현했습니다.
  2. 뷰포트 적응형 스마트 위치 계산 (Viewport-aware Positioning): 사용자가 드래그한 텍스트 영역의 Selection.getRangeAt(0).getBoundingClientRect() 좌표를 추출하여 팝업을 텍스트 상단 중앙에 플로팅합니다. 만약 브라우저 상단 공간이 부족하면 자동으로 텍스트 하단으로 반전(Flip)시키고, 좌우 경계를 벗어날 경우 화면 안쪽으로 오프셋을 안전하게 클램핑(Clamp)하여 화면 잘림 현상을 방지했습니다.
  3. 단일 인스턴스 재사용 및 라이프사이클 관리: 마우스 드래그 시마다 매번 신규 DOM을 생성/제거하지 않고, 단일 팝업 인스턴스를 재사용하며 데이터만 주입합니다. 외부 영역 클릭(Click Outside)이나 ESC 키 입력 시 트랜지션 애니메이션과 함께 닫히도록 리스너를 깔끔하게 관리하여 메모리 누수를 원천 차단했습니다.
💡 팁: 사용자가 긴 문장을 드래그했을 때 억지로 사전 형식으로 분해하지 않고 자연스러운 문맥 번역문으로 부드럽게 전환되도록 프롬프트와 UI를 유기적으로 연계하면, 단일 컴포넌트만으로 단어장과 문장 번역기의 UX를 깔끔하게 통합할 수 있습니다.

4. 실전 검증 및 다국어 지원 결과

영어, 일본어, 중국어 웹페이지에서 다양한 어휘를 드래그하여 실전 테스트를 진행한 결과 다음과 같은 성과를 확인할 수 있었습니다:

  • 0.8초 초고속 반응: 토큰 수가 엄격히 제한된 JSON 응답 덕분에 드래그 즉시 팝업이 부드럽게 렌더링됨.
  • 자연스러운 한글 발음 음차: 일본어 한자어나 영어 단어의 실제 소리가 한글로 정확히 표시되어 학습 편의성 극대화.
  • 서버리스(Serverless) 독립성: 별도 백엔드 서버 없이 사용자의 개인 API Key만으로 모든 기능이 독립 구동됨.

마무리하며

기능을 확장할 때 무조건 외부 전용 서비스를 연동하는 것보다, 현재 시스템의 아키텍처와 유지보수 공수를 고려하여 가장 단순하면서도 강력한 도구(경량 LLM 프롬프트)를 효율적으로 활용하는 것이 실용적인 개발 방향임을 확인할 수 있었습니다.

혹시 웹 확장 프로그램이나 웹 앱에서 단어 사전 팝업을 구현하면서 최적화하셨던 프롬프트 기법이나 UI 구성 아이디어가 있다면 댓글로 공유해 주시기 바랍니다. 다음 글에서는 스팀 커뮤니티나 깃허브 다크모드 환경에서 번역된 글자 색상이 배경에 묻히는 문제를 해결하기 위해 도입한 상위 DOM 배경색 역추적 및 적응형 가독성 엔진 구축 과정을 다루겠습니다.