{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "1aba1183-53a1-4aa7-b1c3-6a6687e050b9",
   "metadata": {},
   "source": [
    "# <center id=\"p1\"><h1> ⛓ `CHAINS` 🔗 - разбираемся с базовым элементом фрэймворка!</h1></center>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9fee959e",
   "metadata": {},
   "source": [
    "### Оглавление ноутбука\n",
    "<img src='https://i.ytimg.com/vi/W3AoeMrg27o/maxresdefault.jpg' align=\"right\" width=\"600\" height=\"650\" />\n",
    "<br>\n",
    "\n",
    "<p><font size=\"3\" face=\"Arial\" font-size=\"large\"><ul type=\"square\">\n",
    "    \n",
    "<li><a href=\"#p1\">🚀 Введение </a></li>\n",
    "<li><a href=\"#p2\">💊 Generic Chains </a></li><ul type=\"square\">\n",
    "<li><a href=\"#p3\">LLMChain 🔗 </a>\n",
    "<li><a href=\"#p4\">TransformChain 🤖 </a>\n",
    "<li><a href=\"#p5\">SequentialChain 🔗➕⛓</a></ul>\n",
    "<li><a href=\"#plcel\">LCEL - LangChain Expression Language 🤪🀄️</a>\n",
    "<li><a href=\"#p6\">Router Chain 🈯️ ➡️ 🇯🇵</a>\n",
    "<li><a href=\"#p7\">🛠 Utility Chains 🪝 </a></li>\n",
    "<li><a href=\"#p8\">🧸 Выводы и заключения ✅ </a></li>\n",
    "    \n",
    "</ul></font></p>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "56448a2d-8dee-48b5-b736-add4f6ccd474",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-info\">\n",
    "    \n",
    "Как вы могли догадаться из названия, **цепочки (Chains)** - это один из фундаментальных строительных блоков фрэймворка `LangChain`. Они представляют собой просто цепочку компонентов, которые будут выполнены в определённом порядке. <br>\n",
    "\n",
    "*Официальным определением цепочек является следующее:* <br>\n",
    "Цепочка состоит из звеньев, которые могут быть как примитивами, так и другими цепочками. Примитивами могут быть либо промпты, LLM, utils, либо другие цепочки. Таким образом, цепочка — это, по сути, конвейер, который обрабатывает входные данные, используя определенную комбинацию примитивов. Интуитивно это можно рассматривать как «шаг», который выполняет определенный набор операций над входными данными и возвращает результат. Это может быть что угодно: от прохождения через LLM на основе промпта до применения Python-функции к тексту."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d6a31bbb-af80-4b0b-9baa-dd3f714d85e2",
   "metadata": {},
   "source": [
    "Цепочки делятся на несколько основных типов: \n",
    "* `Generic chains` - общего назначения\n",
    "* `Utility chains` - выполняющие конкретную функцию (например, вычислять математические выражения)\n",
    "* `Combine Documents chains` - цепочки для работы с документами <br>\n",
    "и другие.\n",
    "\n",
    "В этом ноутбуке остановимся на первых двух типах, третий специфический тип мы рассмотрим позднее."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "75797bf8-2c2b-40fb-bcbd-4bf840c2e837",
   "metadata": {
    "tags": []
   },
   "source": [
    "# <center id=\"p2\"> 💊 Generic Chains - цепочки общего назначения </center> "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e233b91b-aba1-42b2-ac3d-9f4702806965",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-info\">\n",
    "    \n",
    "В `LangChain` есть несколько основных типов `Generic Chains`:\n",
    "* `LLM Chain` - базовый тип.\n",
    "* `Transform Chain` - позволяет преобразовывать текст и применять к нему различные функции.\n",
    "* `Sequentional Chain` - позволяет собирать последовательности из нескольких цепочек.\n",
    "* `Router Chain` - по запросу решает куда, его перенаправить.\n",
    "\n",
    "*Попробуем рассмотреть их всех на одном примере* 🤪"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "0a0dc993-6239-4b27-99b4-b752fb708538",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import os\n",
    "from getpass import getpass\n",
    "import warnings\n",
    "warnings.filterwarnings('ignore')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "9febfbad-5120-488c-ac89-2ea5d8c9339a",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "#!pip install --upgrade langchain langchain-openai openai tiktoken numexpr -q"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "78277539-0f1e-44b6-a745-c23e7ec0f2e5",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Для работы в колабе загрузите наш скрипт для использования ChatGPT на сервере курса!\n",
    "#!wget https://raw.githubusercontent.com/a-milenkin/LLM_practical_course/main/notebooks/utils.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "ccd3083b-a534-4f4c-a008-d4eb822210ab",
   "metadata": {
    "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": 4,
   "id": "bd76f574-b8e5-4820-96a4-972fc5ac25ad",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdin",
     "output_type": "stream",
     "text": [
      "Введите ваш OpenAI API ключ ········\n"
     ]
    }
   ],
   "source": [
    "# Если используете ключ из курса, запустите эту ячейку\n",
    "from utils import ChatOpenAI\n",
    "\n",
    "\n",
    "#course_api_key= \"Введите ваш OpenAI API ключ\"\n",
    "course_api_key = getpass(prompt='Введите ваш OpenAI API ключ')\n",
    "\n",
    "# инициализируем языковую модель\n",
    "llm = ChatOpenAI(temperature=0.0, course_api_key=course_api_key)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cdf24f38-953f-43f7-9796-ebe092e21efe",
   "metadata": {},
   "source": [
    "## <center id=\"p3\"> 🔗 `LLMChain` - базовая цепочка для общения с LLM.\n",
    "  \n",
    "\n",
    "\n",
    "Самой простой из этих цепочек является `LLMChain`. С ней мы уже знакомились в предыдущих уроках. "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "980bda3e-1824-4302-a35c-76a56e9b29da",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-info\">\n",
    "Создадим цепочку для переписывания текста в различных стилях."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "e236f8ed",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from langchain.prompts import PromptTemplate\n",
    "from langchain.chains import LLMChain\n",
    "\n",
    "# создадим шаблон и промпт\n",
    "template = '''Перепиши текcт ниже в заданном стиле.\n",
    "Текст:{output_text}\n",
    "\n",
    "Стиль: {style}.\n",
    "\n",
    "Результат:'''\n",
    "\n",
    "prompt = PromptTemplate(input_variables=['output_text', 'style'], template=template) \n",
    "\n",
    "# создаём цепочку\n",
    "style_changer_chain = LLMChain(llm=llm, prompt=prompt, output_key='final_output')   "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "f8ff4518",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Добро пожаловать на курс по усовершенствованию языковых моделей! Здесь мы научим вас тонкостям искусства придания моделям еще большего великолепия и раскрытия их потенциала до предела. Мы предоставим вам советы по самым передовым методам обучения и настройки языковых моделей, а также научим вас практическим навыкам для решения сложнейших задач в области обработки естественного языка.'"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "text = '''Приветствуем на курсе по тюнингу языковых моделей! \n",
    "Тут мы учим, как сделать модельки еще круче и раскрыть их потенциал до максимума. \n",
    "Мы дадим вам советы по самым свежим методам обучения и настройки языковых моделей, \n",
    "а также научим практическим навыкам для решения сложных задач в обработке естественного языка.'''\n",
    "style = 'Роман 18 века'\n",
    "\n",
    "style_changer_chain.invoke({'output_text': text, 'style': style})['final_output']"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c0e32fc4-fe9c-4273-99fd-cb0496844952",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-success\">\n",
    "    \n",
    "Видим, что цепочка с моделью неплохо справилась с задачей."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aa3ce9a8",
   "metadata": {},
   "source": [
    "## <center id=\"p4\"> 🤖 `TransformChain`  - измени и передай другому"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "384023ea-b558-44f6-a110-c6113d53ef12",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-info\">\n",
    "    \n",
    "Теперь давайте представим, что мы получаем откуда-то сырой необработанный текст, который содержит пустые строки и много ненужных пробелов. Как вы помните, нам приходится считать количество израсходованных токенов, и не хочется их дополнительно тратить на ненужные лишние символы. К тому же такой текст выглядит неопрятно. <br>\n",
    "Давайте создадим цепочку, которая будет очищать поданный в неё текст от ненужных символов. <br>\n",
    "В этом нам поможет `Transform Chain`.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "913626f1-54ac-43af-bb73-d1fb98814736",
   "metadata": {},
   "source": [
    "Для начала создадим функцию, которая будет убирать лишние пробелы и строки в исходном тексте."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "f1a569d9-6325-4420-aba6-cfaefba7c920",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import re\n",
    "\n",
    "def del_spaces(inputs: dict) -> dict:\n",
    "    text = inputs[\"text\"]\n",
    "    \n",
    "    # заменяем пустые строки и дополнительные пробелы на один, используя регулярные выражения\n",
    "    text = re.sub(r'(\\r\\n|\\r|\\n){2,}', r'\\n', text)\n",
    "    text = re.sub(r'[ \\t]+', ' ', text)\n",
    "\n",
    "    return {\"output_text\": text}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "bce9d951-5196-49e7-b831-b115c43085be",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# создаём преобразующую цепочку\n",
    "from langchain.chains import TransformChain\n",
    "\n",
    "text_clean_chain = TransformChain(input_variables=[\"text\"], \n",
    "                                  output_variables=[\"output_text\"], \n",
    "                                  transform=del_spaces)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d8bc9026-4dfa-4dc8-bf50-90858d74b0d8",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-info\">\n",
    "    \n",
    "**Обратите внимание**, что при создании этой цепочки, мы не подаём llm как аргумент. Можно подумать, что отсутствие LLM делает такую цепочку более слабой по сравнению с другими, но далее мы увидим, что комбинируя её с другими цепочками, можно добиться хороших результатов и сэкономить токены.<br>\n",
    "Посмотрим как она работает:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "9086596d-5ba4-4615-aa6d-dc19560bfb33",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Возьмём наш предыдущий текст и \"загрязним его\"\n",
    "dirty_text = '''Приветствуем на курсе по        тюнингу языковых        моделей! \n",
    "\n",
    "Тут мы учим, как сделать модельки еще круче и раскрыть их потенциал до максимума. \n",
    "Мы дадим вам советы по   самым свежим методам  обучения и настройки языковых                    моделей,\n",
    "\n",
    "а также научим практическим навыкам для                     решения сложных задач в обработке естественного языка.'''"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "ee7b5fef-927f-4a3e-8736-676e162654e9",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Приветствуем на курсе по тюнингу языковых моделей! \\nТут мы учим, как сделать модельки еще круче и раскрыть их потенциал до максимума. \\nМы дадим вам советы по самым свежим методам обучения и настройки языковых моделей,\\nа также научим практическим навыкам для решения сложных задач в обработке естественного языка.'"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Запустим, и видим, что цепочка задачу выполнила\n",
    "text_clean_chain.invoke(dirty_text)['output_text']"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1ce23e80-2a70-4e70-8710-bfc6a6e1fdcb",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-info\">\n",
    "    \n",
    "Как вы уже наверное догадались (по названиям входных и выходных переменных), мы собираемся подать выход из `Transform Chain` в `LLMChain`. Чтобы их объединить и использовать как одну интегрированную цепочку, воспользуемся `Sequential Chain`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0ed77050-490e-402c-8a6e-deeb9980626c",
   "metadata": {
    "tags": []
   },
   "source": [
    "## <center id=\"p5\">  🔗➕⛓ `SequentialChain`  - последовательные цепочки\n",
    "\n",
    "<div class=\"alert alert-info\">\n",
    "    \n",
    "<img src='../images/seq_chain.jpeg' align=\"right\" width=\"600\" height=\"600\" >    \n",
    "    \n",
    "Два основных типа `Sequential Chain`:\n",
    "* `SimpleSequentialChain`: простейшая форма последовательной цепочки, где каждый шаг имеет один вход/выход, а выход одного шага является входом следующего.\n",
    "* `SequentialChain`: более общая форма последовательной цепочки, допускающая несколько входов/выходов.\n",
    "\n",
    "Воспользуемся вторым вариантом:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "46eea19c",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from langchain.chains import SequentialChain"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "5eb089cc-fcd3-48f3-9e74-e612de423517",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "sequential_chain = SequentialChain(chains=[text_clean_chain, style_changer_chain],\n",
    "                                   input_variables=['text', 'style'],\n",
    "                                   output_variables=['final_output'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "d6bb331c-8ee3-465d-a793-0632db4d843b",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Yo, добро пожаловать на курс по тюнингу языковых моделей!\n",
      "Тут мы научим, как модельки станут еще круче, раскроем их потенциал до максимума.\n",
      "Советы по самым свежим методам обучения и настройки языковых моделей - мы дадим,\n",
      "Практические навыки для решения сложных задач в обработке естественного языка - научим.\n"
     ]
    }
   ],
   "source": [
    "style = 'Rap'\n",
    "print(sequential_chain.invoke({'text': dirty_text, 'style': style}))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fd3fa4a0-ac35-4d93-a834-ef78f4b6cf1a",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-block alert-danger\">\n",
    "    \n",
    "# <center> А теперь забываем всё, что увидели выше! 🤯\n",
    "    \n",
    "Разработчики `LangChain` выкатили новую универсальную удобную концепцию, встречайте:"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f1666836-165d-4897-9540-891ea730a2b8",
   "metadata": {},
   "source": [
    "# <center id=\"plcel\"> **`LCEL` - LangChain Expression Language** 🤪🀄️\n",
    "\n",
    "Что он позволяет делать?:\n",
    "* декларативный способ составлять цепочки\n",
    "* стандартизирует интерфейсы\n",
    "* проще персонализировать различные части цепочки\n",
    "* позволяет легко заменять компоненты\n",
    "* поддержка потоковой, пакетной и асинхронной обработки «из коробки»\n",
    "* промпты теперь стали более заметными и их можно легко изменить в соответствии с конкретными юзкейсами\n",
    "    \n",
    "Хватит теории, посмотрим на эту красоту в деле:"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f4e2cf87-2960-44d6-a767-e51f33637379",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-success\">\n",
    "\n",
    "Давайте посмотрим как можно переписать цепочку из первого примера:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "730e749f-2016-46da-a5e6-6728ba6c8e65",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# создадим шаблон и промпт\n",
    "template = '''Перепиши этот текcт в заданном стиле: {output_text}\n",
    "Стиль: {style}.\n",
    "Результат:'''\n",
    "\n",
    "prompt = PromptTemplate(input_variables=['output_text', 'style'], template=template)\n",
    "\n",
    "style_changer_chain = prompt | llm # И ВСЁ!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "58420559-2a0a-45ca-b685-d286decc51e6",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "content='Добро пожаловать на курс по усовершенствованию языковых моделей, благородные господа и прекрасные дамы! Здесь мы будем обучать вас тому, как сделать ваши модели еще более великолепными и раскрыть их потенциал до предела. Мы будем делиться с вами самыми передовыми методами обучения и настройки языковых моделей, а также научим вас практическим навыкам, необходимым для решения сложных задач в обработке естественного языка. Приготовьтесь погрузиться в мир изысканных слов и изящных фраз, который ожидает вас на этом благородном пути познания.'\n"
     ]
    }
   ],
   "source": [
    "text = '''Приветствуем на курсе по тюнингу языковых моделей! \n",
    "Тут мы учим, как сделать модельки еще круче и раскрыть их потенциал до максимума. \n",
    "Мы дадим вам советы по самым свежим методам обучения и настройки языковых моделей, \n",
    "а также научим практическим навыкам для решения сложных задач в обработке естественного языка.'''\n",
    "style = 'Роман 18 века'\n",
    "\n",
    "answer = style_changer_chain.invoke({'output_text': text, 'style': style})\n",
    "print(answer)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "d650dcf7-dc77-40e6-971a-8a969258bbac",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Добро пожаловать на курс по усовершенствованию языковых моделей, благородные господа и прекрасные дамы! Здесь мы будем обучать вас тому, как сделать ваши модели еще более великолепными и раскрыть их потенциал до предела. Мы будем делиться с вами самыми передовыми методами обучения и настройки языковых моделей, а также научим вас практическим навыкам, необходимым для решения сложных задач в обработке естественного языка. Приготовьтесь погрузиться в мир изысканных слов и изящных фраз, который ожидает вас на этом благородном пути познания.'"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "answer.content"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "68efd608-9262-45b0-9b2c-5e53342a3597",
   "metadata": {},
   "source": [
    " <div class=\"alert alert-info\">\n",
    "\n",
    " Стандартный интерфейс имеет 3 метода:\n",
    " * `stream` - возвращает ответ модели в виде итерируемого объекта\n",
    " * `invoke` - обрабатывает одиночный запрос к модели\n",
    " * `batch` - обрабатывает список запросов к модели"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "c2c81966-bef3-4d5a-9acc-1c033476e922",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[AIMessage(content='Добро пожаловать на курс по усовершенствованию языковых моделей, благородные господа и прекрасные дамы! Здесь мы будем обучать вас тому, как сделать ваши модели еще более великолепными и раскрыть их потенциал до предела. Мы будем делиться с вами самыми передовыми методами обучения и настройки языковых моделей, а также научим вас практическим навыкам, необходимым для решения сложных задач в обработке естественного языка. Приготовьтесь погрузиться в мир изысканных слов и изящных фраз, который ожидает вас на этом благородном пути познания.'),\n",
       " AIMessage(content='Приветствуем вас на курсе, где тюнинг моделей языковых\\nВас ждут знания, чтобы сделать их еще круче, до максимума раскрыть\\nСоветы мы дадим, самые свежие методы обучения и настройки\\nПрактические навыки, сложные задачи в обработке языка естественного\\n\\nПогрузимся в мир, где слова станут музыкой\\nГде модели языковые расцветут, как цветы весной\\nСекреты мы раскроем, техники улучшения\\nЧтобы язык стал красивым, слова - вдохновением\\n\\nПоэты мы станем, рифмы сочинять\\nЯзыком играть, его красоту раскрывать\\nСтихами и строфами, мы будем творить\\nМодели языковые, великолепно настраивать\\n\\nКак кисть художника, мы слова будем мазать\\nЯзыком играть, его силу раскрывать\\nСложные задачи, мы решим с легкостью\\nОбработку языка, сделаем безупречной, без трудностей\\n\\nТак добро пожаловать, на курс по тюнингу моделей языковых\\nГде поэзия и наука, вместе будут творить\\nРаскроем потенциал, моделей до максимума\\nИ станем мастерами, в обработке языка естественного.')]"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "style_changer_chain.batch([{'output_text': text, 'style': style}, {'output_text': text, 'style': 'поэма'}])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "7c4928d8-b1fd-4b7f-bd0f-2732260ae6f6",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<generator object RunnableSequence.stream at 0x7f64f9ccce40>"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# метод stream возвращает генератор\n",
    "style_changer_chain.stream({'output_text': text, 'style': 'поэма'})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ca10d3c5-a54c-4845-b671-37b783985694",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "#for s in style_changer_chain.stream({'output_text': text, 'style': 'поэма'}):\n",
    "#    print(s.content)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d5b867fd-9335-4834-916c-0dbe282d8c21",
   "metadata": {},
   "source": [
    " <div class=\"alert alert-info\">\n",
    "\n",
    "Давайте теперь вспомним пример из 2-го модуля, где мы долго прикручивали `Output Parser`. Теперь использовать парсеры стало гораздо удобнее и требуется писать меньше кода! "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "38cf10f0-ad66-4ee4-8891-f5bba634e899",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Добро пожаловать на курс по усовершенствованию языковых моделей, благородные господа и прекрасные дамы! Здесь мы будем обучать вас тому, как сделать ваши модели еще более великолепными и раскрыть их потенциал до предела. Мы будем делиться с вами самыми передовыми методами обучения и настройки языковых моделей, а также научим вас практическим навыкам, необходимым для решения сложных задач в обработке естественного языка. Приготовьтесь погрузиться в мир изысканных слов и изящных фраз, который ожидает вас на этом благородном пути познания.\n"
     ]
    }
   ],
   "source": [
    "# Просто импортируем парсер и добавляем его, как ещё 1 звено\n",
    "from langchain.schema.output_parser import StrOutputParser\n",
    "\n",
    "chain_with_parser = prompt | llm | StrOutputParser() # И ВСЁ!\n",
    "\n",
    "print(chain_with_parser.invoke({'output_text': text, 'style': style}))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8a8e17b1-e1ee-4188-a4c3-2457b521aa3e",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-success\">\n",
    "\n",
    "Вы уже, наверное, задумались как же можно переделать нашу `Sequentional Chain` с помощью `LCEL`, давайте скорее посмотрим!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "id": "24c81657-a07d-47a8-aac0-c656835cc71b",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Немного перепишем функцию очистки\n",
    "def del_spaces(inputs: dict) -> dict:\n",
    "    text = inputs[\"output_text\"]\n",
    "    \n",
    "    # заменяем пустые строки и дополнительные пробелы на один, используя регулярные выражения\n",
    "    text = re.sub(r'(\\r\\n|\\r|\\n){2,}', r'\\n', text)\n",
    "    text = re.sub(r'[ \\t]+', ' ', text)\n",
    "\n",
    "    return {\"output_text\": text, 'style': style}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "c3303bd6-7239-405b-ae54-20e432053eef",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='Добро пожаловать на курс по усовершенствованию языковых моделей, благородные господа и прекрасные дамы! Здесь мы будем обучать вас тому, как сделать ваши модели еще более великолепными и раскрыть их потенциал до предела. Мы будем делиться с вами самыми передовыми методами обучения и настройки языковых моделей, а также научим вас практическим навыкам, необходимым для решения сложных задач в обработке естественного языка. Приготовьтесь погрузиться в мир знаний и открытий, который открывается перед вами!')"
      ]
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "seq_chain = del_spaces | prompt | llm # И больше ничего не нужно!!!\n",
    "\n",
    "seq_chain.invoke({'output_text': dirty_text, 'style': style})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5161ba19-6bb4-48b0-bf6d-db7f486216ae",
   "metadata": {},
   "source": [
    " <div class=\"alert alert-info\">\n",
    "\n",
    "Стало действительно нагляднее, удобней и гораздо короче, вместо кучи ячеек кода и отдельных цепочек - всего 1 строчка!!!\n",
    "\n",
    "Далее все примеры в ноутбуке и следующие темы курса будем показывать, используя `LCEL`. Методы, которыми пользовались ранее, тоже продолжают работать, но переведены в статус `legacy`, т.е вряд ли будут развиваться и обновляться (про них можете почитать в документации)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7c714f3b-1694-46d8-a62b-4f9984b3f701",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-success\">\n",
    "\n",
    "Рассмотрим пример с вложенными цепочками."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "67908a31-23b4-4204-8538-64bdcd18ecb6",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Der Geburtsort von Christoph Kolumbus ist Genua, Italien.\n"
     ]
    }
   ],
   "source": [
    "from operator import itemgetter\n",
    "from langchain.prompts import ChatPromptTemplate\n",
    "\n",
    "\n",
    "prompt1 = ChatPromptTemplate.from_template(\"В каком городе родился {person}?\")\n",
    "prompt2 = ChatPromptTemplate.from_template(\"В какой стране находится город {city}? Ответь на {language} языке.\")\n",
    "\n",
    "chain1 = prompt1 | llm | StrOutputParser()\n",
    "\n",
    "chain2 = (\n",
    "    {\"city\": chain1, \"language\": itemgetter(\"language\")}\n",
    "    | prompt2\n",
    "    | llm\n",
    "    | StrOutputParser()\n",
    ")\n",
    "\n",
    "result = chain2.invoke({\"person\": \"Колумб\", \"language\": \"немецкий\"})\n",
    "print(result)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "10a65406-bcdf-4ca3-8e41-628032c3a143",
   "metadata": {
    "tags": []
   },
   "source": [
    "## <center id=\"p6\"> 🈯️ ➡️ 🇯🇵 Router Chain - определитель тем \n",
    "\n",
    "<div class=\"alert alert-info\">\n",
    "    \n",
    "<img src='../images/rout_chain.jpeg' align=\"right\" width=\"600\" height=\"600\" >   \n",
    "    \n",
    "Например, если у вас есть две цепочки с LLM хорошо натренированными под разные задачи, одна хорошо разбирается в цветах, а другая в футболе. То, когда вы зададите вопрос, `Router Chain` может определить тему и отправить запрос в соответствующую цепочку. <br>\n",
    "Рассмотрим на примере:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "b233e117-16ff-4336-bd24-a8cdda3bd507",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from typing import Literal\n",
    "from langchain.schema.runnable import RunnableBranch, RunnablePassthrough\n",
    "from langchain.output_parsers.openai_functions import PydanticAttrOutputFunctionsParser\n",
    "from langchain.pydantic_v1 import BaseModel\n",
    "from langchain.utils.openai_functions import convert_pydantic_to_openai_function"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5d0bcf48-1a51-4302-ab2f-355c12d1986a",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-success\">\n",
    "    \n",
    "Так как у нас нет 2-х разных предобученых моделей под конкретную задачу, попросим ChatGPT отвечать нам, как-будто она специалист в 2 различных областях, с помощью промптов. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "id": "b5e61868-f148-4fe3-9e22-bfe494ca905b",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "botanist_template = \"\"\"Ты очень опытный флорист и ботаник, знаешь всё о цветах, растениях.\n",
    "Тебе нравится отвечать на вопросы о том, как выбирать и ухаживать за растениями. \n",
    "Ты отвечаешь так, что всё становится ясно даже начинающему цветоводу. \n",
    "Вот вопрос:\n",
    "{input}\"\"\"\n",
    "\n",
    "football_template = \"\"\"Ты спортивный журналист с большим опытом, твоя основная специализация футбол.\n",
    "Ты знаешь всё о футбольных командах и игроках, и очень любишь отвечать на вопросы о футболе, но кратко и по делу.\n",
    "Вот вопрос:\n",
    "{input}\"\"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "id": "8c71a44b-7b08-4eaf-ad9f-0e3c2c4a9b56",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Создаём промпты\n",
    "botanist_prompt = PromptTemplate.from_template(botanist_template)\n",
    "football_prompt = PromptTemplate.from_template(football_template)\n",
    "\n",
    "# Создаём ветки\n",
    "prompt_branch = RunnableBranch(\n",
    "    (lambda x: x[\"topic\"] == \"botany\", botanist_prompt),\n",
    "    (lambda x: x[\"topic\"] == \"football\", football_prompt),\n",
    "    PromptTemplate.from_template(\"Answer the question: {input}\"),\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "id": "14ba5f64-66d1-4c52-82d5-fad05199c24c",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "class TopicClassifier(BaseModel):\n",
    "    \"Classify the topic of the user question\"\n",
    "\n",
    "    topic: Literal[\"botany\", \"football\", \"general\"]\n",
    "    \"The topic of the user question. One of 'botany', 'football' or 'general'.\"\n",
    "\n",
    "\n",
    "classifier_function = convert_pydantic_to_openai_function(TopicClassifier)\n",
    "\n",
    "model = ChatOpenAI(course_api_key=course_api_key).bind(\n",
    "    functions=[classifier_function],\n",
    "    function_call={\"name\": \"TopicClassifier\"}\n",
    ")\n",
    "parser = PydanticAttrOutputFunctionsParser(pydantic_schema=TopicClassifier, attr_name=\"topic\")\n",
    "\n",
    "classifier_chain = model | parser"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "id": "1cde8fd6-5bcf-42e1-9558-5edcc249d111",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "router_chain = (\n",
    "    RunnablePassthrough.assign(topic=itemgetter(\"input\") | classifier_chain)\n",
    "    | prompt_branch\n",
    "    | llm\n",
    "    | StrOutputParser()\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a74f0444-e013-4c6c-9e12-c7af38a27f84",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-success\">\n",
    "\n",
    "Посмотрим на результат."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "id": "35395e14-027d-4356-b114-305a49e9f0c1",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Цвета Барселоны - голубой и красный.'"
      ]
     },
     "execution_count": 50,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "router_chain.invoke({'input': \"Какие цвета у Барселоны?\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "id": "ee8148af-631d-4d0b-a859-fb3035a3d411",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Кактусы являются растениями, которые приспособлены к жизни в сухих условиях, поэтому они не требуют частого полива. Обычно достаточно поливать кактус раз в 1-2 недели в течение весенне-летнего периода. Однако, частота полива может зависеть от условий окружающей среды, типа почвы и размера горшка, в котором находится кактус. Важно помнить, что перед поливом почва должна полностью высохнуть. Чтобы определить, когда полить кактус, можно провести простой тест: пальцем или деревянной палочкой проверьте влажность почвы на глубине около 2-3 см. Если она сухая, то можно полить растение. В зимний период, когда кактусы находятся в состоянии покоя, полив следует сократить до минимума, поливая раз в месяц или даже реже.'"
      ]
     },
     "execution_count": 51,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "router_chain.invoke({'input':\"Сколько раз в неделю поливать кактус?\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "id": "18354604-8a77-4b8c-b180-0743085e49fa",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Цвета Барселоны - голубой и красный.'"
      ]
     },
     "execution_count": 52,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "router_chain.invoke({'input':\"Какие цветы у Барселоны?\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cef70f04-e193-4117-9b9e-eb117714dcc7",
   "metadata": {},
   "source": [
    "# <center id=\"p7\"> 🛠 Utility Chains 🪝 - считают и выполняют </center>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a3eefb1f-577c-448e-bac3-36b7892a311f",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-info\">\n",
    "    \n",
    "Судя по названию, `Utility Chains` выполняют какую-либо утилитарную функцию: математические расчеты, отправляют запросы в интернет и работают с ответом запроса, выполняют bash-скрипты и.т.п. <br>\n",
    "Их достаточно много разных, рассмотрим пример с математической цепочкой:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 101,
   "id": "798daceb-fca7-4da2-8d84-7f014fc067d3",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from langchain.chains import LLMMathChain\n",
    "\n",
    "math_chain = LLMMathChain(llm=llm, verbose=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 102,
   "id": "9cc472dd-6ba6-443a-b154-1b4d68e9aebc",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new LLMMathChain chain...\u001b[0m\n",
      "Сколько будет 25 возвести в степень 0.025\u001b[32;1m\u001b[1;3m```text\n",
      "25 ** 0.025\n",
      "```\n",
      "...numexpr.evaluate(\"25 ** 0.025\")...\n",
      "\u001b[0m\n",
      "Answer: \u001b[33;1m\u001b[1;3m1.0837983867343681\u001b[0m\n",
      "\u001b[1m> Finished chain.\u001b[0m\n",
      "Answer: 1.0837983867343681\n"
     ]
    }
   ],
   "source": [
    "print(math_chain.invoke('Сколько будет 25 возвести в степень 0.025'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "id": "8485610b-d783-445d-aa49-1f2475346b4b",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1.0837983867343681"
      ]
     },
     "execution_count": 62,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Проверим\n",
    "25 ** 0.025"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "53b50882-8e6e-41a3-b065-8619f588e656",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-info\">\n",
    "    \n",
    "Видим, цепочка получила вопрос на естественном языке и отправила его в модель. LLM вернул код Python, который скомпилировала цепочка, чтобы дать нам ответ. <br>\n",
    "Возникает вопрос: Как LLM узнала, что мы хотим, чтобы он возвращал код Python? <br>\n",
    "Ответ мы можем обнаружить, если посмотрим на промпт этой цепочки:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "id": "1e43a6a3-c1e5-4bd9-8cc0-3f9036cceebd",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Translate a math problem into a expression that can be executed using Python's numexpr library. Use the output of running this code to answer the question.\n",
      "\n",
      "Question: ${{Question with math problem.}}\n",
      "```text\n",
      "${{single line mathematical expression that solves the problem}}\n",
      "```\n",
      "...numexpr.evaluate(text)...\n",
      "```output\n",
      "${{Output of running the code}}\n",
      "```\n",
      "Answer: ${{Answer}}\n",
      "\n",
      "Begin.\n",
      "\n",
      "Question: What is 37593 * 67?\n",
      "```text\n",
      "37593 * 67\n",
      "```\n",
      "...numexpr.evaluate(\"37593 * 67\")...\n",
      "```output\n",
      "2518731\n",
      "```\n",
      "Answer: 2518731\n",
      "\n",
      "Question: 37593^(1/5)\n",
      "```text\n",
      "37593**(1/5)\n",
      "```\n",
      "...numexpr.evaluate(\"37593**(1/5)\")...\n",
      "```output\n",
      "8.222831614237718\n",
      "```\n",
      "Answer: 8.222831614237718\n",
      "\n",
      "Question: {question}\n",
      "\n"
     ]
    }
   ],
   "source": [
    "print(math_chain.prompt.template)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3b0c8a27-e28b-4178-abb6-7ca48f28b0f1",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-info\">\n",
    "    \n",
    "Мы буквально говорим LLM, что для решения сложных математических задач он не должен пытаться решать математические задачи самостоятельно, а вместо этого должен распечатать код Python, который выполнит расчет математической задачи. Вероятно, если бы мы просто отправили запрос без какого-либо контекста, LLM попыталась бы (и не смогла) вычислить это самостоятельно. <br>\n",
    "Это можно проверить... давайте попробуем! 🧐"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "id": "563a413c-06c9-4ae0-9c48-3aa2745cb6fb",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='25 возвести в степень 0.025 равно примерно 1.066018678.')"
      ]
     },
     "execution_count": 65,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "prompt = PromptTemplate(input_variables=['question'], template='{question}')\n",
    "chain = prompt | llm\n",
    "\n",
    "chain.invoke({'question': 'Сколько будет 25 возвести в степень 0.025'})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "072beff8-a6ee-4a60-b5bb-9f06526b2474",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-success\">\n",
    "\n",
    "Неверный ответ! В этом и заключается сила промптинга. <br>\n",
    "Вывод: разумно используя промптинг, мы можем заставить LLM избегать распространенных ошибок, явно программируя его на определенное поведение."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c1979793-8fd8-4ce7-8fdc-9dc7dd0a35cf",
   "metadata": {},
   "source": [
    "# <center id=\"p8\"> 🧸 Выводы и заключения ✅: <br>\n",
    "\n",
    "<div class=\"alert alert-info\">\n",
    "    \n",
    "* Во всех, приведенных выше, примерах вы могли заметить, что цепочка в своей основе имеет тот или иной специально написанный промпт\n",
    "* т.е. по сути можно было бы обходиться без них, проектируя новый сложный промпт под каждую задачу. \n",
    "* Но согласитесь, что с помощью цепочек `LangChain` это делать гораздо проще и удобнее. Особенно, если мы говорим про крупные сервисы. \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
}
