본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 19. 08:26

LLM을 사용하여 문맥을 유지하며 책 전체를 번역하는 방법

요약

LLM을 활용해 책 전체를 번역할 때 발생하는 문맥 단절과 컨텍스트 윈도우 제한 문제를 해결하기 위한 청킹 전략을 소개합니다. 구조적 파싱, 문장 단위 분할, 그리고 청크 간 문맥 전달을 통해 일관성 있는 번역 파이프라인을 구축하는 방법을 다룹니다.

핵심 포인트

  • 단순 글자 수 분할 대신 문서의 논리적 구조(장, 섹션)를 먼저 추출해야 함
  • spaCy를 활용해 문장 단위로 토큰화하여 의미론적 경계 유지
  • 토큰 예산을 고려하여 시스템 프롬프트와 이전 문맥을 위한 여유 공간 확보
  • 비용과 지연 시간을 최적화하기 위해 적절한 청크 크기 설정 필요

장(chapter)의 일관성을 유지하고, 컨텍스트 윈도우(context windows)를 준수하며, 다국어 도서를 처리하는 우리의 청킹(chunking) 전략.

문제점: 책은 프롬프트에 다 들어가지 않는다

LectuLibre에서 우리는 대규모 언어 모델(LLM)을 사용하여 소설, 기술 매뉴얼, 시와 같은 책 전체를 번역합니다. 각 문단을 LLM에 입력하고 결과를 연결하기만 하면 되니 간단해 보입니다. 하지만 300페이지 분량의 EPUB을 시도한 순간 혼란이 시작되었습니다. 장(chapter)들이 서로 뒤섞이고, 문장이 단어 중간에서 잘렸으며, 5장의 번역은 4장에서 무슨 일이 일어났는지 전혀 알지 못했습니다.

LLM은 제한된 컨텍스트 윈도우(context windows)를 가지고 있습니다. Claude 3의 거대한 200K 토큰 윈도우조차 15만 단어 분량의 책 전체를 담을 수는 없습니다. 설령 담을 수 있다 하더라도, 비용과 지연 시간(latency)이 터무니없이 높을 것입니다. 우리는 수천 페이지에 걸쳐 번역이 일관되게 유지될 수 있도록 충분한 문맥을 보존하면서, 책을 관리 가능한 청크(chunks)로 나누는 방법이 필요했습니다.

다음은 여러분의 비용, 컨텍스트 윈도우, 그리고 책의 서사적 흐름을 모두 존중하도록 설계된 청킹 파이프라인(chunking pipeline)입니다.

1단계: 텍스트뿐만 아니라 구조를 추출하기

단순히 글자 수로 나누는 것은 재앙을 초래하는 지름길입니다. 대신, 우리는 먼저 문서를 파싱(parse)하여 장(chapters), 섹션(sections), 헤딩(headings)과 같은 논리적 단위를 이해합니다. EPUB의 경우 ebooklib을 사용하고, PDF의 경우 pdfplumber를 사용합니다. 두 도구 모두 문단, 헤딩과 같은 아이템 스트림을 제공하며, 우리는 이를 장과 하위 섹션의 트리 구조로 조직화합니다.

import ebooklib
from ebooklib import epub

...

실제로 우리는 BeautifulSoup을 사용하여 <body> 텍스트를 추출하고 헤딩 태그(<h1><h6>)를 식별하여 목차를 생성합니다. 이런 방식을 통해 장(chapter)이 20,000 토큰에 달하더라도, 나중에 분할하기 전까지는 하나의 단위로 묶어 유지할 수 있습니다.

2단계: 토큰 예산을 고려한 문장 인식 분할

모델의 컨텍스트 윈도우(context window)에 맞추기 위해 장(chapter)은 여전히 더 작게 나누어야 합니다. 하지만 우리는 절대 문장 중간에서 나누지 않습니다. spaCy를 사용하여 텍스트를 문장 단위로 토큰화(tokenize)한 다음, 토큰 제한에 도달할 때까지 문장들을 탐욕적(greedily)으로 그룹화합니다.

왜 단순한 문자 기반 분할(character-based splitting)을 사용하지 않을까요? 문장은 의미론적 경계(semantic boundaries)를 가지고 있기 때문입니다. 문장 내부를 끊어버리면 가끔 "He walked to the sta-" / "-tion."과 같은 아티팩트(artefacts)가 발생합니다. LLM은 관대하지만, 그 정도로 관대하지는 않습니다.

import spacy
from transformers import AutoTokenizer  # 정확한 토큰 수 계산을 위함

...

우리는 max_tokens를 1800으로 설정하여 시스템 프롬프트(system prompt), 이전 청크(chunk)의 문맥(context), 그리고 모델의 응답을 위한 여유 공간을 남겨두었습니다. 이는 32K 컨텍스트 윈도우(context window)를 가진 Claude Haiku를 기준으로 한 것입니다. 더 긴 컨텍스트를 가진 모델의 경우 규모를 키우겠지만, 청크를 작게 유지하는 것은 더 빠르고 저렴한 API 호출을 의미하기도 합니다.

3단계: 청크 간 문맥 전달하기

