RAG 챗봇 만들기 5 - 임베딩 이해하기

learning by Seven Fingers Studio 10분
RAG임베딩Embedding벡터OpenAILangChain

블로그 대표 이미지

임베딩 처음 들었을 때 “텍스트를 숫자로 바꾼다”는 게 이해가 안 됐어요. 근데 막상 해보니까 그냥 API 호출 한 번이더라고요. 개념이 어려워 보여도 코드는 간단해요.

오늘은 RAG의 핵심인 임베딩(Embedding)을 배울 거예요. 텍스트를 숫자로 바꿔서 비교할 수 있게 만드는 기술이에요.

임베딩이 뭐예요?

간단한 설명

텍스트를 숫자 배열(벡터)로 바꾸는 거예요.

"연차 휴가 규정" → [0.23, -0.15, 0.87, 0.42, ..., 0.11]
                   (1536개의 숫자)

왜 숫자로 바꿔요?

컴퓨터는 텍스트를 직접 비교하기 어려워요. 숫자로 바꾸면:

  • 유사도 계산 가능: 두 벡터가 얼마나 비슷한지 수학적으로 계산
  • 빠른 검색: 수백만 개 중에서 비슷한 것 빨리 찾기
  • 의미 반영: 비슷한 의미의 텍스트는 비슷한 벡터가 됨

의미가 반영된다는 게 뭐예요?

"강아지"의 벡터와 "개"의 벡터 → 거의 비슷함 (같은 의미니까)
"강아지"의 벡터와 "자동차"의 벡터 → 많이 다름 (다른 의미니까)

이게 가능한 이유는 임베딩 모델이 엄청난 양의 텍스트로 학습해서 의미를 파악하기 때문이에요.

임베딩 해보기

OpenAI 임베딩 사용

from langchain_openai import OpenAIEmbeddings

# 임베딩 모델 초기화
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")

# 텍스트를 벡터로 변환
text = "연차 휴가 규정에 대해 알려주세요"
vector = embeddings.embed_query(text)

print(f"벡터 차원: {len(vector)}")
print(f"벡터 일부: {vector[:5]}")

실행 결과:

벡터 차원: 1536
벡터 일부: [0.0023, -0.0156, 0.0087, 0.0042, -0.0089]

1536개의 숫자로 된 벡터가 나왔어요!

블로그 대표 이미지

유사도 계산해보기

두 텍스트가 얼마나 비슷한지 계산해볼게요.

코사인 유사도

벡터 간의 각도로 유사도를 측정해요. 1에 가까울수록 비슷해요.

import numpy as np
from langchain_openai import OpenAIEmbeddings

def cosine_similarity(vec1, vec2):
    """코사인 유사도 계산"""
    return np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2))

# 임베딩 모델
embeddings = OpenAIEmbeddings()

# 세 개의 텍스트 준비
text1 = "연차 휴가를 신청하고 싶어요"
text2 = "휴가 사용 방법 알려주세요"
text3 = "점심 메뉴 추천해주세요"

# 임베딩
vec1 = embeddings.embed_query(text1)
vec2 = embeddings.embed_query(text2)
vec3 = embeddings.embed_query(text3)

# 유사도 계산
sim_12 = cosine_similarity(vec1, vec2)
sim_13 = cosine_similarity(vec1, vec3)

print(f"'{text1}' vs '{text2}'")
print(f"  유사도: {sim_12:.4f}")
print(f"\n'{text1}' vs '{text3}'")
print(f"  유사도: {sim_13:.4f}")

실행 결과:

'연차 휴가를 신청하고 싶어요' vs '휴가 사용 방법 알려주세요'
유사도: 0.9234
'연차 휴가를 신청하고 싶어요' vs '점심 메뉴 추천해주세요'
유사도: 0.7821

휴가 관련 텍스트끼리는 유사도가 높고, 점심 메뉴랑은 낮네요!

여러 문서 한 번에 임베딩

청크들을 한 번에 임베딩할 수 있어요:

from langchain_openai import OpenAIEmbeddings

embeddings = OpenAIEmbeddings()

