Image

Документный хаос? RAG-система придёт на помощь

134f4511b4bb6ac3d02de4b31fd6af34

Всем привет!

Предисловие

В этой статье я хочу поделиться своим опытом создания приложения на базе Retrieval-Augmented Generation (RAG) системы, которая превращает кучу документов в удобную интерактивную базу знаний. По сути, наша система хранит документы в виде эмбеддингов в векторной базе Qdrant и позволяет задавать к ним вопросы. Когда вы что-то спрашиваете, запрос вместе с контекстом из документов отправляется языковой модели (LLM), которая формирует вам чётко структуированный ответ. Я покажу, как всё устроено изнутри: архитектуру, ключевые компоненты и «кухню», благодаря которой это работает. Если вы только начинаете знакомство с RAG или просто интересно, как может выглядеть ИИ-помощник для документов, эта статья даст наглядное представление о работе такой системы.

Содержание

Введение
1. Что такое RAG?
2. Обзор архитектуры

2.1. База данных
2.2. Бэкенд
2.3. Фронтенд
3. Обработка метаданных документов
3.1. Извлечение заголовков
3.2. Формирование секций
3.3. Пример промпта (тест метаданных)
4. Сложности и возможные улучшения
5. Примечание о конфиденциальности данных
Заключение

Введение

В своей работе я часто сталкивался с проблемой эффективного использования большого объёма документов. Представьте себе огромную библиотеку инструкций, заметок, отчётов и файлов в разных форматах, из которых вам нужно выудить из этого всего лишь один кусочек информации именно тогда, когда он нужен. Раньше я тратил часы на поиск ответов, заставляя клиентов ждать, пока я проверю детали или найду нужный ответ на их вопросы.

В какой-то момент я понял, что должен быть лучший способ. Тогда пришла идея: а что если создать локальное приложение, которое автоматически будет находить нужные фрагменты информации из моей библиотеки документов и отправлять их вместе с моим запросом в LLM? Цель была проста: пусть ИИ берёт на себя тяжёлую работу и выдаёт мне чёткий, структурированный ответ на основе моих собственных данных.

К моему удивлению, это сработало замечательно. Система не только сэкономила огромное количество времени, но и сделала моё общение с клиентами быстрее и точнее. Воодушевлённый этими результатами, я решил поделиться своим опытом того, как я создал это приложение на основе RAG, и кратко объяснить, как оно работает «под капотом». Независимо от того, интересуетесь ли вы RAG или просто хотите собрать собственного интеллектуального помощника для работы с документами, я надеюсь, что эта история поможет вам сделать первые шаги.

1. Что такое RAG?

Представьте, что вы задаёте другу сложный вопрос, а он вместо того, чтобы гадать, просто быстро пролистывает свои заметки и сразу даёт точный ответ. Вот примерно так и работает RAG. Эта штука позволяет языковым моделям «подсматривать» информацию перед тем, как генерировать ответ. То есть вместо того чтобы работать только с тем, чему LLM уже научилась, ей предоставляется нужная информация из внешних источников — например, из документов компании или базы знаний.

Представьте офис, где все время спрашивают про HR: «Сколько у меня отпускных дней?» или «Как оформить возврат расходов?» Ассистент на RAG мгновенно ищет ответы в HR-документах, справочниках или внутреннем вики и выдаёт точные ответы. Сотрудники получают нужную инфу моментально, HR экономит кучу времени на повторяющихся вопросах. Получается, что наша система — это как суперэффективный офисный помощник, который ничего не забывает.

2. Обзор архитектуры

Вот графическая схема компонентов моего приложения, включая в себя фронтенд и бэкенд:

Графическая схема компонентов
Графическая схема компонентов

Чтобы показать, как работает приложение, я закинул туда четыре документа: три про возобновляемую энергетику и два про ИИ-агентов в форматах .txt, .docx и .pdf. Так образом система обладает базой знаний из разных областей, чтобы было наглядно видно, как она справляется с поиском информации по разным темам.

