[WebTranslator 개발기 #07] 구글·Gemini·Libre 다중 엔진 연동과 LLM 응답 정제

크롬 확장 프로그램 개발기 7편: Google, Gemini, OpenAI, Claude, Ollama를 지원하는 다중 엔진 어댑터를 설계하고, LLM의 마크다운 잡음으로 깨지던 JSON 배열을 안전하게 정제한 과정을 다룹니다.
다중 LLM 번역 엔진 어댑터 패턴 및 JSON 배치 정제 파이프라인 아키텍처 다이어그램

지난 6편에서는 마우스 드래그 기반의 '선택 영역 번역' 기능을 신규 도입하고, 부모 DOM의 오염 상태와 무관하게 순수 문자열만을 검증하는 정밀 파서와 50자 기준 문단형 팝업 카드를 구축하여 Part 2 (DOM 파싱 & 렌더링 최적화)를 성공적으로 마무리했습니다.

이제 웹페이지 레이아웃을 100% 보존하면서 번역문을 자연스럽게 렌더링하는 클라이언트 UI 기반이 완성되었습니다. 그러나 기본 엔진인 구글 번역(Google Translate)은 속도가 빠르고 무료라는 장점이 있는 반면, 복잡한 기술 문서나 게임 고유의 뉘앙스를 매끄럽게 번역하지 못하는 직역투의 한계가 명확했습니다.

이번 Part 3 (멀티 LLM 엔진 연동 & 어댑터 패턴)의 첫 글에서는 Google, Google Gemini, OpenAI(GPT-4o), Anthropic Claude, 로컬 프라이버시를 위한 Ollama, 그리고 자체 호스팅(Self-Hosted)이 가능한 오픈소스 기계 번역 LibreTranslate까지 다양한 번역 엔진을 자유롭게 교체할 수 있는 어댑터 패턴(Adapter Pattern) 설계와, LLM 특유의 마크다운 잡음 속에서 JSON 배열을 안전하게 추출하는 응답 정제 파이프라인 구축 과정을 정리해 보고자 합니다.

1. 단일 엔진의 한계와 멀티 LLM 도입 배경

사용자마다 번역 엔진에 요구하는 가치와 환경이 서로 다릅니다.

엔진 분류 적용 모델 / 서비스 검증 상태 주요 특성 및 장단점
기본 기계 번역 Google Translate 테스트 완료 초고속, 무료, 전체 페이지 번역 시 무난한 문맥 유지
클라우드 LLM Gemini 3.5 Flash, GPT-5.6 Luna 테스트 완료 압도적인 문맥 이해도와 자연스러운 한국어 의역
자체 호스팅 (Self-Hosted) LibreTranslate 테스트 완료 자체 서버/도커 구축 가능하나, 도메인 용어 번역 품질 편차 큼
인터페이스 확장 Anthropic Claude, Ollama 인터페이스만 선반영 향후 확장을 위한 어댑터 규격만 등록 (실전 테스트 미진행)

따라서 단일 엔진에 종속되지 않고, 사용자가 상황에 맞춰 최적의 엔진을 선택할 수 있는 유연한 다중 엔진 아키텍처가 필요했습니다.

2. 다중 엔진 어댑터 패턴과 JSON 배치 설계

각 엔진은 서로 다른 엔드포인트 URL, 헤더 인증 규격, 요청 페이로드 포맷을 가지고 있습니다. 이를 단일 인터페이스로 통일하기 위해 어댑터 패턴(Adapter Pattern)을 도입했습니다.

또한 웹페이지 내 수십~수백 개의 텍스트 노드를 번역할 때, 개별 텍스트마다 API를 1회씩 호출하면 네트워크 왕복 시간(RTT)과 API 호출 비용이 폭증합니다. 이를 해결하기 위해 텍스트들을 string[] 형태의 단일 JSON 배열로 묶어 전송하는 배치(Batch) 파이프라인을 구축했습니다.

You are a professional translator. Translate the following JSON array of English strings to Korean.
Keep the array length and order exactly 1:1 matched.
Output ONLY the raw JSON array (e.g. ["번역문1", "번역문2"]). Do NOT wrap in markdown code blocks.

3. AI의 단순 파싱 실패와 LLM 마크다운 잡음 결함

AI 어시스턴트에게 Gemini 및 OpenAI 연동 어댑터를 구현하도록 지시했습니다.

// translator.js: 단순 JSON.parse() 처리 (오류 코드)
async function translateWithGemini(texts, apiKey) {
  const prompt = `Translate this JSON array to Korean: ${JSON.stringify(texts)}`;
  const response = await callGeminiAPI(prompt, apiKey);

  // 치명적 결함: LLM의 응답 문자열을 그대로 파싱 시도
  return JSON.parse(response.text); // SyntaxError: Unexpected token '`', "```json..."
}

발생한 치명적 파싱 실패 분석

프롬프트에 "마크다운 코드 블록 없이 순수 JSON만 출력하라"고 명시했음에도 불구하고, 실제 LLM 모델들은 다음과 같이 친절한 서술형 문장과 백틱으로 응답을 감싸서 반환하는 경우가 빈번했습니다:

Sure! Here is the translated JSON array you requested:
```json
[
  "안녕하세요, 웹페이지에 오신 것을 환영합니다.",
  "장바구니에 추가"
]
```
I hope this translation helps!
⚠️ 핵심 결함: JSON.parse()는 첫 글자가 [{가 아닌 Sure!```로 시작하는 순간 즉시 SyntaxError 예외를 던지며 프로세스를 중단시킵니다.

4. 해결책: 3단계 견고한 JSON 정제 파이프라인 (`parseAndCleanJson`)

어떤 LLM 모델이 어떤 잡담 텍스트나 마크다운 서식을 덧붙여 보내더라도, 순수한 JSON 배열만을 완벽하게 도려내는 다단계 정제 함수(parseAndCleanJson)를 구축하도록 지시를 수정했습니다.

단순 JSON.parse로 끝내지 마라. 정규식을 사용하여 ```json 코드 블록을 먼저 벗겨내고, 문자열의 첫 번째 '['와 마지막 ']' 사이의 내용만 인덱스로 슬라이싱하여 순수한 JSON 배열만 안전하게 복원하는 방어 파이프라인을 작성해라.
정제 단계 처리 로직 목적 및 효과
1단계: 마크다운 제거 replace(/^```(?:json)?\s*/i, "") 선두 및 후미의 코드 블록 백틱 기호 제거
2단계: 배열 구간 슬라이싱 substring(indexOf("["), lastIndexOf("]") + 1) 앞뒤의 모든 인사말, 서술형 텍스트 완벽 차단
3단계: 제어 문자 보정 제어 문자(`\u0000-\u001F`) 및 비표준 따옴표 치환 파싱 실패 시 2차 복구 후 안전한 `JSON.parse` 보장

정제 파이프라인 구현 코드

// src/background/translator.js: 견고한 LLM 응답 정제 파이프라인
export function parseAndCleanJson(rawString) {
  if (!rawString || typeof rawString !== "string") {
    throw new Error("빈 응답 수신");
  }

  let cleaned = rawString.trim();

  // 1. 마크다운 코드 블록 제거
  cleaned = cleaned.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "");

  // 2. 앞뒤 잡담 텍스트 제거 (첫 '[' 와 마지막 ']' 구간만 엄격 슬라이싱)
  const startIdx = cleaned.indexOf("[");
  const endIdx = cleaned.lastIndexOf("]");

  if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
    cleaned = cleaned.substring(startIdx, endIdx + 1);
  }

  try {
    return JSON.parse(cleaned);
  } catch (err) {
    // 3. 따옴표 및 제어 문자 2차 보정 후 재시도
    const sanitized = cleaned
      .replace(/\\'/g, "'")
      .replace(/[\u0000-\u001F]+/g, "");
    return JSON.parse(sanitized);
  }
}

5. 실전 검증 결과 및 엔진별 동작 특성

parseAndCleanJson 파이프라인을 적용한 후 실제 주요 엔진들의 동작을 검증했습니다.

  1. Google Gemini 3.5 Flash: 초당 50개 이상의 문장을 1초 내외로 자연스럽게 배치 번역하여 가장 뛰어난 가성비와 속도를 확인.
  2. OpenAI GPT-5.6 Luna: 서술형 마크다운 응답이 포함되어도 슬라이싱 파이프라인을 통해 순수 1:1 매칭 번역 배열을 완벽하게 추출.
  3. Google Translate: API 키 없이도 전체 웹페이지 문맥을 준수하게 번역하며 초고속 무료 엔진 역할 수행.
  4. LibreTranslate: 자체 호스팅을 통해 데이터 외부 유출 없이 동작하지만, 복잡한 게임/전문 용어에서 번역 품질 편차가 크게 나타남.

마무리하며

다중 번역 엔진 연동을 통해 전체 페이지 번역의 품질을 끌어올렸으나, 선택 영역 번역에서 특정 단어를 드래그했을 때 또 다른 결함이 드러났습니다. Google Translate와 LibreTranslate 같은 NMT 기계 번역 엔진은 사전식 메타데이터 생성이 불가능하며, Gemini와 GPT 같은 고성능 LLM을 연동해 두었음에도 불구하고 엔진 간 분기 없이 단순 1줄짜리 기계 번역 텍스트만 획일적으로 반환되고 있었던 것입니다.

다음 글에서는 각 엔진의 고유 특성을 살려, 선택 영역 단어 번역 시 LLM 엔진은 풍부한 사전식 해설(IPA 발음기호, 품사, 핵심 예문)을 생성하고 NMT 엔진은 가볍게 동작하도록 파이프라인을 특화 분기한 과정을 다루어 보겠습니다.