3장. 멀티모달 RAG를 활용한 복합 데이터 처리

출처: 『RAG 마스터: 랭체인으로 완성하는 LLM 서비스』(브라이스 유·조경아·박수진·김재웅, 프리렉 2025) | 공식: https://www.langchain.com/

텍스트만 다루던 RAG에서 이미지·표·차트까지 함께 검색·생성하는 멀티모달 RAG로 확장한다. 세 가지 구현 전략의 트레이드오프를 비교하고, unstructured + MultiVectorRetriever + 멀티모달 LLM을 조합한 실습을 통해 PDF 속 복합 데이터를 실제로 다루는 방법을 익힌다.

실습 — 책 공식 노트북: Ch03. MultiModal RAG 폴더. 코랩 바로 열기: 멀티모달 RAG 통합unstructured로 PDF 추출 + MultiVectorRetriever + 멀티모달 LLM 종합 실습.

학습 목표

이 장을 끝내면 다음을 할 수 있다.

  • 멀티모달 RAG가 텍스트 전용 RAG와 어떻게 다른지 설명한다.
  • 세 가지 멀티모달 RAG 구현 전략(동일 벡터 공간·기본 모달리티 통일·별도 저장소)을 비교하고 상황에 맞게 선택한다.
  • unstructured로 PDF에서 텍스트·테이블·이미지를 추출하고 멀티모달 LLM으로 요약을 생성한다.
  • MultiVectorRetriever를 구성해 요약 임베딩으로 검색하고 원본 문서를 반환하는 파이프라인을 구현한다.
  • 멀티모달 메시지(텍스트+이미지)를 조합해 멀티모달 RAG 체인을 완성한다.

전체 흐름도

멀티모달 입력(텍스트·이미지·테이블)을 함께 검색·생성하는 RAG 파이프라인 구조.

[ PDF / 비정형 문서 ]
       │  partition_pdf (unstructured)
       ▼
┌──────────────────────────────────────┐
│  텍스트 청크  │  테이블  │  이미지   │
└──────────────────────────────────────┘
       │  LLM 요약 (전략 B) 또는 멀티모달 임베딩 (전략 A)
       ▼
┌─────────────────────┐   ┌─────────────────────┐
│ vectorstore         │   │ docstore             │
│ (요약 임베딩 저장)  │   │ (원본 문서 저장)     │
└─────────────────────┘   └─────────────────────┘
       │  유사도 검색 → doc_id 반환
       ▼
[ MultiVectorRetriever ]
       │  원본 문서(텍스트 + 이미지 base64) 반환
       ▼
[ 멀티모달 LLM ]  ← 텍스트 + 이미지 메시지 조합
       │
       ▼
[ 최종 응답 ]

0. 사전 필수 용어

  • 모달리티(Modality) — 데이터의 형태·유형. 텍스트, 이미지, 음성, 표, 차트 등 각각이 하나의 모달리티다.
  • 멀티모달(Multimodal) — 서로 다른 유형의 데이터를 동시에 사용하는 방식. 텍스트와 이미지를 함께 처리하면 멀티모달이다.
  • 멀티모달 LLM(MLLM) — 텍스트뿐 아니라 이미지·오디오 등을 입력으로 받아 처리할 수 있는 LLM. gpt-4o 같은 비전 지원 모델이 대표적.
  • 이미지 임베딩 — 이미지를 숫자 벡터로 변환하는 것. CLIP 같은 멀티모달 임베딩 모델이 이미지와 텍스트를 동일한 벡터 공간에 인코딩한다.
  • MultiVectorRetriever — 원본 문서(docstore)와 요약본(vectorstore)을 분리 저장하고, 요약본으로 검색해 원본을 돌려주는 랭체인 검색기. 멀티모달 RAG의 핵심 컴포넌트.
  • Base64 인코딩 — 이진 데이터(이미지 파일)를 ASCII 텍스트 문자열로 변환하는 방식. 64개의 ASCII 문자(A-Z, a-z, 0-9, +, /)를 사용하며, LLM API에 이미지를 직접 전송할 때 이 형식을 사용한다.
  • unstructured — PDF·Word·HTML 등 비정형 문서에서 텍스트·표·이미지를 자동 추출하는 파이썬 라이브러리. partition_pdf()가 핵심 함수.
  • CLIP — OpenAI가 개발한 멀티모달 임베딩 모델. 이미지 인코더(ResNet·ViT)와 텍스트 인코더(Transformer)를 대조 학습으로 훈련해 이미지-텍스트 쌍을 같은 벡터 공간에 위치시킨다.

1. 멀티모달 RAG 개요

1.1 멀티모달 RAG란?

