RAG krok po kroku: czat z własnymi dokumentami w Pythonie
Czat z PDF-ami od zera: podział na artykuły, embeddingi lokalnie, wyszukiwanie i odpowiedzi z cytatami. Test na Kodeksie pracy i dwóch innych ustawach.

👁 115 przeczytań
- RAG zbudowany w ok. 70 liniach Pythona poprawnie odpowiedział na 8 z 10 pytań, które miały odpowiedź w dokumentach, i ani razu nie zmyślił.
- System podzielił trzy ustawy (łącznie 149 stron) na 920 kawałków po maks. 1200 znaków, a embeddingi liczył lokalnie modelem bge-m3 w Ollama 0.35.1.
- Koszt 11 odpowiedzi przez model openai/gpt-5.6-luna na OpenRouter wyniósł łącznie 0,0072 USD, a lokalne embeddingi były bezpłatne.
RAG (retrieval-augmented generation) to czat z własnymi dokumentami: program najpierw wyszukuje w nich fragmenty pasujące do pytania, a dopiero potem model odpowiada na ich podstawie i podaje źródło. Zbudowałem to od zera w około 70 liniach Pythona: trzy polskie ustawy w PDF, embeddingi liczone lokalnie w Ollamie, baza wektorowa w numpy (i dla porównania w Chroma). Na 11 pytaniach zadanych potocznym językiem model odpowiedział dobrze w 8 przypadkach, w 2 uczciwie przyznał, że nie znalazł odpowiedzi, i ani razu nie zmyślił. Poniżej cały kod, wszystkie wyniki i dwie poprawki, które sprawdziłem. Tekst jest częścią ścieżki Agenci AI od zera.
Stan na 4 października 2026
- Dokumenty: Kodeks pracy (tekst jednolity Dz.U. 2026 poz. 1245, 97 stron), ustawa o ochronie praw lokatorów (Dz.U. 2023 poz. 725, 23 strony), ustawa o prawach pacjenta (Dz.U. 2024 poz. 581, 29 stron). Pobrane z api.sejm.gov.pl.
- Embeddingi:
bge-m3w Ollamie 0.35.1 na RTX 4090. Model odpowiadający:openai/gpt-5.6-lunaprzez OpenRouter. - Indeks: 920 kawałków po maks. 1200 znaków. Koszt 11 odpowiedzi: 0,0072 USD łącznie. Embeddingi lokalnie: 0 zł.
- Kod: paczka promptowy-agenci-starter, pliki
04_rag.pyi04b_rag_chroma.py.
Jak działa RAG w czterech krokach
- Podział: dokumenty tniesz na kawałki (u mnie: jeden artykuł ustawy, długie artykuły na części z zakładką).
- Embeddingi: każdy kawałek zamieniasz na wektor liczb, który oddaje jego znaczenie. Robisz to raz.
- Wyszukiwanie: pytanie też zamieniasz na wektor i szukasz kawałków o najbliższych wektorach.
- Odpowiedź: 5 najlepszych kawałków doklejasz do polecenia i każesz modelowi odpowiadać tylko na ich podstawie, ze wskazaniem źródła.
Czym są embeddingi i bazy wektorowe, tłumaczę bez kodu w tekście Embeddingi i bazy wektorowe po ludzku. Teoretyczne wprowadzenie do RAG i jego słabych punktów jest w starszym tekście RAG: jak działa.
Krok 1: przygotowanie
- Zainstaluj Ollamę i pobierz model embeddingów:
ollama pull bge-m3(1,2 GB na dysku, w pamięci karty u mnie 664 MB). - W środowisku Pythona:
pip install numpy pypdf requests openai python-dotenv. - Wrzuć PDF-y do folderu
dane/ustawy. Teksty ustaw nie są chronione prawem autorskim (art. 4 ustawy o prawie autorskim), więc możesz ich swobodnie użyć do testów.
Embeddingi możesz też liczyć przez API (OpenAI, Google, Mistral), ale lokalnie nie płacisz nic, a dokumenty nie opuszczają komputera. Bez karty graficznej Ollama policzy je na procesorze, tylko wolniej.
Krok 2: cały kod
"""04 - RAG krok po kroku: czat z własnymi dokumentami (PDF), embeddingi lokalnie w Ollamie, baza = numpy.
Krok 1 (raz): python 04_rag.py indeksuj dane/ustawy
Krok 2 (pytaj): python 04_rag.py pytaj "Ile dni urlopu ma pracownik po 12 latach pracy?"
Wymaga: Ollama z modelem embeddingów (ollama pull bge-m3) i klucza OpenRouter do odpowiedzi.
"""
import json, os, pathlib, re, sys
import numpy as np
import requests
from dotenv import load_dotenv
from openai import OpenAI
from pypdf import PdfReader
load_dotenv()
EMBED = os.getenv("EMBED_MODEL", "bge-m3") # albo nomic-embed-text
MODEL = os.getenv("MODEL", "openai/gpt-5.6-luna")
INDEKS = pathlib.Path(f"indeks-{EMBED.replace(':', '_')}.npz")
OLLAMA = os.getenv("OLLAMA_URL", "http://localhost:11434")
# --- 1. Tekst z PDF i podział na kawałki (tu: po artykułach ustawy) ------------
def kawalki(plik: pathlib.Path, max_zn=1200):
tekst = "\n".join(p.extract_text() or "" for p in PdfReader(plik).pages)
tekst = re.sub(r"Dziennik Ustaw\s*\W\s*\d+\s*\W\s*Poz\. \d+", " ", tekst) # stopki stron
czesci = re.split(r"\s(?=Art\. \d+[a-z]?(?:\[\d+\])?\. )", tekst) # nowy artykuł = nowy kawałek
for cz in czesci:
cz = re.sub(r"\s+", " ", cz).strip()
art = (re.match(r"Art\. (\d+[a-z]?(?:\[\d+\])?)\.", cz) or [None, "wstęp"])[1]
for i in range(0, len(cz), max_zn - 200): # długie artykuły tniemy z zakładką 200 znaków
yield {"plik": plik.name, "art": art, "tekst": cz[i:i + max_zn]}
if i + max_zn >= len(cz): break
# --- 2. Embeddingi: tekst -> wektor liczb (lokalnie, przez Ollamę) -------------
def embed(teksty, prefiks=""):
wektory = []
for i in range(0, len(teksty), 32):
r = requests.post(f"{OLLAMA}/api/embed", json={"model": EMBED, "input": [prefiks + t for t in teksty[i:i + 32]]}, timeout=600)
wektory += r.json()["embeddings"]
v = np.array(wektory, dtype=np.float32)
return v / np.linalg.norm(v, axis=1, keepdims=True) # normalizacja: iloczyn skalarny = podobieństwo cosinusowe
PREF_DOK, PREF_PYT = ("search_document: ", "search_query: ") if "nomic" in EMBED else ("", "")
def indeksuj(katalog):
k = [x for p in sorted(pathlib.Path(katalog).glob("*.pdf")) for x in kawalki(p)]
v = embed([x["tekst"] for x in k], PREF_DOK)
np.savez(INDEKS, wektory=v, meta=np.array([json.dumps(x, ensure_ascii=False) for x in k]))
print(f"zapisano {len(k)} kawałków, wektory {v.shape[1]}-wymiarowe -> {INDEKS}")
# --- 3. Wyszukiwanie: pytanie -> wektor -> najbliższe kawałki -------------------
def szukaj(pytanie, k=5):
d = np.load(INDEKS)
podob = d["wektory"] @ embed([pytanie], PREF_PYT)[0]
top = np.argsort(-podob)[:k]
return [{**json.loads(d["meta"][i]), "podobienstwo": round(float(podob[i]), 3)} for i in top]
# --- 4. Odpowiedź modelu tylko na podstawie znalezionych fragmentów -------------
def pytaj(pytanie):
fragmenty = szukaj(pytanie)
kontekst = "\n\n".join(f"[{f['plik']}, art. {f['art']}]\n{f['tekst']}" for f in fragmenty)
client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=os.environ["OPENROUTER_API_KEY"])
r = client.chat.completions.create(model=MODEL, messages=[
{"role": "system", "content": "Odpowiadasz wyłącznie na podstawie fragmentów ustaw podanych niżej. Na końcu podaj "
"źródło w nawiasie: [plik, art.]. Jeśli we fragmentach nie ma odpowiedzi, napisz: Nie ma tego w dokumentach."},
{"role": "user", "content": f"FRAGMENTY:\n{kontekst}\n\nPYTANIE: {pytanie}"}])
for f in fragmenty:
print(f" znaleziono: {f['plik']} art. {f['art']} (podobieństwo {f['podobienstwo']})")
print(f"\nODPOWIEDŹ: {r.choices[0].message.content}\n(koszt: {getattr(r.usage, 'cost', 0) or 0:.5f} USD)")
if __name__ == "__main__":
{"indeksuj": indeksuj, "pytaj": pytaj}[sys.argv[1]](sys.argv[2])Najważniejsze decyzje w tym kodzie:
- Podział po artykułach. Wyrażenie regularne szuka „Art. 154.” albo „Art. 167[2].” (tak pypdf zapisuje indeksy górne z Dziennika Ustaw). Jeden artykuł to zwykle jedna myśl prawna, więc to lepszy podział niż cięcie co 1000 znaków w połowie zdania.
- Usunięcie stopek „Dziennik Ustaw - 23 - Poz. 1245”, które inaczej trafiają w środek artykułów.
- Normalizacja wektorów: po niej zwykłe mnożenie macierzy daje podobieństwo cosinusowe. Całe „wyszukiwanie wektorowe” to tu jedna linijka:
d["wektory"] @ embed([pytanie])[0]. - Polecenie systemowe z wyjściem awaryjnym: „Jeśli we fragmentach nie ma odpowiedzi, napisz: Nie ma tego w dokumentach”. To ono sprawiło, że w moim teście model nie zmyślał.
Krok 3: indeksowanie
Polecenie python 04_rag.py indeksuj dane/ustawy pocięło 149 stron na 920 kawałków i zapisało wektory (1024 liczby na kawałek) do pliku indeks-bge-m3.npz. Trwało to 155 sekund, ale karta była w tym czasie mocno obciążona przez inny program, więc na wolnej karcie będzie szybciej. Robisz to raz, a potem tylko pytasz.
Krok 4: pytania i odpowiedzi
Zadałem 11 pytań językiem, jakim pyta zwykły człowiek, a nie prawnik. Dziesięć ma odpowiedź w ustawach, jedno (o prędkości na autostradzie) celowo nie.
| Pytanie | Właściwy artykuł | Gdzie w wynikach wyszukiwania | Odpowiedź modelu | Ocena |
|---|---|---|---|---|
| Pracuję od 12 lat. Ile dni urlopu mi przysługuje? | KP art. 154 | poza top 5 (14. miejsce) | „Nie ma tego w dokumentach” | bezpieczna porażka |
| Jaki mam okres wypowiedzenia, jeśli jestem w firmie 4 lata? | KP art. 36 | 1. | 3 miesiące | dobrze |
| Ile dni urlopu na żądanie mogę wziąć w roku? | KP art. 167[2] | 1. | 4 dni w roku kalendarzowym | dobrze |
| Szef każe mi zostać po godzinach w nocy. Ile wynosi dodatek? | KP art. 151[1] | 2. | 100% wynagrodzenia plus dodatek nocny 20% | dobrze |
| Ile maksymalnie może wynosić kaucja za mieszkanie na wynajem? | art. 6 | 2. | do 12-krotności czynszu, przy najmie instytucjonalnym do 6-krotności | dobrze, ale pominął najem okazjonalny |
| Kiedy właściciel musi mi oddać kaucję po wyprowadzce? | art. 6 | 4. | w ciągu miesiąca od opróżnienia lokalu | dobrze |
| Czy właściciel może mnie wyrzucić, jak nie płacę czynszu 3 miesiące? | art. 11 | 1. | tak, po pisemnym uprzedzeniu i dodatkowym miesięcznym terminie | dobrze |
| Ile kosztuje kopia mojej dokumentacji medycznej? | art. 28 | 1. | pierwsza kopia bezpłatnie, kolejne do 0,00007 przeciętnego wynagrodzenia za stronę | dobrze |
| Nie zgadzam się z orzeczeniem lekarza. Gdzie i do kiedy mogę się odwołać? | art. 31 | 1. | sprzeciw do Komisji Lekarskiej przy RPP, 30 dni | dobrze |
| Czy mama może być ze mną w gabinecie podczas badania? | art. 21 | poza top 5 (31. miejsce) | „Nie ma tego w dokumentach” | bezpieczna porażka |
| Jaka jest dozwolona prędkość na autostradzie? | brak w dokumentach | najwyższe podobieństwo tylko 0,43 | „Nie ma tego w dokumentach” | dobrze |
Wynik: 8 dobrych odpowiedzi na 10 pytań, które mają odpowiedź w dokumentach, plus poprawna odmowa przy pytaniu spoza tematu. Obie porażki wzięły się z wyszukiwania, nie z modelu. Model dostał złe fragmenty i powiedział wprost, że w nich odpowiedzi nie ma. Tak ma działać dobrze ustawiony RAG.
Tak wygląda pełny przebieg dla pytania o nadgodziny:
> python 04_rag.py pytaj "Szef każe mi zostać po godzinach w nocy. Ile wynosi dodatek?"
znaleziono: kodeks-pracy-DU-2026-1245.pdf art. 151[8] (podobieństwo 0.682)
znaleziono: kodeks-pracy-DU-2026-1245.pdf art. 151[1] (podobieństwo 0.663)
znaleziono: kodeks-pracy-DU-2026-1245.pdf art. 151[6] (podobieństwo 0.644)
znaleziono: kodeks-pracy-DU-2026-1245.pdf art. 151[7] (podobieństwo 0.621)
znaleziono: kodeks-pracy-DU-2026-1245.pdf art. 151[4] (podobieństwo 0.616)
ODPOWIEDŹ: Za każdą godzinę nadliczbową przypadającą w nocy przysługuje dodatek w wysokości **100% wynagrodzenia**. Ponadto za każdą godzinę pracy w porze nocnej przysługuje dodatek nocny w wysokości **20% stawki godzinowej wynikającej z minimalnego wynagrodzenia za pracę**. Pora nocna obejmuje 8 godzin między 21:00 a 7:00. [kodeks-pracy-DU-2026-1245.pdf, art. 151[1] § 1 pkt 1 lit. a, art. 151[8] § 1, art. 151[7] § 1]
(koszt: 0.00084 USD)Model połączył trzy fragmenty (dodatek za nadgodziny, dodatek nocny, definicję pory nocnej) i przy każdym podał przepis. A tak wygląda porażka wyszukiwania:
> python 04_rag.py pytaj "Pracuję od 12 lat. Ile dni urlopu mi przysługuje?"
znaleziono: kodeks-pracy-DU-2026-1245.pdf art. 167[3] (podobieństwo 0.693)
znaleziono: kodeks-pracy-DU-2026-1245.pdf art. 167[2] (podobieństwo 0.685)
znaleziono: kodeks-pracy-DU-2026-1245.pdf art. 186 (podobieństwo 0.669)
znaleziono: kodeks-pracy-DU-2026-1245.pdf art. 182[3] (podobieństwo 0.664)
znaleziono: kodeks-pracy-DU-2026-1245.pdf art. 172 (podobieństwo 0.66)
ODPOWIEDŹ: Nie ma tego w dokumentach. Z podanych fragmentów wynika jedynie, że urlop na żądanie może wynosić maksymalnie 4 dni w roku kalendarzowym; nie określono natomiast pełnego wymiaru urlopu wypoczynkowego dla osoby pracującej 12 lat. [kodeks-pracy-DU-2026-1245.pdf, art. 167[2]-167[3]]
(koszt: 0.00063 USD)Wyszukiwarka znalazła przepisy o urlopie na żądanie i urlopie rodzicielskim, bo pytanie „ile dni urlopu” jest do nich bardzo podobne. Artykuł 154 zaczyna się od słów „Wymiar urlopu wynosi: 20 dni… 26 dni”, czyli innym słownictwem niż pytanie.
Poprawka 1: przepisanie pytania na język ustawy
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.
Popularna technika (query rewriting) polega na tym, że przed wyszukiwaniem model przepisuje pytanie na język dokumentów. Sprawdziłem ją na 10 pytaniach:
| Pytanie potoczne | Po przepisaniu | Miejsce przed | Miejsce po |
|---|---|---|---|
| Pracuję od 12 lat. Ile dni urlopu mi przysługuje? | Jaki wymiar urlopu wypoczynkowego przysługuje pracownikowi legitymującemu się 12-letnim okresem zatrudnienia? | 14. | 5. |
| Szef każe mi zostać po godzinach w nocy. Ile wynosi dodatek? | W jakiej wysokości przysługuje pracownikowi dodatek do wynagrodzenia z tytułu pracy w godzinach nadliczbowych przypadających w porze nocnej… | 2. | 1. |
| Ile dni urlopu na żądanie mogę wziąć w roku? | Jaki jest maksymalny wymiar urlopu wypoczynkowego udzielanego na żądanie pracownika w roku kalendarzowym? | 1. | 2. |
| Czy mama może być ze mną w gabinecie podczas badania? | Czy matka pacjenta jest uprawniona do obecności w pomieszczeniu, w którym przeprowadzane jest badanie? | 31. | 44. |
Pozostałe 6 pytań nie zmieniło pozycji. W sumie: właściwy artykuł w pierwszej piątce w 9 przypadkach na 10 (wcześniej 8). Pytanie o mamę pokazuje granicę tej metody: ustawa mówi o „osobie bliskiej”, a ani pytanie, ani jego przepisana wersja tego słowa nie mają. Tu pomogłoby wyszukiwanie hybrydowe (słowa kluczowe plus wektory) albo słownik synonimów. Przepisanie kosztowało 0,0017 USD za 10 pytań i dodaje około sekundy do każdej odpowiedzi.
Poprawka 2: próg podobieństwa
Przy pytaniu o autostradę najlepszy kawałek miał podobieństwo 0,43, a przy pytaniach z odpowiedzią w dokumentach 0,64-0,72. To naturalny próg: jeśli najlepszy wynik jest poniżej około 0,5, program może od razu odpowiedzieć „nie mam tego w dokumentach”, bez pytania modelu. Próg trzeba dobrać do swojego modelu embeddingów i dokumentów. Dla nomic-embed-text na tych samych ustawach wszystkie wyniki, także bezsensowne, mieściły się między 0,69 a 0,77, więc taki próg nie miałby sensu.
To samo w bazie Chroma
Numpy wystarcza do kilkudziesięciu tysięcy kawałków. Przy większej liczbie albo gdy chcesz filtrować po metadanych (np. „szukaj tylko w Kodeksie pracy”), wygodniejsza jest baza wektorowa. Ten sam RAG na Chroma 1.5.9:
"""04b - Ten sam RAG, ale z bazą wektorową Chroma zamiast numpy (zapis na dysku, filtrowanie po metadanych).
python 04b_rag_chroma.py indeksuj dane/ustawy
python 04b_rag_chroma.py szukaj "Ile maksymalnie może wynosić kaucja?" [plik.pdf]
"""
import importlib, pathlib, sys
import chromadb
rag = importlib.import_module("04_rag") # bierzemy podział na kawałki i embeddingi z 04_rag.py
baza = chromadb.PersistentClient(path="chroma_db")
kolekcja = baza.get_or_create_collection(f"ustawy-{rag.EMBED.replace(':', '_')}", metadata={"hnsw:space": "cosine"})
def indeksuj(katalog):
k = [x for p in sorted(pathlib.Path(katalog).glob("*.pdf")) for x in rag.kawalki(p)]
v = rag.embed([x["tekst"] for x in k], rag.PREF_DOK)
for i in range(0, len(k), 500): # Chroma przyjmuje dane partiami
kolekcja.upsert(ids=[f"{x['plik']}#{n}" for n, x in enumerate(k[i:i + 500], i)],
embeddings=v[i:i + 500].tolist(), documents=[x["tekst"] for x in k[i:i + 500]],
metadatas=[{"plik": x["plik"], "art": x["art"]} for x in k[i:i + 500]])
print(f"w kolekcji {kolekcja.count()} kawałków")
def szukaj(pytanie, plik=None):
w = kolekcja.query(query_embeddings=rag.embed([pytanie], rag.PREF_PYT).tolist(), n_results=3,
where={"plik": plik} if plik else None) # filtr po metadanych: tylko jedna ustawa
for meta, odl, tekst in zip(w["metadatas"][0], w["distances"][0], w["documents"][0]):
print(f"{meta['plik']} art. {meta['art']} (podobieństwo {1 - odl:.3f}): {tekst[:110]}...")
if __name__ == "__main__":
indeksuj(sys.argv[2]) if sys.argv[1] == "indeksuj" else szukaj(sys.argv[2], *sys.argv[3:4])> python 04b_rag_chroma.py indeksuj dane/ustawy
w kolekcji 920 kawałków
(indeksowanie trwało 126 s)
> python 04b_rag_chroma.py szukaj "Ile maksymalnie może wynosić kaucja za mieszkanie na wynajem?"
ochrona-praw-lokatorow-DU-2023-725.pdf art. 19f (podobieństwo 0.673): emcę kaucji zabez- pieczającej pokrycie należności z tytułu najmu instytucjonalnego lokalu przysługujących wła...
ochrona-praw-lokatorow-DU-2023-725.pdf art. 6 (podobieństwo 0.639): Art. 6. 1. Zawarcie umowy najmu może być uzależnione od wpłacenia przez najemcę kaucji zabezpieczającej pokryc...
ochrona-praw-lokatorow-DU-2023-725.pdf art. 19k (podobieństwo 0.615): ści oświadczenie najemcy, w którym najemca poddał się egzekucji i zobowiązał się do opróżnienia i wy- dania lo...
> python 04b_rag_chroma.py szukaj "Czy mama może być ze mną w gabinecie podczas badania?" prawa-pacjenta-DU-2024-581.pdf
prawa-pacjenta-DU-2024-581.pdf art. 17 (podobieństwo 0.530): Art. 17. 1. Pacjent, w tym małoletni, który ukończył 16 lat, ma prawo do wyrażenia zgody na przeprowadzenie ba...
prawa-pacjenta-DU-2024-581.pdf art. 14 (podobieństwo 0.520): - jącej zawód medyczny. Osoba wykonująca zawód medyczny może wystąpić z wnioskiem do sądu także w przypadku uz...
prawa-pacjenta-DU-2024-581.pdf art. 18 (podobieństwo 0.518): Art. 18. 1. W przypadku zabiegu operacyjnego albo zastosowania metody leczenia lub diagnostyki stwarzających p...Wyniki i podobieństwa są identyczne jak w numpy, bo to te same wektory. Baza wektorowa nie poprawia jakości wyszukiwania, tylko wygodę: zapis na dysku (u mnie 11 MB), dopisywanie nowych dokumentów bez przeliczania wszystkiego i filtry. Drugie zapytanie pokazuje filtr po pliku: nawet ograniczone do ustawy o prawach pacjenta wyszukiwanie nie trafiło w art. 21, co potwierdza, że problemem jest słownictwo, a nie mieszanie ustaw.
Co psuje RAG najczęściej
- Zły podział tekstu. Kawałki ucięte w połowie zdania albo zbyt duże (cały rozdział) to najczęstsza przyczyna słabego wyszukiwania.
- Model embeddingów, który słabo zna polski. Na moich ustawach
bge-m3znalazł właściwy artykuł w pierwszej piątce w 8 przypadkach na 10, anomic-embed-texttylko w 4. Porównanie jest w tekście o embeddingach. - Różnica słownictwa między pytającym a dokumentem („mama” kontra „osoba bliska”).
- Brak wyjścia awaryjnego w poleceniu. Bez zdania „jeśli nie ma, powiedz” model chętnie dopowiada z własnej wiedzy.
- Nieaktualne dokumenty. RAG odpowiada na podstawie tego, co mu dasz. Tekst jednolity ustawy o ochronie praw lokatorów z 2023 roku nie uwzględnia późniejszych zmian.
Następny krok: RAG jako narzędzie agenta
Funkcję szukaj() możesz dać agentowi jako narzędzie. Wtedy agent sam decyduje, czy szukać, w której ustawie, i może zadać kilka wyszukiwań, zanim odpowie (to się nazywa agentic RAG). Jak podpiąć funkcję jako narzędzie, pokazuję w tekście Jak zbudować agenta AI w Pythonie, a jak wystawić ją wszystkim klientom AI naraz, w tekście Własny serwer MCP.
Najczęstsze pytania
Co to jest RAG w prostych słowach?
Ściąga dla modelu. Zanim model odpowie, program wyszukuje w Twoich dokumentach pasujące fragmenty i daje mu je do przeczytania. Model odpowiada na ich podstawie, a nie z pamięci.
RAG czy fine tuning?
Do odpowiadania na pytania z dokumentów prawie zawsze RAG: jest tańszy, aktualizujesz go wrzuceniem nowego pliku i widać źródło odpowiedzi. Fine tuning zmienia styl i zachowanie modelu, ale słabo nadaje się do wkuwania faktów.
Czy RAG działa po polsku?
Tak, jeśli model embeddingów zna polski. Mój test na polskich ustawach z bge-m3 dał 8 dobrych odpowiedzi na 10 pytań z odpowiedzią w dokumentach.
Czy mogę zrobić RAG całkiem lokalnie, bez internetu?
Tak. Embeddingi i tak liczyłem lokalnie, a zamiast modelu z OpenRoutera możesz użyć modelu z Ollamy (w kodzie zmieniasz adres i nazwę modelu). Przy słabszych modelach lokalnych odpowiedzi będą prostsze i częściej nieprecyzyjne.
Ile kosztuje RAG?
U mnie embeddingi nic (lokalnie), a 11 odpowiedzi 0,0072 USD, czyli około 0,00065 USD za pytanie. Największy koszt to tokeny wejściowe: 5 fragmentów po 1200 znaków przy każdym pytaniu.
Jaka baza wektorowa na start?
Do nauki numpy (zero instalacji). Do małego projektu Chroma albo SQLite z rozszerzeniem wektorowym. Do dużych zbiorów i wielu użytkowników: pgvector w PostgreSQL, Qdrant albo usługi chmurowe.
Google · Twoje źródłaPromptowy wyżej w Twoim Google - jednym kliknięciemDodaj do preferowanych źródeł →Źródła i data sprawdzenia
- Teksty ustaw: Dz.U. 2026 poz. 1245, Dz.U. 2023 poz. 725, Dz.U. 2024 poz. 581 (pobrane 4.10.2026).
- Model bge-m3 w bibliotece Ollamy, dokumentacja Chroma.
- Pomiary: mój komputer (RTX 4090, Windows 11), 4 października 2026, 14:30-14:55. Odpowiedzi modelu oceniłem ręcznie na podstawie tekstu ustaw. To nie jest porada prawna.
Cały tydzień w AI, w jednym mailu
Wybrane premiery, narzędzia i analizy. Raz w tygodniu, prosto do skrzynki.
Zapisz się za darmo →
