RAG для документации Wazuh: Руководство, Часть 2

Применение RAG для работы с документацией Wazuh: Пошаговое руководство (Часть 2)

Подготовка к разработке кода

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

Для локальной разработки кода для RAG вам потребуется установить следующие инструменты:

  • Ollama (для запуска языковых моделей и моделей эмбеддингов)
  • Python v3.9+
  • Базовые знания Python
  • Документация Wazuh в формате PDF (подготовленная в первой части)

Запуск и настройка Ollama

Ollama используется в данном проекте для двух задач: генерации эмбеддингов (векторных представлений текста) и формирования ответов на вопросы. Для каждой задачи используется отдельная модель.

  1. Установите Ollama
  2. Получите необходимые модели: llama3.2 и nomic-embed-text.

Модель nomic-embed-text отвечает за преобразование текстовых фрагментов документации в числовые векторы, которые затем сохраняются в векторной базе данных. Модель llama3.2 используется для генерации ответов на основе релевантных фрагментов, найденных по запросу пользователя.

Разрабатываем механизм для загрузки PDF документации

Для разработки мы будем использовать следующие инструменты:

  • LangChain - для создания цепочек обработки данных.
  • Ollama - для запуска и настройки моделей.
  • Python - как основной язык программирования.
  • ChromaDB - в качестве векторного хранилища.

Устанавливаем зависимости:

Создаем файл requirements.txt и добавляем следующие зависимости:

chromadb==0.6.3
unstructured==0.16.14
langchain==0.3.18
langchain-text-splitters==0.3.6
unstructured[all-docs]
langchain-community==0.3.14
langchain-ollama==0.2.2

После установки давайте создадим механизм для загрузки PDF документации.

Создайте Python скрипт upload.py и добавьте следующий код:

Добавьте импорты в начало вашего скрипта:

import argparse
import os

from langchain_community.document_loaders import UnstructuredPDFLoader
from langchain_ollama import OllamaEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma

Теперь создаем функцию для загрузки PDF документации:

def upload(document_path, model_name, collection_name, ollama_base_url):
    current_path = os.path.dirname(os.path.realpath(__file__))
    chroma_persistent_directory = current_path + "/data"
    if not os.path.exists(chroma_persistent_directory):
        os.makedirs(chroma_persistent_directory, exist_ok=True)
    loader = UnstructuredPDFLoader(file_path=document_path)
    data = loader.load()
    text_splitter = RecursiveCharacterTextSplitter(chunk_size=7500, chunk_overlap=100)
    chunks = text_splitter.split_documents(data)
    vector = Chroma.from_documents(
        documents=chunks,
        embedding=OllamaEmbeddings(
            base_url=ollama_base_url,
            model=model_name, show_progress=True
        ),
        collection_name=collection_name,
        persist_directory=chroma_persistent_directory
    )
    return vector

Объяснение функции

Путь к текущему скрипту:

current_path = os.path.dirname(os.path.realpath(__file__))

Эта строка определяет путь к директории, в которой находится текущий скрипт.

Создание директории для хранения данных:

chroma_persistent_directory = current_path + "/data"
if not os.path.exists(chroma_persistent_directory):
    os.makedirs(chroma_persistent_directory, exist_ok=True)

Здесь создается директория data внутри текущей директории, если она еще не существует. Эта директория будет использоваться для хранения данных.

Загрузка PDF-документа:

loader = UnstructuredPDFLoader(file_path=document_path)
data = loader.load()

Используется класс UnstructuredPDFLoader для загрузки PDF-документа по указанному пути document_path.

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

text_splitter = RecursiveCharacterTextSplitter(chunk_size=7500, chunk_overlap=100)
chunks = text_splitter.split_documents(data)

Текст из PDF-документа разбивается на части (chunks) размером 7500 символов с перекрытием в 100 символов. Размер чанка 7500 символов выбран как оптимальный для технической документации – он достаточно велик, чтобы сохранить контекст раздела, но не превышает ограничения контекстного окна модели эмбеддингов.

Перекрытие в 100 символов обеспечивает плавный переход между фрагментами, предотвращая потерю информации на границах чанков. Это особенно важно для документации Wazuh, где конфигурационные примеры и пояснения к ним часто находятся в смежных абзацах.

vector = Chroma.from_documents(
    documents=chunks,
    embedding=OllamaEmbeddings(
        base_url=ollama_base_url,
        model=model_name, show_progress=True
    ),
    collection_name=collection_name,
    persist_directory=chroma_persistent_directory
)

Используется класс Chroma для создания векторного представления текстовых частей. Для этого используется модель OllamaEmbeddings, которая загружается по указанному URL (ollama_base_url) и имени модели (model_name). Векторное представление сохраняется в коллекцию с именем collection_name в директории chroma_persistent_directory.

Возврат результата:

return vector

Функция возвращает объект vector, который представляет собой векторное представление текста.

Теперь завершаем наш скрипт