멀티모달 RAG는 텍스트 정보에만 의존하던 기존 RAG를 확장해, 이미지·차트·테이블 등 시각적·구조적 데이터까지 포함하여 검색하고 생성하는 기법이다.

온라인 쇼핑몰을 생각해보자. 상품 설명 텍스트만으로 검색하는 것보다 상품 사진까지 함께 활용하면 원하는 상품을 훨씬 정확히 찾을 수 있다. LLM에게도 마찬가지다. 텍스트 외에 이미지·차트·테이블 같은 풍부한 정보를 제공하면, 관련 문서를 검색할 때 더 정확한 맥락을 파악할 수 있고 사용자에게 다차원적인 답변을 생성할 수 있다.

1.2 멀티모달 RAG가 어려운 이유

실무 비정형 데이터는 고해상도 이미지, 텍스트, 표, 차트, 다이어그램이 섞인 형태로 PDF 등 다양한 포맷에 분산되어 있다. 멀티모달 RAG가 텍스트 전용 RAG보다 복잡한 이유는 두 가지다.

유형별 상이한 요구사항. 일반 이미지(풍경 사진)는 연못·나무·모래 같은 전반적 개요로 분석하면 되지만, 보고서 차트나 그래프는 각 수치의 의미와 분석이 이루어진 맥락까지 해석해야 한다. 파이프라인 설계 시 각 모달리티의 특성에 맞는 처리 전략이 필요하다.

데이터 일관성. 문서 내에서 차트를 설명하는 텍스트와 차트 자체의 의미가 서로 상충하면 LLM이 혼란스러운 답변을 생성할 수 있다. 여러 모달리티 간 정보가 일관성 있게 관리되어야 한다.

2. 멀티모달 RAG 구현 방법

멀티모달 RAG를 구축할 때 핵심 질문은 하나다: 서로 다른 모달리티를 LLM에게 어떻게 인지시킬 것인가? 이에 따라 세 가지 전략이 존재한다.

2.1 전략 A: 모든 모달리티를 동일한 벡터 공간에 포함

이미지와 텍스트를 하나의 멀티모달 임베딩 모델(예: CLIP)로 동일한 벡터 공간에 인코딩하는 방식이다. 임베딩된 벡터들은 같은 벡터 저장소에 함께 저장되고, 유사도 검색으로 이미지와 텍스트를 동시에 검색할 수 있다.

기존 RAG 인프라에서 임베딩 모델만 교체하면 되므로 구현이 상대적으로 단순하다. 단, 이미지·텍스트·복잡한 표를 정확히 인코딩할 수 있는 강력한 멀티모달 임베딩 모델을 확보하는 것이 필수다.

# CLIP 계열 멀티모달 임베딩으로 이미지와 텍스트를 같은 공간에 임베딩
from langchain_community.embeddings import OpenCLIPEmbeddings
from langchain_community.vectorstores import Chroma

# 멀티모달 임베딩 모델 초기화
embedding = OpenCLIPEmbeddings(model_name="ViT-B-32", checkpoint="openai")

# 텍스트와 이미지 경로를 함께 벡터 저장소에 추가
vectorstore = Chroma(
    collection_name="multimodal_clip",
    embedding_function=embedding
)

# 텍스트 문서 추가
vectorstore.add_texts(["이미지 설명 텍스트 1", "이미지 설명 텍스트 2"])

# 이미지 URI 추가 (CLIP은 이미지도 동일 공간에 임베딩)
vectorstore.add_images(uris=["path/to/image1.jpg", "path/to/image2.jpg"])

# 검색 — 텍스트 쿼리로 관련 이미지·텍스트를 동시에 찾음
results = vectorstore.similarity_search("시각화된 차트 데이터")

CLIP 구조. 이미지 인코더(ResNet·ViT)와 텍스트 인코더(Transformer) 두 가지로 구성된다. 각 인코더가 생성한 벡터를 비교하는 방식으로 동작하며, 이미지-텍스트 쌍이 맞으면 유사하게, 그렇지 않으면 다르게 나타나도록 대조 학습을 통해 조정한다. Zero-shot 학습 능력이 뛰어나 특정 작업 추가 훈련 없이도 새로운 작업을 수행할 수 있다.

2.2 전략 B: 모든 모달리티를 하나의 기본 모달리티로 표현

멀티모달 LLM을 사용해 이미지를 텍스트 요약으로 변환하고, 이 텍스트를 임베딩해 검색하는 방식이다. 텍스트 하나를 기본 모달리티로 삼아 이미지도 텍스트화한다.

