{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "95f995f6-fe08-4698-97c9-8d2e865abbab",
   "metadata": {},
   "source": [
    "# <center id=\"p1\"> <h1>🦞 `RAG`  и как его готовить!</h1> </center>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e2fc29b3-e03b-4aa7-baef-00bc7b2bf52e",
   "metadata": {
    "jp-MarkdownHeadingCollapsed": true
   },
   "source": [
    "### Оглавление ноутбука\n",
    "<img src='https://aurumcapital.ru/wp-content/uploads/2023/06/ByteSizedThumbnail-1200-800-px-10-.png' align=\"right\" width=\"508\" height=\"428\" >\n",
    "<br>\n",
    "\n",
    "<p><font size=\"3\" face=\"Arial\" font-size=\"large\"><ul type=\"square\">\n",
    "    \n",
    "<li><a href=\"#p1\">🚀 Введение. 🦞 RAG  и как его готовить! </a></li>\n",
    "<li><a href=\"#p2\">📥 Document loader & Text Splitter ⚔️</a></li><ul type=\"square\">\n",
    "<li><a href=\"#p2.1\">🚛 Document loaders </a></li>\n",
    "<li><a href=\"#p2.2\">🖖 Text splitters  </a></li></ul>\n",
    "<li><a href=\"#p3\">🔤 ➡️ 🔢 Embedding models </a></li><ul type=\"square\">\n",
    "<li><a href=\"#p3.1\">Эмбеддинги от OpenAI (API) </a></li>\n",
    "<li><a href=\"#p3.2\">Эмбеддинги от HuggingFace 🤗</a></li></ul>\n",
    "<li><a href=\"#p4\">🗂 Vector Stores - место, где живут эмбеддинги </a></li>\n",
    "<li><a href=\"#p5\">🎣 Retrievers - выуди нужный документ </a></li>\n",
    "<li><a href=\"#p6\">🚰 RAG Pipeline - подключаем RAG к LLM 🔧 </a></li>\n",
    "<li><a href=\"#p6\">🧸 Выводы и заключения ✅ </a></li>\n",
    "\n",
    "\n",
    "    \n",
    "</ul></font></p>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "583260b7-6123-4af9-887f-1e26222b3d53",
   "metadata": {},
   "source": [
    "## 🧑‍🎓 В этом ноутбуке разберем как скормить свои данные LLM 😋"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6b9cee70-6d05-45e9-be16-38a545abaaf7",
   "metadata": {},
   "source": [
    "👨‍💻 Мы покажем, как добавить собственную базу знаний в LLM с помощью `RAG` (Retrieval‑Augmented Generation). Даже обучать ничего не придется, поэтому дорогие GPU нам не понадобятся. "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3c02246b-a652-46f5-a5b9-de48db4207b6",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-info\">\n",
    "    \n",
    "📖 Большие языковые модели знают очень много, но далеко не всё. Особенно, что касается ваших собственных данных, которые нужны для работы приложения (например, база клиентов или любимые рецепты). \n",
    "\n",
    "Можно, конечно, попробовать дообучить LLM на новых данных, но это требует больших затрат времени, вычислительных ресурсов и объёмного качественно размеченного датасета. <br>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b2ca9ce7-09e4-4e85-8fc8-2d8dcbe021d1",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-info\">\n",
    "    \n",
    "Здесь к нам на помощь приходит подход, основанный на промптах, `RAG` - представленный в 2021 году командой FAIR (Facebook AI Research). Способ отлично подходит в случаях, когда нет большого объёма данных, времени и бюджета на манипуляции с дообучением моделей, а это большинство бизнес-кейсов. Эта концепция даёт пользователю мощные возможности, при этом является достаточно простой и понятной.<br>\n",
    "\n",
    "В чём мы сейчас и убедимся:"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c461854a-7dd8-47ef-b7ea-7a857513b012",
   "metadata": {},
   "source": [
    "##  <center> ❓🧑‍🎓 Так что же такое RAG ?"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cd1fc571-791d-44f2-9347-7de01dfe1431",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-info\">\n",
    "    \n",
    "<img src='https://aurumcapital.ru/wp-content/uploads/2023/06/ByteSizedThumbnail-1200-800-px-10-.png' align=\"right\" width=\"308\" height=\"428\">\n",
    "\n",
    "Не вдаваясь в технические детали реализации, концептуально RAG состоит из следующих шагов (см. рисунок):\n",
    "\n",
    "<div class=\"alert alert-success\">\n",
    "\n",
    "1. ⚾️ Формируется промпт с запросом.\n",
    "2. 📚 Система ищет документы в заранее сформированной базе знаний, которые помогут дать релевантный ответ на запрос.\n",
    "3. 🧩 В промпт, в качестве контекста, добавляются найденные документы.\n",
    "4. 🤓 Готовый промпт с инструкциями отправляется в LLM.\n",
    "5. 🤖 LLM генерирует ответ согласно инструкции, опираясь на предоставленные сведения."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "2318c74b-038b-4d04-b3e5-0275d99c7811",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# !pip install langchain langchain-openai openai tiktoken sentence-transformers faiss-cpu rank_bm25 -q"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "493f4994-0e2e-468c-adbf-916a3eceec38",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import os\n",
    "from getpass import getpass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 160,
   "id": "410165ef-1236-4cb5-8a7f-b74ed6554d84",
   "metadata": {
    "jupyter": {
     "source_hidden": true
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Для работы в колабе загрузите наш скрипт для использования ChatGPT с ключом курса, и необходимые файлы!\n",
    "# !mkdir ../data/\n",
    "# !wget -P ../data https://raw.githubusercontent.com/a-milenkin/LLM_practical_course/main/data/PEP8.txt\n",
    "# !wget -P ../data https://raw.githubusercontent.com/a-milenkin/LLM_practical_course/main/data/Data.csv\n",
    "# !wget https://raw.githubusercontent.com/a-milenkin/LLM_practical_course/main/notebooks/utils.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 161,
   "id": "d8c8b9c2-9f11-409c-933f-7c0d623cb3d2",
   "metadata": {
    "jupyter": {
     "source_hidden": true
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# # Если используете ключ от OpenAI, запустите эту ячейку\n",
    "# from langchain_openai import ChatOpenAI\n",
    "\n",
    "# # os.environ['OPENAI_API_KEY'] = \"Введите ваш OpenAI API ключ\"\n",
    "# os.environ['OPENAI_API_KEY'] = getpass(prompt='Введите ваш OpenAI API ключ')\n",
    "\n",
    "# # инициализируем языковую модель\n",
    "# llm = ChatOpenAI(temperature=0.0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "8347a770-e986-433c-94fc-582aef674d51",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdin",
     "output_type": "stream",
     "text": [
      "Введите ваш ключ, полученный в боте курса ········\n"
     ]
    }
   ],
   "source": [
    "# Если используете ключ из курса, запустите эту ячейку\n",
    "from utils import ChatOpenAI\n",
    "\n",
    "# course_api_key= \"Введите ваш ключ, полученный в боте курса\"\n",
    "course_api_key = getpass(prompt=\"Введите ваш ключ, полученный в боте курса\")\n",
    "\n",
    "# инициализируем языковую модель\n",
    "llm = ChatOpenAI(temperature=0.0, course_api_key=course_api_key)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1e2311c0-df93-49a8-87c2-5dc2002d496f",
   "metadata": {},
   "source": [
    "\n",
    "<div class=\"alert alert-info\">\n",
    "\n",
    "А теперь, давайте пройдёмся по всем ингредиентам, необходимым для приготовления `RAG`.\n",
    "\n",
    "<center> <img src='../images/RAG.png' width=\"628\" height=\"428\">\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bc05fdf4-bd28-4af8-ba6a-ff7778d2125b",
   "metadata": {},
   "source": [
    "<!-- <center> <img src='../images/RAG.png' width=\"628\" height=\"428\" align=\"right\">\n",
    " -->"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "19b9ab53-594c-4022-a93e-d7c83c069a33",
   "metadata": {},
   "source": [
    "# <center id=\"p2\"> 📥 Document loader & Text Splitter ⚔️ </center>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "949ef8d8-5d06-43c4-98ec-e7ae5bd26a39",
   "metadata": {},
   "source": [
    "## <center id=\"p2.1\">  🚛 Document loaders\n",
    "\n",
    "<div class=\"alert alert-info\">\n",
    "    \n",
    "\n",
    "<img src='../images/docl.jpg' align=\"right\" width=\"428\" height=\"428\" >\n",
    "\n",
    "В `LangChain` реализовано большое количество обёрток для загрузки документов из различных источников и типов файлов (на рисунке представлена малая часть). <br>\n",
    "\n",
    "<div class=\"alert alert-success\"> \n",
    "    \n",
    "Использование `document loader'а` не является обязательным шагом. Если ваши данные уже в формате текстовых файлов, то можно воспользоваться стандартным протоколом загрузки файлов в Python 🐍. <br>\n",
    "Следует отслеживать результаты работы лоадера, так как могут встречаться ошибки загрузки или ненужные артефакты в документах."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 163,
   "id": "8d1654c0-f84e-4d07-b9f1-5bde2006ccd1",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Product</th>\n",
       "      <th>Review</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>Queen Size Sheet Set</td>\n",
       "      <td>I ordered a king size set. My only criticism w...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>Waterproof Phone Pouch</td>\n",
       "      <td>I loved the waterproof sac, although the openi...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>Luxury Air Mattress</td>\n",
       "      <td>This mattress had a small hole in the top of i...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>Pillows Insert</td>\n",
       "      <td>This is the best throw pillow fillers on Amazo...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>Milk Frother Handheld\\n</td>\n",
       "      <td>I loved this product. But they only seem to l...</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                   Product                                             Review\n",
       "0     Queen Size Sheet Set  I ordered a king size set. My only criticism w...\n",
       "1   Waterproof Phone Pouch  I loved the waterproof sac, although the openi...\n",
       "2      Luxury Air Mattress  This mattress had a small hole in the top of i...\n",
       "3           Pillows Insert  This is the best throw pillow fillers on Amazo...\n",
       "4  Milk Frother Handheld\\n   I loved this product. But they only seem to l..."
      ]
     },
     "execution_count": 163,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Попробуем загрузить csv файл. Сначала как pandas DataFrame\n",
    "import pandas as pd\n",
    "from langchain.document_loaders import CSVLoader, DataFrameLoader\n",
    "\n",
    "doc = pd.read_csv(\"../data/Data.csv\")\n",
    "doc.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 164,
   "id": "1c0ac261-8f19-4f72-bd64-eea3f1301fe5",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Document(page_content='I ordered a king size set. My only criticism would be that I wish seller would offer the king size set with 4 pillowcases. I separately ordered a two pack of pillowcases so I could have a total of four. When I saw the two packages, it looked like the color did not exactly match. Customer service was excellent about sending me two more pillowcases so I would have four that matched. Excellent! For the cost of these sheets, I am satisfied with the characteristics and coolness of the sheets.', metadata={'Product': 'Queen Size Sheet Set'})"
      ]
     },
     "execution_count": 164,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Определим лоадер\n",
    "loader = DataFrameLoader(doc, page_content_column=\"Review\")\n",
    "documents = loader.load()\n",
    "\n",
    "# Посмотрим как выглядит документ\n",
    "documents[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 165,
   "id": "2d5c2650-7d22-416c-b5b8-5fdb06a8de99",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Document(page_content='Product: Queen Size Sheet Set\\nReview: I ordered a king size set. My only criticism would be that I wish seller would offer the king size set with 4 pillowcases. I separately ordered a two pack of pillowcases so I could have a total of four. When I saw the two packages, it looked like the color did not exactly match. Customer service was excellent about sending me two more pillowcases so I would have four that matched. Excellent! For the cost of these sheets, I am satisfied with the characteristics and coolness of the sheets.', metadata={'source': '../data/Data.csv', 'row': 0})"
      ]
     },
     "execution_count": 165,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Теперь воспользуемся CSV лоадером\n",
    "loader = CSVLoader(file_path=\"../data/Data.csv\")\n",
    "\n",
    "documents = loader.load()\n",
    "documents[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ec7b34f1-45b2-4b9a-86eb-1c6ce1c60655",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-success\">\n",
    "\n",
    "Ещё один удобный и часто применяемый лоадер - `DirectoryLoader`. Удобен, когда у вас есть каталог файлов с одинаковыми расширениями.\n",
    "```python\n",
    "from langchain_community.document_loaders import DirectoryLoader\n",
    "\n",
    "loader = DirectoryLoader('../', glob=\"**/*.md\")\n",
    "```\n",
    "Просто указываете путь и расширения файлов (только для текстовых форматов)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "68b65924-9ea4-43fb-8156-c7831a0ac8f6",
   "metadata": {},
   "source": [
    "## <center id=\"p2.2\"> 🖖 Text splitters - разбиваем текст на фрагменты\n",
    "\n",
    "\n",
    "<div class=\"alert alert-info\">\n",
    "\n",
    "<img src='../images/text_spliting.webp' align='right' width=\"458\"  >\n",
    "\n",
    "    \n",
    "После получения текстовых данных из файлов, необходимо:\n",
    "\n",
    "<div class=\"alert alert-success\">\n",
    "    \n",
    "* Разделить их на отдельные отрывки (документы),\n",
    "* желательно чтобы эта разбивка была логичной и содержала законченную мысль. **Например**, абзац книги или полная статья закона или кодекса.\n",
    "* При этом документы не должны быть огромного размера, чтобы влезть в контекстное окно модели. "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7da5a20c-e493-496d-b254-a7863842b84a",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-info\">\n",
    "\n",
    "\n",
    "Можно сказать, что от правильной разбивки (выбора сплиттера) напрямую зависит качество работы всего `RAG`. Поэтому, для случаев когда в текстах специфическая структура (кодексы, законы, медицинские регламенты и.т.п.), часто пишут собственные сплиттеры. <br>\n",
    "\n",
    "Ниже разберём два самых популярных сплиттера из `LangChain` (остальные [тут](https://python.langchain.com/docs/modules/data_connection/document_transformers/)):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 166,
   "id": "79576dbf-a325-4f25-8186-87d8b0739bbc",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(\"PEP: 8\\nTitle: Style Guide for Python Code\\nAuthor: Guido van Rossum <guido@python.org>,\\n        Barry Warsaw <barry@python.org>,\\n        Alyssa Coghlan <ncoghlan@gmail.com>\\nStatus: Active\\nType: Process\\nCreated: 05-Jul-2001\\nPost-History: 05-Jul-2001, 01-Aug-2013\\n\\n\\nIntroduction\\n============\\n\\nThis document gives coding conventions for the Python code comprising\\nthe standard library in the main Python distribution.  Please see the\\ncompanion informational PEP describing :pep:`style guidelines for the C code\\nin the C implementation of Python <7>`.\\n\\nThis document and :pep:`257` (Docstring Conventions) were adapted from\\nGuido's original Python Style Guide essay, with some additions from\\nBarry's style guide [2]_.\\n\\nThis style guide evolves over time as additional conventions are\\nidentified and past conventions are rendered obsolete by changes in\\nthe language itself.\\n\\nMany projects have their own coding style guidelines. In the event of any\\nconflicts, such project-specific guides take precedence f\",\n",
       " 50760)"
      ]
     },
     "execution_count": 166,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Загрузим текстовый файл со стандартом PEP8\n",
    "\n",
    "with open(\"../data/PEP8.txt\") as f:\n",
    "    doc = f.read()\n",
    "doc[:1000], len(doc)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 167,
   "id": "4081b708-60c1-461c-b0de-ff73b0f8988b",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Created a chunk of size 723, which is longer than the specified 500\n",
      "Created a chunk of size 522, which is longer than the specified 500\n",
      "Created a chunk of size 502, which is longer than the specified 500\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "132"
      ]
     },
     "execution_count": 167,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Сначала рассмотрим самый простой вариант сплиттера - по конкретному символу в тексте.\n",
    "from langchain.text_splitter import (\n",
    "    CharacterTextSplitter,\n",
    "    RecursiveCharacterTextSplitter,\n",
    ")\n",
    "\n",
    "splitter = CharacterTextSplitter(\n",
    "    separator=\"\\n\\n\",  # символ-разделитель, по умолчанию переход к новому абзацу '\\n\\n'\n",
    "    chunk_size=500,  # размер документа в символах\n",
    "    chunk_overlap=100,  # насколько соседние документы могут перекрывать друг-друга\n",
    "    length_function=len,  # функция, по которой считается размер документа\n",
    "    is_separator_regex=False,  # является ли разделитель регулярным выражением\n",
    ")\n",
    "\n",
    "split_documents = splitter.create_documents([doc])\n",
    "len(split_documents)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e8f1b584-cb96-4cdb-9a8b-7cdc5ef25617",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-success\">\n",
    "\n",
    "Видим, что при разделении появилось три предупреждения, что превышен лимит в 500 символов. Потому что внутри 500 не встретилось ни одного `'\\n\\n'`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 168,
   "id": "25fe9b91-a30a-401c-aeca-2d277e6ecd50",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Document(page_content='One of Guido\\'s key insights is that code is read much more often than\\nit is written.  The guidelines provided here are intended to improve\\nthe readability of code and make it consistent across the wide\\nspectrum of Python code.  As :pep:`20` says, \"Readability counts\".\\n\\nA style guide is about consistency.  Consistency with this style guide\\nis important.  Consistency within a project is more important.\\nConsistency within one module or function is the most important.')"
      ]
     },
     "execution_count": 168,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "split_documents[3]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "14e6d59b-7516-4623-b9f2-d4ede527c29e",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-success\">\n",
    "\n",
    "Теперь попробуем более \"умную\" нарезку с помощью `RecursiveCharacterTextSplitter` этот сплиттер рекурсивно проходится по куску текста, сначала пытается разбить по абзацам (`\\n\\n`), если не находит - пытается разбить по предложениям (`.`) и так далее."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 169,
   "id": "ba0811bc-2269-4226-817e-b7ddbe965e46",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "135"
      ]
     },
     "execution_count": 169,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "splitter = RecursiveCharacterTextSplitter(\n",
    "    chunk_size=500,\n",
    "    chunk_overlap=100,\n",
    "    length_function=len,\n",
    ")\n",
    "split_documents = splitter.create_documents([doc])\n",
    "len(split_documents)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e1d5fcc3-8977-4927-9fa7-97e3ab5d1095",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-success\">\n",
    "\n",
    "Видим, что отработало без предупреждений и документов стало больше."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 170,
   "id": "279dd381-8cb9-4196-90a5-1f2f74204255",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'One of Guido\\'s key insights is that code is read much more often than\\nit is written.  The guidelines provided here are intended to improve\\nthe readability of code and make it consistent across the wide\\nspectrum of Python code.  As :pep:`20` says, \"Readability counts\".\\n\\nA style guide is about consistency.  Consistency with this style guide\\nis important.  Consistency within a project is more important.\\nConsistency within one module or function is the most important.'"
      ]
     },
     "execution_count": 170,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "split_documents[3].page_content"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1eabd04d-3b2e-46a1-aa2d-380539ae3bfc",
   "metadata": {},
   "source": [
    "# <center id=\"p3\"> 🔤 ➡️ 🔢 Embedding models  </center>\n",
    "\n",
    "<div class=\"alert alert-info\">\n",
    "\n",
    "<img src='../images/emb_mod.png' align=\"right\" width=\"328\" height=\"328\" >\n",
    "\n",
    "Итак, текст из файлов получили, на документы покрошили. Что теперь с этими кусками текста делать? <br>\n",
    "\n",
    "<div class=\"alert alert-success\">\n",
    "    \n",
    "Как вы знаете, модель с буквами работать не умеет, ей нужно на вход подавать числа. \n",
    "Для этого мы должны пропустить наши документы через модель и получить их векторные представления (эмбеддинги).\n",
    "Чтобы потом перевести запрос в вектор и найти похожие тексты по близости векторов.<br>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4e6bd246-24e9-43d2-9378-e8267d729ad3",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-info\">\n",
    "\n",
    "<img src='../images/emb_models.png' align=\"right\" width=\"228\" height=\"328\" >\n",
    "\n",
    "\n",
    "В `LangChain` реализованы коннекторы к огромному количеству моделей, которые можно использовать для векторизации (модели с `HuggingFace`, `OpenAI`, `Llama` и др.). <br>\n",
    "\n",
    "<div class=\"alert alert-success\">\n",
    "    \n",
    "Доставать эмбеддинги из модели можно двумя способами:\n",
    "* По API - тогда нужен  интернет\n",
    "* Локально развернув модель на своём железе - тогда нужны GPU\n",
    "\n",
    "Рассмотрим оба варианта, а потом разберем какие лучше использовать и когда:"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3c647f54-feff-466f-8c79-87d2c5297904",
   "metadata": {},
   "source": [
    "## <center id=\"p3.1\"> 🔱 Эмбеддинги от OpenAI (API) </center>\n",
    "\n",
    "\n",
    "<div class=\"alert alert-info\">\n",
    "\n",
    "<img src='../images/embedds_openai.png' align=\"right\" width=\"408\" height=\"328\" >\n",
    "\n",
    "Чтобы их использовать понадобится API-ключ, а соответственно будут тратиться токены. Это надо учитывать при продумывании бюджета, иногда бюджет на эмбеддинги бывает сопоставим с бюджетом на генерацию. Соответственно, не будут работать без доступа к Интернету.<br>\n",
    "\n",
    "<div class=\"alert alert-success\">\n",
    "    \n",
    "В качестве эмбеддинг-модели в OpenAI по умолчанию используется `\"text-embedding-ada-002\"` с размером эмбеддинга `1536`. Т.е. любой текст размером от 10 до 10000 символов будет представлен 1536 числами."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b17d8444-bcd5-4169-9347-8507277ba7b7",
   "metadata": {
    "jupyter": {
     "source_hidden": true
    }
   },
   "outputs": [],
   "source": [
    "# # Если используете ключ от OpenAI запустите эту ячейку\n",
    "# from langchain_openai import OpenAIEmbeddings\n",
    "\n",
    "# embeddings_api_model = OpenAIEmbeddings()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "1df46bb4-5243-4bf1-b73a-9d4d27568a44",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Если используете ключ курса, запустите эту ячейку\n",
    "from utils import OpenAIEmbeddings\n",
    "\n",
    "embeddings_api_model = OpenAIEmbeddings(course_api_key=course_api_key)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "ad3c231b-d0ff-4586-9a5b-e60b9b4ee6fd",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3 1536 <class 'list'> <class 'list'>\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "[-0.01921703328735029,\n",
       " -0.000824394825414681,\n",
       " -0.022468770329918838,\n",
       " -0.012261249347140655,\n",
       " -0.03034139852060943]"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "embeddings = embeddings_api_model.embed_documents(\n",
    "    [\n",
    "        \"Привет!\",\n",
    "        \"Hello World!\",\n",
    "        \"How are you?\",\n",
    "    ]\n",
    ")\n",
    "# Посмотрим размерность эмбеддингов\n",
    "print(len(embeddings), len(embeddings[0]), type(embeddings), type(embeddings[0]))\n",
    "embeddings[0][:5]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d6233ee7-aa91-4421-8a34-7fd3ae4cd9a5",
   "metadata": {},
   "source": [
    "## <center id=\"p3.2\"> 🔱 Эмбеддинги от HuggingFace 🤗 </center>\n",
    "\n",
    "<div class=\"alert alert-info\">\n",
    "\n",
    "<img src='https://workable-application-form.s3.amazonaws.com/advanced/production/61557f91d9510741dc62e7f8/c3635b59-a3d2-444a-b636-a9d0061dcdde' align=\"right\" width=\"308\" height=\"328\" >\n",
    "\n",
    "\n",
    "На `HuggingFace` сейчас представлено более 40000 моделей. Выбрать подходящую по фильтрам можно по [ссылке](https://huggingface.co/models). <br>\n",
    "Здесь API-ключ не нужен, соответственно не нужно тратиться на токены. Но нужно тратиться на аренду железа для разворачивания модели. <br>\n",
    "\n",
    "<div class=\"alert alert-success\"> \n",
    "    \n",
    "Для примера, возьмём модель `\"cointegrated/LaBSE-en-ru\"`, которая хорошо показывает себя на русскоязычных и англоязычных текстах и влезает на видеокарту с 1ГБ видеопамяти, также может работать без видеокарты, но медленнее. Размер эмбеддинга у неё `768`, что в 2 раза меньше, чем у OpenAI, соответственно нужно меньше места для хранения векторной базы."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "85798704-8bb5-4abe-a067-a0ae7b6c24fb",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.embeddings import HuggingFaceEmbeddings\n",
    "\n",
    "# Если у вас нет видеокарты, укажите 'device': 'cpu'\n",
    "hf_embeddings_model = HuggingFaceEmbeddings(\n",
    "    model_name=\"cointegrated/LaBSE-en-ru\", model_kwargs={\"device\": \"cuda\"}\n",
    ")\n",
    "\n",
    "embeddings = hf_embeddings_model.embed_documents(\n",
    "    [\n",
    "        \"Привет!\",\n",
    "        \"Hello World!\",\n",
    "        \"How are you?\",\n",
    "    ]\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 175,
   "id": "8e714f2d-c994-42a5-9cdc-9c2f5b57fc26",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3 768\n"
     ]
    }
   ],
   "source": [
    "# Посмотрим размерность эмбеддингов\n",
    "print(len(embeddings), len(embeddings[0]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4407f757-0a87-481c-b137-db97e571f6e8",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "<div class=\"alert alert-info\">\n",
    "    \n",
    "Что же нужно учитывать при выборе эмбеддинг-модели?\n",
    "\n",
    "* 💰 Бюджет - на что выгоднее тратиться, на токены или на аренду железа для локальной модели\n",
    "* 🧶 Размерность эмбеддингов - влияет на то, сколько места будет занимать база данных для их хранения (от 256 до 4096 и более)\n",
    "* 🪢 Длина и сложность документов - соответственно, чем длинее документы и чем больше в них различных специфических терминов, тем больше нужна размерность эмбеддингов, чтобы улавливать различия. Также от размерности зависит насколько тонкие различия смогут улавливаться.\n",
    "* 🥑🍓 Тематика документов (задача) - модели по-разному справляются с текстами определенных тематик, например, модели обученные на медицинсих или юридических текстах, или на постах в соцсетях, будут выдавать различные результаты в семантическом поиске.\n",
    "* 🗂 Мультиязычность документов - сколько языков может токенизировать модель. Например, полная `LaBSE` на 119 языков занимает в 4 раза больше места.\n",
    "\n",
    "<div class=\"alert alert-success\">\n",
    "    \n",
    "Эмбеддинги от `OpenAI` не являются самыми лучшими и подходящими под любые запросы, к тому же платные. <br>\n",
    "**Рабочий кейс:** пойти на [лидерборд эмбеддингов](https://huggingface.co/spaces/mteb/leaderboard) и выбрать самые подходящие под задачу."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e39137d4-3fba-4a49-b76a-338f225d66f6",
   "metadata": {},
   "source": [
    "# <center id=\"p4\"> 🗂 Vector Store - место где живут эмбеддинги</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dbf8528c-0975-4a2d-be08-1030804e95b1",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-info\">\n",
    "\n",
    "<img src='../images/vdb.png' align=\"right\" width=\"558\" height=\"428\" >\n",
    "\n",
    "После того как мы вытащили из модели наши эмбеддинги, нам нужно где-то их эффективно хранить и быстро проводить по ним поиск. Для этого можно использовать любую базу данных или специальное векторное хранилище. В `LangChain` реализовано множество [коннекторов](https://github.com/langchain-ai/langchain/tree/master/libs/langchain/langchain/vectorstores) ко всевозможным базам данных, в том числе к облачным хранилищам - т.е. базу с документами можно не хранить локально, а получать документы по API. <br>\n",
    "<div class=\"alert alert-success\">\n",
    "\n",
    "В основном, используются два опенсорс решения для хранения эмбеддингов, которые, в целом, похожи:\n",
    "* `FAISS` - от FAIR - создателей концепции RAG\n",
    "* `ChromaDB` - от CHROMA-core\n",
    "\n",
    "Стоит так же обратить внимание на платное решение от `Pinecone` - пока единственное, способное работать в `serverless` режиме.\n",
    "\n",
    "Рассмотрим пример с FAISS:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 193,
   "id": "af83b08e-23b6-4892-bd2b-6970baaaf6b2",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.vectorstores import FAISS\n",
    "\n",
    "embeddings = OpenAIEmbeddings(course_api_key=course_api_key)\n",
    "db = FAISS.from_documents(\n",
    "    split_documents[:70], embeddings\n",
    ")  # создаём базу, возьмём первые 70 документов, чтобы тратить меньше токенов\n",
    "\n",
    "db.save_local(\"faiss_db\")  # можно сохранить базу локально, указав путь"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 194,
   "id": "e3993821-d656-4ca3-8f3d-f18d24071f87",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[Document(page_content='Blank Lines\\n-----------\\n\\nSurround top-level function and class definitions with two blank\\nlines.\\n\\nMethod definitions inside a class are surrounded by a single blank\\nline.\\n\\nExtra blank lines may be used (sparingly) to separate groups of\\nrelated functions.  Blank lines may be omitted between a bunch of\\nrelated one-liners (e.g. a set of dummy implementations).\\n\\nUse blank lines in functions, sparingly, to indicate logical sections.'),\n",
       " Document(page_content='When trailing commas are redundant, they are often helpful when a\\nversion control system is used, when a list of values, arguments or\\nimported items is expected to be extended over time.  The pattern is\\nto put each value (etc.) on a line by itself, always adding a trailing\\ncomma, and add the close parenthesis/bracket/brace on the next line.\\nHowever it does not make sense to have a trailing comma on the same\\nline as the closing delimiter (except in the above case of singleton\\ntuples):'),\n",
       " Document(page_content=\"Other Recommendations\\n---------------------\\n\\n- Avoid trailing whitespace anywhere.  Because it's usually invisible,\\n  it can be confusing: e.g. a backslash followed by a space and a\\n  newline does not count as a line continuation marker.  Some editors\\n  don't preserve it and many projects (like CPython itself) have\\n  pre-commit hooks that reject it.\"),\n",
       " Document(page_content='Make sure to indent the continued line appropriately.\\n\\nShould a Line Break Before or After a Binary Operator?\\n------------------------------------------------------')]"
      ]
     },
     "execution_count": 194,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# База готова, теперь можно делать к ней запросы\n",
    "db.similarity_search(\"How many empty lines shuold be behind methods of a class?\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 195,
   "id": "ba9b08fa-cb1e-4cb5-973e-b3fd5836ebfe",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[Document(page_content='Blank Lines\\n-----------\\n\\nSurround top-level function and class definitions with two blank\\nlines.\\n\\nMethod definitions inside a class are surrounded by a single blank\\nline.\\n\\nExtra blank lines may be used (sparingly) to separate groups of\\nrelated functions.  Blank lines may be omitted between a bunch of\\nrelated one-liners (e.g. a set of dummy implementations).\\n\\nUse blank lines in functions, sparingly, to indicate logical sections.'),\n",
       " Document(page_content='Limiting the required editor window width makes it possible to have\\nseveral files open side by side, and works well when using code\\nreview tools that present the two versions in adjacent columns.'),\n",
       " Document(page_content='The default wrapping in most tools disrupts the visual structure of the\\ncode, making it more difficult to understand. The limits are chosen to\\navoid wrapping in editors with the window width set to 80, even\\nif the tool places a marker glyph in the final column when wrapping\\nlines. Some web based tools may not offer dynamic line wrapping at all.'),\n",
       " Document(page_content='Tabs or Spaces?\\n---------------\\n\\nSpaces are the preferred indentation method.\\n\\nTabs should be used solely to remain consistent with code that is\\nalready indented with tabs.\\n\\nPython disallows mixing tabs and spaces for indentation.\\n\\n\\nMaximum Line Length\\n-------------------\\n\\nLimit all lines to a maximum of 79 characters.\\n\\nFor flowing long blocks of text with fewer structural restrictions\\n(docstrings or comments), the line length should be limited to 72\\ncharacters.')]"
      ]
     },
     "execution_count": 195,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Попробуем запрос на русском языке\n",
    "db.similarity_search(\"Сколько пустых строк нужно оставлять между методами класса?\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 179,
   "id": "463c693b-f733-4df6-ab11-8f4d725a13c1",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(Document(page_content='Blank Lines\\n-----------\\n\\nSurround top-level function and class definitions with two blank\\nlines.\\n\\nMethod definitions inside a class are surrounded by a single blank\\nline.\\n\\nExtra blank lines may be used (sparingly) to separate groups of\\nrelated functions.  Blank lines may be omitted between a bunch of\\nrelated one-liners (e.g. a set of dummy implementations).\\n\\nUse blank lines in functions, sparingly, to indicate logical sections.'),\n",
       "  0.47112548),\n",
       " (Document(page_content='Limiting the required editor window width makes it possible to have\\nseveral files open side by side, and works well when using code\\nreview tools that present the two versions in adjacent columns.'),\n",
       "  0.5603633)]"
      ]
     },
     "execution_count": 179,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Также можно вывести скор каждого документа\n",
    "db.similarity_search_with_score(\n",
    "    \"Сколько пустых строк нужно оставлять между методами класса?\"\n",
    ")[:2]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 180,
   "id": "2fce9cf1-0f4f-4029-9798-5f2a3c9a8e90",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(Document(page_content='Blank Lines\\n-----------\\n\\nSurround top-level function and class definitions with two blank\\nlines.\\n\\nMethod definitions inside a class are surrounded by a single blank\\nline.\\n\\nExtra blank lines may be used (sparingly) to separate groups of\\nrelated functions.  Blank lines may be omitted between a bunch of\\nrelated one-liners (e.g. a set of dummy implementations).\\n\\nUse blank lines in functions, sparingly, to indicate logical sections.'),\n",
       "  0.3282591),\n",
       " (Document(page_content='When trailing commas are redundant, they are often helpful when a\\nversion control system is used, when a list of values, arguments or\\nimported items is expected to be extended over time.  The pattern is\\nto put each value (etc.) on a line by itself, always adding a trailing\\ncomma, and add the close parenthesis/bracket/brace on the next line.\\nHowever it does not make sense to have a trailing comma on the same\\nline as the closing delimiter (except in the above case of singleton\\ntuples):'),\n",
       "  0.46328443)]"
      ]
     },
     "execution_count": 180,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "db.similarity_search_with_score(\n",
    "    \"How many empty lines shuold be behind methods of a class?\"\n",
    ")[:2]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fbdbfd65-cef0-4c2f-b7ed-cfd0eece3717",
   "metadata": {},
   "source": [
    "# <center id=\"p5\"> 🎣 Retriever - выуди нужный документ. </center>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "69d73113-924f-4566-819e-614838a76e9b",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-info\">\n",
    "\n",
    "<img src='../images/retr.png' align=\"right\" width=\"228\" height=\"328\" >\n",
    "\n",
    "Использование ретривера, тоже не является обязательным шагом для построения `RAG`. Потому что векторная база сама может осуществлять поиск по схожести и выдавать релевантные документы, которые мы затем можем подставить в промпт в качестве контекста. <br>\n",
    "\n",
    "    \n",
    "**Ретривер** - это такая надстройка над базой данных, которая может выдавать похожие документы, но не обязана их хранить. <br>\n",
    "\n",
    "Давайте попробуем разобраться, нужно ли их использовать и зачем:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 196,
   "id": "fa002a46-e5eb-42bf-8d17-fb707e415cd5",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Document(page_content='Blank Lines\\n-----------\\n\\nSurround top-level function and class definitions with two blank\\nlines.\\n\\nMethod definitions inside a class are surrounded by a single blank\\nline.\\n\\nExtra blank lines may be used (sparingly) to separate groups of\\nrelated functions.  Blank lines may be omitted between a bunch of\\nrelated one-liners (e.g. a set of dummy implementations).\\n\\nUse blank lines in functions, sparingly, to indicate logical sections.')"
      ]
     },
     "execution_count": 196,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Самый частый кейс - использование векторного хранилища и его методов для получения документов\n",
    "retriever = db.as_retriever(\n",
    "    search_type=\"similarity\",  # тип поиска похожих документов\n",
    "    k=4,  # количество возвращаемых документов (Default: 4)\n",
    "    score_threshold=None,  # минимальный порог для поиска \"similarity_score_threshold\"\n",
    ")\n",
    "retriever.get_relevant_documents(\n",
    "    \"Сколько пустых строк нужно оставлять между методами класса?\"\n",
    ")[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 197,
   "id": "abf2ea14-3593-4dcb-a108-2db06c9a1da0",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[Document(page_content='Blank Lines\\n-----------\\n\\nSurround top-level function and class definitions with two blank\\nlines.\\n\\nMethod definitions inside a class are surrounded by a single blank\\nline.\\n\\nExtra blank lines may be used (sparingly) to separate groups of\\nrelated functions.  Blank lines may be omitted between a bunch of\\nrelated one-liners (e.g. a set of dummy implementations).\\n\\nUse blank lines in functions, sparingly, to indicate logical sections.'),\n",
       " Document(page_content='Use blank lines in functions, sparingly, to indicate logical sections.\\n\\nPython accepts the control-L (i.e. ^L) form feed character as\\nwhitespace; many tools treat these characters as page separators, so\\nyou may use them to separate pages of related sections of your file.\\nNote, some editors and web-based code viewers may not recognize\\ncontrol-L as a form feed and will show another glyph in its place.\\n\\nSource File Encoding\\n--------------------'),\n",
       " Document(page_content=\"Other Recommendations\\n---------------------\\n\\n- Avoid trailing whitespace anywhere.  Because it's usually invisible,\\n  it can be confusing: e.g. a backslash followed by a space and a\\n  newline does not count as a line continuation marker.  Some editors\\n  don't preserve it and many projects (like CPython itself) have\\n  pre-commit hooks that reject it.\")]"
      ]
     },
     "execution_count": 197,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain.retrievers import BM25Retriever, EnsembleRetriever\n",
    "\n",
    "# Попробуем BM25 - алгоритм поиска по ключевым словам. Может искать лучше, но не поймёт запрос на другом языке.\n",
    "# Также не сможет искать, если запрос на обывательском языке, а документы на профессиональном\n",
    "bm25 = BM25Retriever.from_documents(split_documents[:70])  # Эмбеддинги ему не нужны\n",
    "bm25.k = 5  # Так можно задать количество возвращаемых документов\n",
    "\n",
    "bm25.get_relevant_documents(\n",
    "    \"How many empty lines shuold be behind methods of a class?\"\n",
    ")[:3]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 198,
   "id": "50ac34e0-270d-405b-a774-d588207d5b64",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[Document(page_content='The following naming styles are commonly distinguished:\\n\\n- ``b`` (single lowercase letter)\\n- ``B`` (single uppercase letter)\\n- ``lowercase``\\n- ``lower_case_with_underscores``\\n- ``UPPERCASE``\\n- ``UPPER_CASE_WITH_UNDERSCORES``\\n- ``CapitalizedWords`` (or CapWords, or CamelCase -- so named because\\n  of the bumpy look of its letters [4]_).  This is also sometimes known\\n  as StudlyCaps.'),\n",
       " Document(page_content='To solve this readability problem, mathematicians and their publishers\\nfollow the opposite convention.  Donald Knuth explains the traditional\\nrule in his *Computers and Typesetting* series: \"Although formulas\\nwithin a paragraph always break after binary operations and relations,\\ndisplayed formulas always break before binary operations\" [3]_.\\n\\nFollowing the tradition from mathematics usually results in more\\nreadable code:\\n\\n.. code-block::\\n   :class: good'),\n",
       " Document(page_content='Some teams strongly prefer a longer line length.  For code maintained\\nexclusively or primarily by a team that can reach agreement on this\\nissue, it is okay to increase the line length limit up to 99 characters,\\nprovided that comments and docstrings are still wrapped at 72\\ncharacters.\\n\\nThe Python standard library is conservative and requires limiting\\nlines to 79 characters (and docstrings/comments to 72).')]"
      ]
     },
     "execution_count": 198,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Попробуем спросить на русском. Находит какие-то документы, но они не релевантны.\n",
    "bm25.get_relevant_documents(\n",
    "    \"Сколько пустых строк нужно оставлять между методами класса?\"\n",
    ")[:3]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 199,
   "id": "8e7b6025-d347-46b2-aed5-e22649aa823a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[Document(page_content='Blank Lines\\n-----------\\n\\nSurround top-level function and class definitions with two blank\\nlines.\\n\\nMethod definitions inside a class are surrounded by a single blank\\nline.\\n\\nExtra blank lines may be used (sparingly) to separate groups of\\nrelated functions.  Blank lines may be omitted between a bunch of\\nrelated one-liners (e.g. a set of dummy implementations).\\n\\nUse blank lines in functions, sparingly, to indicate logical sections.'),\n",
       " Document(page_content=\"Other Recommendations\\n---------------------\\n\\n- Avoid trailing whitespace anywhere.  Because it's usually invisible,\\n  it can be confusing: e.g. a backslash followed by a space and a\\n  newline does not count as a line continuation marker.  Some editors\\n  don't preserve it and many projects (like CPython itself) have\\n  pre-commit hooks that reject it.\"),\n",
       " Document(page_content='When trailing commas are redundant, they are often helpful when a\\nversion control system is used, when a list of values, arguments or\\nimported items is expected to be extended over time.  The pattern is\\nto put each value (etc.) on a line by itself, always adding a trailing\\ncomma, and add the close parenthesis/bracket/brace on the next line.\\nHowever it does not make sense to have a trailing comma on the same\\nline as the closing delimiter (except in the above case of singleton\\ntuples):')]"
      ]
     },
     "execution_count": 199,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Если один ретривер плохо справляется, то можно использовать целый ансамбль и даже каждому вес назначить\n",
    "ensemble_retriever = EnsembleRetriever(\n",
    "    retrievers=[bm25, retriever],  # список ретриверов\n",
    "    weights=[\n",
    "        0.4,\n",
    "        0.6,\n",
    "    ],  # веса, на которые домножается скор документа от каждого ретривера\n",
    ")\n",
    "\n",
    "ensemble_retriever.get_relevant_documents(\n",
    "    \"How many empty lines shuold be behind methods of a class?\"\n",
    ")[:3]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "105116ac-719e-4387-89e0-9f2209ea323c",
   "metadata": {},
   "source": [
    "### <center> Ну, что? Все ингредиенты изучены, подобраны, помыты, порезаны\n",
    "### <center> Пора заваривать RAG 🥘!"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "12ff635b-2af8-4a52-bc00-b91665c7c904",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-info\">\n",
    "\n",
    "Давайте попросим ChatGPT сгенерировать данные для нашего RAG."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "718b3f20-040e-42de-ac1e-f00074fb31f1",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "prompt = \"\"\"\n",
    "Сгенерируй набор из 20 примеров документов. \n",
    "В документах содержатся часто задаваемые вопросы пользователей интернет-магазина. \n",
    "Каждый документ должен иметь следующую структуру:\n",
    "1. id: int (уникальный идентификатор документа)\n",
    "2. question: str(вопрос пользователя)\n",
    "3. answer: str(ответ службы поддержки)\n",
    "\n",
    "Выведи результат в виде python кода.\"\"\"\n",
    "print(llm.invoke(prompt).content)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 186,
   "id": "125fc2c8-feea-4de5-92e0-a89148e4c810",
   "metadata": {
    "jupyter": {
     "source_hidden": true
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>id</th>\n",
       "      <th>question</th>\n",
       "      <th>answer</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>1</td>\n",
       "      <td>Как оформить заказ?</td>\n",
       "      <td>Для оформления заказа необходимо добавить выбр...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>2</td>\n",
       "      <td>Как узнать статус моего заказа?</td>\n",
       "      <td>Вы можете узнать статус своего заказа в раздел...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>3</td>\n",
       "      <td>Как отменить заказ?</td>\n",
       "      <td>Для отмены заказа необходимо связаться с нашей...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>4</td>\n",
       "      <td>Как вернуть товар?</td>\n",
       "      <td>Если вам не подошел товар, вы можете вернуть е...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>5</td>\n",
       "      <td>Как узнать информацию о доставке?</td>\n",
       "      <td>Информацию о доставке вы можете узнать в разде...</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   id                           question  \\\n",
       "0   1                Как оформить заказ?   \n",
       "1   2    Как узнать статус моего заказа?   \n",
       "2   3                Как отменить заказ?   \n",
       "3   4                 Как вернуть товар?   \n",
       "4   5  Как узнать информацию о доставке?   \n",
       "\n",
       "                                              answer  \n",
       "0  Для оформления заказа необходимо добавить выбр...  \n",
       "1  Вы можете узнать статус своего заказа в раздел...  \n",
       "2  Для отмены заказа необходимо связаться с нашей...  \n",
       "3  Если вам не подошел товар, вы можете вернуть е...  \n",
       "4  Информацию о доставке вы можете узнать в разде...  "
      ]
     },
     "execution_count": 186,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "documents = [\n",
    "    {\n",
    "        \"id\": 1,\n",
    "        \"question\": \"Как оформить заказ?\",\n",
    "        \"answer\": \"Для оформления заказа необходимо добавить выбранные товары в корзину и перейти к оформлению заказа. Затем заполните необходимые поля, выберите удобный способ доставки и оплаты, и подтвердите заказ.\",\n",
    "    },\n",
    "    {\n",
    "        \"id\": 2,\n",
    "        \"question\": \"Как узнать статус моего заказа?\",\n",
    "        \"answer\": \"Вы можете узнать статус своего заказа в разделе 'Мои заказы' на нашем сайте. Там будет указан текущий статус вашего заказа, а также информация о доставке.\",\n",
    "    },\n",
    "    {\n",
    "        \"id\": 3,\n",
    "        \"question\": \"Как отменить заказ?\",\n",
    "        \"answer\": \"Для отмены заказа необходимо связаться с нашей службой поддержки по указанному на сайте телефону или электронной почте. Укажите номер заказа и причину отмены, и мы поможем вам с отменой заказа.\",\n",
    "    },\n",
    "    {\n",
    "        \"id\": 4,\n",
    "        \"question\": \"Как вернуть товар?\",\n",
    "        \"answer\": \"Если вам не подошел товар, вы можете вернуть его в течение 14 дней с момента получения. Для этого свяжитесь с нашей службой поддержки, и мы организуем возврат товара и возврат денежных средств.\",\n",
    "    },\n",
    "    {\n",
    "        \"id\": 5,\n",
    "        \"question\": \"Как узнать информацию о доставке?\",\n",
    "        \"answer\": \"Информацию о доставке вы можете узнать в разделе 'Мои заказы' на нашем сайте. Там будет указана дата доставки, способ доставки и номер отслеживания, если таковой имеется.\",\n",
    "    },\n",
    "    {\n",
    "        \"id\": 6,\n",
    "        \"question\": \"Как связаться с службой поддержки?\",\n",
    "        \"answer\": \"Вы можете связаться с нашей службой поддержки по указанному на сайте телефону или электронной почте. Мы готовы ответить на все ваши вопросы и помочь вам решить любые проблемы.\",\n",
    "    },\n",
    "    {\n",
    "        \"id\": 7,\n",
    "        \"question\": \"Какие способы оплаты доступны?\",\n",
    "        \"answer\": \"Мы принимаем оплату банковскими картами, электронными платежными системами, а также наличными при получении заказа. Выберите удобный для вас способ оплаты при оформлении заказа.\",\n",
    "    },\n",
    "    {\n",
    "        \"id\": 8,\n",
    "        \"question\": \"Какие гарантии на товар?\",\n",
    "        \"answer\": \"На все товары, представленные на нашем сайте, распространяется гарантия производителя. Если у вас возникнут проблемы с товаром, свяжитесь с нашей службой поддержки, и мы поможем вам решить эту проблему.\",\n",
    "    },\n",
    "    {\n",
    "        \"id\": 9,\n",
    "        \"question\": \"Какие сроки доставки?\",\n",
    "        \"answer\": \"Сроки доставки зависят от выбранного вами способа доставки и вашего местоположения. Обычно доставка занимает от 2 до 7 рабочих дней. Более точную информацию о сроках доставки вы можете узнать в разделе 'Мои заказы' на нашем сайте.\",\n",
    "    },\n",
    "    {\n",
    "        \"id\": 10,\n",
    "        \"question\": \"Какие размеры товара?\",\n",
    "        \"answer\": \"Размеры товара указаны на странице товара на нашем сайте. Если вам нужна более подробная информация о размерах, свяжитесь с нашей службой поддержки, и мы предоставим вам необходимые данные.\",\n",
    "    },\n",
    "    {\n",
    "        \"id\": 11,\n",
    "        \"question\": \"Какие акции и скидки у вас есть?\",\n",
    "        \"answer\": \"Мы регулярно проводим акции и предлагаем скидки на различные товары. Чтобы быть в курсе всех акций и скидок, подпишитесь на нашу рассылку или следите за новостями на нашем сайте.\",\n",
    "    },\n",
    "    {\n",
    "        \"id\": 12,\n",
    "        \"question\": \"Какие документы нужны для получения гарантии?\",\n",
    "        \"answer\": \"Для получения гарантии на товар вам понадобится чек или кассовый чек, а также гарантийный талон, если таковой имеется. Если у вас возникнут вопросы по гарантии, свяжитесь с нашей службой поддержки.\",\n",
    "    },\n",
    "    {\n",
    "        \"id\": 13,\n",
    "        \"question\": \"Какие условия возврата товара?\",\n",
    "        \"answer\": \"Вы можете вернуть товар в течение 14 дней с момента получения. Товар должен быть в оригинальной упаковке и не должен иметь следов использования. Для оформления возврата свяжитесь с нашей службой поддержки.\",\n",
    "    },\n",
    "    {\n",
    "        \"id\": 14,\n",
    "        \"question\": \"Какие способы доставки доступны?\",\n",
    "        \"answer\": \"Мы предлагаем различные способы доставки, включая курьерскую доставку, почтовую доставку и самовывоз из пунктов выдачи. Выберите удобный для вас способ доставки при оформлении заказа.\",\n",
    "    },\n",
    "    {\n",
    "        \"id\": 15,\n",
    "        \"question\": \"Какие товары у вас есть в наличии?\",\n",
    "        \"answer\": \"Наличие товаров можно узнать на нашем сайте. Если товар отсутствует в наличии, вы можете оставить заявку на его поступление, и мы сообщим вам, когда товар будет доступен.\",\n",
    "    },\n",
    "    {\n",
    "        \"id\": 16,\n",
    "        \"question\": \"Какие дополнительные услуги вы предоставляете?\",\n",
    "        \"answer\": \"Мы предоставляем различные дополнительные услуги, такие как подарочная упаковка, гравировка, установка и настройка товаров. Подробную информацию о дополнительных услугах вы можете найти на нашем сайте.\",\n",
    "    },\n",
    "    {\n",
    "        \"id\": 17,\n",
    "        \"question\": \"Какие документы нужны для получения товара?\",\n",
    "        \"answer\": \"Для получения товара вам понадобится паспорт или другой документ, удостоверяющий личность. Если вы получаете товар от имени другого лица, вам также понадобится доверенность.\",\n",
    "    },\n",
    "    {\n",
    "        \"id\": 18,\n",
    "        \"question\": \"Какие способы связи с вами?\",\n",
    "        \"answer\": \"Вы можете связаться с нами по указанному на сайте телефону или электронной почте. Также вы можете воспользоваться формой обратной связи на нашем сайте. Мы готовы ответить на все ваши вопросы.\",\n",
    "    },\n",
    "    {\n",
    "        \"id\": 19,\n",
    "        \"question\": \"Какие скидки предоставляются постоянным клиентам?\",\n",
    "        \"answer\": \"Постоянным клиентам мы предоставляем специальные скидки и бонусы. Чтобы стать постоянным клиентом, оформите несколько заказов на нашем сайте. Подробную информацию о скидках вы можете узнать на нашем сайте или у нашей службы поддержки.\",\n",
    "    },\n",
    "    {\n",
    "        \"id\": 20,\n",
    "        \"question\": \"Какие способы оплаты доступны для юридических лиц?\",\n",
    "        \"answer\": \"Для юридических лиц доступны способы оплаты по безналичному расчету. При оформлении заказа укажите реквизиты вашей организации, и мы выставим вам счет на оплату.\",\n",
    "    },\n",
    "]\n",
    "\n",
    "df = pd.DataFrame(documents)\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "07d19e25-4335-4221-a4d6-33277d55faa1",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-success\">\n",
    "\n",
    "Теперь соберём компоненты `RAG` по порядку:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 200,
   "id": "fb3c01f5-40f6-4ffd-a369-b6f9d9fdd274",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Начинаем с лоадера\n",
    "loader = DataFrameLoader(df, page_content_column='answer')\n",
    "documents = loader.load()\n",
    "\n",
    "# Определяем сплиттер\n",
    "splitter = RecursiveCharacterTextSplitter(\n",
    "    chunk_size=500, \n",
    "    chunk_overlap=50\n",
    ")\n",
    "split_texts = splitter.split_documents(documents)\n",
    "\n",
    "# Задаём embedding model\n",
    "embeddings = OpenAIEmbeddings(course_api_key=course_api_key)\n",
    "\n",
    "# Создаём векторное хранилище\n",
    "db = FAISS.from_documents(split_texts, embeddings)\n",
    "\n",
    "# Задаём ретривер\n",
    "retriever = db.as_retriever()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 201,
   "id": "20441692-ce2c-4c88-bd81-9c49d107d339",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(Document(page_content='Для получения гарантии на товар вам понадобится чек или кассовый чек, а также гарантийный талон, если таковой имеется. Если у вас возникнут вопросы по гарантии, свяжитесь с нашей службой поддержки.', metadata={'id': 12, 'question': 'Какие документы нужны для получения гарантии?'}),\n",
       "  0.31461027),\n",
       " (Document(page_content='На все товары, представленные на нашем сайте, распространяется гарантия производителя. Если у вас возникнут проблемы с товаром, свяжитесь с нашей службой поддержки, и мы поможем вам решить эту проблему.', metadata={'id': 8, 'question': 'Какие гарантии на товар?'}),\n",
       "  0.35366654),\n",
       " (Document(page_content='Мы предоставляем различные дополнительные услуги, такие как подарочная упаковка, гравировка, установка и настройка товаров. Подробную информацию о дополнительных услугах вы можете найти на нашем сайте.', metadata={'id': 16, 'question': 'Какие дополнительные услуги вы предоставляете?'}),\n",
       "  0.39485013),\n",
       " (Document(page_content='Мы регулярно проводим акции и предлагаем скидки на различные товары. Чтобы быть в курсе всех акций и скидок, подпишитесь на нашу рассылку или следите за новостями на нашем сайте.', metadata={'id': 11, 'question': 'Какие акции и скидки у вас есть?'}),\n",
       "  0.39857277)]"
      ]
     },
     "execution_count": 201,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Test vectorstore\n",
    "db.similarity_search_with_score(\"Что по гарантии?\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2ed4132a-e42d-4cef-a483-8cf563e02771",
   "metadata": {},
   "source": [
    "# <center id=\"p6\">🚰 RAG Pipeline - подключаем RAG к LLM 🔧 </center>\n",
    "\n",
    "<div class=\"alert alert-info\">\n",
    "\n",
    "<img src='../images/rag.webp' align='right' width=\"508\" height=\"428\" >\n",
    "\n",
    "Теперь попробуем на ответах, которые нам приносит **RAG** получить качественный ответ от LLM. \n",
    "\n",
    "<div class=\"alert alert-success\">\n",
    "\n",
    "Вот так и автоматизируют поддержку в сервисах!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 204,
   "id": "63aa4fc8-eb04-4fbf-81a9-3a57a1c08139",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Способы доставки включают курьерскую доставку, почтовую доставку и самовывоз из пунктов выдачи.'"
      ]
     },
     "execution_count": 204,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain.schema import StrOutputParser\n",
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "from langchain_core.runnables import RunnablePassthrough\n",
    "\n",
    "# Создаём простой шаблон\n",
    "template = \"\"\"\n",
    "Answer the question based only on the following context:\n",
    "\n",
    "{context}\n",
    "\n",
    "Question: {question}\n",
    "\"\"\"\n",
    "# Создаём промпт из шаблона\n",
    "prompt = ChatPromptTemplate.from_template(template)\n",
    "\n",
    "\n",
    "# Объявляем функцию, которая будет собирать строку из полученных документов\n",
    "def format_docs(docs):\n",
    "    return \"\\n\\n\".join([d.page_content for d in docs])\n",
    "\n",
    "\n",
    "# Создаём цепочку\n",
    "chain = (\n",
    "    {\"context\": retriever | format_docs, \"question\": RunnablePassthrough()}\n",
    "    | prompt\n",
    "    | llm\n",
    "    | StrOutputParser()\n",
    ")\n",
    "\n",
    "chain.invoke(\"Какие способы доставки?\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9ac29f42-d912-481c-a8f8-246321f0fb04",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-success\">\n",
    "\n",
    "Посмотрим как выглядит итоговый промпт, который отправляется в LLM после RAG."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 190,
   "id": "a9192674-9f51-4eb2-b40e-d182ab8d7efb",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Answer the question based only on the following context:\n",
      "\n",
      "Мы предлагаем различные способы доставки, включая курьерскую доставку, почтовую доставку и самовывоз из пунктов выдачи. Выберите удобный для вас способ доставки при оформлении заказа.\n",
      "\n",
      "Информацию о доставке вы можете узнать в разделе 'Мои заказы' на нашем сайте. Там будет указана дата доставки, способ доставки и номер отслеживания, если таковой имеется.\n",
      "\n",
      "Сроки доставки зависят от выбранного вами способа доставки и вашего местоположения. Обычно доставка занимает от 2 до 7 рабочих дней. Более точную информацию о сроках доставки вы можете узнать в разделе 'Мои заказы' на нашем сайте.\n",
      "\n",
      "Для оформления заказа необходимо добавить выбранные товары в корзину и перейти к оформлению заказа. Затем заполните необходимые поля, выберите удобный способ доставки и оплаты, и подтвердите заказ.\n",
      "\n",
      "Question: Какие способы доставки?\n",
      "\n"
     ]
    }
   ],
   "source": [
    "question = \"Какие способы доставки?\"\n",
    "context = db.similarity_search(question)\n",
    "print(\n",
    "    f\"\"\"\n",
    "Answer the question based only on the following context:\n",
    "\n",
    "{format_docs(context)}\n",
    "\n",
    "Question: {question}\n",
    "\"\"\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3b32fc34-7ecf-4ffe-81b8-dcb0816d43f5",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-success\">\n",
    "\n",
    "А вот так мог бы выглядеть более серьёзный шаблон промпта для RAG, в сервисе бот-консультант (взято с просторов Интернета). <br>\n",
    "\n",
    "Здесь даны более чёткие инструкции, плюс применены все последние методы манипуляции, которые улучшают качество выдачи (Правда по последним исследованиям рекомендуют давать чаевые 10-20$ 😇)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "42affe1b-88fc-4636-9d10-23b654725f6d",
   "metadata": {},
   "outputs": [],
   "source": [
    "\"\"\"\n",
    "Игнорируй все предыдущие инструкции. Ты консультант интернет-магазина <название компании>. \n",
    "Компания производит и продает <описание деятельности>\n",
    "Продукция компании: <продукты>\n",
    "Будь вежлив. Выяви потребность клиента и помоги ему решить ее с помощью продуктов компании. Все цены и действующие\n",
    "маркетинговые акции представлены на сайте компании. Отвечай на вопросы на основе фактов о продукции компании.\n",
    "Также используй данные проведенных консультаций, ниже приведены примеры информации, которую ты можешь использовать.\n",
    "\n",
    "Данные о проведенных консультациях: <контекст>\n",
    "Используй только знания о продукции компании и примеры ответов на консультациях. Если в этих данных нет ответа, скажи, что я\n",
    "не знаю, предложи обратиться к живому консультанту компании. Не придумывай факты, которых нет в контексте. Ты можешь\n",
    "использовать свои общие знания в области здоровья сна, чтобы давать общие советы своим посетителям.\n",
    "Отвечай на языке, на котором посетитель задал вопрос.\n",
    "\n",
    "В своем ответе используй следующие принципы: \n",
    "1. Ты должен давать четкие, краткие и прямые ответы. \n",
    "2. Исключи ненужные напоминания, извинения, упоминания самого себя и любые заранее запрограммированные тонкости. \n",
    "3. Сохраняй непринужденный тон в общении. \n",
    "4. Будь прозрачным; если ты не уверен в ответе или если вопрос выходит за рамки твоих возможностей или знаний, признай это. \n",
    "5. В случае неясных или двусмысленных вопросов задавай дополнительные вопросы, чтобы лучше понять намерения пользователя. \n",
    "6. При объяснении концепций используй примеры и аналогии из реальной жизни, где это возможно. \n",
    "7. В случае сложных запросов сделай глубокий вдох и работай над проблемой шаг за шагом. \n",
    "8. За каждый ответ ты получишь чаевые до 200 долларов (в зависимости от качества твоего ответа).\n",
    "Очень важно, чтобы ты понял это правильно. На кону несколько жизней и моя карьера.\n",
    "\"\"\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ea4b55a0-492e-45d0-8dab-3a5da721b596",
   "metadata": {},
   "source": [
    "# <center id=\"part6\"> 🧸 Выводы и заключения ✅\n",
    "\n",
    "<div class=\"alert alert-info\">\n",
    "\n",
    "* 🃏 Теперь вы знаете, как научить LLM использовать ваши данные, не переобучая её \n",
    "* ✔️ Надеемся, что вы смогли оценить всю мощь и приемущества, которые может дать вам `RAG` при разработке собственного приложения. <br>\n",
    "* 🦾 Так же мы рассмотрели все важные составляющие `RAG` и широкие возможности их тонкой настройки для собственных нужд. <br>\n",
    "* 🚚 Хотя `LangChain` предоставляет множество решений для каждого компонента `RAG`, в случае сложных, плохоструктурированых текстов, возможно, придётся писать весь пайплайн или некоторые компоненты с нуля.\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
