1단계: 문서 인제스션(Document Ingestion) - 임베딩(Embeddings) 전의 숨겨진 복잡성
요약
성공적인 RAG 시스템 구축을 위해 임베딩 전 단계인 문서 인제스션(Document Ingestion)의 중요성을 설명합니다. 단순 업로드를 넘어 중복 방지를 위한 파일 해싱 등 필수적인 전처리 과정을 다룹니다.
핵심 포인트
- RAG 시스템은 임베딩 전 단계인 인제스션 과정이 매우 복잡함
- 중복 처리를 방지하여 비용과 리소스를 절약해야 함
- 파일 이름이 아닌 실제 바이너리 데이터를 해싱해야 정확함
- 철저한 전처리가 시스템의 답변 정확도를 결정함
전체 이야기: 대부분의 RAG 시스템이 시작하기도 전에 실패하는 이유
이야기의 시작: 왜 당신의 업로드 버튼은 시작일 뿐인가
👦 조카: 삼촌! 드디어 제 RAG 시스템을 만들었어요. 사용자가 PDF를 업로드하면 시스템이 답을 찾아내요. 간단하죠?
👨🦳 삼촌: (알겠다는 듯 미소 지으며) PDF를 업로드해서 답을 얻었다고?
👦 조카: 네! 잘 작동해요!
👨🦳 삼촌: 정확한 답을 얻었니?
👦 조카: 음... 가끔요. 왜 그러시죠?
👨🦳 삼촌: 왜냐하면 "사용자가 PDF를 업로드"하는 것과 "시스템이 임베딩(embeddings)을 생성"하는 것 사이에는 15개의 중요한 단계가 있기 때문이야. 단 하나라도 건너뛰면 시스템은 소리 없이 실패하게 돼. 틀린 답을 얻게 되는데도 왜 그런지조차 모르게 되지.
👦 조카: 15단계라고요? 전 그냥 텍스트를 임베딩(embedded)했을 뿐인데요!
👨🦳 삼촌: 바로 그거야. 그게 문제지. 이리 와보렴, 프로덕션 엔지니어들이 실제로 무엇을 하는지 보여줄게.
1단계의 15단계: 문서 인제스션 (Document Ingestion)
👨🦳 삼촌: 비리야니(biryani) 요리를 하는 것과 같다고 생각해봐. 그냥 쌀과 고기를 한꺼번에 쏟아붓지는 않지, 그렇지?
👦 조카: 아니죠, 먼저 모든 것을 준비해야 하잖아요!
👨🦳 삼촌: 정확해. 요리하기 전에 너는 다음과 같은 일을 하지:
- 쌀 씻기
- 쌀 불리기
- 고기 준비하기
- 고기 양념하기
- 양파 다지기
- ... 그리고 훨씬 더 많은 단계들
그다음에야 비로소 요리를 하는 거야.
문서도 마찬가지야. 임베딩(embeddings)을 하기 전에 반드시 다음 과정을 거쳐야 해:
1. 문서 업로드 (Document Upload, 사용자 작업)
2. 파일 해싱 (File Hashing, 이 파일을 전에 본 적이 있는가?)
3. PDF 파싱 (PDF Parsing, 텍스트 추출)
...
👦 조카: 정말 많네요! 어디서부터 시작해야 하죠?
👨🦳 삼촌: 1단계부터 시작하자. 천천히 가보자고. 기초가 탄탄해야 해.
1단계, 1-2단계: 문서 업로드(Document Upload) 및 파일 해싱(File Hashing)
파일 해싱(File Hashing)이 왜 중요한가?
👦 조카: 왜 파일을 해싱(hash)해야 하나요? 그냥 업로드하면 안 되나요?
👨🦳 삼촌: 인간은 게으르기 때문이야. 인사 담당자가 똑같은 PDF를 세 번 업로드할 수도 있어. 그러면 네 시스템은 그걸 세 번 처리하게 되지. 임베딩(embeddings)도 세 번 생성되고, 비용도 세 배로 들 거야.
👦 조카: 그럼 해싱(hash)이 중복을 방지한다는 말씀이시죠?
👨🦳 삼촌: 맞아. 하지만 여기서 비결이 있어. 파일 이름(filename)을 해싱하지 마라.
👦 조카: 왜 안 되나요?
👨🦳 삼촌: 누군가 내용은 바꾸면서 이름은 그대로 유지할 수 있기 때문이야. 봐봐:
File: HR_Policy.pdf (Version 1)
Content: "30 days notice required"
Filename Hash: HR_Policy.pdf
...
이건 재앙이야.
👦 조카: 그럼 콘텐츠를 해싱(Hash)하나요?
👨🦳 삼촌: 맞아. 실제 바이너리 데이터 (Binary data)를 말이야.
HR_Policy.pdf (Version 1)
↓
[PDF binary bytes: 0xAA 0xBB 0xCC ...]
...
단계 1-2: Node.js 구현
// src/ingestion/fileHasher.ts
import crypto from 'crypto';
...
👦 조카: 그럼 누군가 같은 PDF를 두 번 업로드하면, 이를 감지하고 건너뛰게 되나요?
👨🦳 삼촌: 정확해. 그리고 임베딩 (Embedding) 비용을 절약할 수 있지. 임베딩은 가장 비용이 많이 드는 단계거든.
1단계, 3단계: PDF 파싱 (Parsing) - 적절한 도구 선택
👦 조카: 이제 PDF가 준비되었네요. 텍스트는 어떻게 추출하나요?
👨🦳 삼촌: 여기서 진짜 결정이 내려져. 다섯 가지 주요 도구가 있는데, 각각 트레이드오프 (Tradeoffs)가 있어.
👦 조카: 다섯 가지나 요?! 어떤 걸 사용해야 하죠?
👨🦳 삼촌: 문서의 종류에 따라 달라. 보여줄게.
PDF 파싱 환경 (The PDF Parsing Landscape)
단순 텍스트 PDF → pdf-parse (저렴함, 단순함)
↓
혼합 콘텐츠 (텍스트 + 표) → PDFPlumber (더 나음)
...
도구 비교
| 도구 | 최적 용도 | 비용 | 속도 | 표 지원 | OCR | 메타데이터 | 프로덕션 준비 완료 |
|---|---|---|---|---|---|---|---|
| pdf-parse | 단순 텍스트 | ₹0 (무료) | ⚡⚡⚡ 빠름 | ✗ 아니오 | ✗ | ✗ | ⚠️ 취미용 |
| ... |
👨🦳 삼촌: 각각 설명해 줄게.
도구 1: pdf-parse (무료, 단순함)
// 단순한 접근 방식 - 학습용으로는 좋지만, 프로덕션용으로는 부적합함
const pdf = require('pdf-parse');
...
👨🦳 삼촌: 우리가 무엇을 놓쳤는지 보이니?
원본 PDF:
═══════════════════════════════════════
COMPANY POLICY
...
👦 조카: 그럼 그냥 텍스트 덩어리 (Text soup)만 얻게 되는 건가요?
👨🦳 삼청: 맞아. 그리고 '텍스트 덩어리'를 임베딩하면, '텍스트 덩어리' 같은 답변을 얻게 될 거야.
도구 2: PDFPlumber (더 나음, 여전히 Python 기반)
# PDFPlumber - 표를 더 잘 추출함
import pdfplumber
...
👨🦳 삼청: 더 낫긴 하지만, 여전히 구조를 놓쳐. 그리고 Node.js가 아니라 Python이지.
도구 3: Unstructured (프로덕션 선택지)
👨🦳 삼촌: 이건 대부분의 기업들이 사용하는 방식이야. 구조를 보존해주거든.
// API를 통한 Unstructured 사용 (Node.js 친화적)
import axios from 'axios';
...
👦 조카: 그럼 구조를 보존한다는 거죠?
👨🦳 삼촌: 응. 차이점을 봐봐:
Unstructured 출력 결과:
[
...
도구 4: LlamaParse (최첨단 기술, State of the Art)
👨🦳 삼촌: 정말 복잡한 문서라면 LlamaParse를 사용해.
// LlamaParse - 복잡한 PDF에 최적화
import axios from 'axios';
...
👦 조카: Unstructured와 LlamaParse 중 언제 무엇을 사용해야 하나요?
👨🦳 삼촌: 간단한 규칙은 이래:
문서 유형?
├─ 단순 텍스트 정책 (Simple text policies)
...
도구 5: Azure Document Intelligence (엔터프라이즈급)
// Azure Document Intelligence - 엔터프라이즈 문서를 위한 도구
import { DocumentAnalysisClient, AzureKeyCredential } from "@azure/ai-form-recognizer";
...
👦 조카: 이것들 전부 유료인가요?
👨🦳 삼촌: 응, pdf-parse를 제외하고는 말이야. 그리고 여기서 비밀이 하나 있어. 단순한 문서에는 여전히 pdf-parse를 사용해야 한다는 거야!
👦 조카: 왜요?
👨🦳 삼촌: 비용 때문이지. 만약 문서의 80%가 단순한 정책 문서라면 pdf-parse를 사용해. 비싼 파서(Parser)는 꼭 필요한 나머지 20%에만 사용하라고.
1단계, 4-5단계: 텍스트 추출 및 정제 (Text Extraction & Cleaning)
쓰레기 문제 (The Garbage Problem)
👨🦳 삼촌: 파싱(Parsing)을 하고 나면, 쓰레기 같은 데이터가 나와.
추출된 원문 텍스트:
═════════════════════════════════════════════════════════════
...
👦 조카: 이게 뭐가 문제인가요?
👨🦳 삼촌: "[CONFIDENTIAL]"(기밀) 같은 내용을 임베딩(Embedding)하면, 시스템은 모든 정책이 기밀이라고 학습하게 돼. 누군가 "이 정책은 기밀인가요?"라고 물으면, 대답은 항상 YES가 되어버리지!
// 4-5단계: 텍스트 정제
import logger from '../utils/logger';
...
1단계, 6단계: 메타데이터 추출 (Metadata Extraction)
👨🦳 삼촌: 메타데이터는 청크(Chunk) '안'에 있는 내용이 아니라, 청크에 '관한' 정보야.
👦 조카: 예를 들면 어떤 거요?
👨🦳 삼촌: 예를 들면 이런 거지:
문서: HR_Policy.pdf
부서: 인사부 (Human Resources)
섹션: 휴가 정책 (Leave Policy)
...
이 메타데이터는 검색(Retrieval)을 도와줘.
👦 조카: 어떻게 도와주는데요?
👨🦳 삼촌: 누군가 이렇게 질문한다고 가정해 보자. "휴가에 관한 인사(HR) 정책이 뭐야?"
메타데이터가 없다면:
- 시스템이 모든 문서를 검색함
- 모든 부서의 휴가 정책을 반환함
메타데이터가 있다면:
- 시스템이 HR 문서만 검색해야 한다는 것을 인지함
- HR 휴가 정책만 반환함
훨씬 낫지!
// 6단계: 메타데이터 추출
import logger from '../utils/logger';
interface DocumentMetadata {
source: string;
department?: string;
section?: string;
version?: string;
effectiveDate?: string;
author?: string;
}
/**
* 문서 구조에서 메타데이터를 추출합니다
*
* 출처:
* 1. 폴더 구조: /HR/Pol
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기