Strona główna / Artykuły

Pełnie lokalny PDF RAG z użyciem LangChain, Ollamy i Chromy

Pełnie lokalna aplikacja PDF RAG: LangChain przetwarza pliki, Ollama je włącza, Chroma przechowuje wektory, a odpowiedzi działają offline bez żadnych kluczy API chmurowych.

Wydawca: REACTAPP.TOP5 min czytania1111 słów

Źródła: Implement RAG with Langchain and local Ollama models

Zbuduj w pełni lokalny pipeline RAG dla plików PDF za pomocą LangChain, Ollamy i ChromaDB — rozmawiaj z dokumentami bez użycia chmurowych API i bez wychodzenia danych poza maszynę.

Dlaczego lokalne rozwiązania są ważne

Chmurowe modele LLM są wygodne, ale przy każdym zapytaniu tekst jest przesyłany przez sieć kogoś innego. Umowy prawnicze, dokumenty medyczne oraz wewnętrzne raporty często nie mogą zaakceptować takiego kompromisu. Lokalna architektura eliminuje to ryzyko, umożliwiając jednoczesne zadawanie pytań w języku naturalnym do własnych plików.

Co otrzymujesz

  • Przyjmowanie plików PDF do trwałego magazynu wektorowego
  • Odpowiadanie na pytania w języku naturalnym oparte na tych dokumentach
  • Robota offline podczas przetwarzania — nie są potrzebne klucze API po pobraniu modeli

Stack: LangChain do orkiestracji, Ollama do lokalnej rozmowy i tworzenia embeddingów, ChromaDB jako magazyn wektorowy.

Architektura

Ścieżka pobierania: bajty pliku PDF trafiają do ładowacza opartego na PyPDF, są dzielone na fragmenty, a następnie Ollama wstawia każdy z tych fragmentów do Chromy.

Ścieżka zadawania pytań: pytanie trafia do mechanizmu wyszukiwania, pasujące fragmenty stają się kontekstem, a lokalny model Ollama do rozmów pisze odpowiedź.

Dwie fazy zapewniają uczciwe koszty:

  1. Pobieranie — raz (lub gdy zmieniają się dokumenty): analiza, dzielenie na fragmenty, tworzenie embeddingów, przechowywanie
  2. Wyszukiwanie — przy każdym zapytaniu: najważniejsze fragmenty stają się kontekstem dla LLM

Płacisz za tworzenie embeddingów, gdy zmienia się treść; interaktywne zapytania pozostają szybkie.

Wymagania wstępne

Zainstaluj Ollama i pobierz tutaj używane modele:

ollama pull embeddinggemma:latest   # embedding model
ollama pull gemma4:latest           # chat model

gemma4 został dostosowany pod konkretne instrukcje. Aby zmniejszyć zużycie zasobów, spróbuj gemma2:2b; dla silniejszych procesów rozumowania użyj gemma4:27b — odpowiednio zaktualizuj ciągi konfiguracyjne.

Ustawienie projektu

python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Główne pakiety z wymagań:

langchain
langchain-chroma
langchain-community
langchain-ollama
langchain-text-splitters
pypdf
chromadb
python-dotenv

Zmień domyślne ustawienia za pomocą środowiska bez edycji kodu:

OLLAMA_BASE_URL=http://localhost:11434
OLLAMA_MODEL=gemma4:latest
OLLAMA_EMBED_MODEL=embeddinggemma:latest

Proces pobierania danych (src/ingest.py)

Ładowanie dokumentów

def load_documents(pdf_path: str | Path) -> list:
    path = Path(pdf_path)
    loader = (
        PyPDFLoader(str(path))
        if path.is_file()
        else PyPDFDirectoryLoader(str(path))
    )
    return loader.load()py

Jeden pomocnik obsługuje pojedynczy plik lub katalog. Ładowarki LangChain zawsze zwracają jednolitą listę obiektów Document w obu przypadkach.

