Локальный RAG на FastAPI и Ollama: add PDF, Word file без облака и подписок
Ниже работа с локальными документами выходит за рамки «поболтать с моделью» и превращается в инженерную систему с API, логами, источниками и диагностикой.

RAG (Retrieval-Augmented Generation, генерация с опорой на найденные документы) позволяет модели отвечать не «из головы», а по вашим файлам. Локальный вариант на FastAPI и Ollama держит данные на вашей машине, без облака и подписок.
Автор проекта описал путь от простого вызова локальной LLM до бэкенда с контрактом API, трассировкой запросов, источниками ответа и индексом документов. Не «production-ready» система, а учебный полигон с инженерными акцентами. Материал опубликован как открытый разбор архитектуры, цель которого показать, где именно заканчивается «чат с моделью» и начинается бэкенд-система по документации. Ниже воспроизвожу ключевые шаги и решения из этого разбора.
Что понадобится?
- Python 3.10+ и установленный FastAPI с сервером Uvicorn
- Ollama (опенсорс-инструмент для локального запуска LLM, работает на macOS, Linux, Windows)
- Модель для генерации: в проекте использована gpt-oss:20b (запускается через Ollama)
- Модель для эмбеддингов (векторных представлений текста): embeddinggemma (тоже через Ollama)
- Документы в формате
.mdили.txtв локальной папкеdocuments/ - Фронтенд на HTML/CSS/JS (минимальный, чтобы отправлять вопросы и видеть ответ)
- Примерное время: от 2 до 4 часов на первую работающую версию, если стек знаком
Пошаговая инструкция
1. Соберите минимальный бэкенд с эндпоинтом /ask
На старте схема проста: фронтенд отправляет POST /ask, FastAPI создаёт уникальный request_id, логирует запрос, вызывает модель через Ollama и возвращает ответ.
# Упрощённая схема (не полный код проекта)
@app.post("/ask")
async def ask(question: str):
request_id = str(uuid4())
log_request(request_id, question)
answer = call_ollama(question)
log_response(request_id, answer)
return {"request_id": request_id, "answer": answer}
На этом этапе помощник уже отвечает, но по сути остаётся обычным чатом с моделью. Документы в процессе не участвуют.
2. Осознайте, почему простой вызов LLM ещё не система
Проблемы, которые автор выделяет на этом этапе:
- Модель не знает локальные документы, если не передать их в контекст
- Ответ невозможно проверить по источникам
- Непонятно, что именно попало в промпт (prompt, запрос к модели)
- Если ответ медленный, неясно, какой этап тормозит
- Если документ изменился, неясно, обновился ли индекс
- Модель может «галлюцинировать» (уверенно выдумывать), даже когда ответа нет
3. Добавьте загрузку и нарезку документов на чанки
Положите файлы .md и .txt в папку documents/. Для тех, кому нужно добавить PDF или Word (ollama rag add pdf word file), на этом этапе потребуется предварительная конвертация в текстовый формат: библиотеки вроде pdfplumber для PDF и python-docx для Word извлекут текст, после чего файл обрабатывается так же, как .txt.
Документ целиком редко удобно передавать в промпт. Нарежьте его на чанки (небольшие фрагменты фиксированной длины с перекрытием). Отдельный эндпоинт GET /documents/chunks позволяет убедиться, что бэкенд видит и корректно режет каждый файл.
4. Постройте эмбеддинги и векторное хранилище
Эмбеддинги (embedding, числовое представление смысла текста) считаются через Ollama и модель embeddinggemma. Каждый чанк превращается в вектор, все векторы складываются в хранилище в оперативной памяти (in-memory vector store).
Эндпоинт POST /documents/index/rebuild перечитывает документы, режет, считает эмбеддинги и обновляет хранилище. Эндпоинт GET /documents/index/status показывает текущее состояние индекса.
5. Подключите поиск и сборку промпта
При запросе через /ask бэкенд теперь ищет релевантные чанки по близости векторов, фильтрует по порогу score, проверяет точные термины и собирает из найденных фрагментов промпт для модели.
Упрощённый пайплайн обработки /ask после добавления RAG:
- Принять вопрос, создать
request_id - Найти релевантные чанки в векторном хранилище
- Отфильтровать источники по score
- Проверить наличие точных терминов (exact terms)
- Собрать промпт с найденным контекстом
- Вызвать Ollama
- Вернуть ответ, источники, тайминги и
request_id
6. Оформите контракт ответа API
Автор использовал Pydantic-модели для строгого контракта. Ответ содержит не только текст, но и диагностику:
class AskResponse(BaseModel):
request_id: str
answer: str
sources: list[Source] # документ, путь, номер чанка, score, текст
duration_ms: int
timings: TimingBreakdown # retrieval, filtering, prompt, generation, total
Такой JSON на выходе уже не игрушечный чат, а бэкенд-API с контрактом, диагностикой и привязкой к логам.
7. Добавьте диагностические эндпоинты
Полный набор эндпоинтов из проекта:
POST /ask— главный, для пользователяGET /documents— какие документы видит бэкендGET /documents/chunks— нарезка на чанкиGET /documents/embeddings— статус эмбеддинговPOST /documents/index/rebuild— пересборка индексаGET /documents/index/status— состояние индексаPOST /documents/search— поиск по чанкам без генерацииPOST /rag/debug— полная отладка пайплайна
Эти эндпоинты появились не ради архитектурной красоты: когда каждый слой можно проверить отдельно, искать ошибки на порядок проще.
Вопрос через POST /ask: «Когда нужно выполнять rebuild index?»
Ответ (сокращённо):
{
"request_id": "8b7a8be9-4bfd-4b89-ac9c-5a26f01de229",
"answer": "Rebuild index нужно выполнять после добавления, изменения или удаления документов...",
"duration_ms": 60440,
"timings": {
"retrieval_ms": 2163,
"generation_ms": 58276,
"total_ms": 60440
},
"sources": [
{
"document": "operations.md",
"chunk_index": 5,
"score": 0.6547,
"chunk": "...Пятый случай — смена embedding модели..."
}
]
}
Видно: ответ опирается на конкретный чанк из operations.md, генерация заняла 58 секунд (основное время), поиск около 2 секунд. Источник можно проверить руками.
- Забыли пересобрать индекс. Изменили или добавили документ (включая случай ollama rag add pdf word file после конвертации), а
POST /documents/index/rebuildне вызвали. Модель ответит по старым данным или вообще мимо. - Чанки слишком большие или слишком мелкие. Крупный чанк не влезает в контекст модели, мелкий теряет смысл. Начните с 500 токенов (токен, единица текста для модели, примерно три четверти слова) и подбирайте под ваши документы.
- Нет порога score. Без фильтрации по score модель получает нерелевантные фрагменты и галлюцинирует увереннее, чем без контекста вовсе.
- Не смотрите тайминги. Если
/askработает 60 секунд, аgeneration_msзанимает 58 из них, проблема не в поиске, а в скорости инференса (inference, процесс генерации ответа моделью). Без разбивки по этапам вы оптимизируете не то. - Ждёте от in-memory хранилища масштаба. Хранилище в оперативной памяти годится для десятков и сотен документов. Для тысяч нужна полноценная векторная база данных (ChromaDB, Qdrant, FAISS).
Что делать с этим прямо сейчас?
Разработчикам и техническим авторам. Проект показывает скелет локального RAG-бэкенда с диагностикой. Можно взять архитектуру эндпоинтов как шаблон для внутреннего помощника по документации команды, не отправляя данные в облако.
Авторам Дзена и контент-маркетологам. Если пишете о технологиях, сам разбор подхода «от чата к системе» ложится в формат кейса или серии. Локальный RAG на своих текстах может помочь быстрее находить цитаты и факты по архиву публикаций.
Предпринимателям в РФ и СНГ. Ollama и FastAPI бесплатны, работают без зарубежных API-ключей и подписок. Данные остаются на вашем сервере. Для первого прототипа ИИ-помощника по внутренней базе знаний это рабочий вариант без бюджета на облако. Из российских аналогов для генерации можно попробовать GigaChat API от Сбера или YandexGPT, но они облачные, а не локальные.
Проект ценен не кодом, а инженерной логикой. Автор показал, что между «запустил модель и получил ответ» и «система, которой можно доверять ответ по документации» лежит десяток слоёв: request_id, тайминги, источники, пересборка индекса, порог релевантности. Я проверял похожие связки и могу подтвердить: без диагностических эндпоинтов отладка RAG превращается в гадание. Честная оговорка: in-memory хранилище, модель на 20 млрд параметров и локальный сервер подходят для пилота и обучения. Для нагруженного продакшена потребуется векторная база, более быстрый инференс и очередь запросов. Но как полигон для понимания, что такое RAG изнутри, связка FastAPI плюс Ollama работает.
Хотите внедрять ИИ-инструменты в свой контент-процесс?
В dzen.guru разбираем практические сценарии работы с нейросетями для авторов и предпринимателей.
Узнать большеЛокальный RAG не требует бюджета, не отправляет данные наружу и учит думать слоями. Начните с пяти документов и одного эндпоинта /ask, добавьте тайминги и источники, а потом решайте, нужен ли вам облачный масштаб или хватит сервера под столом.

Основатель dzen.guru. Эксперт по монетизации и продвижению на Дзен. Автор курса «Старт на Дзен 2026».
Читайте также

Graph RAG позволил моделям с 2 млрд параметров решить задачу, которую без графа не решила ни одна
Graph RAG (граф знаний для поиска по документам, дополняющий генерацию) позволяет даже компактным моделям с 2-8 миллиардами параметров решать сложные…

LLM-конвейер молча теряет записи: три бага, которые не видны в сводке
Конвейеры на базе больших языковых моделей (LLM, от англ. Large Language Model) часто теряют записи так, что итоговые проверки показывают успех, а пользователь…
Профи.ру встроил LLM поверх Elasticsearch: 150 000 запросов в час без замены движка
Дмитрий, лид отдела семантики в Профи.ру, рассказал, как команда встроила языковую модель в поиск услуг поверх Elasticsearch, не выбрасывая классический…
Комментарии