Сервис поддерживает несколько популярных форматов файлов, что даёт гибкость при загрузке документов:

self.docx = «.docx» self.xlsx = «.xlsx» self.txt = «.txt» self.pdf = «.pdf» # image-only PDFs are supported through # built-in OCR (tesseract)

2.1. База данных

Qdrant — это векторная база данных, созданная для эффективного хранения и поиска эмбеддингов. В RAG-системе каждый текстовый фрагмент из документов преобразуется в высокоразмерный вектор, который отражает его смысловое содержание. В моём приложении я использую Qdrant для хранения всех загруженных частей документов, чтобы ИИ мог мгновенно их искать каждый раз, когда пользователь отправляет запрос.

Qdrant известен:

  • Быстрым поиском по схожести
    Qdrant может мгновенно сравнивать вектор запроса с миллионами векторов документов и возвращать ближайшие совпадения за миллисекунды.

  • Работой с большими объёмами данных
    Будь то сотни PDF или тысячи фрагментов документов, Qdrant легко масштабируется, обеспечивая быстрый и надёжный поиск.

  • Гибкой поддержкой метаданных
    Каждый фрагмент может хранить дополнительную информацию: заголовки, наименования файлов, которую наш сервис использует для более точной фильтрации и ранжирования результатов поиска.

  • Интеграцией с ИИ-пайплайнами
    Qdrant отлично работает с различными моделями эмбеддингов, позволяя нашему сервису создавать, хранить и искать фрагменты документов с минимальными затратами ресурсов.

Используя Qdrant в приложении, мы превращаем сырые документы в живую, доступную для поиска базу знаний, благодаря чему ИИ может выдавать ответы, основанные на реальном содержимом, а не только на своих предварительно обученных данных.

2.2. Бэкенд

Бэкенд на FastAPI — это сердце всего приложения, которое держит всё в рабочем состоянии. FastAPI я выбрал за его скорость, современность и асинхронность: сервис легко справляется с кучей запросов одновременно, не тормозя. Плюс он сам проверяет типы данных и легко интегрируется с многопоточными задачами, вроде обработки документов и запросов к Qdrant.

В бэкенде есть пять основных эндпоинтов, которые отвечают за ключевой рабочий процесс:

2.2.1. POST /qdrant/upload-documents

Загружает несколько документов в Qdrant и сохраняет их векторные эмбеддинги для последующего поиска.

Чтобы закидывать файлы сразу пачкой, загрузка документов работает с многопоточностью. Файл читается и обрабатывается в фоне, так что сервер не тормозит на тяжёлых задачах вроде парсинга или создания эмбеддингов.

async def upload_document(self, util_service: UtilService, file: UploadFile): content = await file.read() def process_file(): filename = file.filename logger.info(f»Uploading {filename}…») # Extracting content and chunking here await run_in_threadpool(process_file)

После того как файл прочитан, сервис извлекает из него текст и делит его на логические секции. Крупные секции дополнительно разбиваются на маленькие «чанки», чтобы эмбеддинги работали точнее, а поиск информации был эффективнее.

text = util_service.extract_text_from_file(content, filename) headings = util_service.extract_headings(text) sections = util_service.get_sections(text, headings, filename) chunks = [] for section in sections: if len(section.page_content) > CHUNK_SIZE: chunks.extend(self.splitter.split_documents([section])) else: chunks.append(section)

После разбиения на «чанки» каждая секция преобразуется в векторные эмбеддинги с помощью выбранной модели и сохраняется в Qdrant. Благодаря этому система может «находить» нужный текст, когда пользователь задаёт вопрос. Каждый загруженный документ превращается в коллекцию векторов внутри Qdrant, готовую к поиску и поддержке запросов с учётом контекста.