if __name__ == '__main__':
    # Создание парсера аргументов
    parser = argparse.ArgumentParser(description='Upload a PDF to the vector store')
    # Добавление аргументов
    parser.add_argument('-p', '--path', type=str, help='Path to the PDF file', required=True)
    parser.add_argument('-m', '--model', type=str, help='Name of the Ollama model for embedding',
                        default='nomic-embed-text')
    parser.add_argument('-n', '--name', type=str, help='Collection name in ChromaDB', default='wazuh')
    parser.add_argument('-b', '--base-url', type=str, help='Base URL for the Ollama server',
                        default='http://127.0.0.1:11434')
    # Разбор аргументов
    args = parser.parse_args()
    # Вызов функции upload
    upload(document_path=args.path, model_name=args.model, collection_name=args.name, ollama_base_url=args.base_url)

Сохраняем все и запускаем

ollama pull nomic-embed-text
python upload.py -p путь до вашего pdf файла

Загрузка займет какое-то время в зависимости от объема PDF-документа и производительности системы. Для полной документации Wazuh (около 500 страниц) процесс может занять от 10 до 30 минут. Наберитесь терпения.

После завершения загрузки в директории data/ будет создана база данных ChromaDB с векторными представлениями всех фрагментов документации.

Создание механизма запросов

Теперь, когда документация загружена в векторное хранилище, создадим механизм для выполнения запросов. Ключевая особенность нашей реализации – использование MultiQueryRetriever из LangChain. Этот компонент автоматически генерирует несколько вариантов исходного вопроса, что повышает качество поиска релевантных фрагментов в векторной базе данных.

Создаем Python скрипт (например, ask.py), который будет использовать Ollama для получения ответов на вопросы.

import argparse
import os

from langchain.retrievers import MultiQueryRetriever
import chromadb
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate, ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_ollama import ChatOllama
from langchain_ollama import OllamaEmbeddings
from langchain_chroma import Chroma


def ask_ollama(question, collection_name='wazuh', embedding_model='nomic-embed-text', local_model='llama3.2'):
    current_path = os.path.dirname(os.path.realpath(__file__))
    chroma_persistent_directory = current_path + "/data"
    embedding = OllamaEmbeddings(model=embedding_model)
    persistent_client = chromadb.PersistentClient(path=chroma_persistent_directory)
    vector_db = Chroma(
        client=persistent_client,
        collection_name=collection_name,
        embedding_function=embedding,
    )

    load_ollama = ChatOllama(model=local_model)

    prompt_template = PromptTemplate(
        input_variables=["question"],
        template="""You are an AI language model assistant. Your task is to generate 2
    different versions of the given user question to retrieve relevant documents from
    a vector database. By generating multiple perspectives on the user question, your
    goal is to help the user overcome some of the limitations of the distance-based
    similarity search. Provide these alternative questions separated by newlines.
    Original question: {question}""",
    )

    template = """Answer the question based ONLY on the following context:
    {context}
    Question: {question}
    """

    retriever = MultiQueryRetriever.from_llm(vector_db.as_retriever(), load_ollama, prompt=prompt_template)
    prompt = ChatPromptTemplate.from_template(template)
    chain = (
            {"context": retriever, "question": RunnablePassthrough()}
            | prompt
            | load_ollama
            | StrOutputParser()
    )
    return chain.invoke(question)


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Ask Ollama a question')
    parser.add_argument('-q', '--question', type=str, help='The question to ask Ollama', required=True)
    args = parser.parse_args()
    print(ask_ollama(args.question))

Тестирование и результаты

Сохраняем и запускаем скрипт:

python3 ask.py -q "What is it Wazuh and what for?"

Примерный ответ:

Wazuh is an open-source log management system that provides real-time monitoring and alerting capabilities for security, compliance, and IT operations. It was originally developed by Qualys, a leading provider of vulnerability management and compliance solutions.

Wazuh acts as a bridge between the host operating system and external threat intelligence feeds, allowing users to collect, process, and analyze log data from various sources. This enables users to:

1.  **Monitor security events**: Wazuh collects and analyzes log data from various sources (e.g., system logs, application logs, and network devices) to identify potential security threats and anomalies.
2.  **Detect vulnerabilities**: By integrating with external threat intelligence feeds, Wazuh can detect known vulnerabilities in the environment and alert users to take corrective action.
3.  **Enforce compliance**: Wazuh supports various compliance frameworks (e.g., PCI-DSS, HIPAA/HITECH, GDPR) by providing features for logging, auditing, and reporting on security-related data.

Overall, Wazuh helps organizations proactively manage their security posture, detect potential threats, and maintain regulatory compliance.

Как видно из ответа, RAG-система корректно извлекает информацию из документации Wazuh и формирует структурированный ответ. Качество ответов напрямую зависит от качества исходной документации и параметров разбиения на чанки.

Рекомендации по оптимизации

При использовании RAG-системы для документации Wazuh в рабочей среде рекомендуется учитывать следующие аспекты:

  • Размер чанков – для технической документации с большим количеством конфигурационных примеров может потребоваться увеличение размера чанка до 10000 символов, чтобы сохранить целостность примеров кода.
  • Количество релевантных фрагментов – по умолчанию retriever возвращает 4 наиболее релевантных фрагмента. Для сложных вопросов, затрагивающих несколько разделов документации, рекомендуется увеличить это значение.
  • Обновление базы данных – при выходе новой версии документации Wazuh необходимо повторно выполнить загрузку PDF в векторное хранилище для актуализации данных.

Следите за обновлениями.

Связанные материалы


Навигация по серии:


Смотрите также