Главная / Статьи

Полностью локальный PDF RAG с использованием LangChain, Ollama и Chroma

Полностью локальное приложение PDF RAG: LangChain обрабатывает файлы, Ollama встраивает их, Chroma хранит векторы, а ответы формируются офлайн без использования каких-либо ключей API облака.

Издатель: REACTAPP.TOP5 мин чтения1111 слов

Источники: Implement RAG with Langchain and local Ollama models

Создайте полностью локальную платформу RAG для обработки PDF с использованием LangChain, Ollama и ChromaDB — ведите чат с документами без необходимости в облачных API и без выхода данных за пределы устройства.

Почему локальное решение важно

Облачные нейросети удобны, но при каждом запросе текст передается через чужую сеть. Юридические контракты, медицинские записи и внутренние отчеты часто не могут принять такой компромисс. Локальная схема исключает утечку данных, позволяя вести вопросы и ответы на естественном языке с использованием собственных файлов.

Что вы получите

  • Загрузка PDF-файлов в постоянный векторный хранилище
  • Ответы на вопросы на естественном языке, основанные на этих документах
  • Работа офлайн во время обработки запросов — ключи от API не требуются после загрузки моделей

Стек: LangChain для оркестрации, Ollama для локального чата и генерации эмбеддингов, ChromaDB в качестве хранилища векторов.

Архитектура

Путь входа данных: байты PDF поступают в загрузчик на основе PyPDF, делятся на чанки, после чего Ollama встраивает каждый чанк в Chroma.

Путь запроса: вопрос поступает в механизм поиска, соответствующие чанки становятся контекстом, а локальная модель Ollama для чата формирует ответ.

Два этапа позволяют контролировать затраты:

  1. Прием данных — один раз (или при изменении документов): парсинг, разделение на чанки, генерация эмбеддингов, сохранение
  2. Поиск — при каждом запросе: лучшие чанки становятся контекстом для LLM

Вы платите за генерацию эмбеддингов при изменении контента; интерактивные запросы остаются быстрыми.

Предварительные требования

Установите Ollama и скачайте здесь используемые модели:

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

gemma4 настроен под конкретные инструкции. Для меньших ресурсных затрат попробуйте gemma2:2b; для более сильных способностей к рассуждению используйте gemma4:27b — соответственно обновите строки конфигурации.

Настройка проекта

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

Основные пакеты из списка требований:

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

Переопределите стандартные настройки с помощью окружения, не изменяя код:

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

Пайплайн обработки данных (src/ingest.py)

Загрузка документов

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

Один вспомогательный инструмент обрабатывает либо один файл, либо целую папку. Загрузчики LangChain в любом случае всегда возвращают одинаковый список объектов типа Document.

Разделение на части

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

RecursiveCharacterTextSplitter в первую очередь использует разделение на абзацы, затем на предложения, и только потом — на слова, чтобы сохранить семантические границы. Наличие перекрытия в 200 символов позволяет сохранять предложения, простирающиеся через границу, в обоих соседних фрагментах, что снижает количество ошибок при поиске на границах.

Встраивание и сохранение

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 обслуживает эмбеддинги локально по порту 11434. Chroma сохраняет их в папке vector_store/ (файлы SQLite и индекса HNSW), так что при перезапуске процесса не требуется полное повторное встраивание.

Цепочка поиска (src/retrieval.py)

LangChain LCEL объединяет шаги с помощью оператора |.

Шаблон запроса

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}"),
])

Системное сообщение привязывает ответы к полученному контексту — это основной механизм защиты RAG от произвольных галлюцинаций, возникающих только на основе предварительной обучаемости.

Создание цепочки

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()
    )

Слева направо: поисковая собака выбирает k наиболее похожих фрагментов с помощью метрики сходства эмбеддингов, вспомогательные скрипты форматируют эти фрагменты в одну строку контекста, шаблон запроса объединяет этот контекст с вопросом пользователя, а ChatOllama генерирует ответ.

Интерактивный цикл задавания вопросов

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

Простой интерфейс командной строки (или обертка HTTP) загружает цепочку обработки один раз и отвечает на вопросы до тех пор, пока вы не закончите работу. Пусть значение k остается настраиваемым, чтобы можно было компромиссировать между объемом контекста и задержкой.

Работа «от начала до конца»

# 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

Укажите путь к папке с PDF-файлами, убедитесь, что директория хранилища векторов заполнена, затем сформируйте запрос. Все — эмбеддинги, векторы, процесс генерации — остается на локальном хосте.

Выводы

Локальные RAG-решения больше не являются чем-то экзотическим. Ollama предоставляет модели, Chroma хранит векторы, а LangChain обеспечивает связь между компонентами графа. Для частных корпоративных баз данных этой троицы достаточно для создания полезного ассистента по документам без необходимости платить за облачные сервисы или использовать каналы передачи данных наружу.