İçeriğe geç

E · Retrieval ve rerank

bir kere (ingest)her sorudaYükleTemizleChunk'laEmbedVector DBSoruEmbedAraRerankPromptCevap
7 yıllık bir çalışanın yıllık izni kaç iş günü?
İngilizce, logların yazdığı gibi: “How many working days of annual leave does an employee with 7 years of service get?”
→ rerank kapalı ve açık → aynı cümle: “An employee with 7 years of service gets 22 working days of annual leave [1].” ✓
chroma · section · filter edition 2025, access all · iki yolda da 22

Depo dolu, şimdi soru soruyoruz. Günün sorusunda doğru chunk ilk sırada. Ama skorlar neredeyse eşit; fark çok küçük.

soru → bge-m3 → Chroma: en yakın 5 chunk · filtre: edition 2025, access allvektör sırası · 6-retrieve.ts10.614hr-leave@2025#3 · izin tablosu20.604hr-leave@2025#2 · deneme süresi30.602hr-leave@2025#7 · izin devri40.598hr-leave@2025#6 · ücretsiz izin50.598hr-leave@2025#4 · izin planlamailk 5’in farkı: 0.016kesme: k = 560.585benefits@2025#670.583hr-leave@2025#5 · mazeret izinleri80.569hr-leave@2025#8↑ prompt’a ilk 3rerank sonrası · 7-rerank.ts1rel 10hr-leave@2025#3 · izin tablosu2rel 6hr-leave@2025#7 · izin devri3rel 6hr-leave@2025#4 · izin planlama4rel 3hr-leave@2025#2 · deneme süresi5rel 2hr-leave@2025#6 · ücretsiz izin↑ prompt’a ilk 3§2 (deneme süresi) prompt’tan çıktı§4 girdi: rel 6, ama cevap değilarama: 8 ms · rerank: 5 çağrı, 1498 ms(M serisi bir Mac’te ölçüldü)
top-ken yakın k tanebizde k = 5bu repodaeşikskor ≥ eşikkendi verinde seçkavramfiltreedition, accessaramanın içindebu repodaMMRbenzerini almaçeşitlilik eklekavramhybridvektör + kelime(BM25)kavram
  1. Soru da vektör olur

    6-retrieve.ts soruyu embed eder ve Chroma’dan en yakın 5 chunk’ı ister. Filtre aramanın içinde: yalnızca 2025 sürümü, yalnızca herkese açık dokümanlar.

  2. Retriever ne getirdi?

    5 chunk, hepsi izin politikasından (hr-leave). Cevabı taşıyan §3, yani izin tablosu, ilk sırada.

  3. Skorlar neredeyse eşit

    Birinci 0.614, beşinci 0.598: fark yalnızca 0.016. Her hr-leave chunk’ı [Annual Leave Policy › …] ile başlıyor; soruda da “annual leave” geçiyor. Model büyük ihtimalle bu ortak ifadeden bağ kurup beşini de soruya yakın buluyor. §3 birinci, ama çok az farkla.

  4. Kesme çizgisi: k

    En yakın k tanesini almaya top-k denir. k = 5: 6. sıradaki benefits §6 (0.585) dışarıda kaldı. k küçükse cevap kaçabilir, büyükse sonraki adımlara gürültü taşınır.

    npm run step -- 6, sonra data/6-retrieved.json dosyasını açın.
  5. Prompt'a yalnızca 3 chunk girer

    Rerank kapalıyken vektör sırasındaki ilk 3 girer: §3, §2 ve §7. §2 deneme süresini, §7 izin devrini anlatıyor. İkisi de soruyu cevaplamıyor ama prompt’ta yer kaplıyor.

  6. Rerank: soruyu ve chunk'ı birlikte oku

    Her vektörü ayrı yaptık: önce soruyu, sonra her chunk’ı. Reranker her (soru, chunk) çiftine 0–10 arası puan verir. Log’daki rel bu. Bizde bu işi sohbet modelimiz gemma3:4b yapıyor: 5 chunk, 5 çağrı.

  7. Rerank sonrası

    §3 10 puanla birinci kalıyor. Diğerleri en fazla 6 aldı. 0.016’lık fark artık net bir ayrım. §2 3 aldı ve dışarıda kaldı. Kalanların en iyileri, §7 ve §4, 2. ve 3. sıraya geçti. İkisinde de cevap yok.

  8. Bedeli

    Aramanın kendisi 8 ms sürdü (adımın tamamı 22 ms); rerank 1498 ms. Bu soruda cevap değişmedi: iki yolda da 22 iş günü. Rerank, doğru chunk ilk 3’te olmadığında işe yarıyor.

    src/steps/7-rerank.ts içinde rerank’ı kapatın; npm run step -- 7 ve npm run step -- 8. Sonra rerank’ı geri açın.
  9. Doğru chunk hep önde değil

    Şimdi başka kelimelerle sorun: “How much holiday do I get? I started here 4 years ago.” İzin politikasında “holiday” kelimesi hiç geçmiyor; orada “annual leave” yazıyor. Vektör sırasında izin tablosu ancak 5. geliyor, prompt’a giremiyor. Rerank onu 1. sıraya çıkarıyor. Cevap: 18 iş günü.

    npm run ask -- "How much holiday do I get? I started here 4 years ago.": izin tablosu vektör sırasında kaçıncı, rerank sonrası kaçıncı?
  10. Top-k tek yol değil

    Eşik, bir skorun üstündeki chunk’ları tutar. C’de “ilgisiz” çift bile 0.503 aldı; eşiği kendi verinizde seçin. MMR birbirinin kopyası olan chunk’ları atlar. Hybrid, kelime aramasını (BM25) ekler. Bu repoda top-k ve filtre var; gerisi fikir.

    src/steps/6-retrieve.ts içinde erişim filtresini kapatın; npm run ask -- "What is the upper limit of the monthly gross salary band for a senior specialist?". Sonra filtreyi geri açın.

