Базовый RAG за 15 минут
1. Введение
RAG - это метод, который позволяет LLM использовать релевантную информацию из внешних источнков в своих ответах. В этом гайде мы создадим полный RAG пайплайн, используя актуальную информацию о недавно вышедших фильмах/сериалах.
Наш гайд будет включать следующие шаги:
- Загрузка статей и нарезка на chunks
- Создание embeddings и загрузка в векторную БД
- Реализация базового семантического поиска
- Подключение LLM модели
- Проверка отсутствия информации у LLM
- Общение с LLM по информации из внешних источников
2. Подготовка окружения
Установим и импортируем необходимые библиотеки:
#!pip install langchain
#!pip install langchain-compressa
#!pip install langchain_community
#!pip install requests
#!pip install beautifulsoup4
#!pip install faiss-cpu - если вы запускаете на CPU
#!pip install faiss-gpu - если вы запускаете на GPU с поддержкой CUDA
import os
import requests
from bs4 import BeautifulSoup
from langchain_compressa import CompressaEmbeddings, ChatCompressa
from langchain_core.documents import Document
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_core.prompts import ChatPromptTemplate
from langchain.chains import create_retrieval_chain
from langchain_community.vectorstores import FAISS
from langchain.chains.combine_documents import create_stuff_documents_chain
Для ячейки ниже вам понадобится API ключ Compressa. Вы можете получить его после регистрации.
os.environ["COMPRESSA_API_KEY"] = "ваш ключ"
# Если вы запускаете локально на Macbook, укажите также следующие переменные в окружении
# os.environ["TOKENIZERS_PARALLELISM"] = "false"
# os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
3. Загрузка веб-страниц и нарезка на chunks
Загрузим информацию о свежих фильмах и сериалах с Wikipedia, а затем нарежем их на chunks (отрывки из статей)
# Для нашего примера возьмем сериал Сёгун и фильм Мастер и М аргарита
urls = [
"https://ru.wikipedia.org/wiki/Мастер_и_Маргарита_(фильм,_2024)",
"https://ru.wikipedia.org/wiki/Сёгун_(телесериал,_2024)"
]
def fetch_wikipedia_content(url):
# Отправляем GET-запрос к указанному URL
response = requests.get(url)
# Создаем объект BeautifulSoup для парсинга HTML-контента
soup = BeautifulSoup(response.content, 'html.parser')
# Находим основной контейнер с содержимым статьи
content = soup.find(id="mw-content-text").find(class_="mw-parser-output")
text = []
# Итерируемся по всем элементам 'p', 'h2' и 'h3' в основном контенте
for element in content.find_all(['p', 'h2', 'h3']):
if element.name == 'p':
# Если это параграф, добавляем его текст как есть
text.append(element.text)
elif element.name in ['h2', 'h3']:
# Если это заголовок, форматируем его с ## и добавляем перенос строки
text.append(f"\n## {element.text.strip()}\n")
# Объединяем все элементы текста в одну строку, разделяя их переносами строк
return '\n'.join(text)
documents = [Document(page_content=fetch_wikipedia_content(url)) for url in urls]
#Проверим, что все загрузилось корректно
print (documents)
# Разбиваем документы на чанки, конкретные настройки приведены для примера
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, chunk_overlap=100, add_start_index=True
)
chunks = text_splitter.split_documents(documents)
print(f"Всего создано чанков: {len(chunks)}")
print(f"При мер чанка:\n{chunks[0].page_content[:1000]}...")
4. Создание эмбеддингов
Превращаем полученнные куски текстов в embeddings и загружаем в векторную БД для дальнейшего поиска по ней. Для примера будем использовать Faiss, но можно взять и любую другую векторную БД на выбор (например, ChromaDB или Qdrant)
embeddings = CompressaEmbeddings(api_key=os.getenv("COMPRESSA_API_KEY"), base_url="https://compressa-api.mil-team.ru/v1")
# Создаем и заполняем векторное хранилище
vectorstore = FAISS.from_documents(chunks, embeddings)
print("Векторное хранилище успешно создано")
5. Реализация базового семантического поиска
Настроим простой retriever для семантического поиска:
# Setup the retriever for semantic search
retriever = vectorstore.as_retriever(search_type="similarity", search_kwargs={"k": 3})
# Example usage of retriever
query = "Кто режиссер фильма 'Мастер и Маргарита'?"
retrieved_docs = retriever.invoke(query)
print(f'Найдено {len(retrieved_docs)} релевантных документов')
print(f'Пример найденного документа:\n{retrieved_docs[0].page_content[:200]}...')
6. Подключение LLM модели
Подключим и настроим LLM модель:
llm = ChatCompressa(api_key=os.getenv("COMPRESSA_API_KEY"), base_url="https://compressa-api.mil-team.ru/v1")
# Создадим промпт для обработки запросов
system_template = f"""Ты помощник по вопросам-ответам. Используй следующую контекстную информацию,
чтобы ответить на вопрос. Если в контексте нет ответа, ответь 'Не знаю ответа на вопрос'.
Используй максимум три предложения и будь точным н о кратким."""
qa_prompt = ChatPromptTemplate.from_messages([
("system", system_template),
("human", """Контекстная информация:
{context}
Вопрос: {input}
"""),
])
# Создадим цепочку для ответа на вопросы
document_chain = create_stuff_documents_chain(llm, qa_prompt)
7. Проверка отсутствия информации у LLM
Перед использованием RAG, проверим, что у LLM нет информации о наших сериалах:
def check_llm_knowledge(question):
response = llm.invoke(question)
return response.content
questions = [
"Кто режиссер фильма 'Мастер и Маргарита' 2024 года?",
"Кто играет главную роль в сериале 'Сёгун' 2024 года?"
]
print("Проверка знаний LLM без использования RAG:")
for question in questions:
print(f"\nВопрос: {question}")
print(f"Ответ LLM: {check_llm_knowledge(question)}")
8. Сборка полного RAG пайплайна
Теперь соберем все компоненты в единый RAG пайплайн:
rag_chain = create_retrieval_chain(retriever, document_chain)
def ask_question(question):
response = rag_chain.invoke({"input": question})
return response["answer"]
# Тестирование и демонстрация RAG пайплайна
questions = [
"Кто режиссер фильма 'Мастер и Маргарита' 2024 года?",
"Кто играет главную роль в сериале 'Сёгун' 2024 года?",
"Когда вышел сериал 'Сёгун' 2024 года?",
"Какие актеры играют главные роли в фильме 'Мастер и Маргарита' 2024 года?",
"Сколько серий в сериале 'Сёгун' 2024 года?",
]
print("\nИспользование RAG пайплайна:")
for question in questions:
print(f"\nВопрос: {question}")
print(f"Ответ RAG: {ask_question(question)}")
10. Заключение
В этом гайде мы создали базовый RAG пайплайн, который уже работает для такой простой задачи. Более комплексные сценарии скорее всего потребуют дальнейших улучшений.
Например, мы можем улучшить точность поиска с помощью модели CompressaRerank, которая дополнительно приоритизирует найденные отрывки под запрос пользователя. Для этого мы подготовили специальный гайд.
Если вы хотите глубже погрузиться в Embeddings и понять, как технически устроен семантический поиск - посмотрите еше одно наше практическое руководство.
Также стоит отметить важность подбора правильных промптов и настроек LLM модели.