장점은 이미지로부터 생성된 메타데이터가 명확하고 객관적인 질문에 효과적으로 대응하며, 별도의 이미지 임베딩 모델이나 리랭킹 작업이 필요하지 않다는 점이다. 단점은 전처리 LLM API 비용이 발생하고, 이미지의 세부적인 뉘앙스나 정보가 요약 과정에서 손실될 수 있다는 점이다.

import base64
from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI

def encode_image(image_path: str) -> str:
    """이미지 파일을 base64 문자열로 변환"""
    with open(image_path, "rb") as f:
        return base64.b64encode(f.read()).decode("utf-8")

def image_summarize(img_base64: str) -> str:
    """gpt-4o 비전으로 이미지를 검색 최적화 텍스트 요약으로 변환"""
    chat = ChatOpenAI(model="gpt-4o", max_tokens=1024)
    prompt = """
    당신은 이미지를 요약하여 검색을 위해 사용할 수 있도록 돕는 어시스턴트입니다.
    이 요약은 임베딩되어 원본 이미지를 검색하는 데 사용됩니다.
    이미지 검색에 최적화된 간결한 요약을 작성하세요.
    """
    msg = chat.invoke(
        HumanMessage(
            content=[
                {"type": "text", "text": prompt},
                {
                    "type": "image_url",
                    "image_url": {"url": f"data:image/jpeg;base64,{img_base64}"},
                },
            ]
        )
    )
    return msg.content

# 사용 예시
img_base64 = encode_image("chart.jpg")
summary = image_summarize(img_base64)
# → "42주차 말라리아 발생 현황 차트. 경기도에서 가장 많은 환자 발생..."

2.3 전략 C: 서로 다른 모달리티를 별도 저장소에서 다루기

전략 A와 B를 결합한 하이브리드 방식이다. 멀티모달 임베딩 결과는 벡터 저장소 1에, 텍스트 변환(이미지 요약) 임베딩 결과는 벡터 저장소 2에 따로 저장한다. 사용자 질의 시 두 저장소에서 각각 검색하고, 리랭킹(ReRanking)을 거쳐 최적 문서를 선별한다.

이 방식은 전처리에 시간이 더 걸리고 인프라 비용도 배로 들지만, 각 방식의 장점을 결합해 사용자에게 가장 질적으로 우수한 답변을 제공할 수 있다.

세 전략 비교 (핵심 트레이드오프)

전략 핵심 아이디어 장점 단점 인프라 복잡도
A. 동일 벡터 공간 CLIP 등 멀티모달 임베딩으로 이미지·텍스트를 한 벡터 저장소에 기존 RAG 인프라 재사용, 구현 단순 텍스트·이미지·복잡한 표를 정확히 임베딩할 수 있는 모델 확보가 필수 낮음
B. 기본 모달리티 통일 이미지 → 텍스트 요약 후 텍스트 임베딩만 사용 명확한 질의 응답에 효과적, 별도 이미지 임베딩 모델 불필요 전처리 LLM 비용 발생, 이미지 세부 뉘앙스·정보 손실 가능 중간
C. 별도 저장소 A+B 결합. 두 벡터 저장소 운영 후 리랭킹 각 방식의 장점 결합, 최고 품질 검색 전처리 시간↑, 인프라 비용↑↑ 높음

실습에서 선택한 전략은 B다. gpt-4o를 이용해 이미지를 텍스트 요약으로 변환하고, 그 요약을 임베딩해 MultiVectorRetriever로 검색한다.

3. 멀티모달 RAG 실습

이번 실습은 질병관리청 말라리아 주간소식지 PDF(텍스트·테이블·이미지 혼재)를 대상으로, 전략 B(기본 모달리티 통일)를 사용한다.

3.1 환경 설정

필수 패키지 설치.

# 핵심 패키지
pip install -U langchain openai chromadb langchain-experimental pillow pydantic lxml

# OCR·PDF 처리용 시스템 라이브러리
# Linux/Colab
sudo apt install tesseract-ocr libtesseract-dev
sudo apt-get install poppler-utils

# macOS
brew install tesseract poppler

# unstructured (tesseract·poppler 이후 반드시 설치)
pip install -U "unstructured[all-docs]"

의존성 순서 주의. unstructuredtesseractpoppler에 의존성을 가지므로, 두 라이브러리를 먼저 설치한 이후에 unstructured를 설치해야 한다.

WSL2/Ubuntu 환경. pip install--break-system-packages 플래그가 필요하다 (PEP 668).

환경 변수 설정.

from dotenv import load_dotenv

# .env 파일에 OPENAI_API_KEY=sk-... 를 저장해두고 로드
load_dotenv(".env")

3.2 데이터 전처리

PDF에서 텍스트·테이블·이미지를 추출하고, 각 모달리티별 요약을 생성한다.

PDF 요소 추출.

import os
import nltk
from unstructured.partition.pdf import partition_pdf

