Tampilan
5.2 Chunking & Embedding
Tujuan bab
Setelah bab ini kamu bisa:
- Membuat
MDocumentdari teks, HTML, Markdown, atau JSON - Memilih strategi chunking yang sesuai dengan jenis dokumenmu
- Menjelaskan pengaruh
maxSizedanoverlapterhadap mutu retrieval - Menghasilkan embedding untuk banyak potongan sekaligus
- Menghindari kesalahan parameter yang tidak terlihat di dokumentasi lama
Prasyarat
- 5.1 Konsep RAG
- Paket
@mastra/ragterpasang:npm install @mastra/rag@latest
Membuat dokumen
MDocument adalah pembungkus yang menyeragamkan berbagai format masukan:
ts
// src/mastra/rag/ingest.ts
import { MDocument } from '@mastra/rag'
const doc = MDocument.fromText('Isi teks biasa...')
const docFromHTML = MDocument.fromHTML('<html>Isi HTML...</html>')
const docFromMarkdown = MDocument.fromMarkdown('# Isi Markdown...')
const docFromJSON = MDocument.fromJSON(`{ "key": "value" }`)Pilih yang sesuai formatnya. Ini bukan sekadar kerapian — pemilihan yang tepat memungkinkan strategi chunking yang memahami struktur, dan itu berpengaruh nyata pada hasil.
Memotong dokumen
ts
const chunks = await doc.chunk({
strategy: 'recursive',
maxSize: 512,
overlap: 50,
separators: ['\n'],
extract: {
title: true,
summary: true,
keywords: true,
},
})Parameternya maxSize, bukan size
Sebagian dokumentasi yang beredar — termasuk satu halaman resmi — menulis size: 512. Itu ditolak pada versi sekarang.
Type-check terhadap @mastra/rag yang terpasang mengembalikan pesan yang tegas: "'size' does not exist in type … BaseChunkOptions …". Pakai maxSize.
Ini contoh kenapa menyalin potongan kode dari hasil pencarian berisiko, bahkan ketika sumbernya terlihat resmi.
Sembilan strategi
| Strategi | Cocok untuk |
|---|---|
recursive | Pilihan default. Memotong cerdas berdasarkan struktur konten |
character | Pemotongan sederhana berbasis karakter |
token | Pemotongan yang sadar batas token |
markdown | Dokumen Markdown |
semantic-markdown | Markdown, dipotong berdasarkan keluarga heading yang berkaitan |
html | Dokumen HTML, sadar strukturnya |
json | Data JSON, sadar strukturnya |
latex | Dokumen LaTeX |
sentence | Teks yang struktur kalimatnya penting dijaga |
Catatan penting dari dokumentasi: tiap strategi menerima parameter yang berbeda, disesuaikan dengan cara kerjanya. Ini terlihat langsung di tipenya — separators misalnya hanya bermakna untuk recursive.
Dua contoh strategi lain:
ts
// Menjaga struktur kalimat tetap utuh
const chunks = await doc.chunk({
strategy: 'sentence',
maxSize: 450,
minSize: 50,
overlap: 0,
sentenceEnders: ['.'],
})ts
// Markdown, dipotong berdasarkan hubungan antar bagian
const chunks = await doc.chunk({
strategy: 'semantic-markdown',
joinThreshold: 500,
modelName: 'gpt-5',
})Ekstraksi metadata
extract meminta LLM menganalisis tiap potongan dan menambahkan metadata. Field yang tersedia: title, summary, questions, keywords, dan schema untuk ekstraksi terstruktur memakai schema Zod.
Hasilnya masuk ke chunk.metadata:
js
// chunks[0].metadata
{
documentTitle: 'AI Systems Overview',
sectionSummary: 'Ikhtisar konsep dan penerapan kecerdasan buatan',
excerptKeywords: 'KEYWORDS: AI, machine learning, algorithms',
}Ini memanggil LLM, sekali per potongan
Ekstraksi metadata melibatkan panggilan model, sehingga memerlukan API key dan menambah biaya ingest secara linear terhadap jumlah potongan. Aktifkan hanya field yang benar-benar kamu pakai saat retrieval.
Perhatikan juga nama field-nya. Sebagian contoh yang beredar menulis extract: { metadata: true } — itu ditolak compiler pada versi sekarang dengan pesan "'metadata' does not exist in type 'ExtractParams'".
Memilih ukuran potongan
Ini keputusan yang paling menentukan mutu RAG-mu, dan tidak ada angka ajaib yang benar untuk semua kasus. Yang ada adalah pertimbangan.
Potongan terlalu kecil Potongan terlalu besar
┌──────────────────┐ ┌──────────────────────────┐
│ "Hal ini tidak │ │ Bab 3: Kebijakan Retur │
│ berlaku untuk │ │ (3 halaman penuh, memuat │
│ pelanggan │ │ retur, garansi, dan │
│ korporat." │ │ pengiriman sekaligus) │
└──────────────────┘ └──────────────────────────┘
"Hal ini" = apa? Model harus menyaring
Konteks hilang. sendiri, dan sering keliru.Panduan yang bisa dipakai sebagai titik awal:
| Jenis dokumen | maxSize awal yang wajar |
|---|---|
| FAQ, tanya-jawab pendek | 256–384 |
| Artikel bantuan, dokumentasi | 512 |
| Prosa panjang, laporan, buku | 512–1024 |
| Kode atau data terstruktur | Ikuti batas unit alaminya |
Perlakukan angka ini sebagai titik awal, bukan jawaban. Cara mengujinya ada di bagian Coba sendiri.
Kenapa overlap penting
overlap membuat potongan berbagi sebagian teks dengan tetangganya:
Tanpa overlap:
[......potongan 1......][......potongan 2......]
↑ kalimat yang terbelah di sini
kehilangan konteksnya
Dengan overlap 50:
[......potongan 1......]
[......potongan 2......]
↑ bagian ini muncul di keduanyaIni asuransi terhadap pemotongan yang jatuh di tempat yang buruk. Sebuah gagasan yang terbelah tepat di batas potongan tetap punya satu potongan yang memuatnya secara utuh.
Ongkosnya: penyimpanan dan biaya embedding sedikit bertambah, karena sebagian teks diproses dua kali. Untuk sebagian besar kasus, itu pertukaran yang sepadan.
Nilai awal yang wajar: sekitar 10% dari maxSize.
Membuat embedding
Setelah dokumen terpotong, tiap potongan diubah menjadi vektor:
ts
// src/mastra/rag/ingest.ts
import { embedMany } from 'ai'
import { MDocument } from '@mastra/rag'
import { ModelRouterEmbeddingModel } from '@mastra/core/llm'
const doc = MDocument.fromText('Isi dokumenmu di sini...')
const chunks = await doc.chunk({
strategy: 'recursive',
maxSize: 512,
overlap: 50,
})
const { embeddings } = await embedMany({
values: chunks.map(chunk => chunk.text),
model: new ModelRouterEmbeddingModel('openai/text-embedding-3-small'),
})Beberapa hal yang perlu dicatat.
embedMany berasal dari paket ai, bukan dari @mastra/core. Ini salah satu dari sedikit tempat di mana kamu mengimpor dari luar ekosistem Mastra.
Yang di-embed adalah chunk.text, bukan objek chunk-nya. Tiap chunk punya teks dan metadata; yang diubah jadi vektor hanya teksnya.
ModelRouterEmbeddingModel sudah kamu kenal dari 3.3 — format provider/model yang sama.
Memilih model embedding
Dua pertimbangan utama:
Model API (mis. openai/text-embedding-3-small) | Model lokal (@mastra/fastembed) | |
|---|---|---|
| Mutu | Umumnya lebih tinggi | Cukup untuk banyak kasus |
| Biaya | Per token | Nol setelah terpasang |
| Data keluar dari infrastrukturmu | Ya | Tidak |
| Kecepatan | Bergantung jaringan | Bergantung CPU |
Untuk dokumen yang sensitif atau tidak boleh keluar dari lingkunganmu, @mastra/fastembed adalah jawabannya.
Ingat peringatan dari 3.3: mengganti model embedding setelah data terkumpul adalah migrasi, bukan penggantian satu baris. Vektor dari model berbeda tidak sebanding.
Kesalahan umum
Gejala: Error tipe 'size' does not exist in type ... saat memanggil doc.chunk(). Penyebab: Memakai parameter size, yang muncul di sebagian dokumentasi dan contoh lama. Nama yang benar pada versi sekarang adalah maxSize. Perbaikan: Ganti menjadi maxSize. Kalau kamu menemukan parameter lain yang ditolak, periksa apakah ia memang milik strategi yang kamu pakai — tiap strategi menerima parameter yang berbeda.
Gejala: Hasil pencarian sering memuat potongan yang secara topik benar tapi tidak memuat jawabannya, atau memuat kalimat yang merujuk sesuatu yang tidak ada di potongan itu. Penyebab: Potongan terlalu kecil, atau overlap nol sehingga gagasan terbelah di batas potongan. Perbaikan: Naikkan maxSize dan berikan overlap sekitar 10% darinya. Kalau dokumenmu berstruktur — Markdown atau HTML — pertimbangkan strategi yang sadar struktur alih-alih recursive.
Coba sendiri
Tantangan: Bangun skrip ingest untuk satu dokumen nyata, lalu uji secara empiris ukuran potongan mana yang paling baik untuk dokumen itu. Ini latihan mengukur, bukan menebak.
Ketentuan:
- Ambil satu dokumen asli sepanjang minimal 10 halaman — dokumentasi, buku panduan, atau kumpulan artikel. Kalau formatnya Markdown, pakai
MDocument.fromMarkdown() - Tulis skrip yang memotong dokumen itu dengan tiga konfigurasi berbeda:
maxSize256, 512, dan 1024 — semuanya denganoverlap10% - Untuk tiap konfigurasi, cetak: jumlah potongan yang dihasilkan, panjang rata-rata, dan isi lengkap tiga potongan pertama
- Baca ketiga potongan itu sebagai manusia dan nilai: apakah masing-masing memuat satu gagasan yang utuh dan bisa dipahami tanpa membaca tetangganya?
- Ulangi konfigurasi terbaik dengan
overlap: 0, dan bandingkan potongan di sekitar batas - Buat embedding untuk konfigurasi terbaik, dan cetak panjang vektor pertama
Checklist penerimaan:
- [ ] Ketiga konfigurasi berjalan dan menghasilkan jumlah potongan yang jelas berbeda
- [ ] Kamu bisa menunjuk satu potongan dari konfigurasi 256 yang kehilangan konteks — kalimat yang merujuk sesuatu di luar potongan itu
- [ ] Kamu bisa menunjuk satu potongan dari konfigurasi 1024 yang memuat lebih dari satu topik
- [ ] Perbandingan
overlap: 0menunjukkan setidaknya satu gagasan yang terbelah - [ ] Kamu memilih satu konfigurasi dan bisa menjelaskan alasannya berdasarkan apa yang kamu lihat, bukan berdasarkan tabel di bab ini
- [ ]
embeddingsberhasil dibuat, dan kamu tahu berapa dimensi vektornya
Petunjuk: Langkah 4 adalah bagian yang tidak bisa diotomatiskan dan paling sering dilewati. Membaca potongan sebagai manusia adalah satu-satunya cara menilai apakah potongan itu "utuh". Uji sederhana: kalau kamu menunjukkan satu potongan kepada seseorang tanpa konteks lain, apakah ia bisa memahaminya?
Ikhtisar
MDocumentmenyeragamkan masukan dari teks, HTML, Markdown, dan JSON. Pilih yang sesuai format — ini membuka strategi chunking yang sadar struktur.- Parameter ukurannya
maxSize, bukansize. Dokumentasi lama keliru di titik ini. - Ada sembilan strategi chunking, dan tiap strategi menerima parameter yang berbeda.
recursiveadalah default yang wajar. - Ukuran potongan adalah keputusan desain paling berpengaruh pada mutu RAG. Terlalu kecil kehilangan konteks, terlalu besar mengaburkan yang relevan.
overlapmelindungi dari gagasan yang terbelah di batas potongan. Titik awal yang wajar: 10% darimaxSize.embedManydiimpor dari paketai, dan yang di-embed adalahchunk.text.extractmenerimatitle,summary,questions,keywords, danschema— bukanmetadata. Ia memanggil LLM sekali per potongan, jadi perhitungkan biayanya untuk dokumen dalam jumlah besar.
Lanjut ke mana
Potongan sudah menjadi vektor. Sekarang bagian terakhir: menyimpannya, mencarinya, dan menyambungkannya ke agent — 5.3 Vector Store & Retrieval.