LLM을 활용한 긴 문서 분석을 마침내 정복한 방법 (단순한 청킹이 아니었습니다)
요약
긴 문서 분석 시 발생하는 토큰 제한, 환각, 비용 문제를 해결하기 위한 RAG 고도화 과정을 다룹니다. 단순 청킹과 맵-리듀스 방식의 한계를 넘어, 계층적 요약과 하이브리드 검색을 결합하여 정확도를 높인 실전 사례를 공유합니다.
핵심 포인트
- 단순 청킹은 문맥 단절과 정보 손실 문제를 야기함
- 맵-리듀스 방식은 세부 정보가 요약 과정에서 유실됨
- 슬라이딩 윈도우와 리랭킹은 재현율은 높으나 비용과 지연시간이 증가함
- 계층적 구조와 하이브리드 검색 결합이 가장 효과적인 대안임
몇 달 전, 저는 100페이지 분량의 기술 PDF 문서에서 질문에 답하는 시스템을 구축하고 있었습니다. 다들 아시다시피, 사용자가 매뉴얼을 업로드하고 “볼트 A의 토크 규격은 무엇인가요?”라고 물으면 빠르고 정확한 답변을 원하는 상황입니다. 간단한 LangChain 스크립트로 끝날 줄 알았던 작업은 토큰 제한(token limits), 환각(hallucination), 그리고 비용 폭발과의 몇 주에 걸친 사투로 변했습니다. 제가 배운 것들을 공유합니다.
문제점
저에게는 오래된 엔지니어링 문서들을 파악해야 하는 고객이 있었습니다. 각 PDF는 표, 다이어그램, 각주가 포함된 밀도 높은 문서였습니다. 저의 첫 번째 프로토타입은 전체 텍스트를 프롬프트(prompt)에 쑤셔 넣은 GPT-4를 사용했습니다. 10페이지 정도의 문서는 작동했지만, 100페이지에 달하자 질문을 추가하기도 전에 128K 토큰 제한(token cap)에 걸렸습니다. 그리고 억지로 밀어 넣더라도, 모델은 중간에 있는 세부 정보를 "망각"하곤 했습니다(lost-in-the-middle 효과). 쿼리당 비용은 호출당 0.50달러 이상으로, 웃음이 나올 정도로 비쌌습니다.
저는 임의의 길이의 문서를 처리할 수 있으면서도, 정확한 답변을 반환하고, 우리를 파산시키지 않을 솔루션이 필요했습니다.
시도했던 것들 (그리고 실패한 것들)
1. 단순 청킹 (Naive Chunking)
먼저, 문서를 일정 크기의 청크(chunk, 예: 2000 토큰)로 나누고 약간의 중첩(overlap)을 두었습니다. 각 청크를 OpenAI의 text-embedding-ada-002로 임베딩(embedding)하여 벡터 데이터베이스(vector database)에 저장한 뒤, 질문당 가장 유사한 상위 5개의 청크를 검색했습니다. 그리고 그 청크들을 컨텍스트(context)로 제공했습니다.
결과: 답변의 키워드가 질문의 의도와 일치하지 않아 모델이 잘못된 청크를 선택하는 경우가 빈번했습니다. 예를 들어, “알람을 어떻게 리셋하나요?”라고 물으면 “알람 리셋 절차”에 관한 청크는 가져올 수 있지만, 다른 청크에 있는 전제 조건 단계들은 놓칠 수 있습니다. 게다가 답변이 여러 청크에 걸쳐 있는 경우, 부분적인 정보만 얻게 되었습니다.
2. 맵-리듀스 요약 (Map-Reduce Summarization)
다음으로, 전형적인 맵-리듀스(map-reduce) 패턴을 시도했습니다. 각 청크를 독립적으로 요약한 다음, 요약본들을 결합하여 최종 요약을 만들고, 그 후에 답변을 생성하는 방식입니다. 이 방식은 상위 수준의 질문에는 효과적이었지만, 구체적인 내용에서는 실패했습니다. 중간 요약 과정에서 세부 정보가 손실되었습니다. 예를 들어, 토크 규격이 단순히 “단단히 조이세요”와 같은 식으로 변해버렸습니다.
3. 리랭킹을 결합한 슬라이딩 윈도우 (Sliding Window with Reranking)
저는 토큰 제한에 도달할 때까지 연속적인 청크(Chunk)들을 결합하는 슬라이딩 윈도우(Sliding Window)를 구축했습니다. 서로 다른 윈도우를 사용하여 여러 개의 후보 답변을 생성한 다음, 신뢰도(Confidence)를 기준으로 리랭킹(Reranking)을 수행했습니다. 이 방식은 재현율(Recall)을 향상시켰지만, 비용과 지연 시간(Latency)을 4배나 증가시켰습니다(질문당 5~10초 소요). 실시간 사용에는 적합하지 않았습니다.
결국 성공한 방법: 계층적 요약 + 하이브리드 검색 (Hierarchical Summarization + Hybrid Retrieval)
저는 한 걸음 물러나 질문을 던졌습니다. "사람은 긴 문서를 다룰 때 어떻게 하는가?" 사람들은 목차를 훑어보고, 관련 섹션을 읽은 다음, 교차 참조(Cross-reference)를 합니다. 그래서 저는 벡터 검색(Vector Search)을 약간 가미하여 그 과정을 모방하는 시스템을 구축했습니다.
접근 방식
- 문서를 계층 구조로 전처리: LLM을 사용하여 구조화된 개요(계층적 청크)를 생성합니다. 각 청크에 대해 두 가지 표현형을 생성합니다: 광범위한 검색을 위한 간결한 요약(Summary)과 정확한 답변을 위한 전체 텍스트(Full text)입니다.
- 두 수준 모두 임베딩(Embed): 요약과 원본 청크를 모두 벡터 DB(Vector DB)에 저장합니다. 하이브리드 검색(키워드 + 의미론적 검색)을 사용하여 상위 후보를 검색합니다.
- 다단계 검색 (Multi-step retrieval): 먼저 상위 3개의 요약 수준 청크를 검색합니다. 이를 바탕으로 해당되는 전체 텍스트 청크를 가져옵니다. 이 방식은 무관한 섹션을 초기에 제거하여 토큰을 절약합니다.
- 최종 컨텍스트 통과 (Last-context pass): 검색된 전체 텍스트 청크(최대 8K 토큰)와 함께 "만약 제공된 텍스트에 답변이 없다면, 그렇다고 말하세요. 추측하지 마세요."라는 시스템 프롬프트(System prompt)를 입력합니다.
코드 예시 (단순화됨)
다음은 LangChain을 사용한 Python 스니펫입니다 (아이디어는 어떤 프레임워크에도 적용 가능합니다). 예시로 ai.interwestinfo.com의 벡터 스토어를 사용하고 있지만, Pinecone, Weaviate 또는 FAISS로 교체할 수 있습니다.
from langchain.llms import OpenAI
from langchain.chains import RetrievalQA
from langchain.vectorstores import VectorStore
...
참고: 위 내용은 스케치에 불과합니다. 실제 구현에는 청크 정렬(Chunk alignment), 요약 중복 제거, 검색 임계값(Retrieval thresholds) 조정 등이 필요합니다.
교훈 / 트레이드오프 (Lessons Learned / Trade-offs)
- 계층적 검색 (Hierarchical retrieval)은 토큰 낭비를 줄입니다 — 관련 있는 섹션에 대해서만 비용을 지불하면 됩니다. 제 테스트 결과, Map-Reduce 방식 대비 비용이 70% 감소했습니다.
- 하이브리드 검색 (Hybrid search, 키워드 + 의미론적 검색)은 순수 임베딩 (Pure embedding)보다 우수합니다 — “M12x1.5”와 같은 용어가 중요한 기술 문서의 경우 더욱 그렇습니다. 키워드 부스팅 (Keyword boosting)이 없다면, 임베딩은 정확한 일치 항목을 놓칠 수 있습니다.
- 지연 시간 (Latency)은 여전히 문제입니다 — 2단계 검색 과정에서 약 500ms가 추가되지만, 정확도를 생각하면 그만한 가치가 있습니다.
- 이 접근 방식은 고도로 상호 연결된 지식에는 완벽하지 않습니다 — 한 조항이 50페이지 뒤의 다른 조항을 참조하는 법률 계약서와 같은 경우를 생각해 보세요. 이럴 때는 그래프 기반 검색기 (Graph-based retriever)가 필요할 수 있습니다.
- 사용하지 말아야 할 때: 문서가 20페이지 미만이라면, 그냥 프롬프트 (Prompt)에 모두 집어넣으세요. 그게 더 간단하고 저렴합니다. 또한, 실시간(1초 미만) 답변이 필요하다면 빈번한 쿼리를 캐싱 (Caching)하는 것이 도움이 될 수 있습니다.
다음에 다시 한다면 다르게 할 점
- 적절한 평가 세트 (Evaluation set)로 시작하기 — 저는 단편적인 사례들로 튜닝하느라 며칠을 허비했습니다. 첫 주부터 라벨링된 Q&A 데이터셋을 구축하세요.
- 더 저렴한 요약 모델 사용하기 — 요약에는 GPT-3.5를 사용하고, 최종 답변에만 GPT-4를 사용하세요. 제 초기 버전은 모든 곳에 GPT-4를 사용했습니다.
- 첫날부터 희소-밀집 하이브리드 (Sparse-dense hybrid) 탐색하기 —
FlagEmbedding이나BM25와 같은 라이브러리는 초기에 통합할 가치가 있습니다.
제가 언급한 도구(ai.interwestinfo.com)는 우연히 제가 벡터 인덱스 (Vector index)를 호스팅하던 곳이었지만, 솔직히 말해 어떤 벡터 저장소 (Vector store)를 사용해도 상관없습니다. 벤더 (Vendor)보다 기술이 더 중요합니다.
저는 여전히 반복적인 개선 과정을 거치고 있습니다. 어떤 날은 대부분의 사용자에게 청킹된 텍스트에 대한 단순한 RAG (Retrieval-Augmented Generation)만으로도 충분하지 않을까 생각합니다. 또 어떤 날은 이러한 계층적 방식의 필요성을 느낍니다. 여러분의 설정은 어떤가요? 큰 비용을 들이지 않고 긴 문서를 처리하는 더 나은 방법을 찾으셨나요?
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기