지금까지 멀티모달 RAG를 만들려면 텍스트 임베딩 모델, 이미지 임베딩 모델, 비디오 처리기를 따로 연결해야 했습니다. Gemini Embedding 2는 이걸 API 호출 하나로 끝냅니다.
[핵심 요약]
→ 출시: 2026년 3월 10일 (Public Preview), 4월 22일 GA
→ 모델 ID: gemini-embedding-2 (GA), gemini-embedding-2-preview
→ 정체: Google 최초 네이티브 멀티모달 임베딩 모델
→ 지원 입력: 텍스트, 이미지(최대 6개), 비디오(120초), 오디오(180초), PDF(6페이지)
→ 벡터 크기: 3072차원 (기본값), Matryoshka로 축소 가능
→ 언어: 100개+ 지원
→ 핵심: 5가지 모달리티를 단일 임베딩 공간에 매핑
→ 가격: 텍스트 $0.20/1M tokens, 배치 $0.10/1M
→ 주의: gemini-embedding-001(텍스트 전용)과 벡터 공간 비호환 → 전체 재임베딩 필요
왜 이게 게임체인저인가
[기존 멀티모달 RAG 파이프라인]
질문: "이 회의 영상에서 예산 관련 내용 찾아줘"
기존 방식:
1. 비디오 → Whisper로 음성 추출 → 텍스트 변환
2. 텍스트 → text-embedding-3-large로 임베딩
3. 이미지 슬라이드 → CLIP으로 별도 임베딩
4. 두 벡터 DB를 각각 검색
5. 결과 재순위화 (reranking)
6. LLM에 전달
문제:
→ 파이프라인 3개 관리
→ 음성→텍스트 변환 시 정보 손실
→ 3개 모달리티 검색 결과 합치기 복잡
→ 레이턴시: 각 변환마다 추가됨
Gemini Embedding 2:
1. 비디오 + 이미지 + 텍스트 → API 한 번
2. 단일 벡터 DB 검색
3. 끝
→ 실제 사례: Sparkonomy 레이턴시 70% 감소
→ 실제 사례: Everlaw 법률 문서 검색 리콜 20% 향상
실전 1 — 기본 설치 및 사용
# pip install google-genai
from google import genai
from google.genai import types
client = genai.Client(api_key="GEMINI_API_KEY")
# ===== 텍스트 임베딩 =====
result = client.models.embed_content(
model="gemini-embedding-2",
contents=["파이썬에서 리스트 중복 제거하는 법"],
config=types.EmbedContentConfig(
task_type="RETRIEVAL_DOCUMENT",
output_dimensionality=3072 # 기본값
)
)
print(result.embeddings[0].values[:5])
# [0.023, -0.041, 0.089, ...]
# ===== 이미지 임베딩 =====
import base64
from pathlib import Path
def embed_image(image_path: str) -> list[float]:
"""이미지 파일 → 임베딩"""
image_bytes = Path(image_path).read_bytes()
b64 = base64.b64encode(image_bytes).decode()
result = client.models.embed_content(
model="gemini-embedding-2",
contents=[
types.Content(parts=[
types.Part(
inline_data=types.Blob(
mime_type="image/png",
data=b64
)
)
])
]
)
return result.embeddings[0].values
# 사용
img_embedding = embed_image("product_photo.png")
# ===== 비디오 임베딩 (오디오 포함) =====
def embed_video(video_path: str) -> list[float]:
"""비디오 파일 → 임베딩 (음성 변환 없이 직접)"""
video_bytes = Path(video_path).read_bytes()
b64 = base64.b64encode(video_bytes).decode()
result = client.models.embed_content(
model="gemini-embedding-2",
contents=[
types.Content(parts=[
types.Part(
inline_data=types.Blob(
mime_type="video/mp4",
data=b64
)
)
])
]
)
return result.embeddings[0].values
# 기존 방식 대비:
# 기존: video → Whisper(ASR) → 텍스트 → text-embedding
# 신규: video → gemini-embedding-2 (직접)
# → 정보 손실 없음, 레이턴시 감소
# ===== PDF 임베딩 =====
def embed_pdf(pdf_path: str) -> list[float]:
"""PDF 파일 → 임베딩 (OCR 포함)"""
pdf_bytes = Path(pdf_path).read_bytes()
b64 = base64.b64encode(pdf_bytes).decode()
result = client.models.embed_content(
model="gemini-embedding-2",
contents=[
types.Content(parts=[
types.Part(
inline_data=types.Blob(
mime_type="application/pdf",
data=b64
)
)
])
]
)
return result.embeddings[0].values
# 특징: OCR 자동 처리 → 스캔 문서도 임베딩 가능
실전 2 — Task Prefix로 성능 최적화
Gemini Embedding 2는 task prefix를 지정하면 해당 태스크에 최적화된 임베딩을 생성합니다.
# Task Prefix 종류와 사용법
def embed_with_task(content: str, task: str) -> list[float]:
"""태스크별 최적화 임베딩"""
# 태스크에 따라 prefix 적용
prefixed = f"task: {task} | query: {content}"
result = client.models.embed_content(
model="gemini-embedding-2",
contents=[prefixed]
)
return result.embeddings[0].values
# 사용 예시별 태스크 타입
# 1. 검색 쿼리 (사용자 질문)
query_embedding = embed_with_task(
"파이썬 비동기 처리 방법",
"search result" # 검색용 쿼리
)
# 2. Q&A (질문에 답변 찾기)
qa_embedding = embed_with_task(
"FastAPI에서 JWT 토큰은 어떻게 검증하나요?",
"question answering"
)
# 3. 코드 검색
code_embedding = embed_with_task(
"JWT token validation middleware",
"code retrieval"
)
# 4. 팩트 체킹
fact_embedding = embed_with_task(
"파이썬은 1991년에 출시됐다",
"fact checking"
)
# 문서 임베딩 (검색 대상)
def embed_document(content: str, title: str = None) -> list[float]:
if title:
prefixed = f"title: {title} | text: {content}"
else:
prefixed = content
result = client.models.embed_content(
model="gemini-embedding-2",
contents=[prefixed],
config=types.EmbedContentConfig(
task_type="RETRIEVAL_DOCUMENT"
)
)
return result.embeddings[0].values
[Task Type 선택 가이드]
검색 쿼리: "search result" 또는 RETRIEVAL_QUERY
문서 색인: "search result" 또는 RETRIEVAL_DOCUMENT
질의응답: "question answering"
코드 검색: "code retrieval"
팩트 체킹: "fact checking"
→ task prefix 미지정 vs 지정 시 검색 정확도 차이 있음
→ 특히 긴 문서와 짧은 쿼리 간 비대칭 검색에서 효과적
실전 3 — 멀티모달 RAG 파이프라인
import numpy as np
import anthropic
from pathlib import Path
from dataclasses import dataclass
from typing import Any
@dataclass
class MultimodalChunk:
"""멀티모달 청크"""
id: str
content: Any # 텍스트, 이미지 경로, 비디오 경로 등
mime_type: str # "text/plain", "image/png", "video/mp4"...
metadata: dict
embedding: list[float] = None
class MultimodalRAG:
"""Gemini Embedding 2 기반 멀티모달 RAG"""
def __init__(self, gemini_client, llm_client):
self.gemini = gemini_client
self.llm = llm_client
self.chunks: list[MultimodalChunk] = []
def embed_chunk(self, chunk: MultimodalChunk) -> list[float]:
"""단일 청크 임베딩 (모달리티 자동 처리)"""
if chunk.mime_type == "text/plain":
# 텍스트: 직접 임베딩
result = self.gemini.models.embed_content(
model="gemini-embedding-2",
contents=[f"title: {chunk.metadata.get('title', 'none')} | text: {chunk.content}"],
config=types.EmbedContentConfig(
task_type="RETRIEVAL_DOCUMENT"
)
)
else:
# 이미지/비디오/오디오/PDF: 바이너리 임베딩
file_bytes = Path(chunk.content).read_bytes()
b64 = base64.b64encode(file_bytes).decode()
result = self.gemini.models.embed_content(
model="gemini-embedding-2",
contents=[
types.Content(parts=[
types.Part(
inline_data=types.Blob(
mime_type=chunk.mime_type,
data=b64
)
)
])
]
)
return result.embeddings[0].values
def add_chunks(self, chunks: list[MultimodalChunk]):
"""청크 추가 + 임베딩"""
for chunk in chunks:
chunk.embedding = self.embed_chunk(chunk)
self.chunks.append(chunk)
print(f"총 {len(self.chunks)}개 청크 저장됨")
def search(self, query: str, top_k: int = 5) -> list[MultimodalChunk]:
"""쿼리 → 관련 청크 검색"""
# 쿼리 임베딩
query_result = self.gemini.models.embed_content(
model="gemini-embedding-2",
contents=[f"task: question answering | query: {query}"],
config=types.EmbedContentConfig(
task_type="RETRIEVAL_QUERY"
)
)
query_vec = np.array(query_result.embeddings[0].values)
# 코사인 유사도 계산
scores = []
for chunk in self.chunks:
chunk_vec = np.array(chunk.embedding)
similarity = float(
np.dot(query_vec, chunk_vec) /
(np.linalg.norm(query_vec) * np.linalg.norm(chunk_vec))
)
scores.append((chunk, similarity))
# 상위 k개 반환
scores.sort(key=lambda x: x[1], reverse=True)
return [chunk for chunk, _ in scores[:top_k]]
def query(self, question: str) -> str:
"""검색 + 생성"""
retrieved = self.search(question)
# 컨텍스트 구성 (멀티모달)
context_parts = []
for i, chunk in enumerate(retrieved, 1):
if chunk.mime_type == "text/plain":
context_parts.append(f"[{i}] {chunk.content}")
else:
context_parts.append(
f"[{i}] {chunk.mime_type} 파일: {chunk.metadata.get('title', chunk.content)}"
)
context = "\n".join(context_parts)
# LLM으로 답변 생성
response = self.llm.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system=f"다음 자료를 참고하여 질문에 답변하세요:\n\n{context}",
messages=[{"role": "user", "content": question}]
)
return response.content[0].text
# 사용 예시
rag = MultimodalRAG(
gemini_client=client,
llm_client=anthropic.Anthropic()
)
# 다양한 모달리티 청크 추가
rag.add_chunks([
MultimodalChunk(
id="doc1",
content="FastAPI는 Python 기반 고성능 웹 프레임워크입니다...",
mime_type="text/plain",
metadata={"title": "FastAPI 소개"}
),
MultimodalChunk(
id="img1",
content="architecture_diagram.png",
mime_type="image/png",
metadata={"title": "시스템 아키텍처 다이어그램"}
),
MultimodalChunk(
id="vid1",
content="api_tutorial.mp4",
mime_type="video/mp4",
metadata={"title": "API 개발 튜토리얼 영상"}
),
MultimodalChunk(
id="pdf1",
content="api_spec.pdf",
mime_type="application/pdf",
metadata={"title": "API 명세서"}
),
])
# 질문 → 멀티모달 검색 + 생성
answer = rag.query("FastAPI 인증 미들웨어 구현 방법은?")
print(answer)
실전 4 — Matryoshka 차원 축소
# 차원별 트레이드오프
dimension_guide = {
3072: "최고 품질 — 프로덕션 고정밀 검색",
1536: "우수 품질 — 대부분 케이스 충분, 저장 50% 절감",
768: "양호 품질 — 비용/속도 최적화 필요 시",
}
# 차원 축소 사용법
result = client.models.embed_content(
model="gemini-embedding-2",
contents=["검색할 텍스트"],
config=types.EmbedContentConfig(
output_dimensionality=768 # 3072 → 768으로 축소
)
)
embedding_768 = result.embeddings[0].values
print(f"차원: {len(embedding_768)}") # 768
실전 5 — 크로스 모달 검색
# 이미지로 텍스트 검색, 텍스트로 이미지 검색 — 같은 벡터 공간이라 가능
def cross_modal_search():
"""크로스 모달 검색 예시"""
# 텍스트 문서들 색인
text_chunks = [
"고양이는 독립적인 성격의 반려동물입니다",
"강아지는 사람을 좋아하는 사회적인 동물입니다",
"파이썬 코딩 튜토리얼 시작하기"
]
text_embeddings = []
for text in text_chunks:
result = client.models.embed_content(
model="gemini-embedding-2",
contents=[text]
)
text_embeddings.append(np.array(result.embeddings[0].values))
# 이미지로 관련 텍스트 검색 (크로스 모달!)
cat_image_bytes = Path("cat_photo.jpg").read_bytes()
b64 = base64.b64encode(cat_image_bytes).decode()
image_result = client.models.embed_content(
model="gemini-embedding-2",
contents=[
types.Content(parts=[
types.Part(
inline_data=types.Blob(
mime_type="image/jpeg",
data=b64
)
)
])
]
)
image_vec = np.array(image_result.embeddings[0].values)
# 코사인 유사도로 가장 관련 있는 텍스트 찾기
similarities = [
float(np.dot(image_vec, text_vec) /
(np.linalg.norm(image_vec) * np.linalg.norm(text_vec)))
for text_vec in text_embeddings
]
best_match_idx = np.argmax(similarities)
print(f"고양이 이미지와 가장 관련된 텍스트:")
print(f"→ '{text_chunks[best_match_idx]}' (유사도: {similarities[best_match_idx]:.3f})")
# → '고양이는 독립적인 성격의 반려동물입니다' (유사도: 0.847)
cross_modal_search()
Gemini Embedding 2 vs 기존 모델 비교
| 항목 | gemini-embedding-001 | Gemini Embedding 2 | text-embedding-3-large |
| 텍스트 | ✅ | ✅ | ✅ |
| 이미지 | ❌ | ✅ (최대 6개) | ❌ |
| 비디오 | ❌ | ✅ (120초) | ❌ |
| 오디오 | ❌ | ✅ (180초) | ❌ |
| ❌ | ✅ (6페이지) | ❌ | |
| 차원 | 768 | 3072 (축소 가능) | 3072 (축소 가능) |
| 언어 | 다국어 | 100개+ | 영어 중심 |
| 가격 (텍스트) | $0.00002/1K | $0.20/1M | $0.13/1M |
| 상태 | GA | GA (4/22~) | GA |
| 벡터 호환 | ❌ (별도 공간) | ❌ (별도 공간) | - |
[선택 기준]
텍스트만, 비용 최소화: gemini-embedding-001 또는 text-embedding-3-small
텍스트만, 최고 성능: text-embedding-3-large (영어) 또는 Qwen3-Embedding
멀티모달 필요: Gemini Embedding 2 (현재 유일한 상업 옵션)
비디오/오디오 네이티브: Gemini Embedding 2 (경쟁자 없음)
마무리
✅ Gemini Embedding 2 써야 할 때
→ 텍스트 + 이미지 + 비디오 동시 검색 필요
→ 비디오/오디오를 텍스트 변환 없이 직접 임베딩
→ 스캔 PDF + 이미지 문서 검색 (OCR 자동 처리)
→ 크로스 모달 검색 (이미지로 텍스트 검색, 반대도)
→ 기존 3개 파이프라인 → 1개로 단순화
❌ 기존 텍스트 임베딩이 나은 경우
→ 텍스트 전용 RAG (Qwen3-Embedding-8B 또는 text-embedding-3-large가 더 저렴)
→ 순수 한국어 텍스트 (bge-m3가 한국어 특화)
→ 대규모 배치 처리 비용 최소화
→ 기존 gemini-embedding-001 사용 중 (재임베딩 비용 고려)
[주의: 마이그레이션]
gemini-embedding-001 → Gemini Embedding 2:
→ 벡터 공간 완전 비호환
→ 전체 데이터셋 재임베딩 필수
→ 벡터 DB 인덱스 재생성 필요
→ 프로덕션 마이그레이션 계획 신중하게
관련 글:
https://cell-devlog.tistory.com/158
임베딩 모델 완전 가이드 — text-embedding 선택과 RAG 적용
RAG를 만들었는데 검색 품질이 나쁩니다. 청킹도 바꿔보고 프롬프트도 바꿨는데 여전합니다. 임베딩 모델이 문제일 수 있습니다. 선택 기준부터 실전 적용까지 정리했습니다.[핵심 요약]→ 임베
cell-devlog.tistory.com
https://cell-devlog.tistory.com/136
Gemini 3.1 Flash TTS 완전 가이드 — 자연어로 AI 목소리를 연출하는 법
"긴장감 있게 읽어줘", "여기서 잠깐 멈춰", "속삭이듯이". 이제 이 말 한 마디로 AI 목소리를 연출할 수 있습니다.[핵심 요약]→ 출시: 2026년 4월 15일 (Google, 프리뷰)→ 핵심: SSML 없이 자연어로 음성
cell-devlog.tistory.com
https://cell-devlog.tistory.com/24
쿼리 재작성, 반복 검색, 멀티소스 라우팅 — Agentic RAG 동작 원리와 동적 검색 전략 완전 정리
RAG 시스템을 만들고 나면 이런 한계가 생겨요."단순한 질문은 잘 답하는데, '2024년 실적을 바탕으로 2025년 전략을 분석해줘' 같은 복잡한 질문은 엉뚱한 답이 나온다."이건 일반 RAG의 구조적 한계
cell-devlog.tistory.com
'Gemini' 카테고리의 다른 글
| Google Antigravity 완전 가이드 2편 — Agent Manager로 멀티 에이전트 오케스트레이션 실전 (0) | 2026.05.19 |
|---|---|
| Google Antigravity 완전 가이드 1편 — 탄생 배경과 설치, 첫 세팅까지 (0) | 2026.05.19 |
| Gemini 3.1 Pro API 실전 — 1M 컨텍스트 + 멀티모달 제대로 써보기 (0) | 2026.05.18 |
| Google I/O 2026 예고 —5/19 키노트에서 발표될 내용들 (0) | 2026.05.18 |
| 나노바나나 프롬프트 모음집 정리 — 프롬프트 사이트 6곳 추천 (0) | 2026.04.28 |