Lokalny czat z PDF po polsku: RAG w Ollamie, skrypt i test
Działający lokalny czat z dokumentem PDF po polsku: gotowy skrypt w Pythonie, Ollama i bge-m3. Sprawdziłem 10 pytań do ustawy o prawach konsumenta: bez dokumentu, z RAG i z całą ustawą w kontekście.

👁 115 przeczytań
- RAG w Ollamie podniósł trafność odpowiedzi Gemmy 4 12B z 2 do 9 na 10 pytań dotyczących ustawy o prawach konsumenta.
- Alternatywą dla RAG jest wklejenie całego dokumentu do modelu z kontekstem 65 536 tokenów, co dało Gemmie 4 12B wynik 10 na 10 przy 9,7 GB pamięci karty.
- Model bge-m3 (1,2 GB, licencja MIT) obsługuje ponad 100 języków w tym polski i służy do zamiany fragmentów tekstu na wektory podczas wyszukiwania.
Lokalny czat z PDF-em po polsku zrobisz w Ollamie i jednym skrypcie Pythona: dokument nie opuszcza komputera, a model odpowiada z numerem artykułu. Sprawdziłem to na ustawie o prawach konsumenta (Dz.U. 2026 poz. 1244, 24 strony) i 10 pytaniach z odpowiedzią w przepisach. 3 października 2026 roku wyszukiwanie fragmentów (RAG) podniosło trafność Gemmy 4 12B z 2 do 9 na 10. 17 października 2026 roku sprawdziłem drugą drogę: cała ustawa wklejona do modelu dała 10 na 10 przy 9,7 GB pamięci karty. Skrypt, pytania i wyniki są niżej, a szerszy przegląd programów i modeli jest w przewodniku Lokalne AI.
Stan na 17 października 2026
- Ollama 0.40.5 (wydanie stabilne z 14 października 2026), licencja MIT, działa bez konta.
- Wyszukiwanie: model
bge-m3w Ollamie (1,2 GB, licencja MIT, ponad 100 języków, w tym polski). - Odpowiedzi: Gemma 4 12B (Apache 2.0), Llama 3.1 8B (Llama 3.1 Community License), Bielik 11B v3 (Apache 2.0). Wszystkie można używać komercyjnie, Llamę z warunkami licencji Mety.
- Dokument: tekst jednolity ustawy z 30 maja 2014 r. o prawach konsumenta. Akty prawne nie są chronione prawem autorskim (art. 4 ustawy o prawie autorskim).
- Koszt: zero złotych za API. Po pobraniu modeli całość działa bez internetu.
Odpowiedź w skrócie
- Krótki dokument (do kilkudziesięciu stron): wklej go w całości do modelu z długim kontekstem. Gemma 4 12B z kontekstem 65 536 tokenów odpowiedziała poprawnie na 10 z 10 pytań.
- Długi dokument albo cały folder plików: użyj RAG, czyli wyszukiwania fragmentów przed odpowiedzią. W moim teście dało 8-9 trafnych odpowiedzi na 10 przy 7-9 GB pamięci karty.
- Bez Pythona: ten sam mechanizm mają Open WebUI, LM Studio i GPT4All. Opis w sekcji o gotowych programach.
Na czym polega czat z dokumentem (RAG)
Model nie zna Twojego pliku. Można mu go wkleić w całości, ale ustawa o prawach konsumenta to 28,5 tysiąca tokenów (tyle zliczyła Ollama 17 października), a lokalne programy domyślnie dają modelowi 4-8 tysięcy. Dlatego stosuje się RAG. Teorię opisałem w tekście RAG krok po kroku, a wektory w tekście o embeddingach.
- Cięcie. Skrypt tnie ustawę po artykułach, a długie artykuły na części po 1400 znaków z zakładką 200. Wyszło 120 fragmentów.
- Wektory. Każdy fragment zamieniam na wektor modelem
bge-m3i zapisuję na dysku. - Wyszukiwanie. Pytanie też zamieniam na wektor i biorę 5 najbardziej podobnych fragmentów.
- Odpowiedź. Model dostaje tylko te fragmenty i polecenie: odpowiadaj na ich podstawie, podaj numer artykułu, a jeśli odpowiedzi nie ma, napisz to.
Jak zacząć krok po kroku
- Zainstaluj Ollamę z ollama.com i pobierz dwa modele:
ollama pull bge-m3(wyszukiwanie) orazollama pull gemma4:12b-it-qat(odpowiedzi, 7,2 GB). - Zainstaluj Pythona 3.10 lub nowszego i dwie biblioteki:
pip install pypdf numpy. Ja miałem Pythona 3.13, pypdf 6.16 i numpy 2.5. - Pobierz skrypt czat_z_pdf.py (pełny kod niżej) i zapisz obok PDF-a.
- Uruchom:
python czat_z_pdf.py dokument.pdfi zadawaj pytania. Enter bez tekstu kończy rozmowę.
python czat_z_pdf.py prawa-konsumenta.pdf
python czat_z_pdf.py prawa-konsumenta.pdf "Ile mam dni na zwrot towaru kupionego w internecie?"
$env:MODEL = "llama3.1:8b"; python czat_z_pdf.py prawa-konsumenta.pdf
$env:PELNY = "1"; python czat_z_pdf.py prawa-konsumenta.pdfPierwsze uruchomienie liczy wektory dla całego dokumentu. U mnie trwało to 55 s dla 120 fragmentów. Kolejne startują od razu, bo wektory leżą w pliku .bge-m3.npz obok PDF-a. Zmienna PELNY=1 pomija wyszukiwanie i wkleja cały dokument z kontekstem 65 536 tokenów.
Skrypt nie czyta skanów. Jeśli PDF to zdjęcia stron, najpierw przepuść go przez OCR. Dla dokumentów innych niż ustawy zmień funkcję kawalki: zamiast cięcia po artykułach tnij po nagłówkach albo akapitach.
Mój test: 10 pytań do ustawy o prawach konsumenta
Każde pytanie ma odpowiedź w konkretnym przepisie, więc wynik da się sprawdzić bez zgadywania. Każdy model odpowiadał dwa razy: bez dokumentu (z tego, co pamięta z treningu) i z RAG. Odpowiedź uznawałem za trafną, jeśli zawierała właściwą liczbę albo rozstrzygnięcie, a każdą sprawdziłem ręcznie z tekstem ustawy. Temperatura 0, kontekst 8192 tokeny, Ollama 0.35.1, RTX 4090, 3 października 2026.
| Pytanie | Odpowiedź | Przepis | Znaleziony |
|---|---|---|---|
| 1. Termin odstąpienia od umowy zawartej przez internet | 14 dni | art. 27 ust. 1 | tak |
| 2. Termin przy nieumówionej wizycie sprzedawcy w domu | 30 dni | art. 27 ust. 2 | tak |
| 3. Kiedy wygasa prawo, gdy sklep o nim nie poinformował | po 12 miesiącach od zwykłego terminu | art. 29 ust. 1 | tak |
| 4. Termin odpowiedzi na reklamację i skutek milczenia | 14 dni, milczenie = uznanie | art. 7a | tak |
| 5. Czas na odesłanie towaru po odstąpieniu | 14 dni | art. 34 ust. 1 | tak |
| 6. Czy sklep zwraca dopłatę za droższą dostawę | nie | art. 33 | nie |
| 7. Domniemanie niezgodności towaru z umową | 2 lata | art. 43c | tak |
| 8. Odstąpienie od ubezpieczenia zawartego na odległość | 30 dni | art. 40 ust. 2 | tak |
| 9. Próg kwoty umów poza lokalem | 50 zł | art. 3 ust. 1 pkt 10 | nie |
| 10. Czy jednoosobowa firma ma prawa konsumenta | tak, gdy umowa nie ma charakteru zawodowego | art. 7aa | tak |
Kolumna „Znaleziony” mówi, czy właściwy przepis był wśród 5 fragmentów, które wyszukiwanie dało modelowi.
Wyniki: bez dokumentu i z RAG
| Model | Bez dokumentu | Z RAG | Czas z RAG |
|---|---|---|---|
| Gemma 4 12B | 2/10 | 9/10 | 0,4-1,6 s |
| Llama 3.1 8B | 2/10 | 9/10 | 0,8-3,6 s |
| Bielik 11B v3 (Q8_0) | 7/10 | 8/10 | 2,1-6,2 s |
Czas to pełna odpowiedź z wyszukiwaniem, z modelem już w pamięci karty. Pierwsze pytanie trwało od 151 do 521 s, bo modele wczytywały się z wolnego dysku USB.
- RAG podnosi trafność z 2 do 9 na 10 w Gemmie 4 12B i Llamie 3.1 8B. Bez dokumentu obie myliły terminy (14 zamiast 30 dni przy wizycie w domu i przy ubezpieczeniu), a Gemma napisała, że reklamacja bez odpowiedzi jest „odrzucona”, choć ustawa mówi odwrotnie.
- Bielik zna polskie prawo sam z siebie. Bez dokumentu odpowiedział dobrze na 7 pytań, w tym o próg 50 zł, którego żaden inny model nie znał. Z RAG doszedł do 8, ale w pytaniu o droższą dostawę sam sobie zaprzeczył: zaczął od „tak, sklep musi zwrócić całą opłatę”, a potem przytoczył wyjątek z ustawy.
- Na pytanie 9 z RAG nie odpowiedział żaden model.l. Wyszukiwanie nie znalazło art. 3, bo próg 50 zł jest schowany w długiej liście wyłączeń. Modele uczciwie napisały „nie znalazłem tego w dokumencie” zamiast zgadywać.
- Numery artykułów podawały wszystkie modele, choć Gemma raz dopisała do prawidłowego art. 34 nieistniejące tu „art. 55”. Numer przepisu trzeba sprawdzić, zanim się na nim oprze.
A gdyby wkleić całą ustawę?
3 października ta próba skończyła się błędem serwera w Ollamie 0.35.1, więc 17 października powtórzyłem ją w Ollamie 0.40.5. Gemma 4 12B dostała cały tekst ustawy (94 tys. znaków, 28 542-28 564 tokeny razem z pytaniem) i te same 10 pytań przy kontekście 65 536 tokenów.
| Sposób | Trafne | Pamięć karty | Czas odpowiedzi |
|---|---|---|---|
| RAG, 5 fragmentów (3.10) | 9/10 | 8,9 GB | 0,4-1,6 s |
| Cała ustawa w kontekście (17.10) | 10/10 | 9,7 GB | 1,2-2,3 s |
Pamięć przy RAG zmierzyłem 17 października razem z modelem bge-m3. Pierwsze pytanie z całą ustawą trwało 30,7 s: 24,9 s zajęło wczytanie modelu z dysku USB, a 4,8 s przeczytanie dokumentu. Kolejne odpowiedzi przychodziły w 1,2-2,3 s, bo Ollama trzyma przetworzony początek rozmowy w pamięci i przy tym samym dokumencie liczy tylko nowe pytanie.
Wniosek: przy jednym dokumencie do kilkudziesięciu stron i modelu z długim kontekstem wklejenie całości jest prostsze i dokładniejsze. Gemma 4 zajmuje przy długim kontekście mało dodatkowej pamięci, dlatego zmieściła się w niecałych 10 GB. RAG wygrywa przy wielu plikach, przy dokumentach dłuższych niż kontekst modelu i na kartach, na których długi kontekst się nie mieści.
Ile pamięci karty potrzeba
Zmierzyłem 17 października różnicę wskazań nvidia-smi przed i w trakcie odpowiedzi, z bge-m3 i modelem czatu załadowanymi naraz, kontekst 8192 tokeny.
| Model odpowiedzi | Pamięć karty z bge-m3 | Karta |
|---|---|---|
| Llama 3.1 8B (Q4_K_M) | 7,0 GB | 8 GB |
| Gemma 4 12B (Q4_0 QAT) | 8,9 GB | 12 GB |
| Bielik 11B v3 (Q4_K_M) | 9,7 GB | 12 GB |
| Bielik 11B v3 (Q8_0) | 14,1 GB | 16 GB |
Sam bge-m3 zajmował w Ollamie 0,66 GB. Ranking modeli po polsku z pomiarami pamięci na karty 8-24 GB jest w tekście najlepszy lokalny model AI po polsku.
Co poprawić, gdy odpowiedzi są złe
Nie przepłacaj za te subskrypcje
Prowadzę sklep z rocznymi dostępami do narzędzi AI - te same konta, o których piszę wyżej, tylko taniej niż w cenniku producenta.
- Najpierw sprawdź wyszukiwanie, nie model. Jeśli właściwego fragmentu nie ma wśród znalezionych, żaden model nie odpowie dobrze. Wypisz wynik funkcji
szukajprzy kłopotliwym pytaniu. - Weź model wyszukiwania, który zna polski.
bge-m3działa dobrze. Angielskiall-MiniLM-L6-v2, domyślny w Open WebUI, gubi polskie odmiany. - Tnij po strukturze dokumentu. Przy cięciu po artykułach właściwy przepis był wśród znalezionych w 8 pytaniach na 10, przy cięciu co 1400 znaków w 6 (3 października).
- Zwiększ TOPK przy pytaniach ogólnych.
TOPK=8doda więcej fragmentów kosztem dłuższego polecenia.
Gotowe programy zamiast skryptu
| Program | Jak działa z dokumentami | Dla kogo |
|---|---|---|
| Open WebUI | bazy wiedzy z wielu plików, w przeglądarce | kilka osób, wiele dokumentów |
| LM Studio | PDF, DOCX i TXT w rozmowie: krótki wkleja w całości, długi przeszukuje | jedna osoba, bez konfiguracji |
| GPT4All | funkcja LocalDocs przeszukuje wskazany folder | prosty start na słabszym sprzęcie |
| Ten skrypt | każdy krok widoczny i do zmiany | dokumenty prawne, własne cięcie |
W Open WebUI zmień model wyszukiwania w ustawieniach administratora (Documents) na bge-m3 z Ollamy i przelicz dokumenty od nowa. Więcej o drugim prostym programie w tekście Jan i GPT4All.
Dla kogo to jest
- Dla firm i kancelarii, które nie mogą wysyłać umów i pism do chmury.
- Dla każdego z kartą 8-12 GB, kto chce przeszukiwać regulaminy, instrukcje czy ustawy po polsku.
- Nie dla skanów i tabel. Skrypt czyta tylko warstwę tekstu PDF-a, a odpowiedzi do spraw ważnych zawsze trzeba sprawdzić w podanym przepisie.
Gotowy skrypt: czat_z_pdf.py
Około 100 linii, bez frameworków. Sprawdziłem go 17 października 2026 na czystym folderze (tylko PDF i skrypt) z Ollamą 0.40.5 na Windowsie 11, w trybie RAG i w trybie PELNY=1. Plik do pobrania: czat_z_pdf.py.
# -*- coding: utf-8 -*-
"""Lokalny czat z PDF-em po polsku (RAG) na Ollamie. Bez internetu, bez kluczy.
Wymaga: pip install pypdf numpy ; ollama pull bge-m3 ; ollama pull gemma4:12b-it-qat
Użycie:
python czat_z_pdf.py dokument.pdf -> tryb rozmowy
python czat_z_pdf.py dokument.pdf "Twoje pytanie" -> jedna odpowiedź
Zmienne środowiskowe:
MODEL - model do odpowiedzi (domyślnie gemma4:12b-it-qat)
EMB - model wyszukiwania (domyślnie bge-m3)
TOPK - ile fragmentów dać modelowi (domyślnie 5)
PELNY - 1 = zamiast wyszukiwania wklej cały dokument (krótkie pliki, kontekst 65 536 tokenów)"""
import sys, os, re, json, pathlib, urllib.request
import numpy as np
from pypdf import PdfReader
OLLAMA = os.environ.get('OLLAMA_URL', 'http://127.0.0.1:11434')
MODEL = os.environ.get('MODEL', 'gemma4:12b-it-qat')
EMB = os.environ.get('EMB', 'bge-m3')
TOPK = int(os.environ.get('TOPK', '5'))
PELNY = os.environ.get('PELNY', '0') == '1'
def api(path, body):
r = urllib.request.Request(OLLAMA + path, data=json.dumps(body).encode(), headers={'Content-Type': 'application/json'})
return json.load(urllib.request.urlopen(r, timeout=900))
def tekst_z_pdf(plik):
t = 'n'.join(p.extract_text() or '' for p in PdfReader(plik).pages)
t = re.sub(r'(w)-n(w)', r'12', t) # dzielenie wyrazów na końcu linii
t = re.sub(r'[ t]+', ' ', t)
t = re.sub(r'n(?!Art. |d+. |d+) )', ' ', t) # sklejam linie, zostawiam początki artykułów i punktów
return t
def kawalki(t, maks=1400, zakladka=200):
# najpierw tnę po artykułach, długie artykuły dzielę dalej z zakładką
czesci = re.split(r'n(?=Art. d+[a-z]*. )', t)
out = []
for c in czesci:
c = c.strip()
if not c: continue
naglowek = (re.match(r'Art. d+[a-z]*.', c) or [''])[0]
i = 0
while i < len(c):
kawal = c[i:i + maks]
if i > 0 and naglowek: kawal = f'({naglowek} - ciąg dalszy) ' + kawal
out.append(kawal)
i += maks - zakladka
return out
def indeks(plik):
cache = pathlib.Path(plik).with_suffix(f'.{EMB}.npz')
if cache.exists():
d = np.load(cache, allow_pickle=True); return list(d['k']), d['v']
k = kawalki(tekst_z_pdf(plik))
v = []
for i in range(0, len(k), 16):
v += api('/api/embed', {'model': EMB, 'input': k[i:i + 16]})['embeddings']
v = np.array(v, dtype=np.float32); v /= np.linalg.norm(v, axis=1, keepdims=True)
np.savez(cache, k=np.array(k, dtype=object), v=v)
return k, v
def szukaj(pyt, k, v, n=TOPK):
q = np.array(api('/api/embed', {'model': EMB, 'input': [pyt]})['embeddings'][0], dtype=np.float32)
q /= np.linalg.norm(q)
idx = np.argsort(-(v @ q))[:n]
return [k[i] for i in idx]
SYSTEM = ('Odpowiadasz po polsku wyłącznie na podstawie fragmentów dokumentu podanych niżej. '
'Podaj numer artykułu, z którego bierzesz odpowiedź. Jeśli fragmenty nie zawierają odpowiedzi, '
'napisz: "Nie znalazłem tego w dokumencie." Odpowiadaj krótko, w 1-3 zdaniach.')
def odpowiedz(pyt, k, v, model=MODEL, ctx=8192, caly=None):
if caly is not None:
fr, kontekst, ctx = [], f'DOKUMENT:n{caly}', 65536
else:
fr = szukaj(pyt, k, v)
kontekst = 'nn'.join(f'[Fragment {i + 1}]n{f}' for i, f in enumerate(fr))
msg = [{'role': 'system', 'content': SYSTEM}, {'role': 'user', 'content': f'{kontekst}nnPytanie: {pyt}'}]
body = {'model': model, 'messages': msg, 'stream': False, 'options': {'num_ctx': ctx, 'temperature': 0}}
try: r = api('/api/chat', {**body, 'think': False})
except Exception: r = api('/api/chat', body)
return r['message']['content'], fr
if __name__ == '__main__':
sys.stdout.reconfigure(encoding='utf-8')
if len(sys.argv) < 2: print(__doc__); sys.exit(1)
if PELNY:
caly, k, v = tekst_z_pdf(sys.argv[1]), None, None
print(f'Dokument: {len(caly)} znaków w całości, model {MODEL}')
else:
caly = None; k, v = indeks(sys.argv[1])
print(f'Dokument: {len(k)} fragmentów, model {MODEL}, wyszukiwanie {EMB}')
if len(sys.argv) > 2:
print(odpowiedz(sys.argv[2], k, v, caly=caly)[0]); sys.exit()
while True:
try: pyt = input('nPytanie (Enter kończy): ').strip()
except EOFError: break
if not pyt: break
print(odpowiedz(pyt, k, v, caly=caly)[0])
Najczęstsze pytania
Czy da się zrobić czat z PDF po polsku bez internetu?
Tak. Po pobraniu modeli Ollama, bge-m3 i skrypt działają offline. Dokument nie jest nigdzie wysyłany.
Jakie AI do PDF wybrać lokalnie?
W moim teście najlepiej wypadła Gemma 4 12B: 9 na 10 z RAG i 10 na 10 z całym dokumentem w kontekście. Llama 3.1 8B też miała 9 na 10 z RAG i mieści się na karcie 8 GB. Bielik 11B najlepiej znał prawo bez dokumentu, ale z dokumentem raz sam sobie zaprzeczył.
Ile pamięci karty potrzeba do RAG lokalnie?
Według mojego pomiaru z 17 października: 7,0 GB z Llamą 3.1 8B, 8,9 GB z Gemmą 4 12B i 9,7 GB z Bielikiem 11B Q4_K_M, zawsze razem z bge-m3. Cała 24-stronicowa ustawa w kontekście Gemmy 4 12B zajęła 9,7 GB.
Czy lokalny RAG jest dokładny?
Na tyle, na ile dobre jest wyszukiwanie. Modele z RAG odpowiedziały trafnie na 8-9 pytań z 10, a jedyne pytanie, przy którym wyszukiwanie nie znalazło przepisu, wszystkie oznaczyły jako „nie znalazłem”.
Czy mogę przeszukiwać kilka dokumentów naraz?
Tak. Połącz fragmenty z kilku plików w jedną listę i zapisuj przy każdym nazwę pliku, żeby model mógł ją podać. Przy setkach dokumentów lepsza jest baza wektorowa, np. wbudowana w Open WebUI.
Źródła i data sprawdzenia
- Dz.U. 2026 poz. 1244, tekst jednolity ustawy o prawach konsumenta (Sejm RP, ELI).
- bge-m3 w bibliotece Ollamy i karta modelu BAAI/bge-m3 (licencja MIT).
- Gemma 4 w bibliotece Ollamy.
- Wydania Ollamy na GitHubie.
- LM Studio: rozmowa z dokumentami.
- Open WebUI: dokumenty i model wyszukiwania.
- Test własny 1: 10 pytań, 3 modele, z RAG i bez, Ollama 0.35.1, RTX 4090, 3 października 2026.
- Test własny 2: cała ustawa w kontekście i pamięć karty, Ollama 0.40.5, RTX 4090, 17 października 2026.
Sprawdzone 17 października 2026.
Cały tydzień w AI, w jednym mailu
Wybrane premiery, narzędzia i analizy. Raz w tygodniu, prosto do skrzynki.
Zapisz się za darmo →
