Zadaj pytanie naturalnym językiem nad folderem dokumentów → dostań odpowiedź z twardymi
cytatami plik:linie. Retrieval hybrydowy: BM25 (leksykalny) + embeddingi
(semantyczny, lokalny nomic przez LM Studio) łączone przez reciprocal rank fusion (RRF).
Pure Python stdlib, zero zależności, 0 PLN (LM Studio lokalnie).
Żadna pojedyncza metoda nie wystarcza. Na korpusie demo (realne embeddingi, top-5):
| Zapytanie | BM25 | embeddingi | RRF |
|---|---|---|---|
| "WIS stawka podatku..." (rzadki token) | 1 | 5 | 2 |
| "pismo zmieniające wartość transakcji..." (parafraza) | poza top-5 | 3 | 4 |
BM25 sam gubi parafrazy bez wspólnych słów; embeddingi same gubią rzadkie tokeny w
gęstym klastrze. RRF utrzymuje target w top-k w obu przypadkach (9/9 zapytań korpusu).
To odporność, nie magia — RRF bywa neutralny lub lekko gorszy tam, gdzie jedna metoda była
ostro lepsza (np. WIS: RRF=2 vs BM25=1). Pełna tabela: reports/hybryda-vs-pojedyncza.md.
folder → chunking → BM25 + embeddingi (pula 4·k) → RRF → top-k → odpowiedź z cytatami
│
tryb asystent (NL) ────┴──── tryb --extractive
(walidacja cytatów w kodzie) (zero LLM)
# Tryb asystent (wymaga LM Studio: Local Server + model czatowy + embeddingowy)
python3 docs_qa.py "do kiedy składam JPK" --docs corpus
# Tryb extractive (deterministyczny, bez LLM)
EMBED_MOCK=1 python3 docs_qa.py "faktura korygująca" --docs corpus --extractivePrzykładowy output: examples/sample-answer.md.
- Każdy inline cytat
(plik:linie)jest walidowany w kodzie względem zwróconych fragmentów (nie tylko proszony w prompcie). Odpowiedź bez ważnego cytatu → „Nie znalazłem odpowiedzi w dokumentach". - Cytaty inline per-twierdzenie (nie zbiorcza sekcja na końcu).
- Embeddingi padną → degradacja do BM25 z adnotacją (nie crash). LLM padnie → tryb extractive z adnotacją.
- agent-flow — wieloetapowy agent ReAct, retrieval leksykalny.
- rag-lexical-vs-semantic — ławka: porównanie BM25 vs embeddingi (jedna metoda na raz).
- docs-qa (to repo) — produkcyjna synteza: hybryda (BM25+embeddingi via RRF) + warstwa odpowiedzi.
EMBED_MOCK=1 python3 -m unittest discover -s tests -v # offline, mechanika (CI)
python3 -m unittest tests.test_e2e_live -v # teza robustness (wymaga LM Studio)- Honest-RAG — zakres gwarancji: walidacja potwierdza, że cytat WSKAZUJE fragment podany do LLM; NIE gwarantuje, że zdanie wokół cytatu jest wierne treści fragmentu. Pełna weryfikacja semantyczna = v1.
- Brak progu odcięcia: embeddingi zawsze zwracają pulę kandydatów, więc każde pytanie daje k wyników (nawet bez sensownego dopasowania). Deterministyczne „nie wiem" opiera się na braku ważnego cytatu w trybie NL; próg cosinusa = v1.
- Tryb mock (
EMBED_MOCK=1) NIE jest semantyczny — testuje mechanikę, nie tezę. Co więcej, mock zawsze echuje cytat top-1, więc na pytanie spoza korpusu udaje trafienie; realny LLM dostaje instrukcję „jeśli fragmenty nie zawierają odpowiedzi, napisz 'Nie znalazłem...'". Dowód tezy =test_e2e_live. rrf_k=60kalibrowane na duże korpusy; na małym spłaszcza ranking (param--rrf-k).- Chunking dzieli na nagłówkach md; pliki bez nagłówków → grubsze cytaty. Brak scalania mikro-chunków.
- Fallback łapie
ConnectionError; inne wyjątki adaptera LLM nieobsłużone = v1. - W tabelach
reports/"poza top-5" / brak rangi = dokument poza pulą retrievera, nie zero wyników.
MIT License.