서비스 비교표: HolySheep AI vs 공식 API vs 기타 릴레이
| 비교 항목 | HolySheep AI | 공식 OpenAI API | 기타 릴레이 서비스 |
|---|---|---|---|
| 멀티모달 지원 | ✅ GPT-4o, Claude 3.5, Gemini 1.5 | ✅ GPT-4o만 지원 | ⚠️ 제한적 |
| 이미지 임베딩 | ✅ dall-e-3, clip-vit-largethumb | ✅ clip-vit-largethumb | ❌ 미지원 |
| 인식서 결제 | ✅ 로컬 결제 지원 | ❌ 해외 카드 필수 | ✅ 대부분 지원 |
| 가격 (이미지 임베딩) | $0.0004/이미지 | $0.0004/이미지 | $0.0006-0.001/이미지 |
| 텍스트 임베딩 비용 | $0.10/1M 토큰 | $0.10/1M 토큰 | $0.15-0.30/1M 토큰 |
| 평균 지연 시간 | 180-250ms | 200-300ms | 300-500ms |
| 단일 API 키 | ✅ 모든 모델 통합 | ❌ 모델별 분리 | ⚠️ 제한적 |
| 무료 크레딧 | ✅ 가입 시 제공 | ❌ 없음 | ⚠️ 제한적 |
멀티모달 검색 엔진이란?
멀티모달 검색 엔진은 이미지와 텍스트를 동시에 벡터화하여 의미론적 유사성 기반으로 검색하는 시스템입니다. 저는 과거에 이미지 검색 시 키워드 매칭의 한계에何度も苦しみました—사용자가 "따뜻한 커피잔"을 검색했는데 "차가운 아이스컵"이 먼저 나오는尴尬한 상황이죠.
핵심 원리:
- 이미지 벡터화: CLIP 모델이 이미지를 768차원 벡터로 변환
- 텍스트 벡터화: 같은 CLIP 공간에서 텍스트도 동일한 벡터로 변환
- 코사인 유사도: 두 벡터의 각도로 유사도 계산 (1=동일, 0=무관)
- 联合 검색: 단일 쿼리로 이미지+텍스트 결과 통합
아키텍처 설계
┌─────────────────────────────────────────────────────────────┐
│ 멀티모달 검색 아키텍처 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 이미지 입력 │ │ 텍스트 입력 │ │ 混合 쿼리 │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ HolySheep AI CLIP 임베딩 API │ │
│ │ (api.holysheep.ai/v1) │ │
│ └──────────────────────┬──────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 768차원 벡터 임베딩 │ │
│ │ [0.123, -0.456, 0.789, ... , 0.321] │ │
│ └──────────────────────┬──────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 벡터 데이터베이스 (Pinecone/Milvus) │ │
│ │ & 코사인 유사도 계산 → 순위화 결과 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
실전 구현: Python 멀티모달 검색 시스템
1. HolySheep AI API 설정 및 임베딩 생성
"""
멀티모달 검색 엔진 - HolySheep AI CLIP 임베딩 활용
저장소: https://github.com/holysheep-ai/multimodal-search
"""
import requests
import base64
import json
from typing import List, Dict, Union
from PIL import Image
import io
class HolySheepMultimodalEmbedder:
"""
HolySheep AI CLIP 모델을 사용한 멀티모달 임베딩 생성기
- 이미지 + 텍스트联合 벡터화
- 단일 API 키로 모든 모델 통합
"""
def __init__(self, api_key: str):
self.api_key = api_key
# HolySheep AI 공식 엔드포인트 (공식 OpenAI 호환)
self.base_url = "https://api.holysheep.ai/v1"
def encode_text(self, texts: List[str]) -> List[List[float]]:
"""
텍스트를 CLIP 임베딩 벡터로 변환
지연 시간: 평균 120-180ms
비용: $0.10/1M 토큰
"""
response = requests.post(
f"{self.base_url}/embeddings",
headers={
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
},
json={
"model": "clip-vit-large-patch14", # CLIP 모델 지정
"input": texts
}
)
if response.status_code != 200:
raise ValueError(f"임베딩 생성 실패: {response.text}")
result = response.json()
return [item["embedding"] for item in result["data"]]
def encode_image(self, image_source: Union[str, bytes, Image.Image]) -> List[float]:
"""
이미지를 CLIP 임베딩 벡터로 변환
- URL, 바이트, PIL Image 객체 모두 지원
- 지연 시간: 평균 180-250ms
- 비용: $0.0004/이미지
"""
# 이미지 전처리
if isinstance(image_source, str):
# URL 또는 파일 경로
if image_source.startswith("http"):
image_data = requests.get(image_source).content
else:
with open(image_source, "rb") as f:
image_data = f.read()
elif isinstance(image_source, bytes):
image_data = image_source
else:
# PIL Image 객체
buffer = io.BytesIO()
image_source.save(buffer, format="PNG")
image_data = buffer.getvalue()
# Base64 인코딩
base64_image = base64.b64encode(image_data).decode("utf-8")
response = requests.post(
f"{self.base_url}/embeddings",
headers={
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
},
json={
"model": "clip-vit-large-patch14",
"input": [{
"type": "image_url",
"image_url": {"url": f"data:image/png;base64,{base64_image}"}
}]
}
)
if response.status_code != 200:
raise ValueError(f"이미지 임베딩 실패: {response.text}")
return response.json()["data"][0]["embedding"]
def batch_encode_images(self, image_sources: List) -> List[List[float]]:
"""배치 이미지 임베딩 - 대량 처리 시 권장"""
encoded_images = []
for img in image_sources:
try:
vector = self.encode_image(img)
encoded_images.append(vector)
except Exception as e:
print(f"배치 처리 중 오류: {e}")
encoded_images.append(None)
return encoded_images
사용 예시
if __name__ == "__main__":
API_KEY = "YOUR_HOLYSHEEP_API_KEY"
embedder = HolySheepMultimodalEmbedder(API_KEY)
# 텍스트 임베딩 테스트
text_embeddings = embedder.encode_text([
"따뜻한 커피잔",
"차가운 아이스 음료",
"아침 해돋이"
])
print(f"✅ 텍스트 임베딩 완료: {len(text_embeddings)}개 벡터")
print(f" 벡터 차원: {len(text_embeddings[0])}")
# 이미지 URL 임베딩 테스트
try:
img_embedding = embedder.encode_image(
"https://example.com/sample-coffee.jpg"
)
print(f"✅ 이미지 임베딩 완료: 차원 {len(img_embedding)}")
except Exception as e:
print(f"⚠️ 이미지 임베딩 실패: {e}")
2.联合 검색 시스템 구현
"""
이미지 + 텍스트联合 검색 시스템
벡터 유사도 기반 검색 & 순위화
"""
import numpy as np
from numpy.linalg import norm
from dataclasses import dataclass
from typing import List, Optional, Tuple
from enum import Enum
class SearchMode(Enum):
IMAGE_ONLY = "image"
TEXT_ONLY = "text"
JOINT = "joint" # 이미지+텍스트 加權融合
@dataclass
class SearchResult:
item_id: str
score: float
metadata: dict
class MultimodalSearchEngine:
"""
CLIP 공간 기반 멀티모달 검색 엔진
- 이미지-텍스트 联合検索
- 코사인 유사도 사용
- 가중치 조절 가능
"""
def __init__(self, embedder: HolySheepMultimodalEmbedder):
self.embedder = embedder
self.vector_store: List[dict] = [] # {id, vector, type, metadata}
def add_item(
self,
item_id: str,
content: Union[str, Image.Image, bytes],
content_type: str = "auto",
metadata: Optional[dict] = None
):
"""검색 인덱스에 항목 추가"""
if content_type == "auto":
if isinstance(content, (str, bytes)):
content_type = "text" if isinstance(content, str) else "image"
else:
content_type = "image"
if content_type == "text":
vector = self.embedder.encode_text([content])[0]
else:
vector = self.embedder.encode_image(content)
self.vector_store.append({
"id": item_id,
"vector": vector,
"type": content_type,
"content": content,
"metadata": metadata or {}
})
def cosine_similarity(self, a: List[float], b: List[float]) -> float:
"""코사인 유사도 계산"""
a = np.array(a)
b = np.array(b)
return np.dot(a, b) / (norm(a) * norm(b))
def search(
self,
query: Optional[str] = None,
query_image: Optional[Union[str, Image.Image]] = None,
mode: SearchMode = SearchMode.JOINT,
image_weight: float = 0.5,
top_k: int = 10
) -> List[SearchResult]:
"""
멀티모달 검색 수행
Args:
query: 텍스트 쿼리
query_image: 이미지 쿼리
mode: 검색 모드
image_weight: 이미지 가중치 (0.0~1.0)
top_k: 반환 결과 수
"""
query_vectors = []
# 쿼리 벡터 생성
if query and mode in [SearchMode.TEXT_ONLY, SearchMode.JOINT]:
text_vec = self.embedder.encode_text([query])[0]
query_vectors.append(("text", text_vec, 1 - image_weight))
if query_image and mode in [SearchMode.IMAGE_ONLY, SearchMode.JOINT]:
img_vec = self.embedder.encode_image(query_image)
query_vectors.append(("image", img_vec, image_weight))
if not query_vectors:
raise ValueError("최소 하나의 쿼리(텍스트 또는 이미지)를 제공해야 합니다")
# 각 항목과의 유사도 계산
results = []
for item in self.vector_store:
if mode == SearchMode.JOINT:
# 가중 平均 계산
scores = []
weights = []
for qtype, qvec, weight in query_vectors:
sim = self.cosine_similarity(qvec, item["vector"])
scores.append(sim)
weights.append(weight)
final_score = sum(s * w for s, w in zip(scores, weights)) / sum(weights)
else:
qtype, qvec, _ = query_vectors[0]
final_score = self.cosine_similarity(qvec, item["vector"])
results.append(SearchResult(
item_id=item["id"],
score=final_score,
metadata=item["metadata"]
))
# 점수 순으로 정렬
results.sort(key=lambda x: x.score, reverse=True)
return results[:top_k]
def search_with_filters(
self,
query: str,
content_types: Optional[List[str]] = None,
metadata_filters: Optional[dict] = None,
top_k: int = 10
) -> List[SearchResult]:
"""메타데이터 필터링이 포함된 검색"""
results = self.search(query=query, top_k=100)
filtered = []
for r in results:
item = next(i for i in self.vector_store if i["id"] == r.item_id)
# 컨텐츠 타입 필터
if content_types and item["type"] not in content_types:
continue
# 메타데이터 필터
if metadata_filters:
match = all(
item["metadata"].get(k) == v
for k, v in metadata_filters.items()
)
if not match:
continue
filtered.append(r)
if len(filtered) >= top_k:
break
return filtered
실전 사용 예시
if __name__ == "__main__":
# HolySheep AI 초기화
API_KEY = "YOUR_HOLYSHEEP_API_KEY"
embedder = HolySheepMultimodalEmbedder(API_KEY)
search_engine = MultimodalSearchEngine(embedder)
# 테스트 데이터 추가
search_engine.add_item(
item_id="img_001",
content="https://example.com/coffee-morning.jpg",
content_type="image",
metadata={"category": "food", "tag": "coffee"}
)
search_engine.add_item(
item_id="img_002",
content="차가운 아이스 라떼",
content_type="text",
metadata={"category": "food", "tag": "coffee"}
)
search_engine.add_item(
item_id="img_003",
content="따뜻한 핫초코",
content_type="text",
metadata={"category": "food", "tag": "chocolate"}
)
# 텍스트 검색
print("=== 텍스트 검색: '따뜻한 음료' ===")
results = search_engine.search(query="따뜻한 음료", top_k=3)
for r in results:
print(f" {r.item_id}: {r.score:.4f}")
# 이미지 검색
print("\n=== 이미지 검색 ===")
results = search_engine.search(
query_image="https://example.com/search-image.jpg",
mode=SearchMode.IMAGE_ONLY,
top_k=3
)
for r in results:
print(f" {r.item_id}: {r.score:.4f}")
# 이미지+텍스트联合 검색 (가중치 0.6:0.4)
print("\n=== 联合 검색 (이미지 60%, 텍스트 40%) ===")
results = search_engine.search(
query="음료",
query_image="https://example.com/style-image.jpg",
mode=SearchMode.JOINT,
image_weight=0.6,
top_k=5
)
for r in results:
print(f" {r.item_id}: {r.score:.4f}")
실전 应用 사례
전자상거래 商品 검색 시스템
"""
전자상거래 멀티모달 商品 검색 API
- 이미지 업로드로 유사 商品 검색
- 텍스트 설명으로 semantic 검색
- HolySheep AI 비용 최적화 적용
"""
from fastapi import FastAPI, UploadFile, File, Form, HTTPException
from fastapi.responses import JSONResponse
import httpx
import asyncio
from datetime import datetime
app = FastAPI(title="멀티모달 商品 검색 API")
HolySheep AI 설정
HOLYSHEEP_API_KEY = "YOUR_HOLYSHEEP_API_KEY"
HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1"
비용 추적
COST_TRACKER = {
"text_embedding_calls": 0,
"image_embedding_calls": 0,
"total_cost_usd": 0.0
}
EMBEDDING_COST = {
"text_per_1m_tokens": 0.10, # $0.10/1M 토큰
"image_per_call": 0.0004 # $0.0004/이미지
}
class EmbeddingService:
"""HolySheep AI 임베딩 서비스 래퍼"""
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = HOLYSHEEP_BASE_URL
async def get_text_embedding(self, texts: List[str]) -> List[List[float]]:
"""비동기 텍스트 임베딩"""
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(
f"{self.base_url}/embeddings",
headers={"Authorization": f"Bearer {self.api_key}"},
json={
"model": "clip-vit-large-patch14",
"input": texts
}
)
if response.status_code != 200:
raise HTTPException(
status_code=response.status_code,
detail=f"임베딩 실패: {response.text}"
)
# 비용 계산
COST_TRACKER["text_embedding_calls"] += 1
total_tokens = sum(len(t.split()) for t in texts) * 1.3 # 추정 토큰
COST_TRACKER["total_cost_usd"] += (total_tokens / 1_000_000) * EMBEDDING_COST["text_per_1m_tokens"]
return [item["embedding"] for item in response.json()["data"]]
async def get_image_embedding(self, image_base64: str) -> List[float]:
"""비동기 이미지 임베딩"""
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(
f"{self.base_url}/embeddings",
headers={"Authorization": f"Bearer {self.api_key}"},
json={
"model": "clip-vit-large-patch14",
"input": [{
"type": "image_url",
"image_url": {"url": f"data:image/png;base64,{image_base64}"}
}]
}
)
if response.status_code != 200:
raise HTTPException(
status_code=response.status_code,
detail=f"이미지 임베딩 실패: {response.text}"
)
# 비용 계산
COST_TRACKER["image_embedding_calls"] += 1
COST_TRACKER["total_cost_usd"] += EMBEDDING_COST["image_per_image"]
return response.json()["data"][0]["embedding"]
전역 서비스 인스턴스
embedding_service = EmbeddingService(HOLYSHEEP_API_KEY)
search_engine = MultimodalSearchEngine(
HolySheepMultimodalEmbedder(HOLYSHEEP_API_KEY)
)
@app.post("/api/v1/search")
async def search_products(
query: str = Form(None),
image: UploadFile = File(None),
search_mode: str = Form("joint"),
top_k: int = Form(10)
):
"""
멀티모달 商品 검색 엔드포인트
Params:
query: 텍스트 검색어 (선택)
image: 검색용 이미지 파일 (선택)
search_mode: 'text', 'image', 'joint' (기본값: joint)
top_k: 반환 결과 수 (기본값: 10)
Returns:
검색 결과 목록 (상품 ID, 유사도 점수, 메타데이터)
"""
start_time = datetime.now()
try:
query_image_base64 = None
if image:
contents = await image.read()
query_image_base64 = base64.b64encode(contents).decode("utf-8")
# 검색 모드에 따른 검색
if search_mode == "text" and query:
results = search_engine.search(query=query, top_k=top_k)
elif search_mode == "image" and query_image_base64:
results = search_engine.search(
query_image=query_image_base64,
mode=SearchMode.IMAGE_ONLY,
top_k=top_k
)
elif search_mode == "joint" and (query or query_image_base64):
results = search_engine.search(
query=query,
query_image=query_image_base64,
mode=SearchMode.JOINT,
top_k=top_k
)
else:
raise HTTPException(
status_code=400,
detail="query 또는 image 중 하나 이상 제공 필요"
)
elapsed_ms = (datetime.now() - start_time).total_seconds() * 1000
return JSONResponse({
"success": True,
"results": [
{
"item_id": r.item_id,
"score": round(r.score, 4),
"metadata": r.metadata
}
for r in results
],
"performance": {
"latency_ms": round(elapsed_ms, 2),
"search_mode": search_mode
}
})
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/v1/stats")
async def get_stats():
"""비용 및 사용량 통계 반환"""
return JSONResponse({
"text_embedding_calls": COST_TRACKER["text_embedding_calls"],
"image_embedding_calls": COST_TRACKER["image_embedding_calls"],
"total_cost_usd": round(COST_TRACKER["total_cost_usd"], 6),
"estimated_monthly_cost": round(COST_TRACKER["total_cost_usd"] * 1000, 2) # 확장 예측
})
실행: uvicorn main:app --host 0.0.0.0 --port 8000
성능 벤치마크 및 비용 분석
| 작업 유형 | 평균 지연 | HolySheep 비용 | 공식 API 비용 | 절감율 |
|---|---|---|---|---|
| 텍스트 임베딩 (10 토큰) | 85-120ms | $0.0000010 | $0.0000010 | 동일 |
| 이미지 임베딩 1회 | 180-250ms | $0.0004 | $0.0004 | 동일 |
| 1,000개 이미지 배치 처리 | 3-5분 | $0.40 | $0.40 | 동일 |
| 联合 검색 (100회/일) | 200-300ms | $0.08/일 | $0.08/일 | - |
| 월간 100,000회 검색 | - | $80/월 | $80/월 | 결제 편의성 이점 |
💡 비용 최적화 팁:
- 배치 처리: 이미지 임베딩을 일괄 처리하면 API 호출 오버헤드 감소
- 캐싱: 동일 이미지 재검색 시 locally 캐싱하여 API 호출 60% 절감 가능
- 벡터 DB 활용: Pinecone/Milvus에 미리 임베딩 저장 → 런타임 비용 90% 절감
- HolySheep 로컬 결제: 해외 신용카드 불필요 → 환전 수수료 절약
자주 발생하는 오류와 해결책
오류 1: 이미지 임베딩 시 "Invalid image format" 오류
# ❌ 잘못된 예시 - Base64 인코딩 누락
response = requests.post(
f"{self.base_url}/embeddings",
json={
"model": "clip-vit-large-patch14",
"input": [{"type": "image_url", "image_url": {"url": image_path}}]
}
)
✅ 올바른 예시 - Base64 Data URI 형식
import base64
def image_to_base64(image_path):
with open(image_path, "rb") as f:
return base64.b64encode(f.read()).decode("utf-8")
response = requests.post(
f"{self.base_url}/embeddings",
json={
"model": "clip-vit-large-patch14",
"input": [{
"type": "image_url",
"image_url": {"url": f"data:image/png;base64,{image_to_base64(image_path)}"}
}]
}
)
지원 형식: PNG, JPEG, WEBP (PNG 권장 - 무손실 압축)
오류 2: API 키 인증 실패 "401 Unauthorized"
# ❌ 잘못된 예시 - 잘못된 헤더 형식
headers = {"API_KEY": "YOUR_HOLYSHEEP_API_KEY"} # Authorization 아님
❌ 잘못된 예시 - Bearer 토큰 누락
headers = {"Authorization": "YOUR_HOLYSHEEP_API_KEY"} # Bearer 없음
✅ 올바른 예시 - HolySheep AI 표준 인증
headers = {
"Authorization": f"Bearer {HOLYSHEEP_API_KEY}",
"Content-Type": "application/json"
}
✅ API 키 유효성 검사 함수
def validate_holysheep_key(api_key: str) -> bool:
"""HolySheep AI API 키 유효성 검사"""
response = requests.get(
"https://api.holysheep.ai/v1/models",
headers={"Authorization": f"Bearer {api_key}"}
)
return response.status_code == 200
사용
if not validate_holysheep_key("YOUR_HOLYSHEEP_API_KEY"):
raise ValueError("유효하지 않은 API 키입니다. https://www.holysheep.ai/register 에서 가입하세요")
오류 3: 벡터 차원 불일치 "Dimension mismatch"
# ❌ 잘못된 예시 - 다른 모델의 벡터 혼합
text_vec = embedder.encode_text(["hello"])[0] # text-embedding-ada-002 (1536차원)
img_vec = embedder.encode_image(img)[0] # CLIP (768차원)
❌ 검색 시 차원 불일치 발생
score = cosine_similarity(text_vec, img_vec) # 오류!
✅ 올바른 예시 - 동일한 CLIP 모델 사용
class UnifiedMultimodalEmbedder:
"""모든 입력을 CLIP 공간으로 통일"""
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://api.holysheep.ai/v1"
self.model = "clip-vit-large-patch14" # 일관된 모델 사용
def encode(self, content: Union[str, Image.Image, bytes]) -> List[float]:
"""모든 입력 타입을 CLIP 768차원 벡터로 변환"""
if isinstance(content, str):
return self._encode_text([content])[0]
else:
return self._encode_image(content)
def _encode_text(self, texts: List[str]) -> List[List[float]]:
response = requests.post(
f"{self.base_url}/embeddings",
headers={"Authorization": f"Bearer {self.api_key}"},
json={"model": self.model, "input": texts}
)
return [item["embedding"] for item in response.json()["data"]]
def _encode_image(self, image_source) -> List[float]:
# 이미지 → Base64 변환 로직
base64_img = prepare_image(image_source)
response = requests.post(
f"{self.base_url}/embeddings",
headers={"Authorization": f"Bearer {self.api_key}"},
json={
"model": self.model,
"input": [{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{base64_img}"}}]
}
)
return response.json()["data"][0]["embedding"]
이제 모든 벡터가 768차원 CLIP 공간으로 통일됨
embedder = UnifiedMultimodalEmbedder("YOUR_HOLYSHEEP_API_KEY")
text_vec = embedder.encode("따뜻한 커피") # 768차원
img_vec = embedder.encode(Image.open("a.jpg")) # 768차원
score = cosine_similarity(text_vec, img_vec) # ✅ 정상 작동
오류 4: Rate Limit 초과 "429 Too Many Requests"
# ❌ 잘못된 예시 - 동시 요청 폭주
async def bad_request(images):
tasks = [encode_image(img) for img in images] # 동시 100개 요청
await asyncio.gather(*tasks) # Rate Limit 발생!
✅ 올바른 예시 - Rate Limit 핸들링 및 지수 백오프
import asyncio
import time
from functools import wraps
class RateLimitedEmbedder:
"""속도 제한이 있는 임베딩 서비스"""
MAX_REQUESTS_PER_MINUTE = 50
request_timestamps = []
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://api.holysheep.ai/v1"
def _check_rate_limit(self):
"""1분 간 요청 수 확인"""
now = time.time()
# 1분 이전 요청 기록 삭제
self.request_timestamps = [
ts for ts in self.request_timestamps
if now - ts < 60
]
if len(self.request_timestamps) >= self.MAX_REQUESTS_PER_MINUTE:
oldest = self.request_timestamps[0]
wait_time = 60 - (now - oldest) + 1
print(f"Rate limit 도달. {wait_time:.1f}초 대기...")
time.sleep(wait_time)
self._check_rate_limit()
async def encode_with_retry(self, content, max_retries=3):
"""재시도 로직이 포함된 임베딩"""
for attempt in range(max_retries):
try:
self._check_rate_limit()
result = await self._do_encode(content)
self.request_timestamps.append(time.time())
return result
except Exception as e:
if "429" in str(e) and attempt < max_retries - 1:
# 지수 백오프
wait = (2 ** attempt) + random.uniform(0, 1)
print(f"Rate limit 재시도 ({attempt+1}/{max_retries}), {wait:.1f}초 후...")
await asyncio.sleep(wait)
else:
raise
async def batch_encode(self, items: List, batch_size: int = 10):
"""배치 처리로 Rate Limit 우회"""
results = []
for i in range(0, len(items), batch_size):
batch = items[i:i+batch_size]
print(f"배치 {i//batch_size + 1}: {len(batch)}개 처리 중...")
batch_results = await asyncio.gather(*[
self.encode_with_retry(item) for item in batch
])
results.extend(batch_results)
# 배치 간 대기 (Rate Limit 방지)
if i + batch_size < len(items):
await asyncio.sleep(1)
return results
사용
embedder = RateLimitedEmbedder