Aynı sıralama terminalde: önce retriever’ın listesi, sonra rerank kapalı ve açık.

npm run step -- 6
$ npm run step -- 6
━━ STEP 6/8 · RETRIEVE
IN How many working days of annual leave does an employee with 7 years of service get?
WHAT embed the question (bge-m3, 0 ms) · ask chroma for the 5 closest · filter {"edition":"2025","access":["all"]}
OUT 5 chunks handed on · search 8 ms
1 0.614 hr-leave@2025#3 Annual Leave Policy › §3 Part-time staff get the same number as …
2 0.604 hr-leave@2025#2 Annual Leave Policy › §2 One full year of work, counted from the…
3 0.602 hr-leave@2025#7 Annual Leave Policy › §7 Some unused leave can move to the next …
4 0.598 hr-leave@2025#6 Annual Leave Policy › §6 Unpaid leave is for serious personal re…
5 0.598 hr-leave@2025#4 Annual Leave Policy › §4 Each unit enters its yearly leave plan …
─ ─ ─ cut: k = 5 ─ ─ ─
6 0.585 benefits@2025#6 Benefits › §6 Each employee gets 1 working day of bir…
7 0.583 hr-leave@2025#5 Annual Leave Policy › §5 In the cases below, an employee gets pa…
8 0.569 hr-leave@2025#8 Annual Leave Policy › §8 When a contract ends for any reason, un…
TIME 22 ms → data/6-retrieved.json
npm run step -- 7 (RERANK = false)
$ npm run step -- 7 (RERANK = false)
━━ STEP 7/8 · RERANK
IN data/6-retrieved.json (5 chunks for "How many working days of annual leave does an employee with 7 years of service get?")
WHAT rerank is OFF · keep the top 3 in vector order
OUT order unchanged
1 was 1 vec 0.614 hr-leave@2025#3 Annual Leave Policy › §3
2 was 2 vec 0.604 hr-leave@2025#2 Annual Leave Policy › §2
3 was 3 vec 0.602 hr-leave@2025#7 Annual Leave Policy › §7
─ ─ ─ into the prompt: top 3 ─ ─ ─
4 was 4 vec 0.598 hr-leave@2025#6 Annual Leave Policy › §6
5 was 5 vec 0.598 hr-leave@2025#4 Annual Leave Policy › §4
TIME 1 ms → data/7-reranked.json
npm run step -- 8 (rerank kapalıyken)
━━ STEP 8/8 · ANSWER
IN data/7-reranked.json (top 3 of 5 chunks)
WHAT rules (7) + numbered sources + question → gemma3:4b (add --prompt to see it)
OUT 956 prompt tokens · 21 output tokens · 2830 ms
An employee with 7 years of service gets 22 working days of annual leave [1].
[1] hr-leave@2025#3 Annual Leave Policy › §3
[2] hr-leave@2025#2 Annual Leave Policy › §2
[3] hr-leave@2025#7 Annual Leave Policy › §7
TIME 2832 ms → data/8-answer.json
npm run step -- 7
$ npm run step -- 7
━━ STEP 7/8 · RERANK
IN data/6-retrieved.json (5 chunks for "How many working days of annual leave does an employee with 7 years of service get?")
WHAT gemma3:4b scores each (question, chunk) pair 0–10 · re-sort · keep the top 3
OUT 5 chunks scored in 1498 ms · ties: 2 of 5
1 was 1 rel 10 vec 0.614 hr-leave@2025#3 Annual Leave Policy › §3
2 ▲ was 3 rel 6 vec 0.602 hr-leave@2025#7 Annual Leave Policy › §7
3 ▲ was 5 rel 6 vec 0.598 hr-leave@2025#4 Annual Leave Policy › §4
─ ─ ─ into the prompt: top 3 ─ ─ ─
4 ▼ was 2 rel 3 vec 0.604 hr-leave@2025#2 Annual Leave Policy › §2
5 ▼ was 4 rel 2 vec 0.598 hr-leave@2025#6 Annual Leave Policy › §6
TIME 1500 ms → data/7-reranked.json

