RAG z LangChain i LangGraph: od czego zacząć

Wprowadzenie
W poprzedniej serii zbudowaliśmy najprostszy RAG na LlamaIndex. Tym razem to samo zadanie rozwiążemy drugim najpopularniejszym zestawem narzędzi: LangChain i LangGraph. Plan jest identyczny jak ostatnio — dziś teoria i mapa pojęć, w drugiej części klocki z kodem, a w trzeciej kompletne API indeksujące pliki Markdown z dysku i odpowiadające na pytania przez OpenRouter. Jak zawsze: najprostszy możliwy wariant, bez wymyślnych strategii.
Przypomnienie: jak działa RAG
Dla porządku, w trzech zdaniach (szczegóły w pierwszej części poprzedniej serii): model językowy nie zna Twoich dokumentów, więc zamiast go dotrenowywać, RAG wyszukuje fragmenty pasujące do pytania i dokleja je do promptu. Wyszukiwanie działa na embeddingach — wektorach liczb reprezentujących znaczenie tekstu — a dokumenty są wcześniej pocięte na mniejsze fragmenty (chunki) i zapisane w indeksie. Ten schemat jest uniwersalny; zmienia się tylko biblioteka, którą go składamy.
LangChain — skrzynka z klockami
LangChain to biblioteka Pythona z gotowymi komponentami do budowy aplikacji z LLM. W odróżnieniu od LlamaIndex, który jest wyspecjalizowany w indeksowaniu i odpytywaniu dokumentów, LangChain jest bardziej „rozłożony na części" — dostajemy osobne, wymienne klocki:
- model czatu — jednolity interfejs do LLM-ów różnych dostawców (u nas: przez OpenRouter),
- model embeddingów — zamiana tekstu na wektory,
- text splitter — cięcie dokumentu na chunki,
- vector store — magazyn wektorów z wyszukiwaniem podobieństwa (u nas: w pamięci),
- prompt template — szablon promptu, do którego wstawimy znalezione fragmenty i pytanie.
Tam, gdzie LlamaIndex ukrywał te kroki za jednym VectorStoreIndex i query(), LangChain każe je złożyć samodzielnie. To trochę więcej kodu, ale też pełna widoczność tego, co się dzieje na każdym etapie.
LangGraph — przepływ jako graf
Skoro kroki składamy sami, trzeba czymś opisać ich kolejność. Do tego służy LangGraph — biblioteka (od twórców LangChain), w której aplikację definiuje się jako graf stanów:
- stan (state) — wspólny „formularz" przekazywany między krokami; u nas: pytanie, znalezione fragmenty, odpowiedź,
- węzły (nodes) — funkcje Pythona, z których każda czyta stan i dopisuje do niego swój wynik,
- krawędzie (edges) — strzałki mówiące, który węzeł wykonuje się po którym.
Nasz RAG będzie najprostszym możliwym grafem — dwa węzły połączone w linię:
START → [retrieve: znajdź fragmenty] → [generate: zapytaj LLM] → END
Czy do dwóch kroków graf jest niezbędny? Nie — dałoby się to zapisać jako dwie zwykłe funkcje. Ale graf pokazuje swoją wartość w momencie rozbudowy: dodanie kroku weryfikacji odpowiedzi, pętli doprecyzowującej pytanie czy rozgałęzienia to dopisanie węzła i krawędzi, a nie przepisywanie logiki. Ucząc się grafu na prostym przykładzie, dostajemy fundament pod bardziej złożone agentowe aplikacje.
Co będzie potrzebne z Pythona?
Nic nowego względem poprzednich serii — wszystko omawialiśmy w podstawach Pythona i drugiej ich części:
- słowniki — stan grafu w LangGraph to w praktyce słownik; węzły zwracają słowniki z aktualizacjami,
- funkcje — każdy węzeł grafu to zwykła funkcja,
- listy — znalezione fragmenty dokumentów,
- JSON, venv, zmienne środowiskowe — API, izolacja zależności i klucz do OpenRouter poza kodem, jak zawsze.
Podsumowanie
LangChain daje wymienne klocki (model, embeddingi, splitter, vector store, prompt), a LangGraph spina je w czytelny graf: retrieve → generate. Koncepcyjnie to dokładnie ten sam RAG, który znamy z serii o LlamaIndex — różnica polega na tym, że tym razem sami widzimy i kontrolujemy każdy krok. W następnej części każdy z tych klocków dostanie swój kod.