Mimari
RAG’in üç işini gördük: bul, ekle, yaz. Bulabilmek için dokümanların önceden hazırlanmış olması gerekir. Kim hazırlıyor, ne zaman, nereye koyuyor?
Dokümanlar
Kraken Air’in 8 dokümanı (7 politika ve 1 duyuru, 2025 sürümü), Markdown olarak. Her birinin başında kendisi hakkında bilgi var: hangi doküman, hangi sürüm, kim okuyabilir. Üst şerit onları aranabilir hâle getirir ve bir kere çalışır.
Yükle, temizle, böl
1-load.tsdosyaları okur, baştaki bilgiyi metadata’ya çevirir.2-clean.tssayfa gürültüsünü atar, kırık satırları birleştirir.3-chunk.tsdokümanları bölüm başlıklarından chunk’lara böler: arama bütün dokümanı değil, chunk’ları bulur.Embed
4-embed.tsher chunk’ı bge-m3 ile 1024 sayılık bir embedding’e çevirir.Vector DB
5-store.tsher chunk’ı vektörü, metni ve metadata’sıyla Chroma’ya yazar: bizim vector DB’miz.Soru, aynı modelle
Her soruda alt şerit çalışır. Soru da üst şeritteki modelin aynısıyla 1024 sayıya çevrilir; başka bir model, karşılaştırılamayan başka bir uzay demektir.
Ara ve rerank
6-retrieve.tssoruya en yakın 5 chunk’ı bulur. Metadata filtresi eski sürümleri ve soranın okuyamayacağı dokümanları dışarıda tutar.7-rerank.tsher chunk’ı sohbet modeline puanlatır ve yeniden sıralar. Buna rerank denir.Prompt ve cevap
8-answer.tskuralları, en iyi 3 kaynağı ve soruyu tek bir metinde birleştirir. Buna prompt denir. 7 yıllık kıdem için model 22 iş günü der. Kaynağı [1]: Annual Leave Policy, §3.İki şerit, iki buluşma noktası
Şeritler yalnızca iki yerde buluşur: vector DB ve aynı embedding modeli. Sohbet modeli yalnızca alt şeritte çalışır: rerank ve cevap.
Repo turu: her kutu bir dosya
Bölüm başlığı “Repo turu: her kutu bir dosya”Adım adım bölümünde her sayfanın başında bu çizimin küçük hâli durur ve o sayfanın kutusu yanar. Örneğin chunking sayfasında:
Dizinsrc/
Dizinsteps/ her kutu için bir dosya. Her birinin nasıl çalıştırılacağı aşağıdaki tabloda
- 0-bare.ts adım 0: model tek başına, doküman yok
- 1-load.ts politika dosyalarını ve her birinin üst bilgisini okur
- 2-clean.ts sayfa başlıklarını atar, kırık satırları birleştirir
- 3-chunk.ts her dokümanı chunk’lara böler
- 4-embed.ts her chunk’ı 1024 sayıya çevirir
- 5-store.ts chunk’ları vector DB’ye yazar
- 6-retrieve.ts soruyu sayılara çevirir, en yakın 5 chunk’ı bulur
- 7-rerank.ts bu 5 chunk’ı yeniden puanlar, en iyi 3’ünü tutar
- 8-answer.ts prompt’u kurar, cevabı kaynaklarıyla yazar
Dizinlib/
- types.ts adımdan adıma taşınan kayıtlar
- ollama.ts modele giden iki çağrı: embed ve generate
- similarity.ts cosine, L2 ve dot. C sayfasında dot()’u siz yazarsınız
- store.ts iki store için tek arayüz
- chroma.ts Chroma’ya yazma ve arama
- store-json.ts aynı iş, JSON dosyasıyla
- metrics.ts eval’in yazdığı skorlar: hit@1, recall@k, MRR
- config.ts bütün ayarlar tek yerde
Dizincli/
- step.ts tek bir adımı çalıştırır: npm run step – n
- ingest.ts üst şerit: adım 1–5
- ask.ts alt şerit: adım 6–8
- question.ts günün sorusu ve defteri
- eval.ts kaliteyi ölçer
- doctor.ts kurulumu kontrol eder
- catchup.ts geride kalırsanız yetişmenize yardım eder
- capture.ts bu sitedeki bütün log’ları kaydeder
Dizinexplore/
- similar.ts hangi metinler yakın?
- map.ts vektör uzayı, 2 boyutta
- pca.ts harita için 1024 boyutu 2’ye indirir
Dizincorpus/ Kraken Air politikaları, 2025 ve 2026
- …
Dizindata/ her adımın çıktısı buraya yazılır — açıp bakın
- …
Küçük yardımcı dosyalar listede yok: data/ okuma-yazma, log, hata mesajları, toggle sıfırlama ve repo kontrolleri.
| Kutu | Dosya | Çalıştırma komutu | Yazdığı |
|---|---|---|---|
| Yükle | src/steps/1-load.ts |
npm run step -- 1 |
data/1-docs.json |
| Temizle | src/steps/2-clean.ts |
npm run step -- 2 |
data/2-clean.json |
| Chunk’la | src/steps/3-chunk.ts |
npm run step -- 3 |
data/3-chunks.json |
| Embed | src/steps/4-embed.ts |
npm run step -- 4 |
data/4-vectors.json |
| Vector DB | src/steps/5-store.ts |
npm run step -- 5 |
data/5-store.json |
| Embed + Ara | src/steps/6-retrieve.ts |
npm run step -- 6 |
data/6-retrieved.json |
| Rerank | src/steps/7-rerank.ts |
npm run step -- 7 |
data/7-reranked.json |
| Prompt + Cevap | src/steps/8-answer.ts |
npm run step -- 8 |
data/8-answer.json |
İki şerit, iki komut. Üst şerit npm run ingest:
// npm run ingest [-- --edition 2026] [--store json] [--reset]// The offline half, steps 1–5 in one go: load → clean → chunk → embed → store.// Runs once, or whenever the documents change. No chat model involved.// --edition 2026 read corpus/2026/ (added next to the editions already stored)// --reset empty the store firstimport { cli, config } from "../lib/config.js";import { openStore } from "../lib/store.js";import { run as load } from "../steps/1-load.js";import { run as clean } from "../steps/2-clean.js";import { run as chunk } from "../steps/3-chunk.js";import { run as embed } from "../steps/4-embed.js";import { run as store } from "../steps/5-store.js";import { exitOnError } from "./errors.js";
try { if (cli.flags.reset) await openStore().reset(); await load(); await clean(); await chunk(); await embed(); await store(); console.log(`\nIngest done (edition ${config.edition}). Now: npm run question\n`);} catch (e) { exitOnError(e);}Alt şerit npm run ask -- "question":
// npm run ask -- "question" [--prompt] [--bare] [--k 8]// The online half, steps 6–8 for any question: retrieve → rerank → answer.// --prompt print the exact text the model receives// --bare first ask the model with NO sources (step 0), for comparison// --k N how many chunks the retriever hands onimport { cli } from "../lib/config.js";import { run as bare } from "../steps/0-bare.js";import { run as retrieve } from "../steps/6-retrieve.js";import { run as rerank } from "../steps/7-rerank.js";import { run as answer } from "../steps/8-answer.js";import { exitOnError } from "./errors.js";
const question = cli.positionals[0];if (!question) { console.error('usage: npm run ask -- "How much is the international per diem?" [--prompt] [--bare] [--k 8]'); process.exit(1);}try { if (cli.flags.bare) await bare(question); await retrieve(question); await rerank(); await answer();} catch (e) { exitOnError(e);}“Aynı model” kuralı kodda bir kontrol: vector DB başka bir embedding modeliyle doldurulduysa arama hiç başlamaz.
/** * What it does: reads the step 5 receipt and stops if the store used a different embedding model. * * Guard: the store must hold vectors from the current data and the same embedding model. */export async function storeReceipt(): Promise<StoreReceipt> { const receipt = (await readStep<StoreReceipt>("5-store", "4-vectors")).data; if (receipt.embedModel !== config.embedModel) { throw new StaleInputError( `The store was filled with ${receipt.embedModel} vectors, but questions would be embedded with ${config.embedModel}. ` + `Vectors from two models live in different spaces — run steps 4 and 5 again.`, ); } return receipt;}