Retriever’ın bütün işi: filtreyi kur, soruyu embed et, k + 3 sonuç iste (fazlası kesme çizgisinin altını göstermek için).

src/steps/6-retrieve.ts
/**
* What it does: embeds the question and asks the store for the closest chunks that pass the filters.
*
* Embed the question and fetch the k closest chunks that pass the filters.
* @param question plain text, any language
* @returns hits above the cut and the three just below it
*/
export async function retrieve(question: string, o: RetrieveOptions): Promise<Retrieved> {
const k = o.k ?? config.topK;
const edition = o.edition ?? EDITION_FILTER;
const access = "access" in o ? o.access : ASKER_MAY_READ;
const filter: Filter = {};
if (edition === "latest") filter.edition = (await o.store.editions()).at(-1);
else if (edition !== "all") filter.edition = edition;
if (access) filter.access = access;
let t = performance.now();
const { vectors: [q] } = await embed([question]);
const embedMs = Math.round(performance.now() - t);
t = performance.now();
const ranked = await o.store.query(q!, k + 3, filter);
const searchMs = Math.round(performance.now() - t);
return { question, filter, k, hits: ranked.slice(0, k), below: ranked.slice(k), embedMs, searchMs };
}

Reranker’ın sorusu tek bir prompt: chunk’ın en fazla 700 karakterini okur, yalnızca bir sayı döndürür.

src/steps/7-rerank.ts
/**
* What it does: asks the chat model to score one chunk from 0 to 10 for the question.
*
* Score one (question, chunk) pair with the chat model.
* @returns 0 = unrelated … 10 = the answer is right here
*/
export async function relevance(question: string, text: string): Promise<number> {
const prompt =
`QUESTION: ${question}\n\nTEXT:\n${text.slice(0, READ_CHARS)}\n\n` +
`Does this text hold the information needed to answer the question? ` +
`Give a score from 0 (not related at all) to 10 (the answer is exactly in this text).`;
const gen = await generate(prompt, { system: "You judge search results. Return JSON only.", format: SCHEMA });
const score = Number((JSON.parse(gen.text) as { score: unknown }).score);
return Number.isFinite(score) ? Math.max(0, Math.min(10, score)) : 0;
}

Maaş bantları dokümanı access: hr-only. 6-retrieve.ts içindeki toggle, soran kişinin neyi okuyabileceğini belirliyor:

