임베딩: 이론 및 실습
모든 의미 검색 시스템, 모든 RAG 파이프라인, 언어와 작동하는 모든 AI 애플리케이션 천연에는 근본적인 공통점이 있습니다. 임베딩. 나는 번역이다 숫자의 의미, 텍스트 세계와 수학 세계 사이의 다리. 임베딩 없이, 데이터베이스는 "개"와 "자동차"를 구별할 수 없습니다. 임베딩을 사용하면 "개"와 "자동차"를 구분할 수 없습니다. '토스터'보다는 '고양이'에 더 가깝습니다.
이 시리즈의 첫 번째 기사에서 우리는 구성했습니다. pg벡터 그리고 저장하는 법을 배웠어요 PostgreSQL의 쿼리 벡터. 그런데 그 벡터들은 어디서 오는 걸까요? 임베딩을 생성하는 방법 품질? 그리고 무엇보다도, 사용 가능한 수십 가지 모델 중에서 어떤 모델을 선택해야 할까요? 이 기사에서 우리는 대답합니다 수학 이론부터 Python 및 PostgreSQL을 사용한 실습까지 이 모든 질문에 대해 설명합니다.
시리즈 개요
| # | Articolo | 집중하다 |
|---|---|---|
| 1 | pg벡터 | 설치, 운영자, 인덱싱 |
| 2 | 현재 위치 - 임베딩 | 모델, 거리, 세대 |
| 3 | PostgreSQL을 사용한 RAG | 엔드투엔드 RAG 파이프라인 |
| 4 | 고급 유사성 검색 | 하이브리드 검색, 필터링 |
| 5 | 인덱싱 및 성능 | HNSW, IVFFFlat, 튜닝 |
| 6 | 생산 중인 RAG | 모니터링, 확장, CI/CD |
무엇을 배울 것인가
- 임베딩이란 무엇이며 이것이 현대 AI의 기본인 이유
- 역사적 진화: 원-핫 인코딩에서 Word2Vec, GloVe, BERT 및 문장 변환기로
- 임베딩의 수학적 속성: 벡터 유추 및 의미론적 클러스터링
- 공식과 사용 사례가 포함된 4가지 거리 측정항목
- Python으로 임베딩을 생성하는 방법: 로컬 및 API를 통해
- pgVector를 사용하여 PostgreSQL에서 임베딩을 저장하고 쿼리하는 방법
- 다중 모드 임베딩: 텍스트, 이미지, 오디오 및 코드
- 임베딩 모델(MTEB)의 품질을 평가하는 방법
- 수백만 개의 문서에 대한 비용 및 확장 전략
1. 임베딩이란 무엇입니까?
Un 삽입 객체의 조밀한 벡터 표현(단어, 문장, 문서, 이미지)를 축소된 차원으로 연속된 공간에 배치합니다. 실제로는 배열입니다. 해당 객체의 "의미"를 포착하는 부동 소수점 숫자입니다.
# L'embedding della frase "Il gatto dorme sul divano"
# generato con text-embedding-3-small di OpenAI (1536 dimensioni)
embedding = [
0.0231, -0.0456, 0.0891, -0.0123, 0.0567, -0.0234,
0.0789, -0.0345, 0.0123, -0.0678, 0.0456, -0.0891,
# ... altri 1524 valori ...
]
print(f"Tipo: {type(embedding)}") # <class 'list'>
print(f"Dimensioni: {len(embedding)}") # 1536
핵심 통찰력은 다음과 같습니다: 잘 훈련된 벡터 공간에서 기하학적 거리 두 벡터 사이는 다음을 반영합니다. 의미론적 유사성 그들이 나타내는 개념 중. 문구 의미가 비슷한 문장은 가까운 벡터를 가지게 되고, 의미가 다른 문장은 멀리 떨어져 있게 됩니다.
임베딩의 특성
| 재산 | 설명 | Esempio |
|---|---|---|
| 밀집한 | 각 차원에는 0이 아닌 값이 있습니다. | [0.023, -0.045, 0.089, ...] |
| 계속하다 | 이산 값이 아닌 실제 값 | 각 구성 요소는 float32/float16입니다. |
| 고정 차원 | 동일한 모델은 항상 동일한 길이의 벡터를 생성합니다. | 384, 768, 1536 또는 3072 치수 |
| 의미상 중요한 | 벡터 사이의 거리는 의미의 관계를 반영합니다. | sim("고양이", "고양이") > sim("고양이", "자동차") |
임베딩 공간을 맵으로 생각하면 유사한 개념이 "이웃"을 형성합니다. 한 지역에는 동물이 있고, 다른 지역에는 차량이 있고, 또 다른 지역에는 감정이 있습니다. 그러나 아름다움 이러한 관계는 훈련을 통해 자동으로 나타난다는 사실에 있습니다. 수동으로 프로그래밍되었습니다.
2. 단어에서 벡터로: 역사적 진화
임베딩의 역사는 점점 더 정교해지는 아이디어의 발전입니다. 이는 이전 버전의 한계를 해결합니다. 이러한 진화를 이해하면 이유를 이해하는 데 도움이 됩니다. 현대 모델이 너무 잘 작동합니다.
2.1 원-핫 인코딩(1990년대)
가장 간단한 접근 방식: 각 단어는 1과 모두가 하나만 있는 벡터로 표현됩니다. 나머지는 0. 어휘에 V 단어가 있으면 각 벡터의 차원은 V입니다.
# Vocabolario: ["gatto", "cane", "pesce", "auto", "moto"]
# Dimensione vettore = dimensione vocabolario = 5
gatto = [1, 0, 0, 0, 0]
cane = [0, 1, 0, 0, 0]
pesce = [0, 0, 1, 0, 0]
auto = [0, 0, 0, 1, 0]
moto = [0, 0, 0, 0, 1]
# Problema 1: la distanza tra "gatto" e "cane" e uguale
# alla distanza tra "gatto" e "auto"
import numpy as np
dist_gatto_cane = np.linalg.norm(
np.array(gatto) - np.array(cane)
) # sqrt(2) = 1.414
dist_gatto_auto = np.linalg.norm(
np.array(gatto) - np.array(auto)
) # sqrt(2) = 1.414 -- identica!
# Problema 2: con un vocabolario di 100.000 parole,
# ogni vettore ha 100.000 dimensioni (sparso, inefficiente)
원-핫 인코딩의 한계
폭발적인 차원성: 100,000개 단어로 구성된 어휘의 경우 각 벡터의 차원은 100,000개입니다. 거의 모두 0입니다. 의미 정보 없음: 모든 벡터는 등거리입니다 그들 사이. 'Cat'은 '지진'과 마찬가지로 'feline'과도 거리가 멀다. 이 접근법 단어 간의 의미 관계를 포착하지 않습니다.
2.2 TF-IDF(용어빈도 - 역문서빈도)
한 단계 더 나아가: 0/1 대신 벡터 구성요소는 단어가 얼마나 중요한지 나타냅니다. 문서에서 전체 말뭉치와 비교됩니다. 그러나 각 문서는 분산된 벡터가 됩니다. 어휘의 차원.
from sklearn.feature_extraction.text import TfidfVectorizer
documenti = [
"il gatto dorme sul divano",
"il cane gioca nel giardino",
"l'automobile corre sulla strada",
"il felino riposa sulla poltrona",
]
vectorizer = TfidfVectorizer()
tfidf_matrix = vectorizer.fit_transform(documenti)
# Risultato: matrice sparsa (4 documenti x N termini)
print(f"Shape: {tfidf_matrix.shape}") # (4, 14)
print(f"Termini: {vectorizer.get_feature_names_out()}")
# Problema: "gatto dorme" e "felino riposa" sono lontani
# perchè usano parole diverse, anche se il significato e simile
from sklearn.metrics.pairwise import cosine_similarity
sim = cosine_similarity(tfidf_matrix[0:1], tfidf_matrix[3:4])
print(f"Similarità gatto-felino: {sim[0][0]:.3f}") # ~0.07 (bassa!)
TF-IDF는 중요도에 따라 단어에 가중치를 부여하여 원-핫 인코딩을 개선하지만 동일한 문제가 발생합니다. 근본적인 문제: 그는 "고양이"와 "고양이"가 동의어라는 것을 이해하지 못합니다. 정확한 어휘 일치에만 해당됩니다.
2.3 Word2Vec: 혁명(2013)
2013년에 Tomas Mikolov와 그의 Google 팀은 모든 것을 변화시킨 Word2Vec을 출시했습니다. 기발한 아이디어: 단어는 그것이 나타나는 문맥에 의해 정의됩니다. 단어 유사한 맥락에서 나타나는 것은 유사한 표현을 갖습니다.
Word2Vec은 얕은 신경망을 사용하여 밀집된 벡터(일반적으로 100-300차원)를 학습합니다. 큰 텍스트 말뭉치에서. 두 가지 아키텍처:
Word2Vec 아키텍처
| 건축학 | 입력 | 출력 | 설명 |
|---|---|---|---|
| CBOW | 문맥 단어 | 타겟 단어 | 주변 상황을 고려하여 중심 단어를 예측합니다. |
| 스킵그램 | 타겟 단어 | 문맥 단어 | 중심 단어가 주어지면 주변 단어를 예측합니다. |
from gensim.models import Word2Vec
# Corpus di esempio (in produzione: milioni di frasi)
frasi = [
["il", "gatto", "dorme", "sul", "divano"],
["il", "cane", "gioca", "nel", "giardino"],
["il", "felino", "riposa", "sulla", "poltrona"],
["il", "cane", "corre", "nel", "parco"],
]
# Addestramento Word2Vec (Skip-gram)
model = Word2Vec(
sentences=frasi,
vector_size=100, # dimensionalità embedding
window=5, # contesto: 5 parole prima e dopo
min_count=1, # includi parole con almeno 1 occorrenza
sg=1, # 1 = Skip-gram, 0 = CBOW
epochs=100
)
# Ora "gatto" e "felino" sono vicini!
print(model.wv.most_similar("gatto", topn=3))
# [('felino', 0.92), ('cane', 0.85), ('dorme', 0.71)]
# Accesso al vettore
vettore_gatto = model.wv["gatto"]
print(f"Dimensioni: {vettore_gatto.shape}") # (100,)
print(f"Primi 5: {vettore_gatto[:5]}")
2.4 GloVe: 글로벌 벡터(2014)
Stanford는 다른 접근 방식으로 GloVe(단어 표현을 위한 전역 벡터)를 개발했습니다. 신경망 대신 GloVe는 동시발생 매트릭스 글로벌 코퍼스. 이는 LSA와 같은 전역 통계 방법의 장점을 결합합니다. Word2Vec 로컬 컨텍스트.
GloVe는 두 벡터 간의 내적을 보장하는 비용 함수를 최소화합니다. 단어의 수는 동시 발생 확률의 로그에 비례합니다.
2.5 FastText: 하위 단어 임베딩(2016)
Facebook AI Research(FAIR)는 Word2Vec을 다음과 같이 확장했습니다. FastText, 이는 각 단어는 n-그램 문자 집합으로 표시됩니다. 이는 두 가지 중요한 문제를 해결합니다.
- 희귀하거나 어휘에서 벗어난(OOV) 단어: FastText는 하위 세그먼트 벡터를 구성하여 이전에 볼 수 없었던 단어에 대한 임베딩을 생성할 수 있습니다.
- 형태: 형태학적으로 관련된 단어(예: "run", "ran", "runner")는 n-gram을 공유하므로 유사한 벡터를 갖습니다.
진화: 희박한 표현에서 조밀한 표현으로
| 방법 | 년도 | 유형 | 일반적인 치수 | 의미론 |
|---|---|---|---|---|
| 하나의 뜨거운 | - | 뿔뿔이 흩어진 | V (어휘) | 없음 |
| TF-IDF | 1972년 | 뿔뿔이 흩어진 | V (어휘) | 통계 |
| Word2Old | 2013년 | 밀집한 | 100-300 | 지역 상황에 맞는 |
| 장갑 | 2014년 | 밀집한 | 50-300 | 글로벌 + 로컬 |
| FastText | 2016년 | 밀집한 | 100-300 | 하위 단어 + 문맥 |
| 버트 | 2018 | 밀집한 | 768 | 상황별 역학 |
| 문장 변환기 | 2019 | 밀집한 | 384-1024 | 전체 문장 |
3. 임베딩의 수학적 특성
Word2Vec의 가장 흥미로운 발견 중 하나는 벡터 공간이 학습한다는 것입니다. 개념 간의 대수적 관계. 벡터에 대한 산술 연산 의미적으로 일관된 결과를 생성합니다.
3.1 벡터 비유
유명한 비유는 다음과 같습니다. 왕 - 남자 + 여자 = 여왕. 벡터 용어로 말하면, '왕'과 '남자'의 차이는 '왕실'이라는 개념을 포착하고, 이를 '여자'에 추가한 것입니다. 당신은 "여왕"을 얻습니다. 공식적으로:
import gensim.downloader as api
# Carica embeddings GloVe pre-addestrati
model = api.load("glove-wiki-gigaword-100")
# king - man + woman = ?
result = model.most_similar(
positive=["king", "woman"],
negative=["man"],
topn=3
)
print(result)
# [('queen', 0.7698), ('princess', 0.6450), ('monarch', 0.6345)]
# Altre analogie che funzionano:
# Parigi - Francia + Italia = Roma
result2 = model.most_similar(
positive=["paris", "italy"],
negative=["france"],
topn=1
)
print(result2) # [('rome', 0.8722)]
# buono - cattivo + triste = ?
result3 = model.most_similar(
positive=["good", "sad"],
negative=["bad"],
topn=1
)
print(result3) # [('happy', 0.6891)]
3.2 의미론적 클러스터링
임베딩은 자연스럽게 벡터 공간에서 클러스터를 형성합니다. 우리가 투영한다면 2D 벡터(t-SNE 또는 UMAP 사용)에서 동일한 카테고리의 단어를 관찰합니다. 그들은 함께 그룹화됩니다: 동물 근처의 동물, 국가 근처의 국가, 근처의 직업 직업에.
이 속성은 실제 응용 분야의 기본입니다. 유사성 검색이 작동합니다. 이는 유사한 주제에 관한 문서가 벡터 공간에 긴밀하게 임베딩되어 있기 때문입니다.
4. 최신 임베딩: 상황별 임베딩
Word2Vec과 GloVe는 단어에 대한 단일 벡터, 독립 맥락. 하지만 '학교'는 '학교 책상'과 '물고기 학교'에서 다른 의미를 갖습니다. 그만큼 상황별 임베딩, 2018년 BERT와 함께 도입된 이 문제를 해결합니다. 같은 단어라도 문맥에 따라 벡터가 달라집니다.
4.1 BERT 임베딩
BERT(BiDirectional Encoder Representations from Transformers)는 전체 문장을 처리하고 출력합니다. 각 토큰에 대한 벡터입니다. 전체 문장을 포함하려면 일반적으로 다음을 사용합니다.
- CLS 토큰: 첫 번째 특수 토큰[CLS]에는 문장의 집계 표현이 포함됩니다.
- 평균 풀링: 모든 토큰 벡터의 평균 - 일반적으로 유사성 검색에 대해 더 나은 결과를 생성합니다.
BERT는 유사성 검색에 최적이 아닙니다.
원래 BERT는 고품질 문장 임베딩을 생성하도록 훈련되지 않았습니다. CLS 토큰 의미론적 유사성이 아닌 분류에 최적화되었습니다. 유사성 검색의 경우, Sentence Transformers와 같은 특수 모델이 필요합니다.
4.2 문장 변환기(SBERT)
2019년 Reimers와 Gurevych는 BERT를 미세 조정하는 Sentence-BERT를 도입했습니다. 의미 있는 문장 임베딩을 생성하는 Siamese. 이 유사성은 혁명을 일으켰습니다. 검색: 처음으로 간단한 코사인 거리로 문장을 비교할 수 있었고, 고품질의 결과를 얻습니다.
4.3 임베딩 모델: 종합적인 비교
임베딩 모델 비교(2026)
| 모델 | 공급자 | 치수 | MTEB 점수 | 비용 / 100만 토큰 | 메모 |
|---|---|---|---|---|---|
| 텍스트 삽입-3-작은 | 오픈AI | 1536년 | 62.3 | $0.02 | 좋은 품질/가격 비율 |
| 텍스트 삽입-3-대형 | 오픈AI | 3072 | 64.6 | $0.13 | 최대 OpenAI 품질 |
| 삽입-v3 | 코히어 | 1024 | 64.5 | $0.10 | 100개 이상의 언어 지원 |
| 항해-3 | 항해 AI | 1024 | 67.1 | $0.06 | 검색용 상위 |
| 모든-MiniLM-L6-v2 | 포옹얼굴 | 384 | 56.3 | 무료 | 빠르고 로컬이며 컴팩트함 |
| 모든 mpnet-base-v2 | 포옹얼굴 | 768 | 57.8 | 무료 | 최고의 기본 오픈 소스 모델 |
| gte-large-en-v1.5 | 알리바바(HF) | 1024 | 65.4 | 무료 | 상용모델과의 경쟁 |
| bge-large-en-v1.5 | BAAI (HF) | 1024 | 64.2 | 무료 | RAG에 적합 |
모델을 선택하는 방법
- 프로토타입 / 제한된 예산: all-MiniLM-L6-v2(무료, 고속, 384 밝기)
- 생산, 저렴한 비용: 텍스트 임베딩-3-소형(OpenAI, $0.02/1M 토큰)
- 최고 품질의 검색: voyage-3 또는 gte-large-en-v1.5
- 다국어: Cohere embed-v3(100개 이상의 언어)
- 자체 호스팅/개인 정보 보호: bge-large-en-v1.5 또는 gte-large-en-v1.5
5. 벡터 간 거리 측정
거리 측정법의 선택은 유사성 검색 품질에 직접적인 영향을 미칩니다. 네 가지 주요 지표를 수학 공식, 강점과 함께 살펴보겠습니다. 그리고 언제 사용해야 하는지.
5.1 코사인 유사성
텍스트 임베딩에 가장 많이 사용되는 측정항목입니다. 두 벡터 사이의 각도를 측정합니다. 크기(길이)를 무시합니다. 같은 방향을 가리키는 두 벡터 코사인 유사성은 1, 직교는 0, 반대는 -1입니다.
pgVector에서 연산자는 <=> 계산하다 코사인 거리
(= 1 - 코사인 유사성), 여기서 0은 동일함을 의미하고 2는 반대를 의미합니다.
5.2 유클리드 거리(L2)
공간의 두 지점 사이의 "까마귀가 날아갈 때"의 거리입니다. 이는 두 가지를 모두 고려합니다. 벡터의 크기보다 방향이 중요합니다.
pgVector에서 연산자는 <-> 거리 L2를 계산합니다.
5.3 내적(Dot Product)
내적은 방향과 크기를 모두 측정합니다. 정규화된 벡터의 경우 (norm = 1), 내적은 코사인 유사성과 동일합니다.
pgVector에서 연산자는 <#> 음의 내적을 계산합니다(호환성을 위해).
ORDER BY ASC 사용).
5.4 맨해튼까지의 거리(L1)
구성요소별 절대 차이 구성요소의 합입니다. 특이치에 덜 민감함 유클리드 거리에 관해서.
import numpy as np
from scipy.spatial.distance import cosine, euclidean, cityblock
# Due vettori di esempio (normalizzati)
a = np.array([0.5, 0.3, 0.8, 0.1, 0.6])
b = np.array([0.4, 0.35, 0.75, 0.15, 0.55])
# Normalizzazione L2
a_norm = a / np.linalg.norm(a)
b_norm = b / np.linalg.norm(b)
# 1. Cosine Similarity (1 - cosine distance)
cos_sim = 1 - cosine(a_norm, b_norm)
print(f"Cosine similarity: {cos_sim:.6f}") # ~0.999
# 2. Distanza Euclidea (L2)
l2_dist = euclidean(a_norm, b_norm)
print(f"Distanza L2: {l2_dist:.6f}") # ~0.042
# 3. Dot Product (per vettori normalizzati = cosine similarity)
dot = np.dot(a_norm, b_norm)
print(f"Dot product: {dot:.6f}") # ~0.999
# 4. Distanza Manhattan (L1)
l1_dist = cityblock(a_norm, b_norm)
print(f"Distanza L1: {l1_dist:.6f}") # ~0.072
# Relazione L2-Cosine per vettori normalizzati:
# d_L2^2 = 2 * (1 - cos_sim)
print(f"\nVerifica: L2^2 = {l2_dist**2:.6f}")
print(f"2*(1-cos) = {2*(1-cos_sim):.6f}") # uguale!
언제 어떤 측정항목을 사용해야 할까요?
| 미터법 | pg벡터 연산자 | 사용 시기 | 피해야 할 경우 |
|---|---|---|---|
| 작은 것들 | <=> |
크기가 중요하지 않은 경우 텍스트 임베딩 | 크기가 중요한 공간 데이터 |
| L2(유클리드) | <-> |
크기가 중요한 경우 이미지, 수치 데이터 | 구성요소 간 스케일이 다른 벡터 |
| 내적 | <#> |
이미 정규화된 벡터(약간 더 나은 성능) | 정규화되지 않은 벡터(크기에 따라 왜곡된 결과) |
| 맨해튼(L1) | pgVector의 비기본 | 희소 데이터, 이상치에 대한 견고성 | 조밀한 임베딩을 사용한 일반적인 사용 |
실제 규칙
텍스트 임베딩이 포함된 사례의 95%에 대해 다음을 사용합니다. 코사인 거리
(<=> pg벡터에서). 최신 임베딩 모델은 벡터를 생성합니다.
이미 정규화되어 코사인과 내적을 실질적으로 동등하게 만듭니다. 거리
유클리드식은 공간 데이터 또는 벡터 크기가 정보를 전달할 때 적합합니다.
6. Python에서 임베딩 생성
이제 세 가지 접근 방식으로 임베딩을 생성하는 방법을 살펴보겠습니다. 문장 변환기, OpenAI API 및 HuggingFace 추론 API. 각 접근 방식에는 구체적인 장점과 절충점.
6.1 문장 변환기(지역)
가장 유연하고 비공개적인 접근 방식: 모델은 데이터 없이 사용자의 컴퓨터에서 실행됩니다. 네트워크 밖으로 나가면 API 호출 비용이 들지 않습니다.
# pip install sentence-transformers
from sentence_transformers import SentenceTransformer
import numpy as np
# Carica il modello (scaricato automaticamente al primo uso)
model = SentenceTransformer("all-MiniLM-L6-v2")
# Embedding di una singola frase
frase = "PostgreSQL e un database relazionale open-source"
embedding = model.encode(frase)
print(f"Tipo: {type(embedding)}") # numpy.ndarray
print(f"Dimensioni: {embedding.shape}") # (384,)
# Embedding di più frasi (batch - molto più efficiente)
frasi = [
"PostgreSQL e un database relazionale open-source",
"pgvector aggiunge il supporto per vettori a PostgreSQL",
"Il machine learning richiede grandi quantità di dati",
"La pizza margherita e un piatto tipico napoletano",
]
embeddings = model.encode(
frasi,
batch_size=32, # processa 32 frasi alla volta
show_progress_bar=True, # mostra progresso per batch grandi
normalize_embeddings=True # normalizza a norma L2 = 1
)
print(f"Shape: {embeddings.shape}") # (4, 384)
# Calcola similarità tra tutte le coppie
from sentence_transformers.util import cos_sim
similarities = cos_sim(embeddings, embeddings)
print(f"\nMatrice di similarità:\n{similarities}")
# Le prime 2 frasi (su PostgreSQL) avranno alta similarità
# La frase sulla pizza sarà distante dalle altre
6.2 OpenAI 임베딩 API
OpenAI의 API는 인프라 관리 없이 고품질 모델을 제공합니다. 이상적 적당한 양의 생산을 위해.
# pip install openai
from openai import OpenAI
import os
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
def get_embeddings(
texts: list[str],
model: str = "text-embedding-3-small"
) -> list[list[float]]:
"""Genera embeddings per una lista di testi."""
response = client.embeddings.create(
input=texts,
model=model,
)
return [item.embedding for item in response.data]
# Singolo embedding
testo = "PostgreSQL come vector database per AI"
embedding = get_embeddings([testo])[0]
print(f"Dimensioni: {len(embedding)}") # 1536
# Batch di embeddings (fino a 2048 testi per chiamata)
testi = [
"Come installare pgvector su Docker",
"Tutorial per similarity search in PostgreSQL",
"Guida alla cottura della pasta al forno",
]
embeddings = get_embeddings(testi)
print(f"Embeddings generati: {len(embeddings)}") # 3
# Dimensione ridotta con text-embedding-3-small
# Puoi specificare dimensioni inferiori per risparmiare spazio
response = client.embeddings.create(
input=["Testo di esempio"],
model="text-embedding-3-small",
dimensions=512 # ridotto da 1536 a 512
)
emb_ridotto = response.data[0].embedding
print(f"Dimensioni ridotte: {len(emb_ridotto)}") # 512
6.3 HuggingFace 추론 API
로컬 모델과 상용 API 간의 절충: 수천 개의 모델에 대한 액세스 넉넉한 무료 등급을 갖춘 API를 통한 오픈 소스입니다.
# pip install huggingface_hub
from huggingface_hub import InferenceClient
import os
client = InferenceClient(
token=os.getenv("HF_TOKEN")
)
def get_hf_embeddings(
texts: list[str],
model: str = "BAAI/bge-large-en-v1.5"
) -> list[list[float]]:
"""Genera embeddings usando HuggingFace Inference API."""
result = client.feature_extraction(
text=texts,
model=model,
)
return result
# Genera embeddings
testi = [
"Vector search con PostgreSQL e pgvector",
"Come creare indici HNSW per ricerca veloce",
]
embeddings = get_hf_embeddings(testi)
print(f"Embeddings: {len(embeddings)}") # 2
print(f"Dimensioni: {len(embeddings[0])}") # 1024 (bge-large)
6.4 효율적인 일괄 처리
수천 또는 수백만 개의 문서에 대한 임베딩을 생성해야 하는 경우 효율성이 향상됩니다. 일괄 처리가 중요해졌습니다.
import time
from typing import Generator
from sentence_transformers import SentenceTransformer
import numpy as np
def chunk_list(
lst: list, chunk_size: int
) -> Generator[list, None, None]:
"""Divide una lista in chunk di dimensione fissa."""
for i in range(0, len(lst), chunk_size):
yield lst[i:i + chunk_size]
def generate_embeddings_batch(
texts: list[str],
model_name: str = "all-MiniLM-L6-v2",
batch_size: int = 256,
device: str = "cpu" # "cuda" per GPU
) -> np.ndarray:
"""Genera embeddings in batch con progress tracking."""
model = SentenceTransformer(model_name, device=device)
all_embeddings = []
total_batches = (len(texts) + batch_size - 1) // batch_size
start = time.time()
for i, batch in enumerate(chunk_list(texts, batch_size)):
batch_emb = model.encode(
batch,
batch_size=batch_size,
normalize_embeddings=True,
show_progress_bar=False
)
all_embeddings.append(batch_emb)
elapsed = time.time() - start
rate = (i + 1) * batch_size / elapsed
print(
f"Batch {i+1}/{total_batches} - "
f"{rate:.0f} testi/sec"
)
return np.vstack(all_embeddings)
# Utilizzo
texts = [f"Documento numero {i}" for i in range(10_000)]
embeddings = generate_embeddings_batch(
texts,
batch_size=256,
device="cuda" # usa GPU se disponibile
)
print(f"Shape finale: {embeddings.shape}") # (10000, 384)
7. PostgreSQL의 임베딩 기록
이제 임베딩을 생성하는 방법을 알았으므로 pgVector를 사용하여 PostgreSQL에 임베딩을 저장하는 방법을 살펴보겠습니다. 유사성 검색 쿼리를 수행합니다. 이것은 기사 1에 대한 실제 링크입니다. 시리즈의.
7.1 테이블 레이아웃
-- Abilita pgvector
CREATE EXTENSION IF NOT EXISTS vector;
-- Tabella documenti con embedding
CREATE TABLE documents (
id BIGSERIAL PRIMARY KEY,
title VARCHAR(500) NOT NULL,
content TEXT NOT NULL,
source VARCHAR(255),
category VARCHAR(100),
embedding vector(384), -- dimensione del modello scelto
created_at TIMESTAMPTZ DEFAULT NOW(),
metadata JSONB DEFAULT '{}'::jsonb
);
-- Indice HNSW per ricerca veloce (cosine distance)
CREATE INDEX idx_documents_embedding
ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 200);
-- Indice su categoria per filtri combinati
CREATE INDEX idx_documents_category
ON documents (category);
7.2 파이썬에서 삽입하기
import psycopg2
from psycopg2.extras import execute_values
from sentence_transformers import SentenceTransformer
import numpy as np
# Configurazione
DB_CONFIG = {
"host": "localhost",
"port": 5432,
"dbname": "vectordb",
"user": "admin",
"password": "secret_password",
}
# 1. Genera embeddings
model = SentenceTransformer("all-MiniLM-L6-v2")
documenti = [
{
"title": "Introduzione a pgvector",
"content": "pgvector e un'estensione PostgreSQL per vettori...",
"source": "blog",
"category": "database"
},
{
"title": "RAG con LangChain",
"content": "Retrieval Augmented Generation combina retrieval...",
"source": "tutorial",
"category": "ai"
},
{
"title": "Python per Data Science",
"content": "Python e il linguaggio più usato per data science...",
"source": "guide",
"category": "programming"
},
]
# Genera embeddings per i contenuti
testi = [d["content"] for d in documenti]
embeddings = model.encode(testi, normalize_embeddings=True)
# 2. Salva in PostgreSQL
conn = psycopg2.connect(**DB_CONFIG)
cur = conn.cursor()
# Prepara i dati per batch insert
values = []
for doc, emb in zip(documenti, embeddings):
values.append((
doc["title"],
doc["content"],
doc["source"],
doc["category"],
emb.tolist() # converti numpy array in lista Python
))
# Inserimento batch efficiente
execute_values(
cur,
"""INSERT INTO documents
(title, content, source, category, embedding)
VALUES %s""",
values,
template="(%s, %s, %s, %s, %s::vector)"
)
conn.commit()
print(f"Inseriti {len(values)} documenti con embeddings")
cur.close()
conn.close()
7.3 파이썬에서 유사성 검색
def similarity_search(
query: str,
top_k: int = 5,
category: str = None,
threshold: float = 0.3
) -> list[dict]:
"""Cerca documenti simili alla query."""
# Genera embedding della query
query_embedding = model.encode(
query, normalize_embeddings=True
).tolist()
conn = psycopg2.connect(**DB_CONFIG)
cur = conn.cursor()
# Query con filtro opzionale
if category:
cur.execute("""
SELECT id, title, content, category,
1 - (embedding <=> %s::vector) AS similarity
FROM documents
WHERE category = %s
AND 1 - (embedding <=> %s::vector) > %s
ORDER BY embedding <=> %s::vector
LIMIT %s
""", (
query_embedding, category,
query_embedding, threshold,
query_embedding, top_k
))
else:
cur.execute("""
SELECT id, title, content, category,
1 - (embedding <=> %s::vector) AS similarity
FROM documents
WHERE 1 - (embedding <=> %s::vector) > %s
ORDER BY embedding <=> %s::vector
LIMIT %s
""", (
query_embedding,
query_embedding, threshold,
query_embedding, top_k
))
results = []
for row in cur.fetchall():
results.append({
"id": row[0],
"title": row[1],
"content": row[2][:200], # troncato
"category": row[3],
"similarity": round(row[4], 4),
})
cur.close()
conn.close()
return results
# Esempio d'uso
risultati = similarity_search(
"come usare i vettori in un database",
top_k=3,
category="database"
)
for r in risultati:
print(f"[{r['similarity']}] {r['title']}")
7.4 인덱싱: HNSW 대 IVFFlat
수천 개가 넘는 문서가 포함된 데이터 세트의 경우 인덱스가 필수적입니다. 수용 가능한 성능. pgVector는 두 가지 유형의 인덱스를 제공합니다.
HNSW 대 IVFFlat
| 특성 | HNSW | IVFF플랫 |
|---|---|---|
| 쿼리 속도 | 매우 빠르다 | 빠른 |
| 상기하다 | 95-99% | 85-95% |
| 빌드 시간 | 느림(분) | 빠르게(초) |
| 메모리 | 높음(RAM의 그래프) | 낮음(중심) |
| 삽입/업데이트 | 양호(증분 업데이트) | 주기적인 재구축 필요 |
| 권장 대상 | 생산, 고품질 | 프로토타이핑, 정적 데이터세트 |
-- HNSW (raccomandato per produzione)
-- m: connessioni per nodo (16-64, default 16)
-- ef_construction: qualità costruzione (64-512, default 64)
CREATE INDEX idx_hnsw_cosine
ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 200);
-- Per L2 distance
CREATE INDEX idx_hnsw_l2
ON documents
USING hnsw (embedding vector_l2_ops)
WITH (m = 16, ef_construction = 200);
-- IVFFlat (più veloce da costruire)
-- lists: numero di cluster (sqrt(N) come regola base)
CREATE INDEX idx_ivfflat_cosine
ON documents
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100); -- per ~10K documenti
-- Parametri di query per controllare recall vs velocità
SET hnsw.ef_search = 100; -- default 40, alza per più recall
SET ivfflat.probes = 10; -- default 1, alza per più recall
8. 다양한 유형의 데이터에 대한 임베딩
임베딩은 텍스트에만 국한되지 않습니다. 최신 모델은 표현을 생성할 수 있습니다. 이미지, 오디오, 소스 코드 및 다중 모드 데이터에 대한 벡터 그래픽입니다.
다중 모드 임베딩: 데이터 유형 모델
| 데이터 유형 | 모델 | 치수 | 사용 사례 |
|---|---|---|---|
| 텍스트 | all-MiniLM-L6-v2, 텍스트 임베딩-3-소형 | 384-3072 | 의미 검색, RAG, 분류 |
| 이미지 | CLIP(오픈AI), SigLIP(구글) | 512-768 | 이미지 검색, 시각적 분류 |
| 오디오 | 속삭여, 박수 | 512-1280 | 오디오 검색, 음악 분류 |
| 암호 | CodeBERT, StarCoder 임베딩 | 768 | 코드 검색, 중복 감지 |
| 다중 모드 | CLIP, ImageBind(메타) | 512-1024 | 교차 모달 검색(이미지별 텍스트) |
# pip install transformers pillow
from transformers import CLIPProcessor, CLIPModel
from PIL import Image
import torch
import numpy as np
# Carica CLIP
model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
# Embedding di un'immagine
image = Image.open("foto_gatto.jpg")
inputs = processor(images=image, return_tensors="pt")
with torch.no_grad():
image_embedding = model.get_image_features(**inputs)
image_emb = image_embedding[0].numpy()
print(f"Image embedding: {image_emb.shape}") # (512,)
# Embedding di testo (nello STESSO spazio!)
text_inputs = processor(
text=["un gatto che dorme", "un cane che gioca"],
return_tensors="pt",
padding=True
)
with torch.no_grad():
text_embeddings = model.get_text_features(**text_inputs)
text_embs = text_embeddings.numpy()
# Calcola similarità cross-modale
from numpy.linalg import norm
for i, text in enumerate(["un gatto che dorme", "un cane che gioca"]):
sim = np.dot(image_emb, text_embs[i]) / (
norm(image_emb) * norm(text_embs[i])
)
print(f"Similarità '{text}': {sim:.4f}")
# "un gatto che dorme" avra similarità più alta con foto_gatto.jpg
CLIP의 힘은 그 안에 텍스트와 이미지가 살아 있다는 것입니다 동일한 벡터 공간. 텍스트 쿼리로 이미지를 검색하거나 이미지와 관련된 텍스트를 찾을 수 있습니다. 이 PostgreSQL에서 다중 모드 검색과 같은 시나리오를 엽니다. 여기에 CLIP 임베딩을 저장합니다. pgVector 테이블과 텍스트 쿼리가 포함된 원입니다.
9. 임베딩 품질 평가
임베딩 모델이 "좋은"지 어떻게 알 수 있나요? 대답은 작업에 따라 다릅니다. 구체적이지만 표준화된 벤치마크와 객관적인 지표가 존재합니다.
9.1 MTEB: 대규모 텍스트 임베딩 벤치마크
MTEB는 임베딩 모델을 평가하기 위한 참조 벤치마크입니다. 성과 측정 8개 카테고리로 분류된 58개 이상의 작업:
- 검색: 쿼리를 통해 관련 문서 찾기
- 의미론적 텍스트 유사성(STS): 두 문장이 얼마나 비슷한지
- 분류: 텍스트를 범주로 분류
- 클러스터링: 유사한 텍스트 그룹화
- 쌍 분류: 두 텍스트가 관련되어 있는지 확인
- 순위 재지정: 관련성에 따라 결과 재정렬
- 요약: 요약의 품질
- 비트텍스트마이닝: 평행 번역 찾기
from sentence_transformers import SentenceTransformer
from sentence_transformers.evaluation import (
InformationRetrievalEvaluator
)
# Prepara dataset di valutazione
queries = {
"q1": "come installare pgvector",
"q2": "cos'è la similarity search",
"q3": "embedding di immagini con CLIP",
}
corpus = {
"d1": "Guida installazione pgvector su Ubuntu",
"d2": "pgvector per PostgreSQL: setup Docker",
"d3": "Ricerca per similarità vettoriale",
"d4": "CLIP: modello multimodale per immagini e testo",
"d5": "Ricetta pasta alla carbonara",
}
# Mappatura query -> documenti rilevanti
relevant = {
"q1": {"d1": 1, "d2": 1}, # d1 e d2 rilevanti per q1
"q2": {"d3": 1},
"q3": {"d4": 1},
}
# Valuta il modello
model = SentenceTransformer("all-MiniLM-L6-v2")
evaluator = InformationRetrievalEvaluator(
queries=queries,
corpus=corpus,
relevant_docs=relevant,
name="custom-eval"
)
results = evaluator(model)
print(f"NDCG@10: {results['custom-eval_ndcg@10']:.4f}")
print(f"MAP@10: {results['custom-eval_map@10']:.4f}")
9.2 내재적 평가와 외재적 평가
평가에 대한 두 가지 접근 방식
| 유형 | 측정 대상 | Esempio | 사용 시기 |
|---|---|---|---|
| 본질적인 | 벡터 자체의 속성 | 유추, 클러스터링, STS | 모델 간 빠른 비교 |
| 외인성 | 최종 과제 수행 | RAG 품질, 연구 정밀도 | 생산 최종 결정 |
실용적인 조언
MTEB 점수에만 의존하지 마십시오. 모델은 MTEB 점수가 높을 수 있지만 특정 도메인에서 실적이 저조합니다. 항상 데이터세트를 평가하세요.: 귀하의 도메인에서 소규모 관련 쿼리 및 문서 세트를 생성하고 측정하세요. nDCG와 MAP. 이렇게 하면 실제 성능에 대해 훨씬 더 신뢰할 수 있는 추정치를 얻을 수 있습니다.
10. 차원 축소
고차원 벡터는 시각화하기 어렵고 비용이 많이 들 수 있습니다. 저장 및 계산 측면에서. 차원성 감소 기술 시각화와 최적화 모두에 도움이 됩니다.
10.1 시각화 기법
차원 축소 기술
| 기술 | 보존하다 | 속도 | 일반적인 사용 |
|---|---|---|---|
| PCA | 글로벌 분산 | 매우 빠르다 | 보관, 전처리를 위한 크기 감소 |
| t-SNE | 지역 구조 | 느린 | 클러스터의 2D 시각화 |
| UMAP | 로컬 + 글로벌 구조 | 평균 | 2D 시각화, 사전 인덱싱 감소에도 사용 가능 |
# pip install umap-learn matplotlib
import umap
import matplotlib.pyplot as plt
import numpy as np
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("all-MiniLM-L6-v2")
# Testi con categorie diverse
testi = [
# Database
"PostgreSQL e un database relazionale",
"MongoDB e un database NoSQL",
"Redis e un database in-memory",
# AI/ML
"Il deep learning usa reti neurali profonde",
"GPT e un modello di linguaggio",
"La regressione lineare e un algoritmo semplice",
# Cucina
"La pizza si cuoce nel forno a legna",
"Il tiramisu e un dolce italiano",
"La pasta alla carbonara usa uova e guanciale",
]
categorie = ["DB"]*3 + ["AI"]*3 + ["Cucina"]*3
colori = ["blue"]*3 + ["red"]*3 + ["green"]*3
# Genera embeddings (384 dimensioni)
embeddings = model.encode(testi, normalize_embeddings=True)
# Riduci a 2D con UMAP
reducer = umap.UMAP(n_components=2, random_state=42)
emb_2d = reducer.fit_transform(embeddings)
# Visualizza
plt.figure(figsize=(10, 8))
for i, (x, y) in enumerate(emb_2d):
plt.scatter(x, y, c=colori[i], s=100, zorder=5)
plt.annotate(testi[i][:30], (x, y),
fontsize=8, ha='left')
plt.title("Embeddings in 2D (UMAP)")
plt.savefig("embeddings_umap.png", dpi=150)
plt.show()
10.2 마트료시카 임베딩
최근의 혁신적인 기술: i 마트료시카 표현 학습(MRL) 벡터의 처음 N개 구성 요소가 이미 임베딩이 되도록 임베딩이 훈련되었습니다. 유효합니다. 좋은 품질을 유지하면서 벡터를 1536차원에서 512 또는 256차원으로 잘라낼 수 있습니다.
오픈AI text-embedding-3-small e text-embedding-3-large 지원하다
이 기술: 매개변수를 지정할 수 있습니다. dimensions 벡터를 얻기 위해
임베딩을 다시 계산하지 않고도 더 컴팩트해집니다.
from openai import OpenAI
import numpy as np
import os
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
testo = "PostgreSQL come vector database per applicazioni AI"
# Genera lo stesso embedding a dimensioni diverse
for dim in [256, 512, 1024, 1536]:
response = client.embeddings.create(
input=[testo],
model="text-embedding-3-small",
dimensions=dim
)
emb = response.data[0].embedding
print(f"Dimensioni: {dim}, norma: {np.linalg.norm(emb):.4f}")
# In PostgreSQL: usa colonne con dimensione appropriata
# CREATE TABLE docs_compact (
# id BIGSERIAL PRIMARY KEY,
# content TEXT,
# embedding vector(256) -- più compatto, 6x meno storage
# );
11. 확장 비용 및 전략
프로토타입에서 생산으로 이동할 때, 생성 및 저장 비용 임베딩이 중요한 요소가 됩니다. 자세한 분석을 보겠습니다.
11.1 문서 100만개당 비용
예상 비용: 문서 100만개(평균 500개 토큰/문서)
| 모델 | 생성 비용 | 벡터 크기 | 스토리지(float32) | 초기 합계 |
|---|---|---|---|---|
| 모든-MiniLM-L6-v2 | $0(현지) | 384 | ~1.4GB | GPU/CPU 시간만 |
| 텍스트 삽입-3-작은 | ~$10 | 1536년 | ~5.7GB | ~$10 + 저장 공간 |
| 텍스트 삽입-3-대형 | ~$65 | 3072 | ~11.4GB | ~$65 + 저장 공간 |
| 항해-3 | ~$30 | 1024 | ~3.8GB | ~$30 + 저장 공간 |
저장 공식: N개 문서 * 크기 * 4바이트(float32). 예: 이동통신사의 경우 1M * 1536 * 4 = 5.7GB.
11.2 자체 호스팅과 API: 절충
자체 호스팅과 API 비교
| 나는 기다린다 | 자체 호스팅 | API(OpenAI, 코히어) |
|---|---|---|
| 초기비용 | 높음(GPU ~$1-3/시간) | 낮음(종량제) |
| 볼륨당 비용 | ~1천만 문서 이상에서 가장 저렴함 | 선형, 볼륨에 따라 확장 |
| 숨어 있음 | 낮음(네트워크 없음) | 통화당 50-200ms |
| 은둔 | 데이터는 온프레미스에 남아 있습니다. | 제3자에게 데이터 전송 |
| 유지 | GPU 관리, 업데이트, 모니터링 | Zero |
| 품질 | 선택한 모델에 따라 다릅니다. | 일반적으로 높고 일관성이 있음 |
11.3 캐싱 전략
비용과 대기 시간을 줄이려면 캐싱을 포함하는 것이 필수적입니다. 같은 경우 텍스트가 여러 번 요청되면 임베딩을 다시 생성하는 것은 의미가 없습니다.
import hashlib
import json
import psycopg2
from typing import Optional
import numpy as np
class EmbeddingCache:
"""Cache embeddings in PostgreSQL per evitare ricalcoli."""
def __init__(self, db_config: dict):
self.db_config = db_config
self._init_table()
def _init_table(self):
conn = psycopg2.connect(**self.db_config)
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS embedding_cache (
content_hash VARCHAR(64) PRIMARY KEY,
model_name VARCHAR(100) NOT NULL,
embedding vector(1536),
created_at TIMESTAMPTZ DEFAULT NOW()
)
""")
conn.commit()
cur.close()
conn.close()
def _hash(self, text: str, model: str) -> str:
"""Hash deterministico del contenuto + modello."""
key = f"{model}::{text}"
return hashlib.sha256(key.encode()).hexdigest()
def get(
self, text: str, model: str
) -> Optional[list[float]]:
"""Cerca embedding in cache."""
h = self._hash(text, model)
conn = psycopg2.connect(**self.db_config)
cur = conn.cursor()
cur.execute(
"SELECT embedding FROM embedding_cache "
"WHERE content_hash = %s",
(h,)
)
row = cur.fetchone()
cur.close()
conn.close()
return row[0] if row else None
def put(
self, text: str, model: str, embedding: list[float]
):
"""Salva embedding in cache."""
h = self._hash(text, model)
conn = psycopg2.connect(**self.db_config)
cur = conn.cursor()
cur.execute(
"INSERT INTO embedding_cache "
"(content_hash, model_name, embedding) "
"VALUES (%s, %s, %s::vector) "
"ON CONFLICT (content_hash) DO NOTHING",
(h, model, embedding)
)
conn.commit()
cur.close()
conn.close()
12. PostgreSQL AI 시리즈와의 연계
우리가 구축하고 있는 생태계에 임베딩이 어떻게 통합되는지 요약해 보겠습니다. 이 시리즈에서는:
전체 흐름: 1조부터 3조까지
| 단계 | Articolo | 행동 |
|---|---|---|
| 1 | pg벡터(제1조) | pgVector로 PostgreSQL 구성, 벡터 열로 테이블 생성 |
| 2 | 임베딩(Art. 2 - this) | 템플릿을 선택하고, 임베딩을 생성하고, pgVector에 저장하세요. |
| 3 | PostgreSQL을 사용한 RAG(3조) | 질문에 답하기 위해 pgVector를 통한 검색과 LLM을 결합 |
기사 1에서 우리는 인프라를 준비했습니다: pgVector가 설치된 PostgreSQL, 테이블 벡터 열과 HNSW 인덱스가 구성되어 있습니다. 이 글에서 우리는 그 공백을 메웠습니다. 기본: 해당 벡터의 출처, 올바른 모델 선택 방법 및 생성 방법 효율적으로. 다음 기사에서는 모든 것을 사용하는 완전한 RAG 파이프라인을 구축할 것입니다. 이 스택: pgVector에 색인된 문서, 쿼리에 대해 즉시 생성된 임베딩, 검색된 컨텍스트를 기반으로 응답을 생성하는 LLM.
13. 결론 및 체크리스트
임베딩은 자연어를 연결하는 기본 구성 요소입니다. 벡터 데이터베이스의 수학. 올바른 모델 선택, 거리 측정법 적절하고 효율적인 확장 전략은 직접적인 영향을 미치는 결정입니다. AI 시스템의 품질과 비용.
체크리스트: 올바른 임베딩 모델 선택
- 작업을 정의합니다. 검색, 분류, 클러스터링, STS?
- 언어를 확인하세요: 영어로만 가능합니까, 다국어입니까, 아니면 특정 도메인입니까?
- 제약 조건을 평가합니다. 예산, 개인 정보 보호, 대기 시간, 사용 가능한 인프라
- 2~3명의 후보자를 선택하세요. 모델 테이블에서(섹션 4.3)
- 평가 세트 만들기 귀하의 도메인에서(관련 문서가 포함된 쿼리 50-100개)
- nDCG 및 MAP 측정 각 후보자에 대한 데이터 세트
- 비용 계산 예상 볼륨에 대해 완전히 작동 가능(섹션 11)
- 작은 크기를 테스트합니다. 512개의 밝기는 많은 사용 사례에 충분합니다.
- 캐싱 구현 재생 비용을 줄이기 위해
- 모니터 품질 시간이 지남에 따라 평가 세트를 사용하여
다음 기사: PostgreSQL을 사용한 RAG
시리즈의 다음 기사에서는 하나를 만들어 보겠습니다. RAG 파이프라인 완료 (검색 증강 생성) PostgreSQL + pgVector를 지식 기반으로 사용합니다. 유사성 검색과 지능형 청킹을 결합하는 방법, 통합하는 방법을 살펴보겠습니다. GPT-4 및 Claude와 같은 LLM 및 생성된 응답의 품질을 측정하는 방법.







