Memahami text embeddings: mengubah makna teks menjadi vektor angka, cosine similarity untuk semantic search, lalu membangun vector database dengan ChromaDB, Qdrant, Pinecone, atau Weaviate — lengkap dengan indexing, metadata filtering, dan hybrid search keyword + vektor.

Setelah di episode 5 kalian berhasil mengintegrasikan LLM ke aplikasi dengan streaming dan structured output, tibalah pertanyaan berikutnya: bagaimana membuat aplikasi AI memahami data kita sendiri? LLM tidak tahu isi dokumen internal perusahaan, manual produk, atau basis pengetahuan tim. Jawabannya dimulai dari embeddings dan vector databases — fondasi dari RAG yang kita bangun di episode 7.
Mengapa ini penting? Karena pencarian berdasarkan kata kunci (keyword search) sudah tidak cukup. Pengguna bertanya "gimana cara reset password", padahal dokumentasi menulis "memulihkan kredensial akun". Embeddings menangkap makna, bukan sekadar ejaan — dan itu membuat perbedaan antara aplikasi yang "kurang lebih membantu" dan yang "paham".
Sebuah embedding model mengubah sepotong teks menjadi vektor angka (misal 768 atau 1536 dimensi), dengan sifat kunci: teks yang maknanya mirip akan menghasilkan vektor yang berdekatan.
from openai import OpenAI
client = OpenAI()
def embed(text):
r = client.embeddings.create(model="text-embedding-3-small", input=text)
return r.data[0].embedding
v1 = embed("cara mengatur ulang kata sandi")
v2 = embed("bagaimana cara reset password akun")
v3 = embed("resep rendang sapi khas minang")
print(len(v1)) # 1536Tiga kalimat di atas menghasilkan tiga vektor; v1 dan v2 akan berdekatan (makna sama), sementara v3 menjauh (makna berbeda). Ukuran vektor menentukan biaya penyimpanan dan kecepatan pencarian — model embedding yang lebih besar (misal text-embedding-3-large) lebih akurat tetapi lebih mahal.
Ada beberapa cara mengukur kedekatan vektor; yang paling umum adalah cosine similarity — kosinus sudut antara dua vektor, bernilai 1 (searah) sampai -1 (berlawanan).
import numpy as np
def cosine(a, b):
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
print("pertanyaan mirip:", round(cosine(v1, v2), 3)) # ~0.9x
print("topik berbeda:", round(cosine(v1, v3), 3)) # jauh lebih rendahPerhatikan: cosine similarity tidak bergantung pada panjang teks (vektor dinormalisasi), jadi teks pendek dan panjang tetap bisa dibandingkan secara adil — inilah mengapa ia dipilih untuk semantic search.
Semantic search bekerja dalam tiga langkah: embed semua dokumen → simpan sebagai vektor → untuk sebuah query, embed query lalu cari dokumen dengan similarity tertinggi.
Masalahnya: mencari similarity di atas jutaan vektor dengan brute-force (bandingkan semua) terlalu lambat. Di sinilah vector database masuk dengan index ANN (approximate nearest neighbor).
Vector DB menyimpan vektor + metadata, dan menyediakan pencarian tetangga terdekat secara efisien (index seperti HNSW, IVF), beserta filter metadata. Pilihan utamanya:
| Engine | Jenis | Kapan Dipakai |
|---|---|---|
| ChromaDB | Embedded/lokal | Eksperimen, prototipe, tanpa server |
| Qdrant | Server/Docker | Produksi self-host, Rust, cepat |
| Weaviate | Server/Docker | Produksi, fitur hybrid search native |
| Pinecone | SaaS/managed | Tanpa urusan operasional, skala besar |
Untuk series ini kita mulai dengan ChromaDB karena tanpa server — cukup pip install chromadb.
import chromadb
client = chromadb.PersistentClient(path="./vectordb")
collection = client.get_or_create_collection("docs")
collection.add(
ids=["doc-1", "doc-2", "doc-3"],
embeddings=[v1, v2, v3],
metadatas=[{"category": "faq", "date": "2026-01-01"},
{"category": "faq", "date": "2026-02-01"},
{"category": "recipe", "date": "2026-03-01"}],
)
res = collection.query(query_embeddings=[embed("lupa password")], n_results=2)
print(res["ids"]) # doc-1 atau doc-2 paling relevanDengan ChromaDB, embed query dan pencarian digabung jadi satu operasi — ini pola inti yang akan kita pakai untuk membangun RAG di episode 7.
Sering kali kita tidak hanya ingin "yang paling mirip", tetapi "yang paling mirip dari kategori tertentu". ChromaDB mendukung pre-filter:
res = collection.query(
query_embeddings=[embed("cara reset")],
where={"category": {"$eq": "faq"}}, # hanya kategori faq
n_results=2,
)Filter metadata menurunkan jumlah kandidat sebelum pencarian vektor — lebih cepat dan hasil lebih relevan.
Hybrid search menggabungkan keyword search (BM25/FT) dengan vector search. Mengapa? Untuk istilah teknis eksak (kode, nama produk seperti "ChromaDB") keyword search sering menang, sementara untuk makna ("cara ganti kata sandi") vector search menang. Qdrant dan Weaviate punya dukungan native; di ChromaDB bisa disimulasikan dengan menggabungkan hasil dua pencarian.
# 1. Vector: cari 20 terdekat maknanya
vec_hits = collection.query(query_embeddings=[embed(q)], n_results=20)
# 2. Keyword: cari istilah eksak di metadata/content
kw_hits = collection.query(query_texts=[q], n_results=20)
# 3. Gabungkan (reciprocal rank fusion) → ranking finalKombinasi ini adalah standar produksi untuk retrieval yang robust — kita perdalam di episode 8 (RAG lanjutan).
Tip
Simpan teks asli di metadata/collection bersama embedding-nya. Vektor saja tidak bisa ditampilkan ke pengguna — retrieval selalu perlu mengambil teks sumber untuk konteks RAG (episode 7) dan untuk menampilkan citation.
Inti yang harus dibawa pulang:
Di episode 7 selanjutnya kita akan membangun RAG (Retrieval-Augmented Generation) secara utuh — chunking, indexing, retrieval, dan generation yang grounded pada dokumen — plus pipeline dokumen PDF/website ke Q&A dan evaluasi retrieval & kualitas jawaban. Sampai jumpa di episode 7!