# nltk 필수 데이터 다운로드 (unstructured가 내부적으로 사용)
nltk.download("punkt_tab")
nltk.download("averaged_perceptron_tagger_eng")

fpath = "./data"      # PDF가 있는 폴더
fname = "sample.pdf"

# PDF → 텍스트·테이블·이미지 일괄 추출
raw_pdf_elements = partition_pdf(
    filename=os.path.join(fpath, fname),
    extract_images_in_pdf=True,        # 이미지 추출
    infer_table_structure=True,        # 테이블 구조 추론
    chunking_strategy="by_title",      # 섹션 경계 기준 청킹
    extract_image_block_output_dir=fpath,  # 이미지 저장 폴더
)

# 타입 문자열로 테이블 / 텍스트 분류
tables, texts = [], []
for element in raw_pdf_elements:
    t = str(type(element))
    if "Table" in t:
        tables.append(str(element))
    elif "CompositeElement" in t:
        texts.append(str(element))

print(f"텍스트 {len(texts)}개, 테이블 {len(tables)}개 추출 완료")

partition_pdf 주요 파라미터 정리:

  • extract_images_in_pdf — PDF 안에 이미지가 있다면 이미지를 추출한다.
  • infer_table_structure — PDF 안에 테이블이 있다면 테이블 구조를 추출한다.
  • chunking_strategybasic은 글자 수 기준, by_title은 페이지·섹션 경계 기준으로 청킹한다.
  • extract_image_block_output_dir — 추출한 이미지를 저장할 경로.

추출된 요소의 타입 판별: unstructured.documents.elements.Table이 포함되면 테이블, unstructured.documents.elements.CompositeElement가 포함되면 텍스트다.

텍스트·테이블 요약. 복잡한 원문을 그대로 임베딩하면 검색 품질이 낮다. LLM으로 요약해 임베딩 품질을 높인다.

from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

prompt_text = """
당신은 표와 텍스트를 요약하여 검색할 수 있도록 돕는 역할을 맡은 어시스턴트입니다.
이 요약은 임베딩되어 원본 텍스트나 표 요소를 검색하는 데 사용됩니다.
표 또는 텍스트에 대한 간결한 요약을 제공하여 검색에 최적화된 형태로 만들어 주세요.
표 또는 텍스트: {element}
"""

prompt = ChatPromptTemplate.from_template(prompt_text)
model = ChatOpenAI(model="gpt-4o-mini")

# 요약 체인: {element: x} → 프롬프트 → 모델 → 문자열 출력
summarize_chain = {"element": lambda x: x} | prompt | model | StrOutputParser()

# batch()로 여러 항목을 동시에 처리 (max_concurrency: 동시 요청 수)
text_summaries = summarize_chain.batch(texts, {"max_concurrency": 5})
table_summaries = summarize_chain.batch(tables, {"max_concurrency": 5})

요약 결과 예시 (말라리아 PDF 적용 시):

# 테이블 요약 — table_summaries[0]
'특정 연도별 주차에 따른 국내발생 및 해외유입 확진자 수를 나타내는 통계 데이터.
 연도별, 주차별 총 확진자 수, 국내발생 수, 해외유입 수에 대한 정보 포함.'

# 텍스트 요약 — text_summaries[0]
'42주차 말라리아 환자 발생 현황 보고서에 따르면, 총 665명의 말라리아 환자가 발생했으며,
 이 중 630명이 국내에서 발생하고 35명이 해외에서 유입된 것으로 확인됨.'

이미지 요약. 이미지를 base64로 인코딩 후 gpt-4o의 비전 기능으로 텍스트 요약을 생성한다.

import base64
from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI

def encode_image(image_path: str) -> str:
    """이미지 파일 → base64 문자열"""
    with open(image_path, "rb") as image_file:
        return base64.b64encode(image_file.read()).decode("utf-8")

def image_summarize(img_base64: str) -> str:
    """gpt-4o 비전으로 이미지 → 검색 최적화 텍스트 요약"""
    chat = ChatOpenAI(model="gpt-4o", max_tokens=1024)
    prompt = """
    당신은 이미지를 요약하여 검색을 위해 사용할 수 있도록 돕는 어시스턴트입니다.
    이 요약은 임베딩되어 원본 이미지를 검색하는 데 사용됩니다.
    이미지 검색에 최적화된 간결한 요약을 작성하세요.
    """
    msg = chat.invoke(
        HumanMessage(
            content=[
                {"type": "text", "text": prompt},
                {
                    "type": "image_url",
                    "image_url": {"url": f"data:image/jpeg;base64,{img_base64}"},
                },
            ]
        )
    )
    return msg.content