Dzielenie na fragmenty

def split_documents(documents: list) -> list:
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=1000,
        chunk_overlap=200,
    )
    return splitter.split_documents(documents)

RecursiveCharacterTextSplitter preferuje dzielenie na akapity, następnie zdania, a potem słowa, aby zachować granice semantyczne. Przekrycie 200 znaków pozwala na zachowanie zdania przecinającego granicę w obu sąsiednich fragmentach, co zmniejsza liczby nieudanych wyświetleń na krawędziach.

Zaimportowanie i przechowywanie

def build_vector_store(chunks: list) -> Chroma:
    embeddings = OllamaEmbeddings(model=EMBEDDING_MODEL, base_url=OLLAMA_BASE_URL)
    VECTOR_STORE_DIR.mkdir(parents=True, exist_ok=True)
    return Chroma.from_documents(
        documents=chunks,
        embedding=embeddings,
        collection_name=COLLECTION_NAME,
        persist_directory=str(VECTOR_STORE_DIR),
    )

Ollama serwuje embeddingi lokalnie na porcie 11434. Chroma przechowuje je w katalogu vector_store/ (SQLite wraz z plikami indeksów HNSW), dzięki czemu ponowne uruchomienie procesu nie wymusza całkowitego ponownego importowania.

Łańcuch wyświetlania (src/retrieval.py)

LangChain LCEL łączy kroki za pomocą operatora |.

Śablon promptu

RAG_PROMPT = ChatPromptTemplate.from_messages([
    (
        "system",
        "You are an assistant that answers questions based on the provided context "
        "from PDF documents. Use only the information in the context to answer. "
        "If the answer is not in the context, say so clearly.\n\nContext:\n{context}"
    ),
    ("human", "{question}"),
])

Wiadomość systemowa umacnia odpowiedzi w odniesieniu do pobranego kontekstu — to główna zasada RAG chroniąca przed halucynacjami wynikającymi wyłącznie z treningu wstępnego.

Budowanie łańcucha

def build_rag_chain(k: int = 4):
    store = load_vector_store()
    retriever = store.as_retriever(search_kwargs={"k": k})
    llm = ChatOllama(model=MODEL_NAME, base_url=OLLAMA_BASE_URL)
    return (
        {"context": retriever | _format_docs, "question": RunnablePassthrough()}
        | RAG_PROMPT
        | llm
        | StrOutputParser()
    )

Z lewej na prawą: retriever wybiera k najbardziej podobnych fragmentów na podstawie podobieństwa embeddingów, narzędzia pomocnicze formatują te fragmenty w jeden ciąg tekstowy kontekstowego, szablon promptu łączy ten kontekst z pytaniem użytkownika, a ChatOllama generuje odpowiedź.

Interaktywny cykl zadawania pytań

# Index a directory of PDFs (default: data/pdfs/)
python main.py ingest

Lekka interfejs CLI (lub otoczka HTTP) ładuje łańcuch tylko raz i obsługuje pytania aż do wyjścia. Utrzymuj k jako parametr dostępny do regulacji, aby móc pogodzić rozmiar kontekstu z opóźnieniem.

Rozruch od początku do końca

# Index a specific file
python main.py ingest --path /path/to/contract.pdf
# Ask a one-off question
python main.py query "What are the payment terms?"
# Interactive session
python main.py chat

Wskazz folder z plikami PDF, upewnij się, że katalog przechowujący wektory jest wypełniony, a następnie zadaj zapytanie. Wszystko — embeddingi, wektory, proces generowania — pozostaje na lokalnym serwerze.

Wniosek

Local RAG nie jest już czymś egzotycznym. Ollama dostarcza modele, Chroma przechowuje wektory, a LangChain łączy elementy grafu. Dla prywatnych korpusów ta trójka wystarcza do stworzenia przydatnego asystenta dokumentowego bez konieczności korzystania z chmury czy przesyłania danych na zewnątrz.