# 여러 텍스트 준비
texts = [
    "연차는 매년 15일 부여됩니다.",
    "병가는 연간 60일까지 사용 가능합니다.",
    "경조사 휴가는 결혼 5일, 출산 10일입니다."
]

# 한 번에 임베딩
vectors = embeddings.embed_documents(texts)

print(f"임베딩된 문서 수: {len(vectors)}")
print(f"각 벡터 차원: {len(vectors[0])}")

실행 결과:

임베딩된 문서 수: 3
각 벡터 차원: 1536

임베딩 모델 선택

여러 임베딩 모델이 있어요:

OpenAI 모델

from langchain_openai import OpenAIEmbeddings

# 기본 모델 (추천)
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")

# 최신 모델 (더 좋지만 비쌈)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
모델차원비용특징
text-embedding-ada-0021536저렴가성비 좋음
text-embedding-3-small1536중간성능 향상
text-embedding-3-large3072비쌈최고 성능

무료 대안: HuggingFace

from langchain_community.embeddings import HuggingFaceEmbeddings

# 로컬에서 실행 (무료!)
embeddings = HuggingFaceEmbeddings(
    model_name="sentence-transformers/all-MiniLM-L6-v2"
)

무료지만 처음 실행할 때 모델 다운로드에 시간이 좀 걸려요.

비용 절감 팁

임베딩 API는 호출할 때마다 비용이 들어요.

배치로 처리

한 번에 여러 텍스트를 보내면 효율적이에요:

# 비효율적 (API 호출 100번)
for text in texts:
    vector = embeddings.embed_query(text)

# 효율적 (API 호출 1번)
vectors = embeddings.embed_documents(texts)

캐싱

같은 텍스트를 반복해서 임베딩하지 않도록:

from langchain.embeddings import CacheBackedEmbeddings
from langchain.storage import LocalFileStore

# 캐시 저장소
store = LocalFileStore("./embeddings_cache/")

# 캐시 적용 임베딩
cached_embeddings = CacheBackedEmbeddings.from_bytes_store(
    embeddings,
    store,
    namespace=embeddings.model
)

RAG에서 임베딩이 쓰이는 곳

정리하면 임베딩은 두 군데서 쓰여요:

1. 문서 저장할 때

[문서 청크들] → [임베딩] → [벡터 저장소에 저장]

2. 검색할 때

[사용자 질문] → [임베딩] → [벡터 저장소에서 유사한 것 검색]

질문과 문서를 같은 임베딩 모델로 변환해야 제대로 비교할 수 있어요.

운영자 실전 노트

실제 프로젝트 진행하며 겪은 문제

  • 청크당 토큰 제한(8191) 초과 에러 → chunk_size를 줄여서 해결. 임베딩 전 토큰 수 검증 필요
  • 1000개 문서 임베딩하며 API 비용 폭발 → 배치 처리(embed_documents)와 캐싱으로 80% 절감
  • 같은 문서 재처리로 중복 비용 발생 → CacheBackedEmbeddings로 로컬 캐시 구축
  • 무료 HuggingFace 모델 써봤는데 느림 → 초기 프로토타입에는 괜찮지만 프로덕션에선 OpenAI 권장

이 경험을 통해 알게 된 점

  • 임베딩 비용 관리가 핵심. 배치 처리와 캐싱 없이는 비용 부담 큼
  • text-embedding-ada-002가 가성비 최고. 3-small/large는 성능 향상 미미한 편
  • 캐싱 전략은 필수 — 같은 문서 반복 처리 방지로 비용 크게 절감

마무리

임베딩은 개념은 복잡해 보이지만 실제 구현은 API 호출 한 줄이다. 초반엔 OpenAI 임베딩으로 시작하고, 비용이 부담되면 캐싱과 배치 처리로 최적화한다.

다음 편에서는 임베딩한 벡터를 저장하고 검색하는 “벡터 저장소”를 배운다. 파일 기반으로 간단하게 시작해본다.


RAG 챗봇 만들기 시리즈:

← 블로그 목록으로