byteforce

CPN 한국어 자습서 · 러닝패스 2 / 4 — Building with the Claude API

RAG

텍스트 임베딩

Text embeddings

관련 청크를 찾는 가장 흔한 방법이 시맨틱 검색입니다. 정확한 단어 일치를 보는 키워드 검색과 달리, 시맨틱 검색은 질문과 청크의 의미를 이해합니다. 그 바탕이 텍스트 임베딩 — 텍스트의 의미를 숫자 벡터로 바꾼 것입니다. 이번 장은 VoyageAI의 voyage-3-large로 청크를 임베딩해 봅니다.

전체 내레이션영상 나레이션 한국어 번역 (전체)

Stephen Grider · Anthropic 기술 스태프

지난 장에서 문서를 청크로 쪼갰습니다. 이제 사용자 질문과 가장 관련 있는 청크를 어떻게 찾을지 봅니다. 가장 흔한 방법은 시맨틱 검색(semantic search)입니다. 시맨틱 검색은 키워드 검색과 다릅니다. 키워드 검색은 정확히 같은 단어가 들어 있는지를 보지만, 시맨틱 검색은 사용자 질문과 각 청크의 의미와 맥락을 이해해 관련성을 따집니다. 예를 들어 질문에 "버그"라는 단어가 없어도, 의미가 가까운 "결함"·"오류"가 든 청크를 찾아낼 수 있습니다.

시맨틱 검색을 가능하게 하는 핵심이 텍스트 임베딩(text embedding)입니다. 텍스트 임베딩은 텍스트에 담긴 의미를 숫자로 표현한 것입니다. 단어와 문장을 컴퓨터가 수학적으로 다룰 수 있는 형태 — 숫자들의 리스트(벡터)로 바꿉니다. 예를 들어 "I'm very happy today!"는 [0.275, -0.05, ...] 같은 벡터로, "That movie wasn't great"는 [0.597, 0.095, ...] 같은 벡터로 바뀝니다. 임베딩 모델이 텍스트를 입력으로 받아 이런 숫자 리스트를 출력합니다.

여기서 중요한 직관이 있습니다. 임베딩 모델은 의미가 비슷한 텍스트에 비슷한 벡터를 부여합니다. 그래서 두 텍스트의 벡터가 수학적으로 가까우면, 두 텍스트의 의미도 가깝다고 볼 수 있습니다. 질문을 임베딩하고 각 청크를 임베딩한 뒤, 질문 벡터에 가장 가까운 청크 벡터를 찾으면 — 그게 바로 의미상 가장 관련 있는 청크입니다. 벡터를 어떻게 "비교"하는지는 다음 장의 주제입니다.

구현은 VoyageAI 라이브러리를 씁니다. 먼저 %pip install voyageai로 설치한 뒤, load_dotenv()로 API 키를 불러오고 voyageai.Client()로 클라이언트를 만듭니다. 임베딩 모델은 voyage-3-large를 사용합니다. 텍스트 청크에 함수를 한 번 돌리면, 그 의미를 표현하는 부동소수 숫자들의 리스트가 돌아옵니다.

그래서 작은 헬퍼 generate_embedding을 만듭니다. 텍스트 하나를 받아 client.embed([text], model=..., input_type=...)를 호출하고, 결과의 첫 번째 임베딩을 돌려줍니다. input_type은 이 텍스트가 검색 질의(query)인지 저장할 문서(document)인지를 모델에 알려 주는 힌트입니다. 같은 임베딩 모델이라도 질의용·문서용을 구분하면 검색 품질이 조금 더 좋아집니다.

마지막으로 지난 장의 chunk_by_section으로 report.md를 청크로 쪼개고, 첫 번째 청크에 generate_embedding을 돌려 봅니다. 그러면 화면 가득 긴 부동소수 숫자 리스트가 출력됩니다. 이게 그 청크의 의미를 담은 벡터입니다. 임베딩 자체는 이렇게 빠르고 단순합니다. 진짜 과제는 이 임베딩들을 어떻게 비교해 사용자 질문과 가장 비슷한 청크를 찾는지 — 다음 장에서 전체 RAG 흐름과 함께 다룹니다.

이 장에서 배우는 것What you'll learn

약 7분
1

시맨틱 검색 = 단어 일치가 아니라 의미로 관련 청크 찾기

2

텍스트 임베딩 = 텍스트의 의미를 담은 숫자 벡터

3

의미가 비슷하면 벡터도 비슷 — 이게 검색의 핵심 직관

4

VoyageAI voyageai.Client() + voyage-3-large 모델

5

input_type = query(질의) · document(문서) 구분 힌트

6

generate_embedding(chunk) → 긴 부동소수 리스트 = 그 청크의 벡터

먼저 짚고 갈 용어
임베딩 (embedding)
텍스트의 의미를 숫자 벡터로 표현한 것. 의미가 가까우면 벡터도 가깝다.
벡터 (vector)
숫자들의 리스트. 임베딩 모델의 출력 형태로, 고차원 공간의 한 점.
시맨틱 검색 (semantic search)
단어 일치가 아니라 의미·맥락으로 관련 텍스트를 찾는 검색.
voyage-3-large
VoyageAI의 임베딩 모델. 텍스트를 받아 의미 벡터를 출력한다.
input_type
임베딩 대상이 질의(query)인지 문서(document)인지 알려 주는 힌트.

시맨틱 검색이란

Semantic search

