136 lines
12 KiB
Markdown
136 lines
12 KiB
Markdown
|
|
# Raport dedup: fallback embed SOLARIA→PIHA — e7625cd (master) vs 3d4ee38 (task/kb-f4-fallback)
|
|||
|
|
|
|||
|
|
**Data**: 2026-07-30 · **Worktree**: `task/kb-fallback-dedup` · **Status**: recon zakończony,
|
|||
|
|
czeka na decyzję operatora co do listy salvage (sekcja „Rekomendacja zbiorcza").
|
|||
|
|
|
|||
|
|
Dwie równoległe sesje zaimplementowały ten sam krok planu (moduł 5 faza 4, §2 decyzja 2 / §5):
|
|||
|
|
|
|||
|
|
- **e7625cd** (2026-07-29, **ZMERGOWANY do master, wdrożony na PIHA**): `app/embed_router.py`
|
|||
|
|
+ `test_embed_router.py` + `services/ollama-piha` + README z testami A/B/C.
|
|||
|
|
- **3d4ee38** (2026-07-27, `origin/task/kb-f4-fallback`, **NIEZMERGOWANY**): `app/fallback.py`
|
|||
|
|
+ `test_fallback.py` + własny `services/ollama-piha` + `retrieval_eval.py --transport http`
|
|||
|
|
+ session log z kalibracją live.
|
|||
|
|
|
|||
|
|
Master = źródło prawdy. 3d4ee38 **nie będzie mergowany** — ten raport klasyfikuje jego deltę
|
|||
|
|
i rekomenduje, co uratować.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. Porównanie maszyn stanów (`embed_router.py` vs `fallback.py`)
|
|||
|
|
|
|||
|
|
Werdykt: **`embed_router.py` na masterze pokrywa 100% zachowań `fallback.py` i dodaje
|
|||
|
|
kilka istotnych rzeczy ponad to. Nic nie zgubił.**
|
|||
|
|
|
|||
|
|
| Zachowanie (wymóg planu §2 D2 / zadania) | `fallback.py` (branch) | `embed_router.py` (master) |
|
|||
|
|
|---|---|---|
|
|||
|
|
| Health-check cache 30 s (probe `GET /api/tags` po wygaśnięciu) | ✅ `SolCircuitBreaker`, TTL 30 s | ✅ TTL 30 s (env `EMBED_HEALTH_TTL_S`) |
|
|||
|
|
| Timeout probe'a | 0.5 s (stała) | 1.5 s (env; udokumentowane: absorbuje jitter Tailscale, spec zadania mówił „1–2 s") |
|
|||
|
|
| Twardy timeout 3 s na nodze SOLARIA | ✅ `ClientTimeout(total=3)` przez nowy param `embed_chunk(timeout_s=...)` | ✅ `asyncio.wait_for(3 s)` wokół niezmienionego `embed_chunk` — **bez ruszania pakietu** kb-retrieval; obejmuje też parsowanie odpowiedzi |
|
|||
|
|
| One-shot switch (fail mid-embed → **ten sam request** z PIHA, breaker „down" na okno TTL) | ✅ | ✅ + łapie dodatkowo `ValueError` (brak `embedding` w odpowiedzi) i loguje `circuit open for 30s` |
|
|||
|
|
| Noga PIHA bez twardego timeoutu (cold-load, ostatnia deska) | ✅ | ✅ (celowe, udokumentowane w docstringu) |
|
|||
|
|
| Powrót na SOLARIĘ ≤ 30 s po wygaśnięciu TTL | ✅ | ✅ |
|
|||
|
|
| Obsługa 503 (oba backendy padnięte → 503, nie gołe 500) | ⚠️ częściowa: tylko `aiohttp.ClientError` → 503 w `main.py`; **`TimeoutError` z nogi PIHA wyleciałby jako gołe 500** | ✅ dedykowany `EmbedBackendError` → 503; osobno pokryty przypadek „fallback nieskonfigurowany" |
|
|||
|
|
| Inwariant embed-model (query model == `document_chunk.model`) | startowy DB-check + jedna stała `embed_model` na obie nogi (dowód strukturalny w teście) | to samo **plus** leniwa weryfikacja per-backend przy pierwszym użyciu: `/api/tags` backendu musi listować `EMBED_MODEL`, inaczej `ModelMismatchError` → głośne 500 (nigdy cichy embed w złej przestrzeni wektorowej) |
|
|||
|
|
| `/healthz` dzieli cache z `/search` | ✅ `sol_status` | ✅ `sol_status` + dodatkowo `fallback_status` (up/down/unconfigured) |
|
|||
|
|
| `embed_backend` w odpowiedzi `/search` | ❌ (tylko `sol_status`) | ✅ (potrzebne do debugowania jakości per backend) |
|
|||
|
|
| Praca bez fallbacku (env nieustawiony) | placeholder `http://localhost:11434` (fail-closed, ale mylący `sol_status`) | jawny tryb `EMBED_FALLBACK_URL` unset → zachowanie sprzed Kroku 2 (503) |
|
|||
|
|
| Parametryzacja (TTL/timeouty) | stałe modułowe | env vars z defaultami |
|
|||
|
|
| Logging decyzji routingu | ❌ | ✅ (`kb-query.embed`, backend=…, elapsed_ms) |
|
|||
|
|
|
|||
|
|
Różnice czysto kosmetyczne: semantyka wygaśnięcia dokładnie na granicy TTL identyczna
|
|||
|
|
(obie strony: `t == TTL` → wygasłe); branch trzymał breaker jako dataclass z injektowalnym
|
|||
|
|
zegarem, master trzyma to samo wewnątrz `EmbedRouter` (też injektowalny zegar).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. Pełna delta 3d4ee38 (git diff origin/master...origin/task/kb-f4-fallback) + klasyfikacja
|
|||
|
|
|
|||
|
|
| Plik | Rozmiar delty | Klasyfikacja | Rekomendacja | Uzasadnienie |
|
|||
|
|
|---|---|---|---|---|
|
|||
|
|
| `services/kb-query/app/fallback.py` | +113 | duplikat funkcji | **porzuć** | `embed_router.py` = nadzbiór (tabela §1) |
|
|||
|
|
| `services/kb-query/app/main.py` | ±32 | duplikat | **porzuć** | master ma własną, pełniejszą integrację (mapowanie 503/500, env-schemat `EMBED_PRIMARY/FALLBACK_URL`) |
|
|||
|
|
| `services/kb-query/app/search.py` | ±36 | duplikat | **porzuć** | identyczny pomysł (embed raz → `*_retrieve` z gotowym wektorem); master dodaje `embed_backend` |
|
|||
|
|
| `services/kb-query/tests/test_fallback.py` | +221 | duplikat + **3 unikalne scenariusze** | **adaptuj** | luki testowe do przeniesienia do `test_embed_router.py` — patrz §3 |
|
|||
|
|
| `services/kb-query/tests/test_search.py` | ±38 | duplikat | **porzuć** | master zaadaptował te same testy przez `_FakeRouter` (czystsze — nie dubluje maszyny stanów w testach searcha) |
|
|||
|
|
| `services/kb-query/{README,env.example,service.yaml,docker-compose.yml}` | ~±58 | duplikat | **porzuć** | inny (porzucony) schemat env `OLLAMA_PIHA_URL`; master bogatszy (testy A/B/C, rename `OLLAMA_URL`→`EMBED_PRIMARY_URL`) |
|
|||
|
|
| `packages/kb-retrieval/src/kb_retrieval/embed.py` (`timeout_s` w `embed_chunk`) | +16 | duplikat funkcji | **porzuć** | master osiąga twardy timeout przez `asyncio.wait_for` bez zmiany współdzielonego pakietu — mniejsza powierzchnia zmian, ten sam efekt |
|
|||
|
|
| `jobs/documents-ingest/eval/retrieval_eval.py` (`--transport {direct,http}` + `--base-url`) | +109 | **unikalna wartość** | **cherry-pick** | plan §2 decyzja 6 / §9 (bramka HTTP-equivalence) — **na masterze w ogóle nie istnieje**; e7625cd nie tknął tego pliku, patch aplikuje się czysto; kod woła tylko `GET /search` i czyta `envelope_id`/`dist`/`source` — w pełni zgodny z odpowiedzią mastera |
|
|||
|
|
| `jobs/documents-ingest/README.md` | +6 | **unikalna wartość** | **cherry-pick** | dokumentacja powyższego, idzie w parze |
|
|||
|
|
| `docs/sessions/2026-07-27-kb-f4-fallback.md` | +189 | **unikalna wartość** | **adaptuj** | jedyny zapis: (1) znalezisko osieroconego natywnego `ollama.service` na PIHA + jego wyłączenie 2026-07-27 i backlog odinstalowania, (2) kalibracja live ollama-piha (GO: peak ~983 MiB, ~4.2–5.3 s/embed), (3) metodologia i wyniki bramki §9 (HTTP-equivalence 0 rozbieżności; sol-down Δ~3e-4), (4) rsync-deploy → dirty working tree na PIHA. Wciągnąć z dopiskiem redakcyjnym, że zmergowana implementacja to **inny kod** (e7625cd) i wyniki bramki wymagają powtórki |
|
|||
|
|
| `services/ollama-piha/*` (5 plików) | +155 | duplikat | **porzuć** | wersja mastera lepsza: named volume `ollama_piha_models` (uzasadnienie uid-pattern PIHA), healthcheck sprawdza obecność `bge-m3`, bind tylko 127.0.0.1+LAN |
|
|||
|
|
| `hosts/piha/runtime/ollama-piha/docker-compose.override.yml` | +13 | duplikat + **1 unikalny fakt** | **adaptuj (mikro)** | ten sam `mem_limit: 2560m`; ale komentarz brancha zawiera potwierdzony pomiar (peak ~983 MiB), a master wciąż mówi „Confirm/trim after live calibration" — dopisać wynik kalibracji do komentarza override'u i/lub sekcji „Calibration" w `services/ollama-piha/README.md` |
|
|||
|
|
| `hosts/piha/services.yaml` | ±26 | duplikat | **porzuć** | master ma własny wpis `ollama-piha` + soft-dependency kb-query; drobna różnica (`offline_required: true` na branchu vs `false` na masterze) — master źródłem prawdy |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. Pokrycie testowe — `test_embed_router.py` (13 testów) vs `test_fallback.py` (16 testów)
|
|||
|
|
|
|||
|
|
Scenariusze wspólne (pokryte po obu stronach): embed na SOLARIA gdy up; cache werdyktu
|
|||
|
|
w TTL (bez re-probe); down z probe'a → PIHA bez próby embedu na SOLARIA; down cache'owany
|
|||
|
|
(kolejne requesty omijają SOLARIĘ bez probe'a); **one-shot same-request switch** (wariant
|
|||
|
|
connection-error); **powrót na SOLARIĘ ≤30 s** (test C — po wygaśnięciu TTL); oba
|
|||
|
|
backendy padnięte → wyjątek; obie nogi z identycznym `embed_model` (na masterze mocniej:
|
|||
|
|
weryfikacja `/api/tags` per backend).
|
|||
|
|
|
|||
|
|
Master ma ponadto testy, których branch nie miał: `ModelMismatchError` (fallback bez
|
|||
|
|
bge-m3 → głośny błąd, nie cichy embed; primary bez bge-m3 → failover), brak fallbacku
|
|||
|
|
skonfigurowanego → `EmbedBackendError`, `fallback_status` (up/unconfigured), sufiks
|
|||
|
|
`:latest` spełnia gołą nazwę modelu.
|
|||
|
|
|
|||
|
|
**Luki mastera względem `test_fallback.py`** (kandydaci na salvage — pozycja „adaptuj" z §2):
|
|||
|
|
|
|||
|
|
| # | Scenariusz z `test_fallback.py` | Stan na masterze | Waga |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| T1 | One-shot switch przy **timeoutcie** mid-embed (nie tylko connection-error) — na masterze to inna ścieżka kodu (`asyncio.wait_for` → `TimeoutError`) i jest **nieprzetestowana** | brak | **wysoka** — to główny scenariusz produkcyjny („SOLARIA wisi", nie „SOLARIA odrzuca") |
|
|||
|
|
| T2 | Po one-shot switchu breaker **zostaje** „down": kolejny request w tym samym oknie TTL idzie prosto na PIHA bez probe'a i bez próby SOLARIA | brak (master testuje cache „down" tylko z probe'a, nie z mid-embed failure) | średnia |
|
|||
|
|
| T3 | Noga PIHA bez twardego timeoutu (strukturalna asercja) | brak (własność tylko udokumentowana) | niska — opcjonalnie |
|
|||
|
|
| T4 | Wygaśnięcie cache dokładnie na granicy TTL (`t == 30 s`) | brak (master testuje 31 s) | niska — semantyka i tak identyczna, można pominąć |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. Rekomendacja zbiorcza (lista do zatwierdzenia)
|
|||
|
|
|
|||
|
|
1. **S1 — cherry-pick**: `retrieval_eval.py --transport http --base-url` + akapit w
|
|||
|
|
`jobs/documents-ingest/README.md` (plan §2 D6/§9; aplikuje się czysto, zero zależności
|
|||
|
|
od porzuconego kodu brancha).
|
|||
|
|
2. **S2 — adaptuj**: `docs/sessions/2026-07-27-kb-f4-fallback.md` → `docs/sessions/`
|
|||
|
|
z dopiskiem redakcyjnym na górze (implementacja z tej sesji porzucona na rzecz
|
|||
|
|
e7625cd; fakty operacyjne — ollama.service, kalibracja, metodologia bramki — pozostają
|
|||
|
|
w mocy).
|
|||
|
|
3. **S3 — adaptuj**: luki testowe T1 + T2 (T3 opcjonalnie) do `test_embed_router.py`.
|
|||
|
|
4. **S4 — adaptuj (mikro)**: wynik kalibracji 2026-07-27 (peak ~983 MiB, ~4.2–5.3 s,
|
|||
|
|
werdykt GO) do komentarza `hosts/piha/runtime/ollama-piha/docker-compose.override.yml`
|
|||
|
|
i sekcji Calibration w `services/ollama-piha/README.md` — pomiar dotyczył kontenera
|
|||
|
|
ollama-piha (ta sama konfiguracja: obraz, `OLLAMA_KEEP_ALIVE=0`, `mem_limit 2560m`),
|
|||
|
|
więc **przenosi się** na wersję mastera; różni się tylko storage (bind vs named
|
|||
|
|
volume), co nie wpływa na RAM/latencję.
|
|||
|
|
5. **Porzuć** całą resztę delty (kolumna „porzuć" w §2).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. Follow-up (po salvage)
|
|||
|
|
|
|||
|
|
- **(a) Sprzątanie brancha** (wykona Oskar): skasować `origin/task/kb-f4-fallback`
|
|||
|
|
(3d4ee38) i worktree `~/homelab-codex-ws-kb-f4-fallback` po zakończeniu salvage.
|
|||
|
|
- **(b) Powtórka testu sol-down na żywym masterze**: kalibracja i bramka z 2026-07-27
|
|||
|
|
dotyczyły **innego kodu** (`fallback.py`, env `OLLAMA_PIHA_URL`) — na wdrożonym
|
|||
|
|
e7625cd trzeba przejść testy A/B/C z `services/kb-query/README.md` oraz bramkę
|
|||
|
|
`retrieval_eval.py --transport http` (po S1): HTTP-equivalence przy SOLARIA-up
|
|||
|
|
(identyczne `dist`) i sol-down (Δ≤epsilon, kolejność top-k identyczna; baseline
|
|||
|
|
z 27.07: Δ~3e-4). Symulacja wg README: `EMBED_PRIMARY_URL=http://192.0.2.1:11434`
|
|||
|
|
(TEST-NET), nie dotykając SOLARII.
|
|||
|
|
- **(c) Weryfikacja stanu PIHA po podwójnym deployu** (znalezisko z session loga):
|
|||
|
|
sesja 27.07 wdrożyła kod brancha przez **rsync do `~/homelab-codex-ws` na PIHA**
|
|||
|
|
(dirty working tree), a ollama-piha wystartował tam z **bind mountem**
|
|||
|
|
`/opt/homelab/data/ollama-piha`; master definiuje **named volume**
|
|||
|
|
`ollama_piha_models`. Sprawdzić: (1) working tree na PIHA czysty po merge'u e7625cd
|
|||
|
|
i kontenery faktycznie zbudowane z kodu mastera, (2) który storage żywy kontener
|
|||
|
|
naprawdę montuje; jeśli named volume — czy `bge-m3` jest w nim spullowany, a stary
|
|||
|
|
katalog bind (`/opt/homelab/data/ollama-piha`, ~1.2 GB modelu) do skasowania,
|
|||
|
|
(3) `.env` kb-query używa `EMBED_FALLBACK_URL` (schemat mastera), nie martwego
|
|||
|
|
`OLLAMA_PIHA_URL` — szybki test: `/healthz` musi zwracać `fallback_status: "up"`,
|
|||
|
|
nie `"unconfigured"`.
|
|||
|
|
- **(d) Backlog z 27.07** (przetrwa tylko dzięki S2): natywny, osierocony
|
|||
|
|
`ollama.service` na PIHA wyłączony (`systemctl disable --now`) 2026-07-27 —
|
|||
|
|
odinstalować binarkę/unit po ~2 tygodniach ciszy (≈ 2026-08-10).
|