멀티모달 RAG 파이프라인 구축: 이미지·표를 LLM이 이해하게 만드는 법
RAG를 처음 도입했을 때 저도 비슷한 경험을 했습니다. PDF 몇 백 개를 파싱해서 벡터 DB에 넣고 검색해보니, 텍스트로 잘 설명된 내용은 꽤 잘 뽑아왔는데, 문서 중간에 끼어있던 차트나 제품 사진, 회로 다이어그램은 완전히 무시되더군요. 그 시각 정보가 사실상 문서의 핵심인 경우가 많은데도요. 기존 텍스트 RAG의 구조적 한계였습니다.
멀티모달 RAG는 바로 그 지점에서 출발합니다. 이미지, 표, 오디오, 비디오까지 텍스트와 동일한 파이프라인에서 검색하고 생성에 활용하는 아키텍처입니다. 이 글에서는 멀티모달 RAG의 작동 원리와 실제 파이프라인 구성법, 그리고 운영 시 반드시 고려해야 할 트레이드오프를 다룹니다. 이 글은 기본 텍스트 RAG 파이프라인을 한 번이라도 구성해본 독자를 기준으로 합니다. 벡터 DB, 임베딩 모델, 청킹이 아직 낯설다면 RAG 기초 개념을 먼저 살펴보고 오시는 것을 권장합니다.
의료 영상, 이커머스, 기술 문서 등 이미 여러 분야의 프로덕션 서비스에서 쓰이는 기술이지만, 지금이 진입하기 좋은 타이밍입니다. Google Gemini Embedding 2처럼 텍스트·이미지·오디오를 단일 벡터 공간에 매핑하는 네이티브 멀티모달 임베딩 모델이 막 안정화되면서, 1년 전과 비교해 구현 난이도가 눈에 띄게 낮아졌거든요.
핵심 개념
멀티모달 RAG의 세 단계 구조
기존 RAG가 "텍스트 → 임베딩 → 벡터 검색 → 생성"의 단선적 흐름이라면, 멀티모달 RAG는 각 단계에서 모달리티별 처리를 병렬로 수행합니다.
[원본 문서]
├── 텍스트 청크 → Transformer 임베딩
├── 이미지/차트 → Vision Encoder (CLIP, ColPali 등)
└── 표 데이터 → 텍스트 변환 후 Transformer 임베딩
↓ (통합 벡터 공간에 저장)
[질의(Query)] → 교차 모달 검색 → [텍스트 + 이미지 + 표 컨텍스트]
↓
VLM (GPT-4o, Gemini 등)
↓
[최종 응답]1단계 — 멀티모달 인덱싱: 모달리티마다 전용 인코더로 임베딩한 뒤, 하나의 통합 벡터 공간에 저장합니다. 텍스트는 BGE-M3(한국어 포함 다국어 지원이 강한 트랜스포머 기반 임베딩 모델) 같은 모델을 씁니다. 이미지는 CLIP이나 ColPali를 활용하는데, 둘은 접근 방식이 꽤 다릅니다. CLIP이 이미지-텍스트 쌍을 정렬하는 방식이라면, ColPali는 OCR 없이 문서 페이지 이미지 자체를 임베딩하는 Late Interaction 방식으로 레이아웃·서식 정보까지 보존합니다. 여기서 임베딩 품질이 이후 검색 성능 전체를 좌우합니다.
2단계 — 교차 모달 검색(Cross-modal Retrieval): 텍스트 질의가 들어와도 연관된 이미지나 표까지 함께 끌어오는 것이 핵심입니다. 벡터 유사도 검색만으로는 한계가 있어서, BM25 키워드 검색과 결합한 하이브리드 검색에 텐서 리랭킹(Tensor Reranker)을 얹어 정확도를 올리는 방식이 현재 프로덕션 표준에 가깝습니다.
3단계 — 멀티모달 생성: 검색된 텍스트, 이미지, 표를 모두 컨텍스트로 주입해 GPT-4o나 Gemini 같은 VLM이 응답을 만들어냅니다. 텍스트만 넣을 때보다 할루시네이션이 경험적으로 유의미하게 줄어드는데, 실제로 써보면 납득이 갑니다. 시각 근거가 있으니 모델이 함부로 지어내기 어려운 거죠.
기존 텍스트 RAG와 무엇이 다른가
솔직히 처음엔 "그냥 이미지에서 텍스트 뽑아서 넣으면 되지 않나?" 싶었습니다. OCR로 이미지 텍스트 추출하면 기존 RAG로도 커버되지 않나 하고요. 그런데 차트의 색상 분포, 의료 영상의 공간적 패턴, 제품 사진의 질감 같은 정보는 텍스트로 변환하는 순간 손실됩니다. 이게 누적되면 검색 품질에 직접 영향을 미칩니다.
VisRAG: 문서를 텍스트로 파싱하지 않고 페이지 이미지 그대로 검색·추론하는 방식. OCR 변환 과정에서 발생하는 레이아웃 정보 손실 없이 원본 문서 구조를 유지할 수 있습니다.
2025~2026년 주목할 변화들
멀티모달 RAG 생태계가 빠르게 바뀌고 있어서, 한 달 전 아키텍처가 지금은 구식이 되는 경우도 있습니다.
| 트렌드 | 내용 |
|---|---|
| 네이티브 멀티모달 임베딩 | Google Gemini Embedding 2가 텍스트·이미지·비디오·오디오를 단일 벡터 공간에 매핑. 모달리티별 인코더를 따로 두지 않아도 됩니다 |
| Agentic RAG | 검색-생성 단일 루프에서 Retriever·Validator·Summarizer Agent가 협력하는 멀티스텝 추론으로 진화 중 |
| 실시간 RAG | 정적 인덱스에서 뉴스·센서·주가 같은 동적 실시간 데이터 연결 구조로 확장 |
| RAG-as-a-Service | 멀티모달 RAG 기능을 API로 제공하는 엔터프라이즈용 관리형 서비스 급증 |
실전 적용
그러면 이 파이프라인을 실제로 어떻게 구성하는지 코드로 살펴보겠습니다.
예시 1: LlamaIndex로 PDF 문서 멀티모달 파이프라인 구성
기술 문서 어시스턴트 시나리오를 생각해볼게요. 회로도, 다이어그램, 코드 스니펫, 텍스트 설명이 뒤섞인 엔지니어링 PDF를 처리하는 경우입니다. 저도 처음에 storage_context 없이 인덱스 생성을 시도했다가 NameError를 만났는데, 텍스트 스토어와 이미지 스토어를 각각 만든 뒤 반드시 StorageContext로 묶어줘야 합니다. 이 단계를 빠뜨리면 복붙해도 바로 에러가 납니다.
from llama_index.core import SimpleDirectoryReader, StorageContext
from llama_index.multi_modal_llms.openai import OpenAIMultiModal
from llama_index.core.indices.multi_modal import MultiModalVectorStoreIndex
from llama_index.vector_stores.qdrant import QdrantVectorStore
from llama_index.embeddings.clip import ClipEmbedding
import qdrant_client
# 1. 멀티모달 문서 로드 (텍스트 + 이미지 함께 추출)
documents = SimpleDirectoryReader(
input_dir="./docs",
required_exts=[".pdf"],
recursive=True
).load_data()
# 2. 벡터 스토어 설정 (텍스트/이미지 인덱스 분리)
client = qdrant_client.QdrantClient(host="localhost", port=6333)
text_store = QdrantVectorStore(
client=client, collection_name="text_collection"
)
image_store = QdrantVectorStore(
client=client, collection_name="image_collection"
)
# 3. StorageContext로 텍스트/이미지 스토어 통합 (필수 단계)
storage_context = StorageContext.from_defaults(
vector_store=text_store,
image_store=image_store,
)
# 4. CLIP 임베딩으로 이미지-텍스트 정렬
clip_embedding = ClipEmbedding()
# 5. 멀티모달 인덱스 빌드
index = MultiModalVectorStoreIndex.from_documents(
documents,
image_embed_model=clip_embedding,
storage_context=storage_context,
)
# 6. VLM 설정 및 쿼리 엔진 구성
mm_llm = OpenAIMultiModal(
model="gpt-4o",
max_new_tokens=1500
)
query_engine = index.as_query_engine(
multi_modal_llm=mm_llm,
similarity_top_k=5, # 상위 5개 텍스트 청크
image_similarity_top_k=3, # 상위 3개 이미지
)
response = query_engine.query(
"이 회로에서 전압 조정 모듈의 동작 원리를 설명해줘"
)
print(response)| 코드 포인트 | 역할 |
|---|---|
MultiModalVectorStoreIndex |
텍스트·이미지 인덱스를 통합 관리 |
StorageContext.from_defaults |
텍스트·이미지 벡터 스토어를 하나로 묶는 필수 단계 |
ClipEmbedding |
이미지와 텍스트를 같은 벡터 공간에 매핑 |
image_similarity_top_k |
검색 시 이미지 후보 수 조절 (비용-품질 트레이드오프) |
OpenAIMultiModal |
이미지+텍스트 컨텍스트를 함께 처리하는 VLM |
예시 2: 하이브리드 검색으로 교차 모달 정확도 높이기
벡터 검색만으로는 키워드 일치 케이스에서 놓치는 경우가 생깁니다. BM25와 결합한 하이브리드 검색 + 텐서 리랭킹을 적용하면 실질적 차이가 납니다. 프로덕션에서 가장 자주 만지게 되는 파라미터는 similarity_top_k와 top_n인데, 이 둘의 균형이 검색 품질과 레이턴시를 동시에 결정합니다.
from llama_index.multi_modal_llms.openai import OpenAIMultiModal
from llama_index.retrievers.bm25 import BM25Retriever
from llama_index.core.retrievers import QueryFusionRetriever
from llama_index.postprocessor.colbert_rerank import ColbertRerank
from llama_index.core.query_engine import RetrieverQueryEngine
# 독립 실행 시 mm_llm 정의 필요
mm_llm = OpenAIMultiModal(
model="gpt-4o",
max_new_tokens=1500
)
# 벡터 검색 리트리버
vector_retriever = index.as_retriever(similarity_top_k=10)
# BM25 키워드 검색 리트리버
bm25_retriever = BM25Retriever.from_defaults(
index=index,
similarity_top_k=10
)
# 두 리트리버를 Reciprocal Rank Fusion으로 결합
hybrid_retriever = QueryFusionRetriever(
retrievers=[vector_retriever, bm25_retriever],
similarity_top_k=5,
num_queries=1,
mode="reciprocal_rerank",
)
# ColBERT 기반 텐서 리랭킹으로 최종 순위 조정
reranker = ColbertRerank(top_n=3)
query_engine = RetrieverQueryEngine(
retriever=hybrid_retriever,
node_postprocessors=[reranker],
llm=mm_llm,
)Reciprocal Rank Fusion (RRF): 여러 검색 결과의 순위를 점수로 변환해 결합하는 앙상블 기법. 구현이 단순하면서도 성능이 꽤 안정적으로 나와서 현업에서 자주 씁니다.
예시 3: 문서 파싱 단계에서 이미지·표 분리 추출
파이프라인 품질은 인제스천 단계에서 결정됩니다. Unstructured.io로 PDF에서 텍스트, 이미지, 표를 각각 분리해 처리해볼 수 있습니다.
from unstructured.partition.pdf import partition_pdf
def parse_multimodal_document(pdf_path: str) -> dict:
"""PDF에서 텍스트, 이미지, 표를 분리 추출"""
elements = partition_pdf(
filename=pdf_path,
extract_images_in_pdf=True,
extract_image_block_types=["Image", "Table"],
infer_table_structure=True,
strategy="hi_res", # 고해상도 파싱 (느리지만 정확)
)
result = {"texts": [], "images": [], "tables": []}
for element in elements:
element_type = type(element).__name__
if element_type == "Table":
result["tables"].append({
"content": element.text,
"metadata": element.metadata.to_dict()
})
elif element_type == "Image":
if hasattr(element.metadata, "image_base64"):
result["images"].append({
"base64": element.metadata.image_base64,
"metadata": element.metadata.to_dict()
})
else:
result["texts"].append({
"content": element.text,
"metadata": element.metadata.to_dict()
})
return result| 파싱 전략 | 속도 | 정확도 | 권장 상황 |
|---|---|---|---|
fast |
빠름 | 낮음 | 텍스트 중심 문서, 프로토타이핑 |
auto |
중간 | 중간 | 일반적인 PDF |
hi_res |
느림 | 높음 | 이미지·표 많은 기술 문서, 프로덕션 |
장단점 분석
장점
| 항목 | 내용 |
|---|---|
| 정보 손실 감소 | 차트 색상 분포, 이미지 공간 관계 등 텍스트 변환 시 소실되는 시각 정보를 그대로 활용 |
| 할루시네이션 억제 | 멀티소스 그라운딩으로 텍스트만 쓸 때보다 생성 신뢰도가 경험적으로 유의미하게 향상 |
| 풍부한 응답 품질 | 텍스트 + 이미지 + 표를 함께 반환해 사용자 경험 개선 |
| 기존 생태계 호환 | LangChain·LlamaIndex 에코시스템에서 확장 가능. 기존 RAG 자산 재활용 가능 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 인프라 복잡성 | 모달리티별 인코더, 청킹 전략, 인덱스가 달라 운영 부담 급증 | Gemini Embedding 2 같은 네이티브 멀티모달 임베딩으로 단순화 |
| 저장·검색 비용 | 백만 페이지 규모에서 인덱스가 TB 수준으로 증가 | 벡터 DB 샤딩, 이미지 해상도 최적화, 계층적 스토리지 설계 |
| 레이턴시 | 모달리티 처리 단계가 직렬 연결되면 응답 속도 저하 | 비동기 인제스천 큐, 캐싱, 모달리티별 병렬 처리 |
| 교차 모달 정렬 품질 | 텍스트 질의로 이미지 검색 시 임베딩 품질 차이가 성능에 직결 | 고품질 CLIP/ColPali 임베딩 선택, 도메인 파인튜닝 고려 |
| 평가 지표 부재 | 멀티모달 RAG 품질 측정 표준 벤치마크가 아직 미성숙 | RAGAS 프레임워크 활용 + 모달리티별 개별 평가 지표 조합 |
텐서 리랭킹(Tensor Reranker): ColBERT 스타일로 질의와 문서의 토큰 수준 상호작용을 계산해 순위를 재조정하는 기법. 일반 코사인 유사도보다 정확하지만 연산 비용이 높아 상위 후보에만 적용하는 것이 일반적입니다.
실무에서 가장 흔한 실수
-
이미지 전처리를 생략하는 것 — 해상도가 낮거나 노이즈가 많은 이미지를 그대로 임베딩하면 검색 품질이 급격히 떨어집니다. 인제스천 단계에서 리사이징, 정규화, 품질 필터링을 미리 적용해두는 것이 좋습니다.
-
텍스트 청크와 이미지의 위치 연결을 끊어버리는 것 — "3번 그림 참조"라는 텍스트와 실제 3번 그림의 메타데이터 연결이 파싱 과정에서 끊기면, 검색 시 맥락이 없는 이미지가 뽑히게 됩니다. 파싱 단계에서 페이지 번호, 섹션 정보 등 위치 메타데이터를 보존하는 것을 권장합니다.
-
단일 모달리티로 전체 파이프라인을 검증하는 것 — 텍스트 검색이 잘 된다고 이미지 검색도 잘 될 거라 가정하면 나중에 큰 낭패를 봅니다. 모달리티별로 별도 평가 세트를 만들어 각각 검증하는 과정이 필요합니다.
마치며
멀티모달 RAG는 "텍스트만 이해하는 AI"에서 "문서 전체를 이해하는 AI"로 넘어가는 핵심 아키텍처입니다.
복잡해 보이지만 시작점은 생각보다 가깝습니다. 지금 바로 시작해볼 수 있는 3단계입니다.
- 환경 구성 — 아래 명령어로 이 글의 예시 1, 2, 3을 모두 실행할 수 있는 환경이 갖춰집니다. 예시 2의
BM25Retriever와ColbertRerank는 별도 패키지이니 함께 설치해야 합니다.
pip install llama-index-multi-modal-llms-openai \
llama-index-vector-stores-qdrant \
llama-index-embeddings-clip \
llama-index-retrievers-bm25 \
llama-index-postprocessor-colbert-rerank \
unstructured[pdf]
docker run -p 6333:6333 qdrant/qdrant-
가장 단순한 케이스로 파이프라인 검증 — 이미지가 10~20장 정도 포함된 PDF 한두 개로 시작해보세요.
strategy="hi_res"로 파싱하고, CLIP 임베딩으로 인덱싱한 뒤, 이미지를 설명하는 질의를 날려보면 멀티모달 검색이 어떻게 동작하는지 눈으로 확인할 수 있습니다. 첫 실행에서 이미지 검색 결과가 이상해 보여도 당황할 필요 없습니다. 인제스천 단계 로그를 먼저 확인해보시면 원인이 금방 나옵니다. -
하이브리드 검색과 리랭킹은 두 번째 이터레이션에서 — 기본 파이프라인이 동작하는 것을 확인한 뒤 BM25 결합과 ColBERT 리랭킹을 단계적으로 얹어가면서 검색 품질 변화를 비교해보시면 각 컴포넌트의 실제 기여도를 바로 체감할 수 있습니다.
참고 자료
- Building a Multimodal RAG with Text, Images, Tables | Towards Data Science
- mRAG: Elucidating the Design Space of Multi-modal RAG | arXiv 2505.24073
- Multimodal RAG Explained: From Text to Images and Beyond | USAII
- Gemini Embedding 2 — How Multimodal Embeddings Change RAG | jangwook.net
- From RAG to Context: A 2025 Year-End Review | RAGFlow
- What is Multimodal RAG? | IBM
- Multimodal RAG: A Hands-On Guide | DataCamp
- Multimodal RAG Development: 12 Best Practices | Augment Code
- Bridging Modalities: Multimodal RAG for Advanced Information Retrieval | InfoQ
- RAG-Anything: All-in-One RAG Framework | GitHub (HKUDS)
- Real-World Applications of Multimodal Search and RAG | Milvus
- Best Open-Source RAG Frameworks 2026 | Firecrawl
- RAG in 2026: How Retrieval-Augmented Generation Works for Enterprise AI | Techment