2026-08-04 15:00:33 +02:00
|
|
|
|
---
|
|
|
|
|
|
okf: "0.1"
|
|
|
|
|
|
type: phase
|
|
|
|
|
|
visibility: private
|
|
|
|
|
|
status: active
|
|
|
|
|
|
updated: 2026-07-17
|
|
|
|
|
|
links: []
|
|
|
|
|
|
---
|
|
|
|
|
|
|
docs(kb): recon + plan fazy 3 modułu 5 — warstwa kompilacji
Plan-doc (zero kodu): porządki po pilocie retrieval (filtr OCR-śmieci przez
excluded_reason, dedup paperless:14≡74, UNIQUE+model — migracja 003),
document_summary (004, UNIQUE(envelope_id,model) od razu), pilot streszczeń+tagów
dwutorowo API+lokalny GPU z rubryką jakości jako wejście do decyzji mailowej,
kaskada summary→chunk z bramką nie-gorszości na utrwalonym eval-secie, cykliczny
ingest jako systemd-timer@PIHA z metrykami textfile→fleet-prometheus→brain-watchdog,
wiki-kompilat wg szkicu operatora (Karpathy llm-wiki) rozwinięty wykonawczo
(osobne repo kb-wiki, frontmatter z sources, strony jako koperty source='wiki').
7 decyzji z rekomendacjami w §2; granice fazy w §9.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 15:45:50 +02:00
|
|
|
|
# Moduł 5, faza 3 — warstwa kompilacji (RECON + PLAN)
|
|
|
|
|
|
|
|
|
|
|
|
> Status: RECON ZAKOŃCZONY (2026-07-16), plan DO ZATWIERDZENIA. Zero kodu, zero migracji,
|
|
|
|
|
|
> zero pobranych modeli w ramach tego zadania — wyłącznie ten dokument.
|
|
|
|
|
|
>
|
|
|
|
|
|
> Kontynuacja `05-faza2-plan.md` (faza 2: koperta dokumentów + embeddingi + cross-source —
|
|
|
|
|
|
> DOMKNIĘTA 2026-07-16, kroki 1–8 komplet). Faza 3 = pierwsza warstwa **kompilacji**:
|
|
|
|
|
|
> porządki po pilocie retrieval, streszczenia+tagi jako półprodukt kompilacji, kaskada
|
|
|
|
|
|
> retrieval, cykliczny ingest, przygotowanie wiki-kompilatu (wzorzec Karpathy llm-wiki).
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 1. Stan faktyczny (po fazie 2, zweryfikowany w repo i sesjach)
|
|
|
|
|
|
|
|
|
|
|
|
### 1.1 Baza danych (kb-postgres@PIHA)
|
|
|
|
|
|
|
|
|
|
|
|
- `envelope`: **225 216 kopert** — `gmail` 225 030 (wszystkie z headers entities po
|
|
|
|
|
|
backfillu), `paperless` 186. Cross-source join `source_mail`: **180/186** (6 dokumentów
|
|
|
|
|
|
bez linku — spoza pipeline'u faktury-1, stan poprawny).
|
|
|
|
|
|
- `document_chunk`: **2683 chunki** ze 160 dokumentów (26 pustych contentów pominiętych,
|
|
|
|
|
|
1 patologiczny dot-leader chunk odrzucony przez context window Ollamy). Model `bge-m3`,
|
|
|
|
|
|
`VECTOR(1024)`, indeks HNSW cosine. Chunking 600/150 tok (≈2400/600 znaków)
|
|
|
|
|
|
**zatwierdzony bez zmian** w kroku 7.
|
|
|
|
|
|
- Constraint: `UNIQUE (envelope_id, chunk_index)` — **bez `model`**. Znany dług z review
|
|
|
|
|
|
kroku 6 (docstring `chunk_embed.py` flaguje to wprost): drugi model embeddingów cicho
|
|
|
|
|
|
no-opuje się na `ON CONFLICT DO NOTHING`. Naprawiane w tej fazie (§3.3).
|
|
|
|
|
|
|
|
|
|
|
|
### 1.2 Retrieval (pilot, krok 7 fazy 2)
|
|
|
|
|
|
|
|
|
|
|
|
- Progi skalibrowane (cosine distance `<=>`): **<0.45 trafienie, 0.45–0.55 szara strefa,
|
|
|
|
|
|
>0.55 brak odpowiedzi**. Negatywna kontrola ("sernik") = 0.62 — czysta separacja.
|
|
|
|
|
|
- Cross-lingual działa: angielskie zapytanie o FLL scoring wyciąga scoresheet **mimo
|
|
|
|
|
|
mojibake** — wniosek dla filtra śmieci: mojibake ≠ automatycznie śmieć (§3.1).
|
|
|
|
|
|
- Findings nieblokujące fazy 2, ale **blokujące kompilację wiki**: chunki z binarnym
|
|
|
|
|
|
OCR-szumem (zmielone kody kreskowe `\x01...`, zmielone fonty w paperless:119),
|
|
|
|
|
|
duplikaty dokumentów (paperless:14 ≡ paperless:74 — identyczne chunki w wynikach).
|
|
|
|
|
|
- Zestaw zapytań pilotowych żyje dziś tylko w sesji/transkrypcie — **nie jest utrwalony
|
|
|
|
|
|
jako plik**. Bramka kaskady (§6) wymaga powtarzalnego zestawu; utrwalenie to pierwszy
|
|
|
|
|
|
krok tamtego etapu.
|
|
|
|
|
|
|
|
|
|
|
|
### 1.3 Ollama@SOLARIA
|
|
|
|
|
|
|
|
|
|
|
|
- Deklaratywnie na GPU (compose z sekcją `deploy`, przywrócone 2026-07-16). Benchmark:
|
|
|
|
|
|
**207 ms/embed GPU vs 790 ms CPU** (~3.8× sekwencyjnie); overhead HTTP dominuje przy
|
|
|
|
|
|
pojedynczych requestach — batching pozostaje w backlogu (przed fazą mailową, poza
|
|
|
|
|
|
zakresem tej fazy).
|
|
|
|
|
|
- Dostępne modele: `qwen2.5-coder:14b`, `qwen3-coder:30b`, `deepseek-coder:latest`,
|
|
|
|
|
|
`deepcoder:14b`, `bge-m3` — **same modele coder + embedding, zero ogólnego instructa**.
|
|
|
|
|
|
Pilot streszczeń wymaga pobrania modelu instruct (§5).
|
|
|
|
|
|
- Uwaga recon: `hosts/solaria/capabilities.yaml` mówi „NVIDIA RTX 4070", sesje 07-15/16
|
|
|
|
|
|
mówią RTX 4070 Ti SUPER (16 GB) — manifest jest nieaktualny. Follow-up poza tym taskiem
|
|
|
|
|
|
(raportowane, nie ruszane — dyscyplina worktree).
|
|
|
|
|
|
- SOLARIA ma `availability_target: medium` (planowe wyłączenia) — cykliczny embed musi
|
|
|
|
|
|
tolerować Ollamę offline (§7).
|
|
|
|
|
|
|
|
|
|
|
|
### 1.4 Wzorce jobów (do reużycia 1:1)
|
|
|
|
|
|
|
|
|
|
|
|
`jobs/documents-ingest/` (`paperless_adapter.py`, `chunk_embed.py`) i
|
|
|
|
|
|
`jobs/gmail-header-backfill/` ustaliły rodzinę wzorców, którą każdy nowy job tej fazy
|
|
|
|
|
|
**musi** powielić:
|
|
|
|
|
|
|
|
|
|
|
|
- dry-run domyślny, `--apply` jawnie; `--limit`/`--offset` po stabilnym `ORDER BY id`;
|
|
|
|
|
|
- idempotencja: pre-fetch zbioru istniejących kluczy + `ON CONFLICT DO NOTHING` jako
|
|
|
|
|
|
druga linia obrony, z parsowaniem command tagu (cichy no-op ≠ insert);
|
|
|
|
|
|
- **bilans statystyk jako inwariant** (`fetched = suma wyników`), `stats_mismatch` →
|
|
|
|
|
|
niezerowy exit; izolacja błędów per wiersz (nigdy nie ubijać całego slice'a);
|
|
|
|
|
|
- wykonanie na PIHA wg utrwalonego wzorca: rsync `src/` do `/tmp/`, `~/kb/venv`
|
|
|
|
|
|
(Python 3.11), DSN budowany na hoście z `docker inspect kb-postgres`, **zawsze log do
|
|
|
|
|
|
pliku** (`> run.log 2>&1`) — lekcja z utraty 4999 wierszy w tmuxie.
|
|
|
|
|
|
|
|
|
|
|
|
### 1.5 Infrastruktura pod cykliczny ingest i alertowanie (§7)
|
|
|
|
|
|
|
|
|
|
|
|
- `node_exporter` na węzłach: mount `/:/host:ro,rslave` już jest, ale **bez flagi
|
|
|
|
|
|
`--collector.textfile.directory`** — włączenie textfile collectora to jedna linijka
|
|
|
|
|
|
w compose, zero nowych mountów (katalog czytany przez istniejący `/host`).
|
|
|
|
|
|
- `fleet-prometheus` (VPS): reguły w `services/fleet-prometheus/rules/`, **celowo bez
|
|
|
|
|
|
Alertmanagera** — alerty FIRING zbiera `brain-watchdog`@PIHA i forwarduje na Telegram.
|
|
|
|
|
|
Nowy alert = nowy plik/wpis w `rules/`, dostawa za darmo istniejącym torem.
|
|
|
|
|
|
- W repo nie ma dziś żadnego wzorca systemd-timer/cron dla jobów — §7 go ustanawia.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 2. Otwarte decyzje dla Oskara (z rekomendacjami)
|
|
|
|
|
|
|
|
|
|
|
|
### Decyzja 1 — Filtr śmieci: nie tworzyć chunków vs tworzyć i flagować?
|
|
|
|
|
|
|
|
|
|
|
|
**Rekomendacja: tworzyć i flagować** — kolumna `excluded_reason TEXT NULL` w
|
|
|
|
|
|
`document_chunk` (NULL = aktywny; `'ocr_junk'`, `'duplicate'`), nowe śmieciowe chunki
|
|
|
|
|
|
INSERT-owane z flagą i **bez embeddingu** (`embedding = NULL` — schemat to już dopuszcza).
|
|
|
|
|
|
|
|
|
|
|
|
Uzasadnienie:
|
|
|
|
|
|
- Heurystyka będzie się mylić. Flaga jest **odwracalna** (UPDATE + doembedowanie po
|
|
|
|
|
|
korekcie progu); nieutworzony chunk jest niewidoczny — nie da się audytować, co filtr
|
|
|
|
|
|
odrzucił, bez ponownego chunkowania całego korpusu.
|
|
|
|
|
|
- `embedding = NULL` wyrzuca śmieć z indeksu HNSW automatycznie (pgvector pomija NULL-e),
|
|
|
|
|
|
więc retrieval nie potrzebuje nawet filtra w zapytaniu dla *nowych* śmieci; dla już
|
|
|
|
|
|
zembedowanych 2683 chunków wystarczy `WHERE excluded_reason IS NULL` (§3.1 — embeddingi
|
|
|
|
|
|
istniejących śmieci zostawiamy, tylko flagujemy; zerowanie to opcjonalny porządek).
|
|
|
|
|
|
- Jedna kolumna obsługuje oba findingi pilota (szum OCR **i** duplikaty) — §3.1 i §3.2.
|
|
|
|
|
|
|
|
|
|
|
|
Odrzucona alternatywa: filtr wyłącznie przy chunkingu (nie tworzyć). Mniej wierszy, ale
|
|
|
|
|
|
niewidoczne odrzuty + retrofit przy każdej zmianie progu wymaga pełnego re-chunkingu.
|
|
|
|
|
|
|
|
|
|
|
|
### Decyzja 2 — Polityka dedup dokumentów
|
|
|
|
|
|
|
|
|
|
|
|
**Rekomendacja: duplikat zostaje w bazie, jego chunki dostają `excluded_reason='duplicate'`,
|
|
|
|
|
|
koperta dostaje addytywny wpis `entities[type=duplicate_of]`. Nic nie kasujemy.**
|
|
|
|
|
|
|
|
|
|
|
|
- Kanoniczny egzemplarz: ten z linkiem `source_mail` (bogatszy provenance); przy remisie
|
|
|
|
|
|
niższy `paperless:<id>` (starszy).
|
|
|
|
|
|
- `envelope` jest append-only i referencyjna (źródłem prawdy jest Paperless) — DELETE
|
|
|
|
|
|
łamałby zasady kb-00. Wpis `duplicate_of` w entities = audytowalna, odwracalna decyzja
|
|
|
|
|
|
w tej samej konwencji co `source_mail`.
|
|
|
|
|
|
- Usunięcie duplikatu **w Paperlessie** (żeby nie wracał przy cyklicznym ingest) to
|
|
|
|
|
|
osobna, ręczna decyzja operatora per przypadek — poza automatem; automat musi jedynie
|
|
|
|
|
|
być odporny (re-ingest duplikatu z Paperlessa → ponowne oflagowanie, nie błąd).
|
|
|
|
|
|
- Streszczenia (§5) i wiki (§8) generowane **tylko dla kanonicznych**.
|
|
|
|
|
|
|
|
|
|
|
|
### Decyzja 3 — Model do pilota streszczeń: lokalny GPU vs CC vs API
|
|
|
|
|
|
|
|
|
|
|
|
**Rekomendacja: pilot dwutorowy na pełnych 186 dokumentach — (A) zewnętrzne API
|
|
|
|
|
|
(Claude Haiku 4.5 lub Sonnet) jako przebieg referencyjny ORAZ (B) lokalny model instruct
|
|
|
|
|
|
na GPU na tej samej populacji. Oba komplety współistnieją w `document_summary` dzięki
|
|
|
|
|
|
`UNIQUE (envelope_id, model)` — schemat natywnie wspiera A/B.**
|
|
|
|
|
|
|
|
|
|
|
|
Uzasadnienie i kryteria:
|
|
|
|
|
|
- **Koszt API pomijalny**: 186 dok × śr. ~22k znaków ≈ 1.1–1.3M tok wejścia + ~40k tok
|
|
|
|
|
|
wyjścia → Haiku 4.5 ≈ **~1.5 USD**, Sonnet ≈ ~4 USD. To nie jest oś decyzji przy 186 dok.
|
|
|
|
|
|
- **Prywatność**: dokumenty finansowo-tożsamościowe wychodzą na zewnątrz — ale to jest
|
|
|
|
|
|
**zgodne z już podjętą decyzją architektoniczną** (szkic wiki, inwariant 2: kompilację
|
|
|
|
|
|
robi CC/zewnętrzne API; polityka eskalacji fazy 5). Pilot 186 dok to dokładnie
|
|
|
|
|
|
„wyselekcjonowany podzbiór", nie surowy korpus.
|
|
|
|
|
|
- **Po co mimo to lokalny przebieg**: decyzja o **skali mailowej** (docelowo dziesiątki
|
|
|
|
|
|
tysięcy streszczeń) ma zupełnie inną ekonomię — tam lokalny model może być jedyną
|
|
|
|
|
|
rozsądną opcją. Pilot musi zmierzyć, ile jakości tracimy lokalnie, na pełnej populacji,
|
|
|
|
|
|
póki jest tania (2–3 h GPU).
|
|
|
|
|
|
- **CC jako trzeci tor**: nie jako batch (interaktywna sesja nie jest powtarzalnym jobem),
|
|
|
|
|
|
ale jako **oceniający** — porównanie próbki ~25 par streszczeń (API vs lokal) rubryką
|
|
|
|
|
|
z §5.4.
|
|
|
|
|
|
- Kandydat lokalny: **żaden z obecnych modeli na SOLARII nie nadaje się** (same codery;
|
|
|
|
|
|
`qwen3-coder:30b` to MoE coder, ~18–19 GB w q4 — częściowy offload na CPU przy
|
|
|
|
|
|
12–16 GB VRAM, i nie po to trenowany). Do pobrania jeden z: **`gemma3:12b`**
|
|
|
|
|
|
(~8 GB q4, mocny multilingual/PL, kontekst 128k → większość dokumentów bez map-reduce —
|
|
|
|
|
|
**rekomendowany start**) lub `qwen3:14b` (~9 GB, mocny PL, kontekst 32k). Pilot
|
|
|
|
|
|
rozstrzyga empirycznie, nie przesądzamy w planie.
|
|
|
|
|
|
|
feat(kb): faza 3 krok 3 — kaskada retrieval summary→chunk, bramka PASS
documents_ingest.retrieval: flat_query (baseline) i cascade_query (stage1
document_summary model='claude-haiku-4-5' -> stage2 document_chunk), jedno
dzielone wywołanie embeddingu bge-m3 per zapytanie, tylko +1 SQL na kaskadę.
Czyste query_text -> wyniki(dist, source) pod przyszłe kb-query fazy 4.
166/166 testów (10 nowych, mocki: stage1->stage2, koperta bez chunków,
N > liczba kopert, no-summaries short-circuit).
Eval-set utrwalony 1:1 z pilota (docs/kb/eval/retrieval-pilot-2026-07-16.md,
nietknięty) w eval/queries.yaml + skrypt bramki eval/retrieval_eval.py
(integracyjny, read-only, poza pytest).
Wynik bramki (żywa baza, N=10 k=5): kryterium 1 (brak degradacji) PASS,
kryterium 2 (hit@3 kaskada=5/5 vs płaski=5/5) PASS, kryterium 3 (kontrole
negatywne 0.644/0.553 > 0.55 w obu torach) PASS. Sweep N∈{1,2,3,5,10,20}:
N=5 to zmierzony próg bezpieczny (N<5 degraduje zapytania 3-4), N=10 ma
2x margines — potwierdza domyślną wartość z planu zamiast przyjmować ją
z założenia. Kaskada nie poprawia jakości na 186-dok. korpusie (dystanse
identyczne z płaskim przy N≥5) — zgodnie z przewidywaniem planu: to test
architektury pod skalę mailową, nie optymalizacja pilota.
Decyzja: kaskada (N=10, k=5, claude-haiku-4-5) = domyślna ścieżka retrievalu.
Plan-doc §6.3 zaktualizowany wynikiem; §2 D3 zamknięte rozstrzygnięciem
Oskara (tor kompilacyjny=claude-haiku-4-5, gemma3:12b w odwodzie, decyzja
mailowa odłożona do reconu z flagą prywatności/kosztu).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-17 13:56:01 +02:00
|
|
|
|
**ROZSTRZYGNIĘCIE (Oskar, 2026-07-17, z oceny A/B pilota dwutorowego 186/186 dokumentów):**
|
|
|
|
|
|
|
|
|
|
|
|
- **Tor kompilacyjny = `claude-haiku-4-5`** — 157/157 streszczeń bez ani jednego JSON-faila,
|
|
|
|
|
|
100% dyscypliny słownika tagów, wyraźnie lepsza hierarchia treści na długich dokumentach
|
|
|
|
|
|
(OWU, umowy wielostronicowe) niż `gemma3:12b` (155/157, słabsza struktura na długich
|
|
|
|
|
|
dokumentach). Kaskada retrieval (§6) filtruje `document_summary` po `model =
|
|
|
|
|
|
'claude-haiku-4-5'` z tego powodu.
|
|
|
|
|
|
- **`gemma3:12b` zostaje w odwodzie jako tor lokalny** — dane już w bazie (155 streszczeń +
|
|
|
|
|
|
embeddingi), job (`summarize.py`) wspiera oba backendy bez zmian kodu, więc przełączenie
|
|
|
|
|
|
jest jednym flagowym argumentem, nie migracją.
|
|
|
|
|
|
- **Decyzja o modelu dla fazy mailowej (skala: dziesiątki tysięcy streszczeń) odłożona do
|
|
|
|
|
|
reconu fazy mailowej** — tam ekonomia jest inna (koszt API rzędu setek USD zamiast ~1.5 USD)
|
|
|
|
|
|
i wymaga własnej oceny z flagą prywatności (dane finansowo-tożsamościowe w skali masowej)
|
|
|
|
|
|
i szacunkiem kosztu, nie automatycznego przeniesienia wniosku z pilota 186-dokumentowego.
|
|
|
|
|
|
|
docs(kb): recon + plan fazy 3 modułu 5 — warstwa kompilacji
Plan-doc (zero kodu): porządki po pilocie retrieval (filtr OCR-śmieci przez
excluded_reason, dedup paperless:14≡74, UNIQUE+model — migracja 003),
document_summary (004, UNIQUE(envelope_id,model) od razu), pilot streszczeń+tagów
dwutorowo API+lokalny GPU z rubryką jakości jako wejście do decyzji mailowej,
kaskada summary→chunk z bramką nie-gorszości na utrwalonym eval-secie, cykliczny
ingest jako systemd-timer@PIHA z metrykami textfile→fleet-prometheus→brain-watchdog,
wiki-kompilat wg szkicu operatora (Karpathy llm-wiki) rozwinięty wykonawczo
(osobne repo kb-wiki, frontmatter z sources, strony jako koperty source='wiki').
7 decyzji z rekomendacjami w §2; granice fazy w §9.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 15:45:50 +02:00
|
|
|
|
### Decyzja 4 — Tagi: słownik kontrolowany vs free-form
|
|
|
|
|
|
|
|
|
|
|
|
**Rekomendacja: hybryda — startowy słownik kontrolowany w repo + max 3 tagi free-form
|
|
|
|
|
|
per dokument, znormalizowane (lowercase, kebab-case, NFC).**
|
|
|
|
|
|
|
|
|
|
|
|
- Słownik startowy (plik `jobs/documents-ingest/tags-vocab.yaml`, wersjonowany):
|
|
|
|
|
|
domeny życiowe widoczne już w pilocie — `ubezpieczenie`, `bank`, `kredyt`, `faktura`,
|
|
|
|
|
|
`umowa`, `urzad`, `auto`, `nieruchomosc`, `zdrowie`, `szkola`, `fll`, `praca`,
|
|
|
|
|
|
`subskrypcja`, `regulamin`. Prompt wymusza wybór z listy; free-form tylko jako
|
|
|
|
|
|
uzupełnienie.
|
|
|
|
|
|
- Po pilocie: przegląd free-form tagów (SQL `jsonb_array_elements` + count) → awans
|
|
|
|
|
|
częstych do słownika. Czysty słownik od początku = spójne fasety dla kaskady i wiki;
|
|
|
|
|
|
czysty free-form = eksplozja synonimów (`polisa`/`ubezpieczenie`/`insurance`), którą
|
|
|
|
|
|
potem trzeba sprzątać migracją danych.
|
|
|
|
|
|
|
|
|
|
|
|
### Decyzja 5 — Writeback tagów do Paperless
|
|
|
|
|
|
|
|
|
|
|
|
**Rekomendacja: NIE w fazie 3 — odłożyć do oceny po pilocie.** (Zadanie: „oceń, nie
|
|
|
|
|
|
przesądzaj" — ocena poniżej, decyzja na później.)
|
|
|
|
|
|
|
|
|
|
|
|
- Za: tagi widoczne w UI Paperlessa (jedyne miejsce, gdzie Oskar przegląda dokumenty);
|
|
|
|
|
|
Paperless ma API (`POST /api/tags/`, `PATCH /api/documents/<id>/`).
|
|
|
|
|
|
- Przeciw: cały pipeline KB→Paperless jest dziś **ściśle read-only** (docstring adaptera
|
|
|
|
|
|
gwarantuje „GET only") — writeback łamie tę granicę i tworzy pętlę: cykliczny ingest
|
|
|
|
|
|
(§7) wczyta nasze własne tagi z powrotem do `entities[type=tag]`. Pętla jest niegroźna
|
|
|
|
|
|
(idempotentna), ale zaciera pochodzenie tagu (LLM vs człowiek) — wymagałaby konwencji
|
|
|
|
|
|
(np. prefiks `kb:` w nazwie tagu).
|
|
|
|
|
|
- Jeśli po pilocie tagi okażą się dobre: osobny mały job `--writeback` za jawną flagą,
|
|
|
|
|
|
z prefiksem `kb:`, nigdy w domyślnym przebiegu.
|
|
|
|
|
|
|
|
|
|
|
|
### Decyzja 6 — Cykliczny ingest: gdzie timer i jak alertować
|
|
|
|
|
|
|
|
|
|
|
|
**Rekomendacja: systemd-timer na PIHA (always-on, ma LAN do Paperlessa i lokalny DB) +
|
|
|
|
|
|
metryki do node_exporter textfile collector + reguła w fleet-prometheus (dostawa
|
|
|
|
|
|
istniejącym torem brain-watchdog→Telegram).** Szczegóły §7.
|
|
|
|
|
|
|
|
|
|
|
|
Odrzucone alternatywy: (a) cron — brak `Persistent=true` (nadganianie po reboocie PIHA)
|
|
|
|
|
|
i gorsza obserwowalność; (b) emisja eventów przez `scripts/lib/events.sh` → supervisor —
|
|
|
|
|
|
tablica routingu supervisora jest dziś zamknięta na eventy stability-agent/ha-diag,
|
|
|
|
|
|
nowy typ eventu = zmiana w supervisorze; tor Prometheus jest tańszy i już zwalidowany
|
|
|
|
|
|
bojowo (Etap 0 cutoveru).
|
|
|
|
|
|
|
|
|
|
|
|
### Decyzja 7 — Repozytorium wiki: osobne repo vs katalog w homelab-codex-ws
|
|
|
|
|
|
|
|
|
|
|
|
**Rekomendacja: osobne repo `kb-wiki` (Forgejo, prywatne), klonowane na PIHA do
|
|
|
|
|
|
`/opt/homelab/data/kb-wiki/`.** (Szkic operatora zostawia to jawnie „do rozstrzygnięcia
|
|
|
|
|
|
w planie" — rozstrzygamy tutaj.)
|
|
|
|
|
|
|
|
|
|
|
|
- Inna klasa wrażliwości: wiki to **destylat danych osobistych** (zdrowie, finanse,
|
|
|
|
|
|
umowy), a homelab-codex-ws to kod infry — rozdzielenie minimalizuje blast radius
|
|
|
|
|
|
każdego przyszłego udostępnienia/klonu repo infry.
|
|
|
|
|
|
- Inny cykl commitów: kompilator LLM będzie commitował często i maszynowo — w repo infry
|
|
|
|
|
|
zaśmiecałoby to historię i gryzło się z dyscypliną worktree (każda zmiana przez task
|
|
|
|
|
|
branch); wiki potrzebuje trybu „executor commituje do master za zgodą operatora".
|
|
|
|
|
|
- Git history = darmowy audit trail kompilacji (inwariant 6 szkicu) — czystszy, gdy
|
|
|
|
|
|
w historii są wyłącznie strony wiki.
|
|
|
|
|
|
- Koszt: jedno repo więcej w Forgejo + klon na PIHA. Akceptowalny.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 3. Krok 1 — porządki z pilota retrieval (OBOWIĄZKOWE przed kompilacją)
|
|
|
|
|
|
|
|
|
|
|
|
Śmieć wkompilowany w stronę wiki propaguje się — filtr (3.1) i dedup (3.2) są twardym
|
|
|
|
|
|
warunkiem wstępnym dla §5 (streszczenia czytają chunki) i §8 (wiki czyta wyniki
|
|
|
|
|
|
retrievalu). Zmiana schematu (3.3) idzie w tej samej migracji.
|
|
|
|
|
|
|
|
|
|
|
|
### 3.1 Filtr śmieciowych chunków (`excluded_reason='ocr_junk'`)
|
|
|
|
|
|
|
|
|
|
|
|
**Co pilot ujawnił:** chunki będące binarnym szumem OCR — zmielone kody kreskowe
|
|
|
|
|
|
(sekwencje `\x01...`), zmielone glify ze złych fontów (paperless:119). Jednocześnie:
|
|
|
|
|
|
chunki z *częściowym* mojibake (`integraln¹ czêœæ`) **są użyteczne** — retrieval
|
|
|
|
|
|
wyciągnął scoresheet mimo mojibake. Filtr celuje w szum, nie w brzydki tekst.
|
|
|
|
|
|
Decyzja per **chunk**, nigdy per dokument (dokument z 1 stroną kodów kreskowych i 10
|
|
|
|
|
|
stronami tekstu traci 1 chunk, nie 11).
|
|
|
|
|
|
|
|
|
|
|
|
**Heurystyka (trzy sygnały, od najtańszego):**
|
|
|
|
|
|
|
|
|
|
|
|
1. **Znaki kontrolne** — jakikolwiek znak z C0 poza `\t\n\r` (`\x00–\x08`, `\x0b–\x1f`)
|
|
|
|
|
|
→ `ocr_junk` bezwarunkowo. Legalny tekst z OCR ich nie zawiera.
|
|
|
|
|
|
2. **Udział znaków słownych** — odsetek znaków spoza klasy
|
|
|
|
|
|
`[a-zA-Z0-9ąćęłńóśźż...interpunkcja...whitespace]` powyżej progu (start: >30%) → junk.
|
|
|
|
|
|
Łapie zmielone kody kreskowe i glifowy szum.
|
|
|
|
|
|
3. **Udział tokenów słowo-podobnych** — odsetek tokenów (split po whitespace) pasujących
|
|
|
|
|
|
do `^[[:alpha:]ąćęłńóśźż-]{2,30}$` poniżej progu (start: <25%) → junk. Łapie „tekst"
|
|
|
|
|
|
złożony z pseudolosowych zbitek liter, którego sygnał 2 nie widzi.
|
|
|
|
|
|
|
|
|
|
|
|
Entropia Shannona świadomie pominięta — sygnały 2+3 pokrywają te same przypadki taniej
|
|
|
|
|
|
i są objaśnialne (łatwo powiedzieć *dlaczego* chunk wypadł).
|
|
|
|
|
|
|
|
|
|
|
|
**Kalibracja przed backfillem (read-only):** policzyć sygnały 2 i 3 dla wszystkich 2683
|
|
|
|
|
|
chunków (skrypt kalibracyjny, wynik do CSV), posortować, ręcznie obejrzeć okolice
|
|
|
|
|
|
kandydackich progów (spodziewana wyraźna bimodalność: normalny tekst ≪10% nie-słownych
|
|
|
|
|
|
znaków, szum ≫50%). Progi zapisane jako stałe w kodzie **z komentarzem skąd pochodzą**.
|
|
|
|
|
|
Oczekiwana skala: pojedyncze dziesiątki chunków (pilot widział je punktowo), nie setki —
|
|
|
|
|
|
jeśli kalibracja pokaże setki, zatrzymać się i obejrzeć próbkę zamiast ufać progowi.
|
|
|
|
|
|
|
|
|
|
|
|
**Wpięcie (obie warstwy, zgodnie z decyzją 1):**
|
|
|
|
|
|
|
|
|
|
|
|
- **Backfill istniejących 2683**: one-shot `UPDATE document_chunk SET excluded_reason='ocr_junk'
|
|
|
|
|
|
WHERE id = ANY(...)` na podstawie kalibracji. Embeddingi oflagowanych zostają (odwracalność);
|
|
|
|
|
|
ich wyzerowanie to opcjonalny porządek później.
|
|
|
|
|
|
- **Nowe chunki** (`chunk_embed.py`): ocena heurystyką przed embedem; junk → INSERT
|
|
|
|
|
|
z `excluded_reason='ocr_junk'`, `embedding=NULL`, **bez wywołania Ollamy** (oszczędza
|
|
|
|
|
|
GPU i nie zaśmieca HNSW). Nowe liczniki w bilansie: `chunks_junk_flagged` — bilans
|
|
|
|
|
|
chunków rozszerzony i nadal domknięty.
|
|
|
|
|
|
- **Retrieval i wszyscy konsumenci** (kaskada §6, streszczenia §5, wiki §8): zawsze
|
|
|
|
|
|
`WHERE excluded_reason IS NULL`.
|
|
|
|
|
|
|
|
|
|
|
|
### 3.2 Dedup dokumentów (`excluded_reason='duplicate'` + `entities[duplicate_of]`)
|
|
|
|
|
|
|
|
|
|
|
|
**Zbadanie skali (read-only, przed polityką):**
|
|
|
|
|
|
|
|
|
|
|
|
```sql
|
|
|
|
|
|
-- Kandydaci: identyczna pełna treść chunków per koperta
|
|
|
|
|
|
WITH doc_hash AS (
|
|
|
|
|
|
SELECT envelope_id,
|
|
|
|
|
|
md5(string_agg(text, E'\n' ORDER BY chunk_index)) AS content_hash,
|
|
|
|
|
|
count(*) AS n_chunks
|
|
|
|
|
|
FROM document_chunk
|
|
|
|
|
|
GROUP BY envelope_id
|
|
|
|
|
|
)
|
|
|
|
|
|
SELECT content_hash, count(*) AS copies,
|
|
|
|
|
|
array_agg(envelope_id ORDER BY envelope_id) AS envelopes
|
|
|
|
|
|
FROM doc_hash
|
|
|
|
|
|
GROUP BY content_hash
|
|
|
|
|
|
HAVING count(*) > 1;
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Do skonfrontowania z Paperlessem: `documents_document.checksum` (MD5 oryginalnego pliku)
|
|
|
|
|
|
— identyczny checksum = duplikat binarny (jak 14≡74 najpewniej: ten sam PDF wszedł raz
|
|
|
|
|
|
przez consume faktury-1, raz inną drogą); różny checksum przy identycznych chunkach =
|
|
|
|
|
|
duplikat treściowy (np. re-OCR tego samego skanu). Paperless ma wbudowaną detekcję
|
|
|
|
|
|
duplikatów **przy consume** — nie działa wstecz i nie widzi par binarnie różnych.
|
|
|
|
|
|
|
|
|
|
|
|
**Polityka:** wg decyzji 2 — kanoniczny zostaje aktywny, chunki duplikatu dostają
|
|
|
|
|
|
`excluded_reason='duplicate'`, koperta duplikatu dostaje addytywnie:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{"type": "duplicate_of", "envelope_id": "paperless:14", "detected": "2026-07-XX",
|
|
|
|
|
|
"method": "chunk_content_hash"}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
One-shot skrypt (wzorce §1.4: dry-run, bilans, log), skala dziś to najpewniej 1–3 pary —
|
|
|
|
|
|
ale skrypt zostaje, bo cykliczny ingest (§7) może wprowadzać nowe duplikaty.
|
|
|
|
|
|
|
|
|
|
|
|
### 3.3 Migracja 003 — `UNIQUE(envelope_id, chunk_index, model)` + `excluded_reason`
|
|
|
|
|
|
|
|
|
|
|
|
Obie zmiany `document_chunk` w jednej addytywnej migracji:
|
|
|
|
|
|
|
|
|
|
|
|
```sql
|
|
|
|
|
|
-- services/kb-postgres/init/003_chunk_model_key.sql
|
|
|
|
|
|
-- (a) UNIQUE rozszerzony o model — bez tego drugi model embeddingów cicho się no-opuje
|
|
|
|
|
|
-- na ON CONFLICT DO NOTHING (finding z review kroku 6 fazy 2).
|
|
|
|
|
|
-- (b) excluded_reason — flaga wykluczenia chunka z retrievalu i kompilacji
|
|
|
|
|
|
-- (NULL = aktywny; 'ocr_junk' | 'duplicate').
|
|
|
|
|
|
|
|
|
|
|
|
ALTER TABLE document_chunk
|
|
|
|
|
|
DROP CONSTRAINT IF EXISTS document_chunk_envelope_id_chunk_index_key;
|
|
|
|
|
|
ALTER TABLE document_chunk
|
|
|
|
|
|
ADD CONSTRAINT document_chunk_envelope_chunk_model_key
|
|
|
|
|
|
UNIQUE (envelope_id, chunk_index, model);
|
|
|
|
|
|
|
|
|
|
|
|
ALTER TABLE document_chunk
|
|
|
|
|
|
ADD COLUMN IF NOT EXISTS excluded_reason TEXT; -- NULL = aktywny
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Uwagi wykonawcze:
|
|
|
|
|
|
|
|
|
|
|
|
- Pliki w `init/` odpalają się tylko przy świeżej inicjalizacji wolumenu
|
|
|
|
|
|
(docker-entrypoint-initdb.d); na żywej bazie migrację stosuje się ręcznie przez `psql`
|
|
|
|
|
|
— ten sam tryb co przy 002. Sekwencja plików pozostaje spójna dla świeżego odtworzenia
|
|
|
|
|
|
(001 → 002 → 003 → 004).
|
|
|
|
|
|
- Nazwa starego constraintu to autogenerowana
|
|
|
|
|
|
`document_chunk_envelope_id_chunk_index_key` — zweryfikować na żywej bazie
|
|
|
|
|
|
(`\d document_chunk`) przed puszczeniem, `IF EXISTS` chroni świeżą bazę.
|
|
|
|
|
|
- **Zmiany w `chunk_embed.py` w tym samym kroku**: `ON CONFLICT (envelope_id,
|
|
|
|
|
|
chunk_index, model)` w `_INSERT_SQL`, aktualizacja docstringów (usunąć zastrzeżenie
|
|
|
|
|
|
o no-opie — przestaje być prawdziwe), heurystyka junk z 3.1, licznik
|
|
|
|
|
|
`chunks_junk_flagged` w bilansie, testy (junk-detekcja: kody kreskowe, mojibake
|
|
|
|
|
|
częściowy NIE-junk, tekst czysty; konflikt z innym modelem wstawia się zamiast no-opa).
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 4. Krok 2 — migracja 004: `document_summary`
|
|
|
|
|
|
|
|
|
|
|
|
Wzorzec 002_chunks, z lekcją z 3.3 wbudowaną od razu (`model` w kluczu unikalności):
|
|
|
|
|
|
|
|
|
|
|
|
```sql
|
|
|
|
|
|
-- services/kb-postgres/init/004_summaries.sql
|
|
|
|
|
|
-- Streszczenie + tagi per dokument — półprodukt kompilacji (faza 3 modułu 5).
|
|
|
|
|
|
-- 1:N do envelope po model: ta sama koperta może mieć streszczenia z wielu modeli
|
|
|
|
|
|
-- (pilot A/B: API vs lokalny GPU); retrieval wybiera model konfiguracją.
|
|
|
|
|
|
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS document_summary (
|
|
|
|
|
|
id BIGSERIAL PRIMARY KEY,
|
|
|
|
|
|
envelope_id TEXT NOT NULL REFERENCES envelope(id) ON DELETE CASCADE,
|
|
|
|
|
|
summary TEXT NOT NULL,
|
|
|
|
|
|
tags JSONB NOT NULL DEFAULT '[]',
|
|
|
|
|
|
model TEXT NOT NULL, -- model, który NAPISAŁ streszczenie
|
|
|
|
|
|
embedding VECTOR(1024), -- NULL dopóki nie zembedowane
|
|
|
|
|
|
embedding_model TEXT, -- model embeddingu (bge-m3); NULL z embedding
|
|
|
|
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
|
|
|
|
UNIQUE (envelope_id, model)
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS document_summary_envelope_idx
|
|
|
|
|
|
ON document_summary (envelope_id);
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS document_summary_embedding_hnsw_idx
|
|
|
|
|
|
ON document_summary USING hnsw (embedding vector_cosine_ops);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Uwagi projektowe:
|
|
|
|
|
|
|
|
|
|
|
|
- **Dwa pola modelu są konieczne**, bo w tej tabeli działają dwa różne modele: instruct
|
|
|
|
|
|
pisze `summary`, bge-m3 robi `embedding`. `model` (streszczający) jest częścią klucza —
|
|
|
|
|
|
to on definiuje tożsamość treści; `embedding_model` to metadana re-indeksu, dokładnie
|
|
|
|
|
|
jak `model` w `document_chunk`.
|
|
|
|
|
|
- `tags` jako JSONB-tablica stringów znormalizowanych (decyzja 4). Zapytania po tagach:
|
|
|
|
|
|
`WHERE tags ? 'ubezpieczenie'` — wystarczające bez dodatkowego indeksu przy tej skali;
|
|
|
|
|
|
GIN na `tags` dopiero gdyby fasety weszły do ścieżki zapytań na skali mailowej.
|
|
|
|
|
|
- Brak `UNIQUE` po samym `envelope_id` jest **celowy** (A/B pilota). Konsument (kaskada
|
|
|
|
|
|
§6) zawsze filtruje po jednym, skonfigurowanym `model`.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 5. Krok 3 — pilot streszczeń + tagów (186 dokumentów)
|
|
|
|
|
|
|
|
|
|
|
|
### 5.1 Job: `documents-ingest-summarize` (w `jobs/documents-ingest/`)
|
|
|
|
|
|
|
|
|
|
|
|
Nowy moduł `summarize.py` obok `chunk_embed.py`, ta sama rodzina wzorców (§1.4):
|
|
|
|
|
|
|
|
|
|
|
|
- **Wejście**: koperty `source='paperless'` bez wpisu `duplicate_of`; treść dokumentu =
|
|
|
|
|
|
konkatenacja `document_chunk.text` `WHERE excluded_reason IS NULL ORDER BY chunk_index`
|
|
|
|
|
|
(nie surowy `entities[content]`) — filtr śmieci i dedup propagują się za darmo, a
|
|
|
|
|
|
lekka redundancja z overlapu chunków streszczeniu nie szkodzi.
|
|
|
|
|
|
- **Wyjście LLM**: wymuszony JSON `{"summary": "<3–8 zdań PL>", "tags": ["...", ...]}`;
|
|
|
|
|
|
walidacja: parsowalny JSON, tagi znormalizowane i zweryfikowane vs słownik (spoza
|
|
|
|
|
|
słownika max 3, reszta ucinana z logiem `tag_truncated`). Jedna próba ponowienia przy
|
|
|
|
|
|
niepoprawnym JSON, potem `llm_errors` i dalej.
|
|
|
|
|
|
- **Backend przez flagę `--backend ollama|anthropic`**: jeden job, dwa przebiegi pilota
|
|
|
|
|
|
(decyzja 3). Ollama: `POST /api/chat`, `format: json`, `OLLAMA_URL` jak w chunk_embed.
|
|
|
|
|
|
Anthropic: klucz z env (`ANTHROPIC_API_KEY`), nigdy w repo/logu; model z `--model`.
|
|
|
|
|
|
- **Długie dokumenty**: dokumenty > limitu kontekstu backendu (przy gemma3 128k realnie
|
|
|
|
|
|
tylko outlier 360k znaków) → map-reduce: streszczenia częściowe po ~20 chunków →
|
|
|
|
|
|
synteza z częściowych; licznik `documents_mapreduce`.
|
|
|
|
|
|
- **Idempotencja**: pre-fetch istniejących `(envelope_id, model)`; re-run pomija gotowe.
|
|
|
|
|
|
- **Bilans**: `documents_fetched = duplicates_skipped + no_active_chunks + already_summarized
|
|
|
|
|
|
+ summarized + llm_errors`; niezerowy exit przy rozjeździe lub `llm_errors > 0`.
|
|
|
|
|
|
- **Embedding streszczeń**: osobny, drugi przebieg (`--embed-summaries`, backend bge-m3,
|
|
|
|
|
|
wzorzec chunk_embed 1:1) — rozdzielenie pisania od embedowania pozwala re-embedować
|
|
|
|
|
|
bez ponownych kosztów LLM i działa, gdy SOLARIA śpi a API nie (i odwrotnie).
|
|
|
|
|
|
|
|
|
|
|
|
### 5.2 Prompt (szkielet do iteracji w pilocie)
|
|
|
|
|
|
|
|
|
|
|
|
System: „Jesteś archiwistą domowej bazy wiedzy. Streszczasz dokumenty po polsku, zwięźle
|
|
|
|
|
|
i faktograficznie: kto, co, kiedy, kwoty, numery umów/polis, terminy. Nie zgadujesz —
|
|
|
|
|
|
czego nie ma w tekście, tego nie piszesz. Zwracasz wyłącznie JSON."
|
|
|
|
|
|
User: słownik tagów + treść dokumentu. Kwoty/daty/numery **muszą** trafić do streszczenia
|
|
|
|
|
|
— to one niosą sygnał dla kaskady retrieval (zapytania pilotowe były punktowe).
|
|
|
|
|
|
|
|
|
|
|
|
### 5.3 Wykonanie pilota
|
|
|
|
|
|
|
|
|
|
|
|
1. `ollama pull gemma3:12b` na SOLARII (albo `qwen3:14b` — decyzja 3); smoke na 2–3
|
|
|
|
|
|
dokumentach, inspekcja ręczna JSON-ów.
|
|
|
|
|
|
2. Przebieg lokalny: całe 186 (`--backend ollama --model gemma3:12b --apply`), na PIHA
|
|
|
|
|
|
lub SOLARII, log do pliku. Szacunek: ~30–60 s/dok na GPU → **2–3 h**.
|
|
|
|
|
|
3. Przebieg API: całe 186 (`--backend anthropic --model claude-haiku-4-5 --apply`).
|
|
|
|
|
|
Szacunek kosztu: **~1.5 USD** (1.2M tok in / 40k tok out). Czas: minuty.
|
|
|
|
|
|
4. Embedding obu kompletów streszczeń (bge-m3, 372 embeddingi ≈ 2 min na GPU).
|
|
|
|
|
|
|
|
|
|
|
|
### 5.4 Ocena jakości (wejście do decyzji o skali mailowej)
|
|
|
|
|
|
|
|
|
|
|
|
Próbka 25 dokumentów (stratyfikowana: polisy, faktury, umowy, urzędowe, FLL/szkolne,
|
|
|
|
|
|
≥3 z mojibake). Sesja CC porównuje pary streszczeń (lokal vs API) przeciw chunkom
|
|
|
|
|
|
źródłowym, rubryka 0–2 na wymiar:
|
|
|
|
|
|
|
|
|
|
|
|
- **wierność** (0 = halucynacja/przekręcona kwota; 2 = wszystko potwierdzone w źródle),
|
|
|
|
|
|
- **kompletność faktów kluczowych** (kwoty, daty, strony, numery),
|
|
|
|
|
|
- **jakość tagów** (trafność + zgodność ze słownikiem).
|
|
|
|
|
|
|
|
|
|
|
|
Wynik do `docs/kb/modules/05-faza3-pilot-streszczen.md`: tabela per dokument + wnioski.
|
|
|
|
|
|
**Kryterium „lokalny wystarcza na skalę mailową"**: mediana wierności = 2 (zero tolerancji
|
|
|
|
|
|
dla przekręconych kwot — to trafia do wiki) i kompletność ≥ 80% punktów API. Jeśli lokalny
|
|
|
|
|
|
nie daje rady → decyzja o skali mailowej rozważa API z polityką eskalacji fazy 5 (koszt
|
|
|
|
|
|
na 225k kopert: rząd setek USD — wtedy selektywność, nie wszystko).
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 6. Krok 4 — kaskada retrieval: summary → chunk
|
|
|
|
|
|
|
|
|
|
|
|
### 6.1 Mechanizm
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
zapytanie → embed (bge-m3)
|
|
|
|
|
|
→ top-N document_summary (model = <wybrany>, embedding <=> q) -- pre-filtr
|
|
|
|
|
|
→ top-k document_chunk WHERE envelope_id IN (top-N dokumentów)
|
|
|
|
|
|
AND excluded_reason IS NULL
|
|
|
|
|
|
AND embedding IS NOT NULL
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Start: N=10, k=5 (sweep N ∈ {5, 10, 20} w ewaluacji). Przy 186 dokumentach pre-filtr nic
|
|
|
|
|
|
nie przyspiesza — **to test architektury pod skalę mailową** (225k kopert, gdzie płaski
|
|
|
|
|
|
skan chunków przestanie być tani), nie optymalizacja pilota. Bramka mierzy wyłącznie
|
|
|
|
|
|
jakość.
|
|
|
|
|
|
|
|
|
|
|
|
### 6.2 Bramka jakościowa (wzorzec kroku 7 fazy 2)
|
|
|
|
|
|
|
|
|
|
|
|
- **Utrwalić zestaw ewaluacyjny**: `jobs/documents-ingest/eval/queries.yaml` — zapytania
|
|
|
|
|
|
pilotowe z kroku 7 (odtworzone z sesji 2026-07-16) + oczekiwany dokument per zapytanie
|
|
|
|
|
|
+ negatywne kontrole („sernik"). Od teraz każda zmiana retrievalu przechodzi przez ten
|
|
|
|
|
|
plik — koniec z zapytaniami żyjącymi w transkryptach.
|
|
|
|
|
|
- **Skrypt ewaluacyjny** (read-only, `eval/retrieval_eval.py`): każde zapytanie puszcza
|
|
|
|
|
|
torem płaskim i kaskadowym, raportuje hit@3 (oczekiwany dokument w top-3 po dystansie),
|
|
|
|
|
|
najlepszy dystans, wynik negatywnych kontroli.
|
|
|
|
|
|
- **Kryterium przejścia (kaskada MUSI być nie gorsza):**
|
|
|
|
|
|
1. każde zapytanie trafione płasko (dystans < 0.45) jest trafione kaskadą — żaden hit
|
|
|
|
|
|
nie degraduje do szarej strefy ani braku;
|
|
|
|
|
|
2. hit@3 kaskady ≥ hit@3 płaskiego na całym zestawie;
|
|
|
|
|
|
3. negatywne kontrole pozostają > 0.55 w obu torach.
|
|
|
|
|
|
- **Gdy kaskada wypada gorzej**: diagnoza w kolejności — (a) N za małe (dokument wypada
|
|
|
|
|
|
w pre-filtrze → sweep N), (b) streszczenie nie niesie faktu, o który pyta zapytanie
|
|
|
|
|
|
(→ iteracja promptu §5.2 — to bramka na jakość streszczeń, nie tylko retrievalu),
|
|
|
|
|
|
(c) dopiero potem wniosek o architekturze. Kaskada nieprzechodząca bramki **nie
|
|
|
|
|
|
zastępuje** płaskiego retrievalu — płaski zostaje domyślny do skutku.
|
|
|
|
|
|
|
feat(kb): faza 3 krok 3 — kaskada retrieval summary→chunk, bramka PASS
documents_ingest.retrieval: flat_query (baseline) i cascade_query (stage1
document_summary model='claude-haiku-4-5' -> stage2 document_chunk), jedno
dzielone wywołanie embeddingu bge-m3 per zapytanie, tylko +1 SQL na kaskadę.
Czyste query_text -> wyniki(dist, source) pod przyszłe kb-query fazy 4.
166/166 testów (10 nowych, mocki: stage1->stage2, koperta bez chunków,
N > liczba kopert, no-summaries short-circuit).
Eval-set utrwalony 1:1 z pilota (docs/kb/eval/retrieval-pilot-2026-07-16.md,
nietknięty) w eval/queries.yaml + skrypt bramki eval/retrieval_eval.py
(integracyjny, read-only, poza pytest).
Wynik bramki (żywa baza, N=10 k=5): kryterium 1 (brak degradacji) PASS,
kryterium 2 (hit@3 kaskada=5/5 vs płaski=5/5) PASS, kryterium 3 (kontrole
negatywne 0.644/0.553 > 0.55 w obu torach) PASS. Sweep N∈{1,2,3,5,10,20}:
N=5 to zmierzony próg bezpieczny (N<5 degraduje zapytania 3-4), N=10 ma
2x margines — potwierdza domyślną wartość z planu zamiast przyjmować ją
z założenia. Kaskada nie poprawia jakości na 186-dok. korpusie (dystanse
identyczne z płaskim przy N≥5) — zgodnie z przewidywaniem planu: to test
architektury pod skalę mailową, nie optymalizacja pilota.
Decyzja: kaskada (N=10, k=5, claude-haiku-4-5) = domyślna ścieżka retrievalu.
Plan-doc §6.3 zaktualizowany wynikiem; §2 D3 zamknięte rozstrzygnięciem
Oskara (tor kompilacyjny=claude-haiku-4-5, gemma3:12b w odwodzie, decyzja
mailowa odłożona do reconu z flagą prywatności/kosztu).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-17 13:56:01 +02:00
|
|
|
|
### 6.3 Implementacja i wynik bramki (2026-07-17) — **PASS, kaskada = domyślna ścieżka**
|
|
|
|
|
|
|
|
|
|
|
|
**Kod**: `jobs/documents-ingest/src/documents_ingest/retrieval.py` — dwie czyste funkcje
|
|
|
|
|
|
wejściowe pod przyszłe kb-query (faza 4), `query_text → chunki z dist i source`:
|
|
|
|
|
|
`flat_query` (baseline) i `cascade_query` (`summary_model='claude-haiku-4-5'` — decyzja 3
|
|
|
|
|
|
rozstrzygnięta wyżej). Oba dzielą **jedno** wywołanie embeddingu zapytania (bge-m3) — kaskada
|
|
|
|
|
|
dokłada tylko jeden dodatkowy SQL (stage 1), zero dodatkowych wywołań Ollamy. Testy
|
|
|
|
|
|
jednostkowe z mockami: `tests/test_retrieval.py` (stage1→stage2 zawężenie, koperta bez
|
|
|
|
|
|
aktywnych chunków, N większe niż liczba kopert, no-summaries short-circuit) — 166/166 testów
|
|
|
|
|
|
pakietu przechodzi.
|
|
|
|
|
|
|
|
|
|
|
|
**Eval-set utrwalony**: `jobs/documents-ingest/eval/queries.yaml` (7 zapytań z pilota
|
fix(kb): przepiecie wszystkich odwolan wewnetrznych po migracji
126 plikow (md, yaml, sh, py) odwolywalo sie do sciezek sprzed migracji.
15 markdown-linkow [..](..) -> policzona sciezka WZGLEDNA wobec pliku
odsylajacego (wczesniej czesc z nich byla repo-root-relative i nie
rozwiazywala sie z katalogu, w ktorym lezala)
200 odwolan tekstowych (backticki, proza, yaml, importy w kodzie)
-> nowa sciezka repo-root-relative, zgodnie z konwencja repo
5 linkow rodzenstwa (gole nazwy plikow, np. "](DEPLOY.md)") — dzialaly
tylko w starym katalogu; przeliczone recznie
Objete m.in.: CLAUDE.md (scripts/onboard/README.md -> kb/runbooks/
node-onboarding-tool.md, docs/backlog.md -> kb/phases/backlog.md),
README.md, .claude/skills/, 20 session logow, kod jobow.
Ostatnie 5 odwolan pochodzi z tresci wciagnietej rebasem z origin/master
(session log 2026-07-31, override node-agenta na SOLARII, dwie pozycje
backlogu) — wskazywaly na docs/incidents/, docs/kb/modules/ i
services/narty27/README.md sprzed migracji.
Dodany wzajemny link miedzy kb/services/control-plane.md (stub kodu)
a kb/subsystems/control-plane.md (opis, deprecated) — dwa dokumenty o tym
samym systemie, latwe do pomylenia.
Weryfikacja na 790 plikach: 0 odwolan do starych sciezek,
0 martwych linkow markdown. Lint OKF: 190/190 plikow ZGODNE.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 15:12:24 +02:00
|
|
|
|
07-16, 1:1 z `kb/phases/kb-m5-eval-retrieval-pilot.md`, ten plik pozostał nietknięty —
|
feat(kb): faza 3 krok 3 — kaskada retrieval summary→chunk, bramka PASS
documents_ingest.retrieval: flat_query (baseline) i cascade_query (stage1
document_summary model='claude-haiku-4-5' -> stage2 document_chunk), jedno
dzielone wywołanie embeddingu bge-m3 per zapytanie, tylko +1 SQL na kaskadę.
Czyste query_text -> wyniki(dist, source) pod przyszłe kb-query fazy 4.
166/166 testów (10 nowych, mocki: stage1->stage2, koperta bez chunków,
N > liczba kopert, no-summaries short-circuit).
Eval-set utrwalony 1:1 z pilota (docs/kb/eval/retrieval-pilot-2026-07-16.md,
nietknięty) w eval/queries.yaml + skrypt bramki eval/retrieval_eval.py
(integracyjny, read-only, poza pytest).
Wynik bramki (żywa baza, N=10 k=5): kryterium 1 (brak degradacji) PASS,
kryterium 2 (hit@3 kaskada=5/5 vs płaski=5/5) PASS, kryterium 3 (kontrole
negatywne 0.644/0.553 > 0.55 w obu torach) PASS. Sweep N∈{1,2,3,5,10,20}:
N=5 to zmierzony próg bezpieczny (N<5 degraduje zapytania 3-4), N=10 ma
2x margines — potwierdza domyślną wartość z planu zamiast przyjmować ją
z założenia. Kaskada nie poprawia jakości na 186-dok. korpusie (dystanse
identyczne z płaskim przy N≥5) — zgodnie z przewidywaniem planu: to test
architektury pod skalę mailową, nie optymalizacja pilota.
Decyzja: kaskada (N=10, k=5, claude-haiku-4-5) = domyślna ścieżka retrievalu.
Plan-doc §6.3 zaktualizowany wynikiem; §2 D3 zamknięte rozstrzygnięciem
Oskara (tor kompilacyjny=claude-haiku-4-5, gemma3:12b w odwodzie, decyzja
mailowa odłożona do reconu z flagą prywatności/kosztu).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-17 13:56:01 +02:00
|
|
|
|
`queries.yaml` to jego wersjonowana kopia robocza). Skrypt bramki (read-only, integracyjny,
|
|
|
|
|
|
**nie wchodzi do pytest**): `jobs/documents-ingest/eval/retrieval_eval.py`.
|
|
|
|
|
|
|
|
|
|
|
|
**Kalibracja N** (sweep na żywej bazie, k=5 stałe): przy N∈{1,2,3} kaskada **degraduje**
|
|
|
|
|
|
zapytania 2–5 (np. zapytanie 4 spada z 0.448 do 0.532 przy N=1 — trafienie znika); **N=5 jest
|
|
|
|
|
|
najmniejszym progiem bezpiecznym** — od N=5 wzwyż wynik identyczny z płaskim dla wszystkich
|
|
|
|
|
|
7 zapytań. Rekomendacja planu (**N=10, k=5**) ma więc 2× margines nad zmierzonym progiem
|
|
|
|
|
|
minimalnym — potwierdzona bez zmian, nie tylko przyjęta z założenia.
|
|
|
|
|
|
|
|
|
|
|
|
**Wynik bramki przy N=10, k=5** (żywa baza, `claude-haiku-4-5`, 157 streszczeń,
|
|
|
|
|
|
2545 aktywnych chunków z 2683):
|
|
|
|
|
|
|
|
|
|
|
|
| Kryterium | Wynik | Werdykt |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| 1. żaden płaski hit nie degraduje | 0 degradacji na 5 zapytań-trafień | PASS |
|
|
|
|
|
|
| 2. hit@3 kaskada ≥ hit@3 płaski | 5/5 vs 5/5 | PASS |
|
|
|
|
|
|
| 3. negatywne kontrole > 0.55 (oba tory) | płaski 0.621/0.553, kaskada 0.644/0.553 | PASS |
|
|
|
|
|
|
|
|
|
|
|
|
**Czy kaskada COŚ poprawia?** Nie przy tej skali (186 dok.) — dystanse top-1 kaskady są
|
|
|
|
|
|
**identyczne co do bita** z płaskim dla wszystkich 5 zapytań-trafień przy N≥5, w tym dla
|
|
|
|
|
|
zapytania 3 (szara strefa "FLL scoring PL", 0.4289 w obu torach — pre-filtr po streszczeniu
|
|
|
|
|
|
ani nie pomaga, ani nie szkodzi). To zgodne z przewidywaniem planu (§6.1): przy 157
|
|
|
|
|
|
skompilowanych kopertach pre-filtr nie ma czego przyspieszać ani czego poprawiać — test
|
|
|
|
|
|
weryfikuje **architekturę** pod skalę mailową (225k kopert), nie jakość na tym korpusie.
|
|
|
|
|
|
Filtr `ocr_junk` z kroku 1 potwierdził się przy okazji: chunk z kodami kreskowymi, który w
|
|
|
|
|
|
pilocie zajmował poz. 3/5 dla zapytania 2, już nie występuje w wynikach (usunięty z indeksu).
|
|
|
|
|
|
|
|
|
|
|
|
**Koszt na zapytanie**: płaski = 1 embed + 1 SQL; kaskada = 1 embed (dzielony) + 2 SQL
|
|
|
|
|
|
(stage 1 top-N streszczeń, stage 2 top-k chunków w ich obrębie) — jeden dodatkowy SQL,
|
|
|
|
|
|
zero dodatkowych wywołań Ollamy.
|
|
|
|
|
|
|
|
|
|
|
|
**Decyzja**: bramka PASS → **kaskada (`cascade_query`, N=10, k=5, `summary_model=
|
|
|
|
|
|
'claude-haiku-4-5'`) jest odtąd domyślną ścieżką retrievalu** dla przyszłego kb-query
|
|
|
|
|
|
(faza 4). Płaski (`flat_query`) zostaje w kodzie jako baseline/fallback do porównań i do
|
|
|
|
|
|
architektur bez warstwy streszczeń.
|
|
|
|
|
|
|
docs(kb): recon + plan fazy 3 modułu 5 — warstwa kompilacji
Plan-doc (zero kodu): porządki po pilocie retrieval (filtr OCR-śmieci przez
excluded_reason, dedup paperless:14≡74, UNIQUE+model — migracja 003),
document_summary (004, UNIQUE(envelope_id,model) od razu), pilot streszczeń+tagów
dwutorowo API+lokalny GPU z rubryką jakości jako wejście do decyzji mailowej,
kaskada summary→chunk z bramką nie-gorszości na utrwalonym eval-secie, cykliczny
ingest jako systemd-timer@PIHA z metrykami textfile→fleet-prometheus→brain-watchdog,
wiki-kompilat wg szkicu operatora (Karpathy llm-wiki) rozwinięty wykonawczo
(osobne repo kb-wiki, frontmatter z sources, strony jako koperty source='wiki').
7 decyzji z rekomendacjami w §2; granice fazy w §9.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 15:45:50 +02:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 7. Krok 5 — cykliczny ingest (adapter + embed jako timer)
|
|
|
|
|
|
|
|
|
|
|
|
Adapter i embed są już idempotentne — nowość to wyłącznie orkiestracja i alarm o failu.
|
|
|
|
|
|
|
|
|
|
|
|
### 7.1 Wykonanie (PIHA, systemd-timer — decyzja 6)
|
|
|
|
|
|
|
|
|
|
|
|
- **Instalacja stała** (koniec z rsync do `/tmp` dla tego przypadku — to był wzorzec dla
|
|
|
|
|
|
one-shotów): dedykowany venv `/opt/homelab/kb/venv`, `pip install -e packages/kb-mail
|
|
|
|
|
|
jobs/documents-ingest` z checkoutu deploy-only na PIHA przy deployu (checkout służy tu
|
|
|
|
|
|
jako źródło instalacji, praca deweloperska nadal poza nim). Sekrety: istniejący
|
|
|
|
|
|
`/opt/homelab/kb/.env` (token Paperless już tam mieszka, 600).
|
|
|
|
|
|
- **Jednostki** w repo: `jobs/documents-ingest/systemd/kb-ingest.{service,timer}` +
|
|
|
|
|
|
skrypt `kb-ingest-run.sh`; instalacja udokumentowana w README (symlink/copy do
|
|
|
|
|
|
`/etc/systemd/system/`, `systemctl enable --now kb-ingest.timer`). Pierwszy
|
|
|
|
|
|
systemd-timer w repo — świadomie host-level, nie kontener (joby potrzebują jednocześnie
|
|
|
|
|
|
LAN, DB i plików hosta; konteneryzacja nic tu nie daje).
|
|
|
|
|
|
- **Harmonogram**: `OnCalendar=*-*-* 03:30`, `Persistent=true` (nadgania po reboocie).
|
|
|
|
|
|
- **Sekwencja skryptu**: adapter `--apply` → chunk_embed `--apply`
|
|
|
|
|
|
(`OLLAMA_URL=http://solaria:11434`) → (po decyzji z pilota, rozszerzenie później:
|
|
|
|
|
|
summarize nowych dokumentów). Log do `/opt/homelab/logs/kb-ingest/run-YYYYMMDD.log`.
|
|
|
|
|
|
- **Tolerancja na SOLARIĘ offline** (`availability_target: medium`): wrapper odróżnia
|
|
|
|
|
|
„Ollama nieosiągalna" (probe `GET /api/tags` przed embedem; brak → pomiń embed,
|
|
|
|
|
|
odnotuj, **to nie jest fail** — nadrobi następny run, bo embed jest idempotentny) od
|
|
|
|
|
|
realnych błędów (exit ≠ 0 adaptera, `stats_mismatch`, błędy embedu przy żywej Ollamie).
|
|
|
|
|
|
|
|
|
|
|
|
### 7.2 Obserwowalność i alarm (istniejący tor Prometheus)
|
|
|
|
|
|
|
|
|
|
|
|
- **Metryki** (wrapper pisze plik `.prom` atomowo — tmp + rename — do
|
|
|
|
|
|
`/opt/homelab/state/node-exporter/kb-ingest.prom`):
|
|
|
|
|
|
`kb_ingest_last_run_timestamp`, `kb_ingest_last_success_timestamp`,
|
|
|
|
|
|
`kb_ingest_documents_inserted`, `kb_ingest_chunks_inserted`,
|
|
|
|
|
|
`kb_ingest_embed_skipped` (0/1 — SOLARIA spała), `kb_ingest_embed_backlog`
|
|
|
|
|
|
(chunki aktywne bez embeddingu — rosnący backlog = SOLARIA śpi za długo).
|
|
|
|
|
|
- **node_exporter@PIHA**: dodać `--collector.textfile.directory=/host/opt/homelab/state/node-exporter`
|
|
|
|
|
|
w compose (mount `/:/host:ro` już to pokrywa — zero nowych wolumenów).
|
|
|
|
|
|
- **Reguły** w `services/fleet-prometheus/rules/` (nowy plik `kb-ingest.yml`, konwencja
|
|
|
|
|
|
liveness.yml — tylko FIRING, dostawę robi brain-watchdog):
|
|
|
|
|
|
- `KbIngestStale`: `time() - kb_ingest_last_success_timestamp{node="piha"} > 172800`
|
|
|
|
|
|
(2 doby = 2 nieudane runy) — severity critical;
|
|
|
|
|
|
- `KbEmbedBacklogGrowing`: `kb_ingest_embed_backlog > 0` przez `for: 72h` — severity
|
|
|
|
|
|
warning (SOLARIA nie wstała od 3 dni albo embed systematycznie pada).
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 8. Krok 6 — Wiki-kompilat (docelowy moduł syntezy)
|
|
|
|
|
|
|
|
|
|
|
|
### 8.1 Szkic operatora (decyzja architektoniczna 2026-07-15 — NIE podlega zmianie)
|
|
|
|
|
|
|
|
|
|
|
|
> Wzorzec: Karpathy llm-wiki (gist karpathy/442a6bf555914893e9891c11519de94f).
|
|
|
|
|
|
> Architektura dwuwarstwowa:
|
|
|
|
|
|
> - **Warstwa dowodowa** (jest): RAG/pgvector na chunkach — skala 225k+ kopert, źródło
|
|
|
|
|
|
> prawdy, zawsze lokalna.
|
|
|
|
|
|
> - **Warstwa pamięci** (faza 3+): wiki markdownów utrzymywana przez LLM — strony-encje
|
|
|
|
|
|
> (firma, umowa, sprawa, temat: „PZU", „kredyt", „FLL 25-26"), kompilowane
|
|
|
|
|
|
> **Z WYNIKÓW RETRIEVALU**, nie z surowców.
|
|
|
|
|
|
>
|
|
|
|
|
|
> Inwarianty:
|
|
|
|
|
|
> 1. Każda strona wiki linkuje envelope_id + chunk ids, z których powstała
|
|
|
|
|
|
> (audytowalność, mitygacja propagacji błędów kompilacji).
|
|
|
|
|
|
> 2. Kompilację i lint robi CC/zewnętrzne API — lokalny model za słaby na
|
|
|
|
|
|
> wielostronicowe operacje; spina się z polityką eskalacji (faza 5): dane wychodzące
|
|
|
|
|
|
> = wyselekcjonowane chunki, nie surowy korpus.
|
|
|
|
|
|
> 3. Lint okresowy: sprzeczności między stronami, strony-sieroty, brakujące pojęcia
|
|
|
|
|
|
> (linkowane a nieistniejące), spot-check stron vs źródłowe chunki.
|
|
|
|
|
|
> 4. Pętla zwrotna: dobre odpowiedzi z zapytań (faza 5) wracają do wiki jako nowe
|
|
|
|
|
|
> strony/aktualizacje.
|
|
|
|
|
|
> 5. Wiki wchodzi do retrievalu jako dodatkowe źródło (embedding stron obok summaries)
|
|
|
|
|
|
> — kaskada: wiki → summary → chunk.
|
|
|
|
|
|
> 6. Repozytorium wiki: git (osobne repo albo katalog w homelab-codex-ws — do
|
|
|
|
|
|
> rozstrzygnięcia w planie), historia zmian = darmowy audit trail kompilacji.
|
|
|
|
|
|
>
|
|
|
|
|
|
> Kolejność: pierwsze strony wiki dopiero PO pilocie streszczeń (streszczenia to
|
|
|
|
|
|
> półprodukt kompilacji) i PO filtrze śmieciowych chunków. Pełna wiki po fazie mailowej
|
|
|
|
|
|
> (przyrostówka) — wcześniej kompilat byłby fotografią przeszłości.
|
|
|
|
|
|
|
|
|
|
|
|
### 8.2 Rozwinięcie wykonawcze (szczegóły, decyzje architektoniczne bez zmian)
|
|
|
|
|
|
|
|
|
|
|
|
**Repozytorium** (rozstrzygnięcie punktu 6 szkicu): osobne repo `kb-wiki` — decyzja 7,
|
|
|
|
|
|
uzasadnienie w §2. Klon roboczy na PIHA: `/opt/homelab/data/kb-wiki/`.
|
|
|
|
|
|
|
|
|
|
|
|
**Struktura katalogów** (typy stron = katalogi; płasko wewnątrz, linki `[[...]]` robią
|
|
|
|
|
|
graf, nie hierarchia plików):
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
kb-wiki/
|
|
|
|
|
|
├── INDEX.md # spis stron per typ, utrzymywany przy kompilacji
|
|
|
|
|
|
├── podmioty/ # firmy i instytucje: pzu.md, mbank.md, urzad-skarbowy.md
|
|
|
|
|
|
├── osoby/ # tożsamości/aliasy (spina się z backfillem headers)
|
|
|
|
|
|
├── sprawy/ # procesy w czasie: kredyt-hipoteczny.md, fll-2025-26.md
|
|
|
|
|
|
├── umowy/ # aktywne kontrakty/polisy: polisa-oc-auto.md
|
|
|
|
|
|
├── tematy/ # przekrojowe: ubezpieczenia.md, subskrypcje.md
|
|
|
|
|
|
└── _meta/
|
|
|
|
|
|
├── conventions.md # ten format, słownik typów, zasady linkowania
|
|
|
|
|
|
└── lint-reports/ # raporty lintu, datowane
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Frontmatter** (inwariant 1 — audytowalność — realizowany przez blok `sources`):
|
|
|
|
|
|
|
|
|
|
|
|
```markdown
|
|
|
|
|
|
---
|
|
|
|
|
|
title: PZU
|
|
|
|
|
|
type: podmiot # podmiot | osoba | sprawa | umowa | temat
|
|
|
|
|
|
status: active # active | archived
|
|
|
|
|
|
tags: [ubezpieczenie] # słownik z §5 (decyzja 4) — wspólny z document_summary
|
|
|
|
|
|
sources:
|
|
|
|
|
|
- envelope_id: paperless:119
|
|
|
|
|
|
chunks: [4211, 4213] # document_chunk.id
|
|
|
|
|
|
- envelope_id: "CABtrY-...@mail.gmail.com"
|
|
|
|
|
|
chunks: [] # koperta bez chunków (np. sam manifest/headers)
|
|
|
|
|
|
compiled_by: claude-sonnet-5 # albo cc-session
|
|
|
|
|
|
compiled_at: 2026-07-20
|
|
|
|
|
|
updated_at: 2026-07-20
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
# PZU
|
|
|
|
|
|
|
|
|
|
|
|
Treść strony... Fakty niosące kwoty/daty z przypisem źródłowym w miejscu użycia:
|
|
|
|
|
|
składka 1 234 zł/rok [^paperless:119#4212]. Linki do innych stron: [[polisa-oc-auto]].
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Konwencja: każdy fakt liczbowy (kwota, data, numer umowy) ma przypis inline
|
|
|
|
|
|
`[^<envelope_id>#<chunk_id>]`; blok `sources` agreguje wszystkie źródła strony. Lint
|
|
|
|
|
|
(inwariant 3) weryfikuje oba poziomy.
|
|
|
|
|
|
|
|
|
|
|
|
**Integracja z retrievalem** (inwariant 5) — strony wiki wchodzą do bazy **istniejącą
|
|
|
|
|
|
architekturą kopert, bez nowego schematu**:
|
|
|
|
|
|
|
|
|
|
|
|
- strona = koperta `source='wiki'`, `id='wiki:podmioty/pzu'`, `raw_ref=<ścieżka w repo>`,
|
|
|
|
|
|
`ts=updated_at`, `entities=[{type: content, ...}, {type: wiki_meta, ...}]`;
|
|
|
|
|
|
- chunki strony → `document_chunk` normalnie (strony są krótkie: 1–3 chunki), embed
|
|
|
|
|
|
bge-m3 tym samym jobem;
|
|
|
|
|
|
- pełna kaskada docelowa: **wiki → summary → chunk** — zapytanie najpierw trafia strony
|
|
|
|
|
|
(skompilowana wiedza), potem streszczenia, potem chunki (dowód); implementacyjnie to
|
|
|
|
|
|
rozszerzenie eval-skryptu z §6 o trzeci poziom, po pierwszych stronach.
|
|
|
|
|
|
|
|
|
|
|
|
**Kompilacja i commit** (inwariant 2): sesja CC/API dostaje wyniki retrievalu dla encji
|
|
|
|
|
|
(top chunki + streszczenia, `WHERE excluded_reason IS NULL`), pisze/aktualizuje stronę,
|
|
|
|
|
|
commituje do `kb-wiki` z opisem „compile: <strona> ← <envelope_ids>". Dane wychodzące =
|
|
|
|
|
|
wyselekcjonowane chunki — zgodnie z polityką eskalacji.
|
|
|
|
|
|
|
|
|
|
|
|
**Lint** (inwariant 3): okresowa sesja CC (w fazie 3 ręcznie wyzwalana, nie timer):
|
|
|
|
|
|
linki `[[...]]` do nieistniejących stron, strony bez linków przychodzących, spot-check
|
|
|
|
|
|
N losowych przypisów `[^...]` vs treść chunka w bazie, sprzeczności między stronami
|
|
|
|
|
|
(fakty o tej samej encji). Raport do `_meta/lint-reports/YYYY-MM-DD.md`.
|
|
|
|
|
|
|
|
|
|
|
|
**Zakres wiki w fazie 3** (kolejność ze szkicu jest wiążąca): utworzenie repo, struktura,
|
|
|
|
|
|
`_meta/conventions.md` + **3–5 stron proof-of-concept** kompilowanych ręcznie sesją CC
|
|
|
|
|
|
z wyników retrievalu — dopiero **po** kroku 1 (filtr śmieci) i kroku 3 (pilot
|
|
|
|
|
|
streszczeń). Kandydaci na pierwsze strony (encje z pilota): PZU/WARTA (polisy), FLL
|
|
|
|
|
|
2025-26, bank/kredyt. Pełna kompilacja korpusu — po fazie mailowej, poza tą fazą.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 9. Poza zakresem fazy 3
|
|
|
|
|
|
|
|
|
|
|
|
Granice planu — wszystko poniżej jest świadomie odłożone, z istniejącym miejscem w
|
fix(kb): przepiecie wszystkich odwolan wewnetrznych po migracji
126 plikow (md, yaml, sh, py) odwolywalo sie do sciezek sprzed migracji.
15 markdown-linkow [..](..) -> policzona sciezka WZGLEDNA wobec pliku
odsylajacego (wczesniej czesc z nich byla repo-root-relative i nie
rozwiazywala sie z katalogu, w ktorym lezala)
200 odwolan tekstowych (backticki, proza, yaml, importy w kodzie)
-> nowa sciezka repo-root-relative, zgodnie z konwencja repo
5 linkow rodzenstwa (gole nazwy plikow, np. "](DEPLOY.md)") — dzialaly
tylko w starym katalogu; przeliczone recznie
Objete m.in.: CLAUDE.md (scripts/onboard/README.md -> kb/runbooks/
node-onboarding-tool.md, docs/backlog.md -> kb/phases/backlog.md),
README.md, .claude/skills/, 20 session logow, kod jobow.
Ostatnie 5 odwolan pochodzi z tresci wciagnietej rebasem z origin/master
(session log 2026-07-31, override node-agenta na SOLARII, dwie pozycje
backlogu) — wskazywaly na docs/incidents/, docs/kb/modules/ i
services/narty27/README.md sprzed migracji.
Dodany wzajemny link miedzy kb/services/control-plane.md (stub kodu)
a kb/subsystems/control-plane.md (opis, deprecated) — dwa dokumenty o tym
samym systemie, latwe do pomylenia.
Weryfikacja na 790 plikach: 0 odwolan do starych sciezek,
0 martwych linkow markdown. Lint OKF: 190/190 plikow ZGODNE.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 15:12:24 +02:00
|
|
|
|
roadmapie (`kb/subsystems/kb-overview.md` „Stan etapów/Backlog", sesja
|
docs(kb): recon + plan fazy 3 modułu 5 — warstwa kompilacji
Plan-doc (zero kodu): porządki po pilocie retrieval (filtr OCR-śmieci przez
excluded_reason, dedup paperless:14≡74, UNIQUE+model — migracja 003),
document_summary (004, UNIQUE(envelope_id,model) od razu), pilot streszczeń+tagów
dwutorowo API+lokalny GPU z rubryką jakości jako wejście do decyzji mailowej,
kaskada summary→chunk z bramką nie-gorszości na utrwalonym eval-secie, cykliczny
ingest jako systemd-timer@PIHA z metrykami textfile→fleet-prometheus→brain-watchdog,
wiki-kompilat wg szkicu operatora (Karpathy llm-wiki) rozwinięty wykonawczo
(osobne repo kb-wiki, frontmatter z sources, strony jako koperty source='wiki').
7 decyzji z rekomendacjami w §2; granice fazy w §9.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 15:45:50 +02:00
|
|
|
|
`docs/sessions/2026-07-16.md`, backlog operatora):
|
|
|
|
|
|
|
|
|
|
|
|
| Temat | Gdzie zakotwiczone | Kiedy |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| Batching wywołań Ollamy (embed) | backlog sesji 07-16 („batching pozostaje dźwignią") | przed fazą mailową |
|
|
|
|
|
|
| Backfill embeddingów 225k kopert mailowych | kb-00 etap 6 (mail-indexer) | faza mailowa |
|
|
|
|
|
|
| Streszczenia korpusu mailowego | decyzja po pilocie §5.4 | faza mailowa |
|
|
|
|
|
|
| IMAP/JMAP przyrostówka (Gmail/Fastmail live) | kb-00 etapy 3–4 | osobne etapy |
|
|
|
|
|
|
| Google Drive / pełny Takeout jako źródło | kb-00 backlog | osobny moduł |
|
|
|
|
|
|
| kb-query / UI zapytań | kb-00 backlog | po warstwie kompilacji |
|
|
|
|
|
|
| Synteza odpowiedzi + polityka eskalacji | faza 5 | po fazie 3/4 |
|
|
|
|
|
|
| Pełna kompilacja wiki (poza proof-of-concept) | §8.2 | po fazie mailowej |
|
|
|
|
|
|
| `maintenance_work_mem` HNSW przy milionach chunków | 05-faza2-plan §1.5 | checkpoint przy fazie mailowej |
|
|
|
|
|
|
| Aktualizacja `hosts/solaria/capabilities.yaml` (model GPU) | finding recon §1.3 | drobny osobny task |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 10. Plan implementacji (kolejność = zależności)
|
|
|
|
|
|
|
|
|
|
|
|
| # | Krok | Zależy od | Szacunek |
|
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
| 1 | Migracja 003 (`UNIQUE+model`, `excluded_reason`) + dostosowanie `chunk_embed.py` + testy | — | 0.5 sesji |
|
|
|
|
|
|
| 2 | Kalibracja filtra śmieci na 2683 chunkach (read-only) → progi → backfill flag + heurystyka w `chunk_embed.py` | 1 | 1 sesja |
|
|
|
|
|
|
| 3 | Dedup: pomiar skali (SQL §3.2) → flagi `duplicate` + `entities[duplicate_of]` | 1 | 0.5 sesji |
|
|
|
|
|
|
| 4 | Migracja 004 (`document_summary`) | 1 | z krokiem 5 |
|
|
|
|
|
|
| 5 | Job `summarize.py` (+ słownik tagów, testy, smoke) | 2, 3, 4 | 1–1.5 sesji |
|
|
|
|
|
|
| 6 | `ollama pull gemma3:12b` + pilot dwutorowy: lokalny 186 + API 186 + embed streszczeń | 5 | 0.5 sesji + ~3 h GPU + ~1.5 USD API |
|
|
|
|
|
|
| 7 | Ocena jakości (rubryka §5.4, próbka 25) → werdykt lokalny-vs-API do skali mailowej | 6 | 0.5–1 sesja |
|
|
|
|
|
|
| 8 | Utrwalenie eval-setu (`queries.yaml`) + `retrieval_eval.py` + bramka kaskady | 6 | 1 sesja |
|
|
|
|
|
|
| 9 | Cykliczny ingest: venv na PIHA, unit+timer, metryki textfile, flaga node_exporter, reguły Prometheus | 1–2 (embed z filtrem) | 1 sesja |
|
|
|
|
|
|
| 10 | Wiki: repo `kb-wiki`, struktura, conventions, 3–5 stron proof (sesje CC) | 2, 7 | 1 sesja |
|
|
|
|
|
|
|
|
|
|
|
|
Kroki 8–9 są od siebie niezależne (mogą iść równolegle/w dowolnej kolejności po 6–7).
|
|
|
|
|
|
Definition of Done per krok wg CLAUDE.md: build/smoke + pytest przed commitem; joby
|
|
|
|
|
|
odpalane na nodach zawsze z logiem do pliku.
|
|
|
|
|
|
|
|
|
|
|
|
**Kryterium ukończenia fazy 3:** (a) zero chunków śmieciowych/duplikatów w retrievalu
|
|
|
|
|
|
(`excluded_reason` działa end-to-end), (b) 186 dokumentów ma streszczenia+tagi z co
|
|
|
|
|
|
najmniej jednego modelu + werdykt jakościowy lokal-vs-API, (c) kaskada przechodzi bramkę
|
|
|
|
|
|
§6.2 albo udokumentowana diagnoza czemu nie, (d) timer cyklicznego ingestu chodzi ≥ tydzień
|
|
|
|
|
|
z metrykami w Prometheus, (e) repo kb-wiki istnieje z ≥3 stronami proof spełniającymi
|
|
|
|
|
|
inwarianty 1–2.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 11. Szacunki zbiorcze
|
|
|
|
|
|
|
|
|
|
|
|
- **Dane**: 186 streszczeń × 2 modele + embeddingi — pojedyncze MB; dziesiątki flag
|
|
|
|
|
|
`excluded_reason`. Pomijalne dla kb-postgres (1 GB limit, 202 MB użyte).
|
|
|
|
|
|
- **GPU**: pilot lokalny ~2–3 h (30–60 s/dok); embeddingi streszczeń ~2 min; brak wpływu
|
|
|
|
|
|
na inne workloady poza oknem pilota.
|
|
|
|
|
|
- **Koszty zewnętrzne**: przebieg API ~1.5 USD (Haiku) / ~4 USD (Sonnet); ocena CC
|
|
|
|
|
|
w ramach subskrypcji; kompilacja 3–5 stron proof — pomijalna.
|
|
|
|
|
|
- **Czas ludzki/sesyjny**: ~7–8 sesji roboczych łącznie (tabela §10), z czego bramki
|
|
|
|
|
|
wymagające udziału Oskara: zatwierdzenie tego planu, werdykt pilota (krok 7), review
|
|
|
|
|
|
stron proof (krok 10).
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 12. Podsumowanie dla Oskara
|
|
|
|
|
|
|
|
|
|
|
|
**Fundament jest**: 225 216 kopert, 2683 chunki, retrieval z zaufanymi progami, GPU
|
|
|
|
|
|
działa, wzorce jobów (bilans/idempotencja/dry-run) sprawdzone bojowo. Faza 3 nie dodaje
|
|
|
|
|
|
żadnej nowej „infrastruktury" — dodaje **jakość** (filtr, dedup, poprawny klucz
|
|
|
|
|
|
unikalności), **pierwszy półprodukt kompilacji** (streszczenia+tagi z pomiarem
|
|
|
|
|
|
lokal-vs-API), **kaskadę** z twardą bramką nie-gorszości, **automat** (timer + alert
|
|
|
|
|
|
istniejącym torem Prometheus→Telegram) i **szkielet wiki** z 3–5 stronami proof.
|
|
|
|
|
|
|
|
|
|
|
|
**7 decyzji w §2, wszystkie z rekomendacjami**; realnie otwarte (zmieniają przebieg) są
|
|
|
|
|
|
trzy: model pilota streszczeń (D3 — rekomendacja: dwutorowo API+lokalny), writeback tagów
|
|
|
|
|
|
(D5 — rekomendacja: nie teraz), repo wiki (D7 — rekomendacja: osobne `kb-wiki`). D1, D2,
|
|
|
|
|
|
D4, D6 to rekomendacje techniczne do przyklepnięcia.
|
|
|
|
|
|
|
|
|
|
|
|
Nic nie zostało zaimplementowane, zdeployowane ani pobrane w ramach tego recon — wynik
|
|
|
|
|
|
to wyłącznie ten dokument.
|