src/steps/6-retrieve.ts
// TOGGLE access filter — who is asking?
const ASKER_MAY_READ: string[] | undefined = ["all"]; // default — a regular employee: public documents only
// const ASKER_MAY_READ: string[] | undefined = undefined; // alternative — nobody checks: every document reaches everyone
npm run ask -- "What is the upper limit of the monthly gross salary band for a senior specialist?"
━━ STEP 6/8 · RETRIEVE
IN What is the upper limit of the monthly gross salary band for a senior specialist?
WHAT embed the question (bge-m3, 33 ms) · ask chroma for the 5 closest · filter {"edition":"2025","access":["all"]}
OUT 5 chunks handed on · search 10 ms
1 0.470 travel-expenses@2025#4 Travel and Per Diem Policy › §4 The amounts below are in euro (EUR). Th…
2 0.464 hr-working-hours@2025#4 Working Hours and Remote Work › §4 Overtime is work beyond 40 hours a week…
3 0.460 travel-expenses@2025#3 Travel and Per Diem Policy › §3 The hotel upper limit per night is the …
4 0.458 announcement-meal-card@2025#2 Announcement: Meal Card Limit Update › §2 From 1 April 2025, the daily amount on …
5 0.439 benefits@2025#3 Benefits › §3 All employees join the company's privat…
─ ─ ─ cut: k = 5 ─ ─ ─
6 0.422 announcement-meal-card@2025#1 Announcement: Meal Card Limit Update › §1 Dear colleagues, food costs have gone u…
7 0.416 benefits@2025#4 Benefits › §4 The company pays up to 600 TRY a month …
8 0.413 hr-leave@2025#3 Annual Leave Policy › §3 Part-time staff get the same number as …
TIME 83 ms → data/6-retrieved.json
… aynı komutun devamı: rerank ve cevap
━━ STEP 7/8 · RERANK
IN data/6-retrieved.json (5 chunks for "What is the upper limit of the monthly gross salary band for a senior specialist?")
WHAT gemma3:4b scores each (question, chunk) pair 0–10 · re-sort · keep the top 3
OUT 5 chunks scored in 4454 ms · ties: 4 of 5
1 was 1 rel 10 vec 0.470 travel-expenses@2025#4 Travel and Per Diem Policy › §4
2 ▲ was 3 rel 10 vec 0.460 travel-expenses@2025#3 Travel and Per Diem Policy › §3
3 ▼ was 2 rel 2 vec 0.464 hr-working-hours@2025#4 Working Hours and Remote Work › §4
─ ─ ─ into the prompt: top 3 ─ ─ ─
4 was 4 rel 0 vec 0.458 announcement-meal-card@2025#2 Announcement: Meal Card Limit Update › §2
5 was 5 rel 0 vec 0.439 benefits@2025#3 Benefits › §3
TIME 4457 ms → data/7-reranked.json
━━ STEP 8/8 · ANSWER
IN data/7-reranked.json (top 3 of 5 chunks)
WHAT rules (7) + numbered sources + question → gemma3:4b (add --prompt to see it)
OUT 847 prompt tokens · 22 output tokens · 2564 ms
The hotel upper limit for a senior specialist is 4,000 TRY per night [2].
[1] travel-expenses@2025#4 Travel and Per Diem Policy › §4
[2] travel-expenses@2025#3 Travel and Per Diem Policy › §3
[3] hr-working-hours@2025#4 Working Hours and Remote Work › §4
TIME 2567 ms → data/8-answer.json

Filtre açıkken maaş tablosu prompt’a hiç gelmiyor; bu kısım çalışıyor. Ama model “This is not in the policies.” demiyor. Seyahat tablosunda da “Senior specialist” satırı ve bir “upper limit” var. Model kimsenin sormadığı bir soruyu cevaplıyor: gecelik 4,000 TRY otel limiti. Filtre veriyi korur; cevabı doğru yapmaz.

ASKER_MAY_READ = undefined (kimse kontrol etmiyor)
$ npm run ask -- "What is the upper limit of the monthly gross salary band for a senior specialist?" (ASKER_MAY_READ = undefined)
filter {"edition":"2025"}
1 0.646 hr-salary-bands@2025#3 Salary Bands and Pay Raises › §3 The monthly gross salary bands are belo…
2 0.566 hr-salary-bands@2025#4 Salary Bands and Pay Raises › §4 The salary of a new employee is set bet…
3 0.557 hr-salary-bands@2025#7 Salary Bands and Pay Raises › §7 The annual performance bonus is paid in…
4 0.536 hr-salary-bands@2025#5 Salary Bands and Pay Raises › §5 The general raise is given once a year,…
5 0.526 hr-salary-bands@2025#6 Salary Bands and Pay Raises › §6 A promoted employee gets a salary incre…
─ ─ ─ cut: k = 5 ─ ─ ─
6 0.506 hr-salary-bands@2025#1 Salary Bands and Pay Raises › §1 This policy sets the monthly gross sala…
7 0.505 hr-salary-bands@2025#2 Salary Bands and Pay Raises › §2 Only the Human Resources and Compensati…
8 0.470 travel-expenses@2025#4 Travel and Per Diem Policy › §4 The amounts below are in euro (EUR). Th…
The upper limit of the monthly gross salary band for a senior specialist is 115,000 TRY [2].
[1] hr-salary-bands@2025#3 Salary Bands and Pay Raises › §3
[2] hr-salary-bands@2025#4 Salary Bands and Pay Raises › §4
[3] hr-salary-bands@2025#7 Salary Bands and Pay Raises › §7

