BGE-M3 임베딩 API 사용 가이드
외부에서 이 서버로 텍스트 임베딩을 받아 쓰는 방법입니다. 아래 예제의 주소는 지금 접속한 주소로 자동으로 채워집니다.
개요
BAAI/bge-m3 모델로 문장·문서를 벡터로 바꿔 주는 서버입니다. 한국어, 영어를 포함해 100개 이상의 언어를 지원하고, 언어가 달라도 뜻이 같으면 가까운 벡터가 나옵니다. RAG, 의미 검색, 중복 탐지, 분류 같은 곳에 씁니다.
- dense: 문장 전체의 의미를 담은 1024차원 벡터 하나. 길이가 1로 정규화돼 있어서 내적이 곧 코사인 유사도입니다. 일반적인 벡터 검색은 이것만 쓰면 됩니다.
- sparse: 토큰별 가중치(키워드 매칭용). BM25처럼 정확한 단어 일치를 잡아 주며, dense와 섞어 하이브리드 검색에 씁니다.
- colbert: 토큰마다 1024차원 벡터(멀티 벡터). 재정렬(rerank)에 쓰면 정확도가 오르지만 응답이 큽니다.
질의(query)와 문서(passage)에 별도 접두어나 지시문을 붙일 필요가 없습니다. 둘 다 그대로 보내세요.
빠른 시작
터미널에서 바로 확인해 보세요. 인증이 꺼져 있으면 Authorization 줄은 빼도 됩니다.
# 1) 서버 상태
curl {{BASE}}/health
# 2) OpenAI 호환 임베딩 (dense)
curl {{BASE}}/v1/embeddings \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "bge-m3", "input": ["오늘 날씨 어때?", "How is the weather today?"]}'
# 3) dense + sparse 한 번에
curl {{BASE}}/embed \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"texts": ["BGE-M3는 다국어 임베딩 모델입니다."], "dense": true, "sparse": true}'인증
서버에 API 키가 설정돼 있으면 모든 API 요청에 아래 헤더가 필요합니다. 키는 서버 관리자에게 받으세요.
Authorization: Bearer YOUR_API_KEY
키가 없거나 틀리면 401이 돌아옵니다. /health, /guide, /docs는 키 없이 열립니다.
엔드포인트
POST/v1/embeddings · OpenAI 호환
OpenAI Embeddings API와 형식이 같습니다. OpenAI SDK, LangChain 같은 도구에서 base URL만 바꾸면 그대로 동작합니다. dense 벡터만 반환합니다.
| 필드 | 타입 | 설명 |
|---|---|---|
input | string | string[] | 임베딩할 텍스트. 필수. 배열이면 최대 2048개. |
model | string | 아무 값이나 가능(무시됨). 보통 "bge-m3". |
encoding_format | "float" | "base64" | 기본 float. base64는 float32 리틀엔디언 바이트로, 응답이 약 3배 작습니다. |
dimensions | int | 생략하거나 1024만 가능. |
{
"object": "list",
"data": [
{"object": "embedding", "index": 0, "embedding": [0.0123, -0.0456, ...]}
],
"model": "BAAI/bge-m3",
"usage": {"prompt_tokens": 9, "total_tokens": 9}
}POST/embed · dense / sparse / colbert
BGE-M3의 세 가지 출력을 원하는 조합으로 한 번에 받습니다.
| 필드 | 타입 | 설명 |
|---|---|---|
texts | string | string[] | 임베딩할 텍스트. 필수. 최대 2048개. |
dense | bool | 기본 true. 1024차원 벡터. |
sparse | bool | 기본 false. 토큰 가중치. |
colbert | bool | 기본 false. 토큰별 벡터. |
max_length | int | 기본 8192. 이보다 긴 입력은 잘립니다. 짧게 잡으면 빨라집니다. |
{
"model": "BAAI/bge-m3",
"usage": {"tokens": 12},
"dense": [[0.0123, -0.0456, ...]], // 텍스트당 1024개
"sparse": [{"indices": [6, 4865, 1530], "values": [0.08, 0.31, 0.27]}], // 토큰 ID → 가중치
"colbert": [[[0.01, ...], [0.02, ...]]] // 텍스트당 [토큰 수 × 1024]
}sparse의 indices/values 형식은 Qdrant, Milvus의 sparse 벡터 형식과 같아서 그대로 넣을 수 있습니다.
기타
| 경로 | 설명 |
|---|---|
GET/health | 서버 상태, 인증 사용 여부. 키 불필요. |
GET/v1/models | OpenAI 형식 모델 목록. |
GET/stats | 대시보드용 지표(JSON). 모니터링 연동에 사용. |
GET/dashboard | 실시간 상태 대시보드. |
GET/docs | Swagger 자동 문서. 브라우저에서 직접 요청을 보내 볼 수 있습니다. |
언어별 예제
Python · requests
import requests
BASE = "{{BASE}}"
HEADERS = {"Authorization": "Bearer YOUR_API_KEY"}
r = requests.post(f"{BASE}/embed", headers=HEADERS, timeout=60, json={
"texts": ["첫 번째 문서", "두 번째 문서"],
"dense": True,
"sparse": True,
})
r.raise_for_status()
data = r.json()
dense = data["dense"] # [[1024개 float], ...]
sparse = data["sparse"] # [{"indices": [...], "values": [...]}, ...]
print(len(dense), len(dense[0]), data["usage"])Python · OpenAI SDK
from openai import OpenAI
client = OpenAI(base_url="{{BASE}}/v1", api_key="YOUR_API_KEY")
resp = client.embeddings.create(model="bge-m3", input=["안녕하세요", "Hello"])
vectors = [d.embedding for d in resp.data] # 각 1024차원
print(resp.usage.total_tokens)인증이 꺼져 있어도 SDK는 api_key를 요구하므로 아무 문자열이나 넣으면 됩니다.
LangChain
from langchain_openai import OpenAIEmbeddings
emb = OpenAIEmbeddings(
model="bge-m3",
base_url="{{BASE}}/v1",
api_key="YOUR_API_KEY",
check_embedding_ctx_length=False, # 필수: 토큰 ID 대신 원문 텍스트를 보내도록
)
doc_vecs = emb.embed_documents(["문서 1", "문서 2"])
query_vec = emb.embed_query("질문")check_embedding_ctx_length=False를 빼면 LangChain이 tiktoken 토큰 ID 배열을 보내서 422 오류가 납니다.
JavaScript · Node.js 18+
const BASE = "{{BASE}}";
const res = await fetch(`${BASE}/v1/embeddings`, {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: "Bearer YOUR_API_KEY" },
body: JSON.stringify({ model: "bge-m3", input: ["안녕하세요", "Hello"] }),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
const { data } = await res.json();
console.log(data[0].embedding.length); // 1024다른 도메인의 웹페이지(브라우저)에서 직접 호출하는 것은 CORS가 열려 있지 않아 막힙니다. 백엔드 서버에서 호출하세요. API 키를 브라우저 코드에 넣지 않는 것도 같은 이유로 중요합니다.
검색에 활용하기
유사도 계산
import numpy as np
def dense_score(q, d):
"""dense: 정규화돼 있으므로 내적 = 코사인 유사도 (-1 ~ 1)"""
return float(np.dot(q, d))
def sparse_score(q, d):
"""sparse: 두 텍스트에 공통으로 있는 토큰의 가중치 곱의 합"""
dw = dict(zip(d["indices"], d["values"]))
return sum(v * dw.get(i, 0.0) for i, v in zip(q["indices"], q["values"]))
def colbert_score(q, d):
"""colbert: 질의 토큰마다 문서 토큰과의 최대 유사도를 구해 평균 (MaxSim)"""
q, d = np.asarray(q), np.asarray(d)
return float((q @ d.T).max(axis=1).mean())
# 세 점수를 섞는 하이브리드 예 (BGE-M3 저자들이 쓴 가중치)
score = 0.4 * dense_score(qd, dd) + 0.2 * sparse_score(qs, ds) + 0.4 * colbert_score(qc, dc)Qdrant 하이브리드 검색 (dense + sparse)
import requests
from qdrant_client import QdrantClient, models
BASE, HEADERS = "{{BASE}}", {"Authorization": "Bearer YOUR_API_KEY"}
def embed(texts):
r = requests.post(f"{BASE}/embed", headers=HEADERS, timeout=120,
json={"texts": texts, "dense": True, "sparse": True})
r.raise_for_status()
return r.json()
qd = QdrantClient(url="http://localhost:6333")
qd.create_collection(
"docs",
vectors_config={"dense": models.VectorParams(size=1024, distance=models.Distance.COSINE)},
sparse_vectors_config={"sparse": models.SparseVectorParams()},
)
docs = ["BGE-M3는 100개 이상의 언어를 지원한다.", "Qdrant는 벡터 데이터베이스다."]
e = embed(docs)
qd.upsert("docs", points=[
models.PointStruct(
id=i,
vector={"dense": e["dense"][i], "sparse": models.SparseVector(**e["sparse"][i])},
payload={"text": t},
)
for i, t in enumerate(docs)
])
q = embed(["다국어 임베딩 모델"])
hits = qd.query_points(
"docs",
prefetch=[
models.Prefetch(query=q["dense"][0], using="dense", limit=20),
models.Prefetch(query=models.SparseVector(**q["sparse"][0]), using="sparse", limit=20),
],
query=models.FusionQuery(fusion=models.Fusion.RRF), # 두 결과를 순위 기반으로 합침
limit=5,
)
for p in hits.points:
print(round(p.score, 3), p.payload["text"])바로 테스트
두 문장의 dense 임베딩을 받아 코사인 유사도를 계산합니다. 이 서버의 /v1/embeddings를 실제로 호출합니다.
참고로 유사도는 대략 0.75 이상이면 같은 뜻, 0.5 안팎이면 관련 있음, 0.4 이하는 거의 무관한 경우가 많습니다. 데이터에 따라 다르니 검색 임계값은 직접 확인해서 정하세요.
제한과 팁
| 항목 | 값 | 설명 |
|---|---|---|
| 요청당 텍스트 수 | 최대 2048개 | 넘으면 413. 나눠서 보내세요. |
| 텍스트 길이 | 최대 8192 토큰 | 넘는 부분은 오류 없이 잘립니다. 긴 문서는 미리 청크로 나누는 것이 검색 품질에도 좋습니다. |
| 요청 본문 크기 | 10 MB | 외부 도메인(프록시) 기준. 넘으면 413. |
| 응답 대기 시간 | 60초 | 프록시 기준. 아주 큰 배치는 시간 초과(504)가 날 수 있으니 나눠 보내세요. |
- 여러 개를 묶어서 보내세요. 텍스트 하나씩 1000번 보내는 것보다 100개씩 10번 보내는 게 훨씬 빠릅니다. 32~256개 묶음을 추천합니다.
- GPU는 한 번에 한 요청씩 처리합니다. 동시에 많이 보내면 순서대로 대기하므로, 병렬 요청 수는 2~4개면 충분합니다.
- 대량 색인에서는
encoding_format: "base64"로 응답 크기를 줄일 수 있습니다. 디코딩은np.frombuffer(base64.b64decode(s), "<f4")로 하면 됩니다. colbert는 텍스트당 [토큰 수 × 1024] 크기라 응답이 매우 큽니다. 꼭 필요할 때만 켜세요.- dense 벡터는 정규화돼 있으므로 벡터 DB의 거리 함수는 Cosine이나 Dot을 쓰면 됩니다.
오류 코드
| 코드 | 의미 | 해결 |
|---|---|---|
400 | 빈 입력, 지원하지 않는 dimensions, 출력 종류를 하나도 고르지 않음 | detail 메시지를 확인하세요. |
401 | API 키 없음 또는 틀림 | Authorization: Bearer 키 헤더를 확인하세요. |
413 | 텍스트 2048개 초과, 또는 본문 10MB 초과 | 요청을 나눠 보내세요. |
422 | JSON 형식 오류(필드 이름/타입) | 예: input에 토큰 ID 배열을 보낸 경우(LangChain 설정 확인). |
502 | 서버가 꺼져 있거나 재시작 중 | 잠시 후 다시 시도하세요. 계속되면 관리자에게 알려 주세요. |
504 | 처리 시간 60초 초과 | 배치를 줄이세요. |