# PDF에서 추출된 이미지를 순서대로 읽어 base64 변환
img_base64_list = []
for img_file in sorted(os.listdir(fpath)):
    if img_file.endswith(".jpg"):
        img_path = os.path.join(fpath, img_file)
        img_base64_list.append(encode_image(img_path))

# 각 이미지에 대해 요약 생성
image_summaries = [image_summarize(b64) for b64 in img_base64_list]

이미지 전달 방식 3가지. OpenAI API에 이미지를 보낼 때 (1) URL 방식(image_url.url에 https 링크), (2) base64 방식(data:image/jpeg;base64,...), (3) 복수 이미지 방식을 선택할 수 있다. 이 실습은 PDF 추출 이미지를 로컬에서 base64로 전송한다.

detail 파라미터. {"detail": "low"}는 512×512 저해상도로 85토큰, {"detail": "high"}는 고해상도 타일당 170토큰을 사용한다. 기본값 auto가 입력 크기에 맞게 자동 선택한다.

3.3 멀티-벡터 검색기

MultiVectorRetriever요약본을 임베딩해 검색하고, 검색 결과로는 원본 문서를 반환하는 구조다. 요약이 간결해 임베딩 품질이 높고, 답변 생성 시엔 원본의 상세 정보를 활용할 수 있다.

import uuid
from langchain.retrievers import MultiVectorRetriever
from langchain_core.stores import InMemoryStore
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain.schema.document import Document

# 벡터 저장소: 요약본을 임베딩 벡터로 저장
vectorstore = Chroma(
    collection_name="multi_modal_rag",
    embedding_function=OpenAIEmbeddings(),
)

# docstore: 원본 문서(텍스트·테이블·이미지 base64)를 메모리에 저장
docstore = InMemoryStore()

id_key = "doc_id"

retriever = MultiVectorRetriever(
    vectorstore=vectorstore,
    docstore=docstore,
    id_key=id_key,
)

# ── 원본 데이터를 docstore에 저장 ──

# 텍스트 원본 (uuid로 중복 없는 id 생성)
doc_ids = [str(uuid.uuid4()) for _ in texts]
retriever.docstore.mset(list(zip(doc_ids, texts)))

# 테이블 원본
table_ids = [str(uuid.uuid4()) for _ in tables]
retriever.docstore.mset(list(zip(table_ids, tables)))

# 이미지 원본 (base64 문자열)
img_ids = [str(uuid.uuid4()) for _ in img_base64_list]
retriever.docstore.mset(list(zip(img_ids, img_base64_list)))

# ── 요약본을 vectorstore에 임베딩 저장 ──

# 텍스트 요약 벡터 저장 (메타데이터에 doc_id 연결)
summary_texts = [
    Document(page_content=s, metadata={id_key: doc_ids[i]})
    for i, s in enumerate(text_summaries)
]
retriever.vectorstore.add_documents(summary_texts)

# 테이블 요약 벡터 저장
summary_tables = [
    Document(page_content=s, metadata={id_key: table_ids[i]})
    for i, s in enumerate(table_summaries)
]
retriever.vectorstore.add_documents(summary_tables)

# 이미지 요약 벡터 저장
summary_imgs = [
    Document(page_content=s, metadata={id_key: img_ids[i]})
    for i, s in enumerate(image_summaries)
]
retriever.vectorstore.add_documents(summary_imgs)

MultiVectorRetriever 동작 원리.

[사용자 쿼리]
      ↓
  vectorstore (요약 임베딩 검색)   ← "의미적으로 유사한 요약 찾기"
      ↓ doc_id
  docstore (원본 문서 반환)         ← "원본 텍스트·테이블·이미지 반환"
      ↓
  LLM에 원본 문서 + 쿼리 전달

3.4 멀티모달 RAG 구현

검색 결과를 이미지와 텍스트로 분류.

from base64 import b64decode

def split_image_text_types(docs: list) -> dict:
    """검색 결과를 이미지(base64)와 텍스트로 분리"""
    images, texts_out = [], []
    for doc in docs:
        try:
            b64decode(doc)      # base64 디코드 성공 → 이미지
            images.append(doc)
        except Exception:
            texts_out.append(doc)   # 디코드 실패 → 텍스트
    return {"images": images, "texts": texts_out}

# 검색 테스트 — "말라리아 군집 사례" 질의 시 이미지 3개, 텍스트 1개 반환됨
docs = retriever.invoke("말라리아 군집 사례는 어떤가요?")
docs_by_type = split_image_text_types(docs)

멀티모달 RAG 파이프라인 완성.

from operator import itemgetter
from langchain.schema.runnable import RunnablePassthrough, RunnableLambda
from langchain_core.output_parsers import StrOutputParser
from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI

