AIRAGLLMunstructuredVector DBDocument AI

RAG(검색 증강 생성) 설명 및 unstructured로 비정형 문서 처리하기

RAG 아키텍처의 개념과 구현 방법, 그리고 PDF·Word·HTML 등 비정형 문서를 처리하는 unstructured 라이브러리 활용법을 설명합니다.

VWV2026-04-035분 읽기

RAG란?

Retrieval-Augmented Generation(RAG) 은 LLM의 응답 생성 시 외부 지식 베이스에서 관련 문서를 검색(Retrieve)하여 컨텍스트로 제공하는 기법입니다.

질문 → [검색 엔진] → 관련 문서 → [LLM] → 정확한 답변

왜 RAG가 필요한가?

문제 RAG 해결 방법
LLM의 지식 컷오프 최신 문서를 실시간 검색
환각(Hallucination) 실제 문서 기반으로 답변 생성
사내 문서 질의 불가 내부 데이터를 벡터 DB에 인덱싱
긴 컨텍스트 비용 필요한 청크만 선택적으로 제공

RAG 파이프라인 구조

[문서 수집]
    ↓
[문서 파싱 — unstructured]
    ↓
[청크 분할]
    ↓
[임베딩 생성]
    ↓
[벡터 DB 저장]
    ↓ ← 질문 임베딩
[유사 청크 검색]
    ↓
[LLM 프롬프트 조합]
    ↓
[최종 답변]

unstructured 라이브러리

unstructured는 PDF, Word, PPT, HTML, 이미지 등 다양한 비정형 문서를 일관된 방식으로 파싱하는 오픈소스 라이브러리입니다.

설치

pip install unstructured[all-docs]
# PDF 처리 시 추가
pip install unstructured-inference pdfminer.six

지원 파일 형식

형식 지원 여부
PDF ✅ (텍스트/OCR)
DOCX / DOC
PPTX
HTML
Markdown
CSV / XLSX
이미지 (PNG/JPG) ✅ (OCR)
이메일 (EML)

기본 사용법

from unstructured.partition.auto import partition

# 파일 형식 자동 감지
elements = partition("report.pdf")

for el in elements:
    print(type(el).__name__, "|", str(el)[:80])
# Title      | 2026 보안 위협 동향 보고서
# NarrativeText | 올해 랜섬웨어 공격은 전년 대비 34% 증가하였으며...
# Table      | | 공격 유형 | 증가율 | ...

요소 타입 분류

타입 설명
Title 제목 / 소제목
NarrativeText 본문 단락
Table 표 데이터
ListItem 목록 항목
Image 이미지 (캡션 포함)
CodeSnippet 코드 블록

전체 RAG 파이프라인 구현

from unstructured.partition.auto import partition
from unstructured.chunking.title import chunk_by_title
from sentence_transformers import SentenceTransformer
import chromadb

# 1. 문서 파싱
elements = partition("security_report.pdf")

# 2. 청크 분할 (제목 기준으로 의미 단위 분할)
chunks = chunk_by_title(elements, max_characters=500)

# 3. 임베딩 모델 로드
embedder = SentenceTransformer("BAAI/bge-m3")

# 4. 벡터 DB 초기화
client = chromadb.Client()
collection = client.create_collection("security_docs")

# 5. 임베딩 생성 및 저장
texts = [str(chunk) for chunk in chunks]
embeddings = embedder.encode(texts, normalize_embeddings=True).tolist()

collection.add(
    ids=[f"chunk_{i}" for i in range(len(texts))],
    embeddings=embeddings,
    documents=texts,
)

# 6. 질의 응답
def answer(question: str, top_k: int = 3) -> str:
    q_emb = embedder.encode([question], normalize_embeddings=True).tolist()
    results = collection.query(query_embeddings=q_emb, n_results=top_k)
    context = "\n\n".join(results["documents"][0])

    # OpenAI API 호출 예시
    from openai import OpenAI
    client = OpenAI()
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": "아래 문서를 참고하여 질문에 답변하세요.\n\n" + context},
            {"role": "user", "content": question},
        ],
    )
    return response.choices[0].message.content

print(answer("2026년 주요 랜섬웨어 그룹은?"))

청킹 전략 비교

전략 장점 단점 적합 상황
고정 크기 청킹 구현 단순 문맥 단절 가능 균일한 텍스트
제목 기반 청킹 의미 단위 유지 제목이 없으면 비효율 구조화된 문서
재귀적 청킹 균형적 분할 설정 복잡 혼합 문서
의미 기반 청킹 최고 품질 느림, 비용 높음 고품질 RAG

고급 RAG 패턴

HyDE (Hypothetical Document Embedding)

# 질문으로 가상 답변을 먼저 생성한 뒤, 그 답변을 기반으로 검색
hypothetical_answer = llm.generate(f"다음 질문에 대한 상세 답변을 작성하세요: {question}")
search_embedding = embedder.encode([hypothetical_answer])
# 이 임베딩으로 실제 문서 검색 → 더 정확한 관련 문서 찾기

Reranking

from sentence_transformers import CrossEncoder

reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")

# 상위 10개 검색 후 리랭커로 상위 3개 재선정
candidates = collection.query(query_embeddings=q_emb, n_results=10)
pairs = [(question, doc) for doc in candidates["documents"][0]]
scores = reranker.predict(pairs)
top_3 = sorted(zip(scores, candidates["documents"][0]), reverse=True)[:3]

정리

RAG는 환각 없는 신뢰할 수 있는 LLM 응답을 구현하는 가장 실용적인 방법입니다. unstructured로 다양한 문서 형식을 손쉽게 파싱하고, 적절한 청킹 전략과 벡터 DB를 조합하면 기업 내부 문서 기반의 강력한 AI Q&A 시스템을 구축할 수 있습니다.