청크를 만들었으니, 이제 질문과 가장 관련 있는 청크를 찾을 차례입니다. 가장 흔한 방법이 시맨틱 검색 — 정확한 단어 일치가 아니라 의미로 관련성을 따집니다.

시맨틱 vs 어휘 · 같은 질문, 다른 검색

어휘(키워드) 검색단어 일치

질문에 든 단어가 그대로 들어 있는 청크를 찾습니다.

질의: "소프트웨어 결함을 몇 개 고쳤나?"

"결함"이라는 단어가 청크에 없으면 — 정작 bug를 고쳤다는 청크를 놓칠 수 있습니다.

시맨틱 검색의미 일치

질문과 청크를 임베딩해 의미가 가까운 청크를 찾습니다.

질의: "소프트웨어 결함을 몇 개 고쳤나?"

"결함"과 bug·오류는 의미가 가까워 벡터도 가깝습니다 — 단어가 달라도 맞는 청크를 찾습니다.

RAG에서 관련 청크를 찾을 때 주로 시맨틱 검색을 쓰는 이유입니다. 단어가 정확히 일치하지 않아도 의미로 연결하기 때문입니다. (뒤 장에서 두 방식을 함께 쓰는 하이브리드도 다룹니다.)

텍스트 임베딩

Text embeddings

시맨틱 검색을 가능하게 하는 핵심이 텍스트 임베딩입니다. 텍스트의 의미를 숫자 벡터로 바꿉니다 — 의미가 비슷하면 벡터도 비슷합니다.

임베딩 흐름 · 문장을 클릭하면 그 벡터가 켜집니다
오늘 정말 행복해요!I'm very happy today!
기분이 아주 좋은 하루.What a wonderful day.
그 영화는 별로였어.That movie wasn't great.
●○●임베딩 모델 voyage-3-large
[0.275, -0.05, 0.41, ...] · 1024차원
[0.262, -0.04, 0.39, ...] · 1024차원
[0.597, 0.095, -0.12, ...] · 1024차원

임베딩 모델은 텍스트를 받아 의미를 담은 숫자 벡터를 출력합니다. 1번·2번 문장은 의미가 비슷해 벡터의 첫 숫자(0.275 · 0.262)가 가깝고, 뜻이 다른 3번(0.597)은 멉니다.

VoyageAI로 임베딩 만들기

Embedding with VoyageAI

구현은 VoyageAI 라이브러리를 씁니다. 설치 후 클라이언트를 만들고, voyage-3-large 모델로 청크를 임베딩합니다.

터미널 · 라이브러리 설치
# VoyageAI 임베딩 라이브러리 설치
%pip install voyageai
002_embeddings.ipynb · 클라이언트 셋업
from dotenv import load_dotenv
import voyageai

load_dotenv()                 # .env 에서 VOYAGE_API_KEY 로드
client = voyageai.Client()     # 임베딩 클라이언트 생성

지난 장의 chunk_by_section도 그대로 가져옵니다. 그리고 임베딩 헬퍼를 정의합니다 — 텍스트 하나를 받아 의미 벡터를 돌려줍니다. input_type은 이 텍스트가 질의인지 문서인지를 모델에 알려 주는 힌트입니다.

chunk_by_section · 섹션 기준 청킹
import re

def chunk_by_section(document_text):
    pattern = r"\n## "
    return re.split(pattern, document_text)
generate_embedding · 의미 벡터 생성
def generate_embedding(text, model="voyage-3-large", input_type="query"):
    result = client.embed([text], model=model, input_type=input_type)
    return result.embeddings[0]

실제로 임베딩해 보기

Run it

report.md를 읽어 섹션으로 쪼갠 뒤, 첫 번째 청크에 generate_embedding을 돌립니다.

report.md 첫 청크 임베딩
with open("./report.md", "r") as f:
    text = f.read()

chunks = chunk_by_section(text)
generate_embedding(chunks[0])
출력 · 그 청크의 의미 벡터
[-0.0543627, 0.0146131, -0.0170223, 0.0005244, 0.0331902,
 -0.0218764, 0.0092118, -0.0411530, 0.0157744, 0.0029381,
 0.0260417, -0.0083921, 0.0194552, -0.0376610, 0.0011248,
 ... (총 1024개의 부동소수 — 이 청크의 의미를 담은 벡터)]
진짜 과제는 다음 장

임베딩 자체는 이렇게 빠르고 단순합니다. 핵심은 이 벡터들을 어떻게 비교해 질문과 가장 비슷한 청크를 찾는지입니다 — 다음 장에서 코사인 유사도·벡터 데이터베이스와 함께 전체 RAG 흐름을 봅니다.

핵심 정리
  • 시맨틱 검색은 단어 일치가 아니라 의미로 관련 청크를 찾는다.
  • 텍스트 임베딩 = 텍스트의 의미를 담은 숫자 벡터. 의미가 비슷하면 벡터도 비슷하다.
  • VoyageAI voyage-3-large로 임베딩. input_type은 query·document 힌트.
  • generate_embedding(chunk) → 긴 부동소수 리스트. 비교 방법은 다음 장.
MEMBER SESSION REQUIRED · REGISTRATION IS FREE

여기부터는 등록한 분에게 열립니다.

전 코스는 계속 무료입니다. 등록하면 이 코스의 남은 76개 레슨을 끝까지 읽을 수 있습니다.

등록하고 이어서 읽기

이미 등록하셨다면 그때 쓰신 이메일을 넣어 주세요.