def prompt_func(data: dict) -> list:
    """검색 결과(텍스트+이미지)와 질문을 멀티모달 메시지로 조합"""
    format_texts = "\n".join(data["context"]["texts"])
    text = f"""
다음 문맥에만 기반하여 질문에 답하세요. 문맥에는 텍스트, 표, 그리고 아래 이미지가 포함될 수 있습니다.

질문: {data["question"]}

텍스트와 표:
{format_texts}
    """
    # 메시지 구성: 텍스트 + 이미지(있는 경우)
    content = [{"type": "text", "text": text}]
    if data["context"]["images"]:
        content.append({
            "type": "image_url",
            "image_url": {
                "url": f"data:image/jpeg;base64,{data['context']['images'][0]}"
            },
        })
    return [HumanMessage(content=content)]

model = ChatOpenAI(temperature=0, model="gpt-4o", max_tokens=1024)

# 멀티모달 RAG 체인
chain = (
    {
        "context": retriever | RunnableLambda(split_image_text_types),
        "question": RunnablePassthrough(),
    }
    | RunnableLambda(prompt_func)
    | model
    | StrOutputParser()
)

# 실행 결과 예시
answer = chain.invoke("말라리아 군집 사례는 어떤가요?")
# → "말라리아 군집 사례는 총 4개 시도(경기, 서울, 인천, 강원)에서 46건이 확인되었습니다..."

RunnableLambda vs RunnablePassthrough.

  • RunnableLambda — 임의의 파이썬 함수를 러너블로 감싸 체인 중간에 사용자 정의 로직을 삽입할 때 쓴다. 데이터를 변형하거나 특정 로직을 적용하는 작업에 적합하다.
  • RunnablePassthrough — 입력을 그대로 다음 단계로 전달한다. {"context": ..., "question": RunnablePassthrough()} 패턴으로 질문을 그대로 넘길 때 활용한다.

핵심 개념 정리

개념 한 줄 요약
멀티모달 RAG 텍스트 외 이미지·표·차트까지 검색·생성에 활용하는 RAG 확장 기법
전략 A (동일 벡터 공간) CLIP 등 멀티모달 임베딩으로 이미지·텍스트를 하나의 벡터 저장소에 — 구현 단순, 강력한 임베딩 모델 필요
전략 B (기본 모달리티 통일) 이미지를 LLM으로 텍스트 요약 후 텍스트만 임베딩 — 정보 손실 위험 있지만 구현 실용적
전략 C (별도 저장소) A+B 결합, 두 저장소에서 검색 후 리랭킹 — 최고 품질, 인프라 비용↑
MultiVectorRetriever 요약(vectorstore)으로 검색 → 원본(docstore) 반환. 검색 품질과 원본 보존 동시 해결
unstructured PDF에서 텍스트·테이블·이미지를 자동 분류 추출 (partition_pdf)
Base64 이미지 바이너리를 텍스트로 인코딩해 API로 전송하는 방식
CLIP 이미지·텍스트를 동일 벡터 공간에 인코딩하는 멀티모달 임베딩 모델 (OpenAI, 오픈소스)

한 문장 요약 — 멀티모달 RAG는 이미지·표·차트를 텍스트 요약이나 통합 임베딩으로 변환해 MultiVectorRetriever에 저장하고, 멀티모달 LLM으로 검색·생성하는 기법이다. 세 전략(통합·변환·분리)의 트레이드오프를 이해하고 데이터·비용·품질 요구에 맞게 선택하는 것이 핵심이다.

실무 체크리스트

  • [ ] 이미지 처리가 필요한지 먼저 확인. 텍스트만 있는 PDF라면 2장 기본 RAG로 충분.
  • [ ] 전략 선택 기준: 빠른 프로토타입 → B(텍스트 변환), 고품질 → C(별도 저장소), 이미지 직접 검색 필요 → A(CLIP).
  • [ ] unstructured 설치 전 tesseract·poppler를 반드시 먼저 설치한다 (의존성 순서).
  • [ ] gpt-4o 비전 지원 여부는 항상 OpenAI 공식 모델 목록에서 확인. 비전 미지원 모델에 이미지를 보내면 오류 발생.
  • [ ] MultiVectorRetriever에서 docstore는 원본, vectorstore는 요약 — 혼동하지 말 것.
  • [ ] 이미지 요약 품질이 RAG 전체 품질을 결정한다. 프롬프트에 "검색에 최적화된 요약"이라고 명확히 지시한다.
  • [ ] detail: "high"는 토큰 소모가 크다. 차트·다이어그램처럼 세부 정보가 필요한 경우에만 사용.
  • [ ] 대량 처리 시 summarize_chain.batch(..., {"max_concurrency": 5})로 동시 처리량을 조절해 API 제한을 피한다.