진정한 마법은 청크
_사이_에 우리가 하는 작업에 있습니다. 청크 #5를 단독으로 번역한다면, 주인공이 청크 #4에서 방금 어두운 동굴에 들어갔다는 사실을 전혀 알 수 없습니다. 두 가지 기술이 이 문제를 해결합니다:

  1. 이전 문장들의 슬라이딩 윈도우 (Sliding window of previous sentences) — 이전 청크의 마지막 5~10개 문장을 "남겨진 문맥(context left)"으로서 프롬프트에 직접 포함합니다.
  2. 누적 요약 (A running summary) — 청크를 번역한 후, LLM에게 해당 청크에 대한 한 문장 요약을 생성하도록 요청합니다. 이 요약은 계속 누적되어 이후의 모든 프롬프트에 입력되므로, 모델이 상위 수준의 사건들을 기억할 수 있게 합니다.
def build_prompt(chunk, previous_context_sentences, summary_so_far):
    context_left = " ".join(previous_context_sentences)
    prompt = f"""You are translating a book. Here is a summary of the story so far:
...
"""

요약은 별도의 저렴한 호출을 통해 생성됩니다(메인 번역에 Claude를 사용하더라도, 요약에는 DeepSeek를 사용합니다). 이를 통해 장기적인 일관성(long-range coherence)을 유지하면서도 문맥 토큰 사용량을 최소화할 수 있습니다.

왜 이전 청크 전체를 포함하지 않을까요? 그렇게 하면 호출당 토큰 수가 두 배로 늘어납니다. 20만 단어 분량의 책이라면 이는 수백 달러의 추가 비용으로 이어집니다. 요약 방식은 품질 저하를 거의 일으키지 않으면서도 비용을 약 80% 절감합니다.

번역 루프(translation loop)는 다음과 같은 구조를 가집니다:

overall_summary = ""
previous_context = []
full_translation = []
...

우리는 번역 시간을 합리적으로 유지하기 위해 asynciohttpx를 사용하여 청크(chunks)를 병렬로 처리합니다.

실제 결과 및 트레이드오프 (trade-offs)

12만 단어 분량의 스페인어 소설(“El Quijote”)을 Claude 3 Haiku를 사용하여 영어로 번역하는 데 엔드 투 엔드(end-to-end)로 약 4분이 소요되었습니다. 총 API 비용은 $0.67였습니다. 번역은 놀라울 정도로 매끄러웠습니다. 장(chapters)들이 서로 연결된 느낌을 주었으며, 가끔 등장하는 회상 장면이나 대명사 참조(예: 3페이지 전에 소개된 캐릭터를 가리키는 "she")도 정확하게 해결되었습니다. 문맥 파이프라인(context pipeline)이 없었다면, 동일한 책은 일관성 없는 오류로 가득했을 것입니다.

우리는 다른 모델들도 실험해 보았습니다. DeepSeek-V3는 절반의 가격으로 유사한 품질을 제공했지만 지연 시간(latency)이 더 높았으며, 이는 속도가 중요하지 않은 배치 작업(batch jobs)에 더 적합합니다. GPT-4 Turbo는 문체적 수식(stylistic flourishes)을 더 자연스럽게 재현했지만, 16K 컨텍스트 윈도우(context window)로 인해 더 작은 청크를 사용해야 했고, 이로 인해 때때로 대화가 파편화되었습니다. Claude가 가장 좋은 균형을 보여주었습니다.

하지만 완벽하지는 않습니다. 요약(summary)이 지속되는 농담(running joke)을 모두 담아낼 수 없기 때문에 유머와 관용구는 여전히 가끔 어색하게 느껴집니다. 기술 서적 내부의 코드 블록(code blocks)과 표(tables)는 특별한 처리가 필요합니다. 우리는 이를 감지하여 [CODE] 마커로 감싸는 파서(parser)를 개발 중이며, 이를 통해 LLM이 변수 이름을 번역하려고 시도하지 않도록 할 것입니다. 또한 줄 바꿈과 운율이 있는 시(poetry)는 여전히 과제로 남아 있으며, 시를 인식할 수 있는 전용 청커(poetry-aware chunker) 도입을 고려하고 있습니다.

핵심 요약 (key takeaway)

LLM을 사용하여 긴 문서 번역 시스템을 구축하고 있다면, 다음과 같은 파이프라인에 투자하십시오:

  • 분할하기 전에 문서 구조(장, 단락)를 존중할 것.
  • 문장 단위로 분할하고, 항상 문맥을 위한 여유 공간을 남겨둘 것.
  • 각 청크에 직전 문맥(마지막 몇 문장)과 전역 문맥(요약)을 모두 제공할 것.
  • 비용을 낮추기 위해 요약과 같은 보조 작업에는 별도의 저렴한 모델을 사용할 것.

우리의 코드는 아직 오픈 소스(open-source)가 아니지만, 더 많은 형식에서 실전 테스트(battle-tested)를 거친 후 핵심 청킹 라이브러리를 공개할 계획입니다.

LLM 번역에서 문맥(Context)을 어떻게 처리하시나요? 저희는 특히 수식, 각주, 상호 참조(Cross-references)가 포함된 고도의 기술 서적을 다루는 방법에 대해 매우 궁금합니다. 여러분의 아이디어를 댓글로 남겨주세요 — 함께 해결책을 찾아봅시다.

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0