25

멀티모달 RAG, 이렇게 하면 망한다

Day 1: 멀티모달 RAG

학습 목표

멀티모달 RAG 구현 시 자주 발생하는 실수를 파악한다 각 실수에 대한 해결책을 설명할 수 있다

멀티모달 RAG, 이렇게 하면 망한다

실제 프로젝트에서 반복되는 실수 5가지. 미리 알면 삽질을 줄인다.


실수 1: 이미지 해상도를 무시한다

# 잘못된 코드
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{
        "role": "user",
        "content": [{
            "type": "image_url",
            "image_url": {
                "url": f"data:image/png;base64,{data}",
                "detail": "low"  # 항상 low? 비용은 아끼지만...
            }
        }]
    }]
)

문제: 도면의 치수, 작은 글씨가 인식 안 됨

해결:

# 이미지 크기에 따라 detail 동적 설정
from PIL import Image

img = Image.open(image_path)
width, height = img.size

if width * height > 1_000_000:  # 100만 픽셀 이상
    detail = "high"  # 고해상도 분석
else:
    detail = "low"   # 저해상도로 충분

실수 2: 이미지 설명 품질을 검증 안 한다

# 잘못된 접근
description = analyze_image(image_path)
# 바로 DB에 저장 -> "이 이미지는 기계 부품입니다" 같은 뻔한 설명도 저장됨
collection.add(documents=[description], ...)

문제: GPT-4o (비전 내장)가 "잘 모르겠습니다", "이미지가 불명확합니다" 같은 무의미한 결과를 반환해도 그대로 인덱싱됨

해결:

# 품질 필터링
REJECT_PATTERNS = [
    "잘 모르겠", "불명확", "확인할 수 없",
    "이미지가 너무", "해상도가 낮",
]

def is_valid_description(desc: str) -> bool:
    if len(desc) < 50:  # 너무 짧은 설명
        return False
    if any(p in desc for p in REJECT_PATTERNS):
        return False
    return True

description = analyze_image(image_path)
if is_valid_description(description):
    collection.add(documents=[description], ...)
else:
    log_failed_image(image_path, description)

실수 3: 표 데이터를 텍스트로만 변환한다

# 잘못된 변환
원본 표:
| 부품명 | 규격 | 재질 | 수량 |
| 주축 베어링 | 7210C | SUJ2 | 2 |
| 가이드 부시 | SH25 | S45C | 4 |

변환 결과: "부품명 규격 재질 수량 주축 베어링 7210C SUJ2 2 가이드 부시 SH25 S45C 4"

문제: 구조 정보가 사라져서 "주축 베어링의 재질"을 못 찾음

해결:

# 마크다운 + 자연어 설명 동시 생성
[마크다운 표]
| 부품명 | 규격 | 재질 | 수량 |
|--------|------|------|------|
| 주축 베어링 | 7210C | SUJ2 | 2 |
| 가이드 부시 | SH25 | S45C | 4 |

[자연어 요약]
주축 베어링은 규격 7210C, 재질 SUJ2이며 2개가 필요합니다.
가이드 부시는 규격 SH25, 재질 S45C이며 4개가 필요합니다.

실수 4: 이미지 주변 컨텍스트를 버린다

# 잘못된 접근
# 이미지만 단독으로 분석
description = analyze_image(image_path)  # "기어 도면입니다"

문제: "그림 3-5: 주축 기어 조립도"라는 캡션이 바로 위에 있는데 무시

해결:

# 이미지 주변 텍스트를 컨텍스트로 제공
def get_context(elements, image_idx, window=2):
    texts = []
    for i in range(max(0, image_idx - window),
                   min(len(elements), image_idx + window + 1)):
        if i != image_idx and elements[i].type in ('text', 'title'):
            texts.append(str(elements[i]))
    return " ".join(texts)

context = get_context(elements, idx)
description = analyze_image(image_path, context=context)
# "이 이미지는 그림 3-5 주축 기어 조립도로, 기어 A(모듈 2.5)와 기어 B(모듈 3.0)의..."

실수 5: API 비용이 폭발한다

매뉴얼 800페이지, 이미지 300장
- GPT-4o (비전 내장) detail=high: 이미지당 ~$0.03
- 300장 x $0.03 = $9 (한 문서)
- 매뉴얼 50권이면? $450!

해결:

import hashlib

# 1. 중복 이미지 제거
def image_hash(path):
    with open(path, 'rb') as f:
        return hashlib.md5(f.read()).hexdigest()

seen = set()
unique_images = []
for img in all_images:
    h = image_hash(img)
    if h not in seen:
        seen.add(h)
        unique_images.append(img)

# 2. 결과 캐싱
import json

CACHE_FILE = "image_analysis_cache.json"
cache = json.load(open(CACHE_FILE)) if os.path.exists(CACHE_FILE) else {}

def analyze_with_cache(image_path):
    h = image_hash(image_path)
    if h in cache:
        return cache[h]
    result = analyze_image(image_path)
    cache[h] = result
    with open(CACHE_FILE, 'w') as f:
        json.dump(cache, f, ensure_ascii=False)
    return result

# 3. 단계적 분석 (중요도별)
# detail=low로 먼저 분류 -> 도면/안전만 detail=high

체크리스트

시작 전에 확인하자:

  • 이미지 해상도에 따른 detail 동적 설정
  • 이미지 설명 품질 필터링 (길이, 거부 패턴)
  • 표 변환 시 마크다운 + 자연어 설명 동시 생성
  • 이미지 주변 텍스트를 컨텍스트로 활용
  • 중복 이미지 제거 + 결과 캐싱
AI로 학습하기 — 꿀팁
멀티모달 RAG 실수 점검AI 학습 팁

멀티모달 RAG 구현 시 OCR 노이즈, 이미지 과신, 청킹 전략 오류 등이 없는지 점검해 보세요.

멀티모달 RAG 구현 실수에 대한 아래 주장이 맞는지 각각 검증해줘. (1) 'PDF에서 OCR로 추출한 텍스트는 원본과 동일하므로 별도 품질 검증이 필요 없다.' (2) '도면 이미지는 텍스트보다 항상 더 많은 정보를 담으므로 이미지 임베딩 벡터에 더 높은 가중치를 줘야 한다.' (3) 'Unstructured 라이브러리의 partition_pdf()는 기본 설정으로도 표와 도면을 완벽히 분리 추출한다.' (4) '멀티모달 청크는 페이지 단위로 잘라야 검색 정확도가 가장 높다.' 제조 문서 RAG 맥락으로 교정해줘.
이 팁이 도움이 됐나요?