연습문제

  1. 개념. 멀티모달 RAG에서 이미지를 "요약 텍스트로 변환(전략 B)"하는 방식과 "CLIP으로 직접 임베딩(전략 A)"하는 방식의 가장 큰 트레이드오프 1가지를 설명하라.
  2. 비교. MultiVectorRetriever는 일반 Chroma 검색기와 무엇이 다른가? docstore와 vectorstore의 역할을 각각 설명하라.
  3. 코드. image_summarize 함수에서 HumanMessagecontent가 리스트인 이유는 무엇인가? 각 원소의 역할을 설명하라.
  4. 적용. 의료 보고서 PDF(텍스트·MRI 이미지·혈액검사 표 혼재)에 멀티모달 RAG를 적용한다. 어느 전략을 선택하고, 그 이유는 무엇인가?
  5. 설계. split_image_text_types 함수가 base64 디코딩 성공 여부로 이미지와 텍스트를 구분하는 이유를 설명하고, 이 방식의 한계를 제시하라.

최신 동향 (2026-05 기준)

최신 동향 (검증 2026-05-21) — 멀티모달 LLM 분야는 변화 속도가 매우 빠르다. 비전 지원 모델의 종류·성능·API 사양, CLIP 계열 오픈소스 임베딩 모델의 신버전, unstructured 라이브러리의 파싱 전략이 지속적으로 업데이트되고 있다. 최신 버전·모델은 각 제공자 공식 문서(LangChain, OpenAI, Anthropic, Google AI, Hugging Face)에서 확인하라. (책의 멀티모달 RAG 개념·세 전략·MultiVectorRetriever 구조는 그대로 유효)

부록 A. 용어 사전

한글 영문 의미
멀티모달 Multimodal 텍스트·이미지·오디오 등 서로 다른 데이터 유형을 동시에 처리하는 방식
모달리티 Modality 데이터의 형태·유형. 텍스트, 이미지, 음성, 표, 차트 등이 각각 하나의 모달리티
멀티모달 LLM Multimodal LLM (MLLM) 텍스트 외 이미지·오디오 등을 입력으로 받아 처리할 수 있는 대형 언어 모델
이미지 임베딩 Image Embedding 이미지를 숫자 벡터로 변환하는 것. CLIP 계열 모델이 이미지와 텍스트를 동일한 벡터 공간에 인코딩
대조 학습 Contrastive Learning 이미지-텍스트 쌍을 같은 벡터 공간에 위치시키는 훈련 방식. CLIP 학습에 활용
멀티벡터 검색기 MultiVectorRetriever 요약본(vectorstore)으로 검색하고 원본(docstore)을 반환하는 랭체인 검색 컴포넌트
Base64 인코딩 Base64 Encoding 이진 데이터(이미지)를 ASCII 텍스트 문자열로 변환하는 방식. LLM API 이미지 전송에 사용
이미지 캡셔닝 Image Captioning 이미지 내용을 텍스트로 설명하는 작업. 멀티모달 RAG 전략 B의 핵심 전처리
unstructured unstructured PDF·Word·HTML 등 비정형 문서에서 텍스트·표·이미지를 자동 추출하는 파이썬 라이브러리
리랭킹 ReRanking 여러 검색 결과를 재정렬해 최적 문서를 선별하는 후처리 단계. 전략 C에서 활용

부록 B. 핵심 비교표

멀티모달 RAG 구현 방식 비교 — 이미지 캡셔닝 vs 멀티모달 임베딩

구분 전략 B: 이미지 캡셔닝 전략 A: 멀티모달 임베딩 (CLIP)
핵심 아이디어 이미지를 LLM으로 텍스트 요약 후 텍스트 임베딩만 사용 이미지·텍스트를 동일 벡터 공간에 직접 인코딩
이미지 정보 보존 요약 과정에서 세부 정보 손실 가능 원본 이미지 정보 직접 반영
필요 모델 멀티모달 LLM (비전 지원) + 텍스트 임베딩 모델 멀티모달 임베딩 모델 (CLIP 등)
전처리 비용 LLM API 호출 비용 발생 임베딩 연산 비용 (API 또는 로컬)
텍스트 검색과의 통합 자연스러움 (텍스트로 통일) 동일 벡터 공간 공유로 통합 가능
구현 복잡도 중간 낮음
적합한 상황 차트·보고서 등 맥락 해석이 중요한 이미지 이미지-텍스트 크로스 검색이 필요한 경우

MultiVectorRetriever vs 일반 벡터 검색기 비교