self.embeddings_model = HuggingFaceEmbeddings( model_name=EMBEDDING_MODEL, # intfloat/multilingual-e5-large cache_folder=CACHE_FOLDER) ## Qdrant.from_documents( documents=chunks, embedding=self.embeddings_model, url=self.qdrant_url, collection_name=filename)

2.2.2. GET /qdrant/get-documents

Возвращает список всех коллекций документов, которые сейчас хранятся в Qdrant, по сути, вашу текущую библиотеку. Это быстрый способ посмотреть, с чем ИИ-ассистент может работать и откуда брать ответы на вопросы.

2.2.3. DELETE /qdrant/delete-document

Удаляет коллекцию документов по имени, удаляя все её эмбеддинги из Qdrant (иногда нужно убрать устаревшие или тестовые документы из векторной базы).

2.2.4. PUT /ai/update-model

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

2.2.5. POST /ai/get-answer

Отправляет запрос (промпт) ИИ-ассистенту, который забирает релевантный контекст из Qdrant и возвращает сгенерированный LLM ответ.

Сервис использует векторные эмбеддинги, чтобы понимать смысл вашего запроса и текста в документах. Каждый запрос преобразуется в высокоразмерный вектор, отражающий его семантику. При этом каждый текстовый «чанк» в Qdrant тоже хранится в виде вектора. Система затем выполняет поиск по схожести, фактически измеряя, насколько близок вектор запроса к векторам документов в пространстве эмбеддингов.

В итоге выбираются только TOP_K наиболее релевантных разделов, которые обрабатываются LLM, чтобы ответ был основан на реальном, контекстно значимом содержании.

async def search_collection(c): results = await run_in_threadpool( self.client.search, collection_name=c.name, query_vector=query_embedding, limit=TOP_K * TOP_K_COEF) for r in results: payload = r.payload metadata = payload.get(self.metadata, {}) all_results.append(( r.score, payload.get(self.page_content, «»), metadata.get(self.heading, «»), metadata.get(self.filename, «»))) await asyncio.gather(*(search_collection(c) for c in collections)) # Selecting top documents here return «n».join([text for _, text, _ in top_docs])

ИИ-помощник берёт ваш запрос и добавляет к нему найденный контекст из самых подходящих кусочков документов из Qdrant, чтобы сформировать запрос локальной LLM для формирования ответа. Эти кусочки дают модели точную и нужную информацию, так что ей не приходится полагаться только на то, что она «помнит» из обучения. Благодаря этому ИИ может отвечать более точно, подробно и с учётом контекста.

Другими словами, модель не просто угадывает, она «читает» самые релевантные куски ваших документов и использует их, чтобы дать осмысленный ответ. В этом и заключается суть RAG: объединение вашей базы знаний с рассуждениями языковой модели.

self.prompt_template = PromptTemplate( input_variables=[«context», «prompt»], template=( «Вы — методолог. Дайте подробный ответ, строго опираясь » «на предоставленный контекст.nn» «Контекст:n{context}nn» «Вопрос:n{prompt}nn» «Если в контексте нет информации для ответа, ответьте: » «В предоставленных документах нет информации для ответа на этот вопрос.»)) self.llm = Llama.from_pretrained( repo_id=LLM_MODEL, # from ‘PUT /ai/update-model’ filename=LLM_FILENAME, cache_dir=CACHE_FOLDER) ## async def get_answer(self, qdrant_service: QdrantService, prompt: str) -> str: context = await qdrant_service.get_context(prompt) # LLM answer generation here return str(raw_answer)

2.3. Фронтэнд

Созданный на React фронтенд — это «лицо» приложение, обеспечивающее плавный и интерактивный пользовательский опыт. React был выбран из-за своей скорости и компонентной архитектуры, что делает его идеальным для создания динамичных интерфейсов, которые мгновенно обновляются, когда ИИ возвращает ответы или загружаются документы. Он также предлагает богатую экосистему библиотек, повторно используемых UI-компонентов и простую интеграцию с бэкендом на FastAPI через REST API. Фронтенд отвечает за основные взаимодействия с пользователем: отправку запросов, отображение ответов ИИ, загрузку документов, переключение языковых моделей и управление базой знаний.

2.3.1. Компоненты

Фронтенд включает пять основных компонентов, каждый из которых отвечает за конкретную часть пользовательского интерфейса:

ChatPanel.jsx Dashboard.jsx (container for all other components) DocumentList.jsx ModelSelector.jsx UploadForm.jsx

2.3.2. Обработка Markdown

Для отображения ответов от LLM в удобочитаемом виде фронтенд использует рендеринг Markdown. Это позволяет модели возвращать текст с переносами строк, форматированием и даже простыми таблицами или списками, которые пользователь видит именно так, как задумано.

Для этого используются два основных пакета:

  • react-markdown: отображает содержимое Markdown как React-компоненты;

  • remark-breaks: сохраняет переносы строк без необходимости добавлять двойные пробелы.

import ReactMarkdown from «react-markdown»; import remarkBreaks from «remark-breaks»; // <div className=»border p-3 rounded bg-gray-50 text-xl whitespace-pre-wrap»> <ReactMarkdown remarkPlugins={[remarkBreaks]}> {safeResponse} </ReactMarkdown> </div>

3. Обработка метаданных документов

Чтобы сделать процесс поиска информации удобнее, система разбивает каждый загруженный документ на разделы по заголовкам. Заголовки определяются автоматически с помощью набора регулярных выражений, которые умеют распознавать разные форматы: нумерованные списки, заголовки прописными буквами, Markdown-заголовки и даже римские цифры.

Такой подход позволяет разбивать большие документы (отчёты, инструкции или научные статьи) на осмысленные части, сохраняя их структуру и контекст для последующего поиска.

3.1. Извлечение заголовков

Метод extract_headings() просматривает текст построчно и проверяет, соответствует ли каждая строка одному из нескольких шаблонов заголовков. В качестве заголовков разделов считаются только те строки, которые достаточно короткие (меньше max_words_heading) и подходят под один из заранее определённых типов заголовков.

def extract_headings(self, text: str) -> List[str]: headings = [] for line in text.splitlines(): line = line.strip() if not line: continue words = line.split() if len(words) > self.max_words_heading: continue if (self.numbered_heading.match(line) or self.uppercase_heading.match(line) or self.markdown_heading.match(line) or self.roman_heading.match(line)): headings.append(line) return headings

3.2. Формирование секций

Как только заголовки определены, метод get_sections() разбивает текст документа на части, каждая из которых связана с конкретным заголовком. Если заголовки не найдены, весь текст сохраняется как один раздел.

Каждый раздел содержит метаданные, такие как исходное имя файла и путь заголовков (последовательность заголовков от верхнего уровня до текущего раздела), которые при поиске помогают показать, откуда именно взят тот или иной ответ.

def get_sections(self, text: str, headings: List[str], filename: str) -> List[Document]: sections = [] if not headings: cleaned_text = self.remove_page_numbers(text.strip()) sections.append(Document( page_content=cleaned_text, metadata={self.heading: self.full_text, self.filename: filename})) return sections # Heading-based sectioning here sections.append(Document( page_content=content, metadata={self.heading: accumulated_heading, self.filename: filename})) return sections

3.3. Пример промпта (тест метаданных)

Например, мы знаем заголовок и наименование файла, по которым хотим получить информацию. Рассмотрим этот фрагмент из файла ии_на_службе_бизнеса.pdf:

Фрагмент из документа
Фрагмент из документа

Давайте взглянем на логи:

INFO: MATCH: 5.2 бизнес и производство 5.2.2 оптимизация цепочек поставок и операционный менеджмент INFO: PROMPT: О чем рассказывает 5.2.2 оптимизация цепочек поставок в ии_на_службе_бизнеса.pdf? INFO: CONTEXT (truncated): 5.2.2 Оптимизация цепочек поставок и операционный менеджмент ИИ-агенты революционизируют управление цепочками поставок, обеспечивая эффективное прогнозирование, контроль запасов и оптимизацию логистики. Сложный и динамичный характер современных цепочек поставок делает их идеальной областью для применения ИИ, поскольку такие системы способны обрабатывать огромные объемы данных, выявлять закономерности, предсказывать сбои и предлагать корректирующие действия [74]. Применение предиктивной аналитики позволяет компаниям с высокой точностью прогнозировать сп… INFO: 172.18.0.1:51408 — «POST /api/ai/get-answer HTTP/1.1» 200 OK

Теперь давайте посмотрим на ответ нашего ИИ-ассистента:

Раздел 5.2.2 описывает, как ИИ-агенты трансформируют управление цепочками поставок за счёт следующих аспектов:

— Сквозная оптимизация:
Системы ИИ обрабатывают большие, сложные и динамичные данные цепочек поставок, чтобы выявлять закономерности, предсказывать сбои и предлагать корректирующие действия [74].

— Предиктивное прогнозирование спроса:
Аналитика на основе прогнозов повышает точность оценки спроса, снижая перепроизводство и дефицит товаров, что приводит к значительной экономии затрат.

— Оптимизация логистики и маршрутов:
Алгоритмы учитывают такие факторы, как трафик, погодные условия и сроки доставки, чтобы вычислять наиболее эффективные маршруты. Это снижает расход топлива и время доставки, а также способствует достижению целей устойчивого развития [75][76].

— Автоматизация складов:
Автономные роботы, использующие машинное обучение и технологии компьютерного зрения, выполняют задачи сортировки, упаковки и инвентаризации, сокращая количество ошибок, повышая производительность и улучшая общую операционную эффективность [77].

В целом, ИИ-агенты рассматриваются как незаменимый инструмент для прогнозирования, контроля запасов, оптимизации логистики и автоматизации складских операций в современных цепочках поставок.

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

4. Сложности и возможные улучшения

Создавать RAG-сервис, который объединяет FastAPI, Qdrant и React в единую рабочую цепочку, было одновременно интересно и непросто. По ходу работы возникали идеи, которые расширяли пространство для будущих улучшений.

  1. Работа с разными форматами файлов
    Поддержка разных форматов вроде .txt, .docx, .xlsx и .pdf (в том числе PDF с изображениями) оказалась сложнее, чем ожидалось. Каждому формату нужна была своя стратегия извлечения, чтобы текст для эмбеддингов был консистентным. OCR на tesseract иногда давал шум из-за качества сканов.

    Потенциальные улучшения: использование гибридного подхода OCR-LLM для более чистого извлечения текста.

  2. Разбитие на «чанки» и извлечение контекста
    Подобор подходящих размеров «чанков» оказался одной из самых сложных задач. Слишком маленькие — контекст распадается; слишком большие — эмбеддинги теряют точность. Сейчас система использует разбиение по заголовкам, что хорошо работает для структурированных отчётов.

    Потенциальные улучшения: добавление семантического или динамического разбиения, которое подстраивается под структуру документа и плотность содержимого.

  3. Пользовательский интерфейс и ответы
    Фронтенд на React сделал взаимодействие удобным и понятным, но больше обратной связи в реальном времени могло бы улучшить опыт пользователя. Например, потоковая подача частичных ответов ИИ или отображение источников при поиске.

    Потенциальные улучшения: добавление подсветки цитат и потоковой подачи ответов, чтобы процесс поиска и формирования ответа был более прозрачным.

5. Примечание о конфиденциальности данных

Стоит иметь в виду, что, когда вы отправляете фрагменты документов во внешний ИИ-сервис (например, ChatGPT или другие облачные модели), вы делитесь их содержимым с третьей стороной. То есть любая конфиденциальная или чувствительная информация из файлов может потенциально «выйти» за пределы вашей локальной среды.

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

Заключение

То, что начиналось как простая идея разобраться с растущей кучей документов, превратилось в полностью рабочее приложение на основе Retrieval-Augmented Generation (RAG). В процессе я понял, что RAG — это не просто набор компонентов, а способ связать знания, структуру и интеллект. Метаданные, разбиение на фрагменты и эмбеддинги звучат пугающе и технически, но вместе они составляют основу инструмента, который реально помогает людям в работе.

Впереди ещё куча возможностей для улучшений: более умное разбиение на фрагменты, потоковые ответы, более продуманный алгоритм ранжирования и новые интеграции моделей. Но даже в нынешнем виде проект ясно показывает одно: с правильной архитектурой ИИ-агент может полностью изменить то, как мы работаем с собственной информацией.

Контакты

Telegram: @yelis_txt
Email: arselidex@yandex.ru

Источник: habr.com

✅ Найденные теги: Документный, новости

ОСТАВЬТЕ СВОЙ КОММЕНТАРИЙ

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Каталог бесплатных опенсорс-решений, которые можно развернуть локально и забыть о подписках

галерея

Фото сгенерированных лиц: исследование показывает, что люди не могут отличить настоящие лица от сгенерированных
Нейросети построили капитализм за трое суток: 100 агентов Claude заперли…
Скетч: цифровой осьминог и виртуальный мир внутри компьютера с человечком.
Сцена с жестами пальцами, где один жест символизирует "VPN", а другой "KHP".
‼️Paramount купила Warner Bros. Discovery — сумма сделки составила безумные…
Скриншот репозитория GitHub "Claude Scientific Skills" AI для научных исследований.
Структура эффективного запроса Claude с элементами задачи, контекста и референса.
Эскиз и готовая веб-страница платформы для AI-дизайна в современном темном режиме.
ideipro logotyp
Image Not Found
Звёздное небо с галактиками и туманностями, космос, Вселенная, астрофотография.

Система оповещения обсерватории Рубина отправила 800 000 сигналов в первую ночь наблюдений.

Астрономы будут получать оповещения о небесных явлениях в течение нескольких минут после их обнаружения. Теренс О'Брайен, редактор раздела «Выходные». Публикации этого автора будут добавляться в вашу ежедневную рассылку по электронной почте и в ленту новостей на главной…

Мар 2, 2026
Женщина с длинными тёмными волосами в синем свете, нейтральный фон.

Расследование в отношении 61-фунтовой машины, которая «пожирает» пластик и выплевывает кирпичи.

Обзор компактного пресса для мягкого пластика Clear Drop — и что будет дальше. Шон Холлистер, старший редактор Публикации этого автора будут добавляться в вашу ежедневную рассылку по электронной почте и в ленту новостей на главной странице вашего…

Мар 2, 2026
Черный углеродное волокно с текстурой плетения, отражающий свет.

Материал будущего: как работает «бессмертный» композит

Учёные из Университета штата Северная Каролина представили композит нового поколения, способный самостоятельно восстанавливаться после серьёзных повреждений.  Речь идёт о модифицированном армированном волокном полимере (FRP), который не просто сохраняет прочность при малом весе, но и способен «залечивать» внутренние…

Мар 2, 2026
Круглый экран с изображением замка и горы, рядом электронная плата.

Круглый дисплей Waveshare для креативных проектов

Круглый 7-дюймовый сенсорный дисплей от Waveshare создан для разработчиков и дизайнеров, которым нужен нестандартный экран.  Это IPS-панель с разрешением 1 080×1 080 пикселей, поддержкой 10-точечного ёмкостного сенсора, оптической склейкой и защитным закалённым стеклом, выполненная в круглом форм-факторе.…

Мар 2, 2026

Впишите свой почтовый адрес и мы будем присылать вам на почту самые свежие новости в числе самых первых