Filtre kapalıyken cevap maaş rakamını veriyor: 115,000 TRY. Bu sayıyla her çalışan arasında yalnızca koddaki tek bir toggle var. Cevap [2]’yi gösteriyor ama sayı [1]’de. F sayfasındaki “Cevaptaki [1] doğru mu, nasıl bilirim?” sorusuna bakın. Bu iki cevabın ne anlattığı Güvenlik sayfasında.

1 · Listeyi açın. npm run step -- 6 ve data/6-retrieved.json: hits (prompt’a aday 5 chunk), below (kesme çizgisinin altındaki 3), filter. Başka bir soru: npm run step -- 6 "How much holiday do I get?". Sonra npm run step -- 6 komutunu tekrar çalıştırın.

2 · Rerank’ı kapatın. src/steps/7-rerank.ts → TOGGLE rerank bloğunda true; // default satırını kapatın, false; // alternative satırını açın. npm run step -- 7 ve npm run step -- 8. Toggle’ı geri alıp npm run step -- 7: data/7-reranked.json içinde her chunk için was (eski sıra), relevance (rerank puanı) ve score (vektör skoru). Sonra npm run step -- 8.

3 · Rerank’ın asıl işi. npm run ask -- "How much holiday do I get? I started here 4 years ago.". Log’da izin tablosunun (hr-leave@2025#3) vektör sırasını ve rerank sonrası sırasını bulun. Prompt’a girdi mi? Cevap 18 mi?

4 · Erişim filtresini kapatın. src/steps/6-retrieve.ts → TOGGLE access filter bloğunda alternatif satırı açın, varsayılanı kapatın. npm run ask -- "What is the upper limit of the monthly gross salary band for a senior specialist?". Sonra mutlaka geri alın.

Geride kaldıysanız: npm run catchup -- 7.

k’yı değiştirin. npm run step -- 6 --k 8 → kesme çizgisi aşağı iner. Sonra npm run step -- 6 ile 5’e dönün.

k'yı nasıl seçerim?

Ölçerek: npm run eval, doğru bölümün ilk k içinde kaç soruda geldiğini (recall@k) sayıyor (Kaliteyi ölçmek). Bizde retriever 5 aday veriyor, prompt’a 3 giriyor (topK ve contextK, src/lib/config.ts); ikisi de bizim seçimimiz.

Rerank her soruda şart mı?

Hayır. Retriever doğru chunk’ı zaten ilk 3’e koyuyorsa rerank süresi boşa gider. Kaç soruda gerektiğini değerlendirme gösterir.

Reranker yanılır mı?

Evet. Maaş sorusunda seyahat politikasının iki tablosuna da 10 puan verdi: yurt dışı (EUR) ve yurt içi (TRY). Beraberlikler de oluyor: log’daki ties satırı, aynı puanı alan chunk sayısını yazıyor.

Bi-encoder ve cross-encoder ne demek?

Embedding modeli bir bi-encoder: soru ve chunk ayrı ayrı vektöre çevrilir, chunk vektörleri önceden hesaplanır, arama hızlıdır. Cross-encoder ikisini birlikte okur; daha dikkatlidir ama her soruda her aday için yeniden çalışır. Bizim reranker bir sohbet modeli ama aynı fikri uyguluyor; özel cross-encoder modelleri bu işi daha hızlı yapar, onları ölçmedik.

Doğru chunk’ı bulmak yetmez; prompt’a girecek sıraya da çıkması gerekir.