구분 MultiVectorRetriever 일반 Chroma 검색기
저장 구조 vectorstore(요약) + docstore(원본) 분리 단일 벡터 저장소
검색 대상 요약 임베딩 (간결, 검색 품질 높음) 원본 문서 임베딩 (길면 품질 저하)
반환 값 원본 문서 (상세 정보 보존) 벡터 저장소에 저장된 문서
멀티모달 지원 이미지 base64 원본 보존 가능 텍스트 위주
인프라 복잡도 높음 (두 저장소 관리) 낮음

부록 C. 추천 참고 자료

검증된 외부 자료 (Tier 1 공식, 생존 확인 2026-05-21)

자료 링크
LangChain 멀티모달 입력(how-to) python.langchain.com
Multi-Vector Retriever python.langchain.com
문서 로더(이미지·PDF) python.langchain.com
CLIP (GitHub) github.com/openai/CLIP
unstructured 라이브러리 공식 문서 docs.unstructured.io
실습 코드(깃허브) github.com/langchain-kr/langchain-tutorial (Ch03)
자료 설명
책 2장 기본 RAG 파이프라인 — 멀티모달 RAG 이전의 텍스트 전용 구현
책 4장 MultiQueryRetriever·앙상블 검색 등 RAG 고도화 전략 — 멀티모달에도 적용 가능

부록 D. 연습문제 풀이

  1. (정보 손실 vs 임베딩 모델 의존) 전략 B(이미지 → 텍스트 요약)는 LLM이 이미지를 자연어로 해석하므로 차트의 수치나 미세한 시각 정보가 요약 과정에서 유실될 수 있다. 반면 전략 A(CLIP 직접 임베딩)는 원본 이미지 정보를 벡터에 직접 반영하지만, 텍스트·이미지·표를 동시에 정확히 인코딩할 수 있는 강력한 멀티모달 임베딩 모델이 필수적으로 요구된다 (§2 비교표 참조).

  2. (저장소 분리 vs 단일 저장소) 일반 Chroma 검색기는 단일 벡터 저장소만 사용해 원본 문서를 임베딩한 뒤 그대로 반환한다. MultiVectorRetriever는 vectorstore(요약 임베딩 저장·검색 담당)와 docstore(원본 텍스트·테이블·이미지 base64 저장 담당)를 분리하는 구조다. 유사도 검색은 간결한 요약 임베딩으로 수행해 검색 품질을 높이고, 실제 LLM에 전달하는 문서는 docstore에서 꺼낸 상세한 원본이다.

  3. (멀티모달 메시지 구조) OpenAI의 멀티모달 API는 한 메시지 안에 텍스트와 이미지를 함께 보낼 수 있도록 content를 리스트로 정의한다. 리스트의 첫 번째 원소 {"type": "text", "text": prompt}는 이미지 분석 지침을 담은 텍스트이고, 두 번째 원소 {"type": "image_url", ...} 는 base64로 인코딩된 이미지 데이터다. 두 원소를 하나의 리스트로 묶어야 모델이 텍스트 지시와 이미지를 동시에 처리할 수 있다 (§3.2 이미지 요약 코드 참조).

  4. (전략 B 선택) 의료 보고서는 MRI 이미지의 병변 위치·크기, 혈액검사 표의 수치 해석 등 맥락 이해가 검색 품질을 좌우한다. 따라서 멀티모달 LLM(비전 지원)으로 각 이미지와 표를 검색 최적화된 텍스트 요약으로 변환한 뒤 MultiVectorRetriever로 저장하는 전략 B가 적합하다. CLIP 계열 임베딩(전략 A)은 의미 파악보다 시각 유사도에 치중하므로 수치·맥락 해석이 중요한 의료 데이터에는 정보 손실 위험이 더 크다 (§2 비교표 및 실무 체크리스트 참조).

  5. (base64 판별 방식의 원리와 한계) MultiVectorRetriever의 docstore에는 텍스트·테이블은 일반 문자열로, 이미지는 base64 인코딩 문자열로 저장된다. b64decode(doc)가 예외 없이 성공하면 유효한 base64이므로 이미지로 분류하고, 예외가 발생하면 일반 텍스트로 분류한다. 한계는 일반 텍스트도 우연히 유효한 base64 문자셋(A-Z, a-z, 0-9, +, /)으로만 구성될 경우 디코딩에 성공해 이미지로 오분류될 수 있다는 점이다. 더 견고한 방식은 docstore 저장 시 메타데이터에 타입을 명시해두고 이를 기준으로 분류하는 것이다 (§3.4 참조).

난이도
에피소드
질문
카드를 로딩 중...
답변

클릭하거나 Space를 눌러 뒤집기

0 / 0
학습 진도 0%
이동   Space 뒤집기   R 셔플