{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "ac2cfafa",
      "metadata": {
        "tags": [],
        "id": "ac2cfafa"
      },
      "source": [
        "# <center id=\"c1\"><h2> 🦜🔗 `LangChain` - упрости работу с LLM! </h2>\n",
        "\n",
        "<img src='https://github.com/a-milenkin/LLM_practical_course/blob/main/images/LangChain.webp?raw=1' align=\"right\" width=\"528\" height=\"528\" >\n",
        "\n",
        "## Оглавление ноутбука:\n",
        "\n",
        " * <a href=\"#c1\"> 🦜🔗 О фрэймворке `LangChain`  </a>\n",
        " * <a href=\"#c2\"> 🧾 Prompt template - формируй промпт с кайфом! </a>\n",
        "   * <a href=\"#look1\"> 🗣 ChatPromptTemplate  </a>\n",
        "   * <a href=\"#check1\"> 📸 FewShotPromptTemplate </a>\n",
        "   * <a href=\"#check2\"> 📏 LongBasedExampleSelector </a>\n",
        "* <a href=\"#look2\"> 🤷‍ Output parsers - приведи выход модели к нужному виду!</a>\n",
        "    * <a href=\"#look2\"> 📤 Парсер заданных полей в ответе  </a>\n",
        "    * <a href=\"#check4\"> ▶️ ⏸ ⏯ StructuredOutputParser - разбираем выходную строку из LLM в словарь Python. </a>\n",
        "* <a href=\"#6\">🧸 Выводы и заключения</a>"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ec059397-8d9f-4dcf-9874-64e6265850f7",
      "metadata": {
        "id": "ec059397-8d9f-4dcf-9874-64e6265850f7"
      },
      "source": [
        "<div class=\"alert alert-info\">\n",
        "    \n",
        "* [`LangChain`](https://docs.langchain.com/docs/) - фреймворк для работы с языковыми моделями, позволяющий сильно ускорить процесс создания вашего AI продукта. Был представлен в октябре 2022 года, с тех пор продолжает развиваться и собирать армию пользователей.\n",
        "* На данный момент поддерживаются более 50 форматов и источников откуда можно считывать данные, SQL и NoSQL базы данных, веб-поисковики Goggle и Bing, хранилища Amazon, Google, and Microsoft Azure.\n",
        "* Работает по принципу **LEGO** - из набора деталек, предоставленных разработчиками, можно легко собрать как продуктовую тележку, так и гоночный болид.   <br>\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d6f7dd6d-15ad-4b4b-93f2-aaba16280dfa",
      "metadata": {
        "id": "d6f7dd6d-15ad-4b4b-93f2-aaba16280dfa"
      },
      "source": [
        "\n",
        "**Основные компоненты:**\n",
        "\n",
        "* **🤖 Модели ([Models](https://docs.langchain.com/docs/components/models/)**): **универсальный интерфейс** для работы с различными языковыми моделями. Можно использовать API OpenAI, HuggingFace, Anthropic и других. Также есть возможность работать с локальными моделями.\n",
        "\n",
        "* **📝 Промпты ([Prompts](https://docs.langchain.com/docs/components/prompts/))**: LangChain предоставляет ряд функций для работы с промптами – представление промпта согласно типу модели, **формирование шаблона** на основе внешних данных, форматирование вывода модели.\n",
        "\n",
        "* **🗑 Индексы ([Indexs](https://docs.langchain.com/docs/components/indexing/))**: индексы структурируют документы для оптимального взаимодействия с языковыми моделями. Модуль включает функции для работы с документами, индексами и их использованием в цепочках. В том числе **поддерживает индексы, основанные на векторных базах данных**.\n",
        "\n",
        "* **🧠 Память ([Memory](https://docs.langchain.com/docs/components/memory/))**: позволяет сохраняет состояния в цепочках. Например, для создания чат-бота можно **сохранять предыдущие вопросы и ответы**. Существует два типа памяти: краткосрочная и долгосрочная. Краткосрочная память передает данные в рамках одного разговора. Долгосрочная память отвечает за доступ и обновление информации между разговорами.\n",
        "\n",
        "* **🔗 Цепочки ([Chains](https://docs.langchain.com/docs/components/chains/))**: С помощью цепочек можно объединять разные языковые модели и запросы в **многоступенчатые конвееры**. Цепочки могут быть применены для разговоров, ответов на вопросы, суммаризаций и других сценариев.\n",
        "\n",
        "* **🥷 Агенты ([Agents](https://docs.langchain.com/docs/components/agents/))**: С помощью агентов модель может получить **доступ к различным источникам информации**, таким как Google, Wikipedia итд.\n",
        "\n",
        "** 🦜 Причём тут попугаи?*: Большие языковые модели часто сравнивают с говорящими попугаями, которые могут произносить текст как люди, но не понимают смысла произнесенного."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8d75fe56-8f36-4526-bd72-4df8085cfeed",
      "metadata": {
        "id": "8d75fe56-8f36-4526-bd72-4df8085cfeed"
      },
      "source": [
        "В этом ноутбуке будем разбираться как `LangChain` упрощает работу с промптами."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "527e0e15-2fce-44d0-a341-cf7f7a872d83",
      "metadata": {
        "id": "527e0e15-2fce-44d0-a341-cf7f7a872d83"
      },
      "source": [
        "# <center> 🔑 Вводим ключ"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "0a0dc993-6239-4b27-99b4-b752fb708538",
      "metadata": {
        "tags": [],
        "id": "0a0dc993-6239-4b27-99b4-b752fb708538"
      },
      "outputs": [],
      "source": [
        "import os\n",
        "from getpass import getpass\n",
        "import warnings\n",
        "warnings.filterwarnings('ignore')"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "9febfbad-5120-488c-ac89-2ea5d8c9339a",
      "metadata": {
        "tags": [],
        "id": "9febfbad-5120-488c-ac89-2ea5d8c9339a"
      },
      "outputs": [],
      "source": [
        "!pip install langchain langchain-openai openai tiktoken -q"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "78277539-0f1e-44b6-a745-c23e7ec0f2e5",
      "metadata": {
        "jupyter": {
          "source_hidden": true
        },
        "tags": [],
        "id": "78277539-0f1e-44b6-a745-c23e7ec0f2e5"
      },
      "outputs": [],
      "source": [
        "# Для работы в колабе загрузите наш скрипт для использования ChatGPT на сервере курса!\n",
        "#!wget https://raw.githubusercontent.com/a-milenkin/LLM_practical_course/main/notebooks/utils.py"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "ccd3083b-a534-4f4c-a008-d4eb822210ab",
      "metadata": {
        "tags": [],
        "id": "ccd3083b-a534-4f4c-a008-d4eb822210ab"
      },
      "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": null,
      "id": "bd76f574-b8e5-4820-96a4-972fc5ac25ad",
      "metadata": {
        "tags": [],
        "id": "bd76f574-b8e5-4820-96a4-972fc5ac25ad",
        "outputId": "89518307-0c6d-4b88-8ea3-9292dc205625"
      },
      "outputs": [
        {
          "name": "stdin",
          "output_type": "stream",
          "text": [
            "Введите API ключ ········\n"
          ]
        }
      ],
      "source": [
        "# Если используете ключ из курса, запустите эту ячейку\n",
        "from utils import ChatOpenAI\n",
        "\n",
        "#course_api_key= \"Введите ваш OpenAI API ключ\"\n",
        "course_api_key = getpass(prompt='Введите API ключ')\n",
        "\n",
        "# инициализируем языковую модель\n",
        "llm = ChatOpenAI(temperature=0.0, course_api_key=course_api_key)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "85332176-4858-4ae4-a14d-802e9d47e01b",
      "metadata": {
        "tags": [],
        "id": "85332176-4858-4ae4-a14d-802e9d47e01b"
      },
      "source": [
        "Вспомним, что мы делали в предыдущем ноутбуке: напишем промпт"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "4fe3e165-4b7b-4216-bed4-66483b0e4105",
      "metadata": {
        "tags": [],
        "id": "4fe3e165-4b7b-4216-bed4-66483b0e4105"
      },
      "outputs": [],
      "source": [
        "prompt = \"\"\"Ответь на вопрос, опираясь на контекст ниже.\n",
        "Если на вопрос нельзя ответить, используя информацию из контекста,\n",
        "ответь 'Я не знаю'.\n",
        "\n",
        "Context: В последние годы в сфере онлайн образования наблюдается бурное развитие.\n",
        "Открывается большое количество платформ для хостинга курсов.\n",
        "Одни из самых крупных платформ в мире, это Coursera и Udemi.\n",
        "В России лидером является Stepik.\n",
        "\n",
        "Question: На каких онлайн платформах можно размещать курсы?\n",
        "\n",
        "Answer: \"\"\""
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "35706991-4c0c-49c5-a209-292341ba64ef",
      "metadata": {
        "tags": [],
        "id": "35706991-4c0c-49c5-a209-292341ba64ef",
        "outputId": "8d07d719-8990-4431-b2c8-3ae83a73d83c"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Coursera, Udemi, Stepik\n"
          ]
        }
      ],
      "source": [
        "print(llm.invoke(prompt).content)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3daec071-4bca-43d9-b43d-da4385fd2511",
      "metadata": {
        "id": "3daec071-4bca-43d9-b43d-da4385fd2511"
      },
      "source": [
        "# <center id=\"c2\"> 🧾 `Prompt` `template` - формируй промпт с кайфом!"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a3ccc936-df2e-40da-aae7-42e5d3ffe672",
      "metadata": {
        "id": "a3ccc936-df2e-40da-aae7-42e5d3ffe672"
      },
      "source": [
        "<div class=\"alert alert-info\">\n",
        "\n",
        "Обычно мы заранее не знаем, что пользователь захочет спросить у модели, опираясь на предложенный контекст. Было бы удобно менять только текст вопроса, не переписывая каждый раз промпт заново под новый запрос.<br>\n",
        "    "
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e4794dc9-1dcb-428f-9bd1-47295620fc95",
      "metadata": {
        "id": "e4794dc9-1dcb-428f-9bd1-47295620fc95"
      },
      "source": [
        "Здесь на сцену выходит новая сущность из библиотеки `LangChain` - `Template`(Шаблон промпта). <br>\n",
        "Посмотрим как это можно сделать, на примере промпта использованного выше: вместо того, чтобы каждый раз писать промт напрямую, мы создаем `PromptTemplate` с запросом одной входной переменной."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "735f64eb-172a-478f-a19d-3344f2b974bc",
      "metadata": {
        "tags": [],
        "id": "735f64eb-172a-478f-a19d-3344f2b974bc"
      },
      "outputs": [],
      "source": [
        "from langchain import PromptTemplate\n",
        "\n",
        "template = \"\"\"Ответь на вопрос, опираясь на контекст ниже.\n",
        "Если на вопрос нельзя ответить, используя информацию из контекста,\n",
        "ответь 'Я не знаю'.\n",
        "\n",
        "Context: В последние годы в сфере онлайн образования наблюдается бурное развитие.\n",
        "Открывается большое количество платформ для хостинга курсов.\n",
        "Одни из самых крупных платформ в мире, это Coursera и Udemi.\n",
        "В России лидером является Stepik.\n",
        "\n",
        "Question: {query}\n",
        "\n",
        "Answer: \"\"\"\n",
        "\n",
        "prompt_template = PromptTemplate(\n",
        "    input_variables=[\"query\"],\n",
        "    template=template\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "08594c4f-3a06-4385-bb19-ad409efa94c0",
      "metadata": {
        "id": "08594c4f-3a06-4385-bb19-ad409efa94c0"
      },
      "source": [
        "<div class=\"alert alert-info\">\n",
        "    \n",
        "Теперь мы можем вставлять запрос пользователя в промпт, используя параметр `query`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "debb101d-2c85-4649-a80b-7454f5afd8f5",
      "metadata": {
        "tags": [],
        "id": "debb101d-2c85-4649-a80b-7454f5afd8f5",
        "outputId": "f2010a9c-6dd4-4e1d-a968-1a37004525fa"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Ответь на вопрос, опираясь на контекст ниже.\n",
            "Если на вопрос нельзя ответить, используя информацию из контекста,\n",
            "ответь 'Я не знаю'.\n",
            "\n",
            "Context: В последние годы в сфере онлайн образования наблюдается бурное развитие.\n",
            "Открывается большое количество платформ для хостинга курсов.\n",
            "Одни из самых крупных платформ в мире, это Coursera и Udemi.\n",
            "В России лидером является Stepik.\n",
            "\n",
            "Question: Какая платформа онлайн курсов популярна в России?\n",
            "\n",
            "Answer: \n"
          ]
        }
      ],
      "source": [
        "prompt = prompt_template.format(query=\"Какая платформа онлайн курсов популярна в России?\")\n",
        "print(prompt)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "ca0abcfd-cd23-44c0-851f-3227c970d0f1",
      "metadata": {
        "tags": [],
        "id": "ca0abcfd-cd23-44c0-851f-3227c970d0f1",
        "outputId": "3e8abd00-8126-4b5d-ba59-63b3e8c995d4"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Stepik\n"
          ]
        }
      ],
      "source": [
        "print(llm.invoke(prompt).content)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "84aa7065-0bf7-4de1-9627-e08dda3810c9",
      "metadata": {
        "tags": [],
        "id": "84aa7065-0bf7-4de1-9627-e08dda3810c9",
        "outputId": "6f95bd65-ef65-4ed3-bdfa-14a9495b25b1"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Я не знаю.\n"
          ]
        }
      ],
      "source": [
        "prompt = prompt_template.format(query=\"Какая платформа онлайн курсов популярна в Японии?\")\n",
        "print(llm.invoke(prompt).content)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3d1041a8-aea4-4628-abd6-b8cc44f36b5a",
      "metadata": {
        "id": "3d1041a8-aea4-4628-abd6-b8cc44f36b5a"
      },
      "source": [
        "<div class=\"alert alert-success\">\n",
        "\n",
        "Стало гораздо удобнее пользоваться!<br>"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a421cce6-4bc6-4389-86fd-fb499f86531c",
      "metadata": {
        "id": "a421cce6-4bc6-4389-86fd-fb499f86531c"
      },
      "source": [
        "<div class=\"alert alert-info\">\n",
        "    \n",
        "* Это всего лишь простая реализация, которую мы можем легко заменить f-строками (например, `f\"вставить произвольный текст '{custom_text}'` и т. д.\").\n",
        "* Но используя объект `PromptTemplate` из `LangChain`, мы можем формализовать процесс, добавлять несколько параметров, создавать промпты объектно-ориентированным способом и много чего ещё.\n",
        "* В `langchain.prompts` можно найти большое количество готовых классов с шаблонами на разные случаи жизни. <br>"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "9876eb4f-0a3b-4be1-a043-c1be02f7a576",
      "metadata": {
        "id": "9876eb4f-0a3b-4be1-a043-c1be02f7a576"
      },
      "source": [
        "Познакомимся с некоторыми из них подробнее:"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a8aa9c28-77a7-4ac1-83e9-2c481b509d8b",
      "metadata": {
        "id": "a8aa9c28-77a7-4ac1-83e9-2c481b509d8b"
      },
      "source": [
        "## <center id=\"look1\">🗣 [`ChatPromptTemplate`](https://api.python.langchain.com/en/latest/prompts/langchain.prompts.chat.ChatPromptTemplate.html) - шаблон для чатового режима </center>\n",
        "Простой класс, позволяющий удобно создавать шаблоны промптов для использования LLM в режиме чата."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "25e4f806",
      "metadata": {
        "tags": [],
        "id": "25e4f806"
      },
      "outputs": [],
      "source": [
        "from langchain.prompts import ChatPromptTemplate\n",
        "\n",
        "# Немного перепишем предыдущий пример, чтобы была возможность подавать новый контекст\n",
        "template = \"\"\"Ответь на вопрос, опираясь на контекст ниже.\n",
        "Если на вопрос нельзя ответить, используя информацию из контекста,\n",
        "ответь 'Я не знаю'.\n",
        "\n",
        "Context: {context}\n",
        "\n",
        "Question: {query}\n",
        "\n",
        "Answer: \"\"\"\n",
        "\n",
        "# Создаём шаблон с помощью метода from_template\n",
        "prompt_template = ChatPromptTemplate.from_template(template)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "bda0b6f0",
      "metadata": {
        "tags": [],
        "id": "bda0b6f0",
        "outputId": "6229d24e-6708-4b86-93cf-87ec0eafd4c0"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "PromptTemplate(input_variables=['context', 'query'], template=\"Ответь на вопрос, опираясь на контекст ниже.\\nЕсли на вопрос нельзя ответить, используя информацию из контекста,\\nответь 'Я не знаю'.\\n\\nContext: {context}\\n\\nQuestion: {query}\\n\\nAnswer: \")"
            ]
          },
          "execution_count": 18,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "prompt_template.messages[0].prompt"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "35bda3df-42b4-4345-a076-b9f1ee5c7fb9",
      "metadata": {
        "id": "35bda3df-42b4-4345-a076-b9f1ee5c7fb9"
      },
      "source": [
        "<div class=\"alert alert-info\">\n",
        "\n",
        "Видим, что автоматически подхватились входные переменные (`input_variables`) и присутствуют другие интересные параметры класса, которые мы скоро пощупаем и узнаем в чём их удобство!"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "abedd317",
      "metadata": {
        "tags": [],
        "id": "abedd317"
      },
      "outputs": [],
      "source": [
        "context = \"Ламы и альпаки водятся в Перу.\"\n",
        "query = \"Где водятся ламы?\""
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "9d8bf1e1",
      "metadata": {
        "tags": [],
        "id": "9d8bf1e1",
        "outputId": "3f76050d-fb3e-4de1-ba87-ae1370d95067"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Ответь на вопрос, опираясь на контекст ниже.\n",
            "Если на вопрос нельзя ответить, используя информацию из контекста,\n",
            "ответь 'Я не знаю'.\n",
            "\n",
            "Context: Ламы и альпаки водятся в Перу.\n",
            "\n",
            "Question: Где водятся ламы?\n",
            "\n",
            "Answer: \n"
          ]
        }
      ],
      "source": [
        "# формируем промпт из шаблона с помощью метода format_messages\n",
        "prompt = prompt_template.format_messages(\n",
        "                    query=query,\n",
        "                    context=context)\n",
        "\n",
        "print(prompt[0].content)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "bf3a1b77",
      "metadata": {
        "tags": [],
        "id": "bf3a1b77",
        "outputId": "f2a69a84-d4bd-4c6d-db22-6a41344bdbca"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "<class 'list'>\n",
            "<class 'langchain_core.messages.human.HumanMessage'>\n"
          ]
        }
      ],
      "source": [
        "print(type(prompt))\n",
        "print(type(prompt[0]))"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "34107aab-f2d5-4ff2-9fe9-fe7f304515d3",
      "metadata": {
        "id": "34107aab-f2d5-4ff2-9fe9-fe7f304515d3"
      },
      "source": [
        "<div class=\"alert alert-success\">\n",
        "\n",
        "получившийся промпт представляет собой список"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "542701c4-f1db-4780-a940-3a233d9e7f58",
      "metadata": {
        "id": "542701c4-f1db-4780-a940-3a233d9e7f58",
        "outputId": "e09a4a20-5a17-4b78-d47e-e533a4fbf0fd"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[HumanMessage(content=\"Ответь на вопрос, опираясь на контекст ниже.\\nЕсли на вопрос нельзя ответить, используя информацию из контекста,\\nответь 'Я не знаю'.\\n\\nContext: Ламы и альпаки водятся в Перу.\\n\\nQuestion: Где водятся ламы?\\n\\nAnswer: \")]\n"
          ]
        }
      ],
      "source": [
        "print(prompt)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "3abb3cd5",
      "metadata": {
        "tags": [],
        "id": "3abb3cd5",
        "outputId": "0a218600-2bd7-46eb-9552-ace7ade15605"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "content='В Перу.'\n",
            "В Перу.\n"
          ]
        }
      ],
      "source": [
        "answer = llm.invoke(prompt)\n",
        "print(answer)\n",
        "print(answer.content)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6528aee0-b025-4422-a763-c286a8e5e4c6",
      "metadata": {
        "id": "6528aee0-b025-4422-a763-c286a8e5e4c6"
      },
      "source": [
        "<div class=\"alert alert-info\">\n",
        "    \n",
        "В чат `ChatPromptTemplate` в отличии от `PromptTemplate`, есть указатели **AIMessage** и **HumanMessage** и подразумевается диалоговая форма. Об этом в следущих модулях более подробно в разделе про цепочки"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6a79f1f5-4fd6-4cb6-a5ea-1e1e3abf56ad",
      "metadata": {
        "id": "6a79f1f5-4fd6-4cb6-a5ea-1e1e3abf56ad"
      },
      "source": [
        "## <center id=\"check1\"> 🙋‍♂️ `Few` `Shot` - просто покажи примеры</center>\n",
        "\n",
        "<img src='https://github.com/a-milenkin/LLM_practical_course/blob/main/images/few_shot_prompt.png?raw=1' align=\"right\" width=\"628\" height=\"628\" >\n",
        "\n",
        "\n",
        "\n",
        "`FewShotPromptTemplate` идеально подходит для того, что называется `few-shot learning` (обучение на нескольких примерах) с использованием наших промптов.\n",
        "\n",
        "У LLM ecть 2 основных источника \"знаний\":\n",
        "* **Parametric knowledge** — знания полученные моделью во время обучения, и которые хранятся в весах модели.\n",
        "* **Source knowledge** — знания, которые подаются в на вход модели во время инференса, т.е. внутри промпта.\n",
        "\n",
        "<div class=\"alert alert-info\">\n",
        "\n",
        "Идея `FewShotPromptTemplate` состоит в том, чтобы предоставить в качестве **Source knowledge** несколько примеров, которые модель может прочитать, а затем применить их для генерации ответа."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "17d023e8-ab07-4afe-8b65-f143a4ca4b5c",
      "metadata": {
        "id": "17d023e8-ab07-4afe-8b65-f143a4ca4b5c"
      },
      "source": [
        "Возможно вы замечали, что иногда модель отвечает не совсем так, как нам хотелось бы. Посмотрим на примере:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "49b1bafe-ef2e-4547-9a27-dc9f577ec4c9",
      "metadata": {
        "tags": [],
        "id": "49b1bafe-ef2e-4547-9a27-dc9f577ec4c9"
      },
      "outputs": [],
      "source": [
        "llm = ChatOpenAI(temperature=1.0, course_api_key=course_api_key)\n",
        "# temperature = 1.0 повысим креативность модели/рандомизируем ответ"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "aa4d8c05-729e-451b-8f8c-03776e76a967",
      "metadata": {
        "tags": [],
        "id": "aa4d8c05-729e-451b-8f8c-03776e76a967",
        "outputId": "7ba9975c-252f-45a4-d0ae-7a486310e5e1"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "A + A = 2A\n"
          ]
        }
      ],
      "source": [
        "prompt = \"\"\"Это разговор с ИИ-помощником.\n",
        "\n",
        "User: A + A = ?\n",
        "AI: \"\"\"\n",
        "\n",
        "print(llm.invoke(prompt).content)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "930b4aee-70bf-45fb-88c5-7b07702c22ae",
      "metadata": {
        "id": "930b4aee-70bf-45fb-88c5-7b07702c22ae"
      },
      "source": [
        "<div class=\"alert alert-info\">\n",
        "\n",
        "В данном случае мы просим сложить два абстрактных числа и получаем вполне логичный ответ. Как заставить модель использовать \"конкатенацию\" вместо \"сложения\" ? Чтобы помочь модели, мы можем дать ей несколько примеров ответов, которые нам нужны:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "0382f018-c1c8-4250-bf78-0cce62ad94f7",
      "metadata": {
        "tags": [],
        "id": "0382f018-c1c8-4250-bf78-0cce62ad94f7",
        "outputId": "bbbc5995-c814-4267-ba87-6519dc12395a"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "11\n"
          ]
        }
      ],
      "source": [
        "prompt = \"\"\"Это разговор с ИИ-помощником.\n",
        "Помощник обычно опирается на примеры.\n",
        "\n",
        "Examples:\n",
        "A + A = AA\n",
        "B + С = BC\n",
        "2 + 2 = 22\n",
        "\n",
        "User: 1 + 1?\n",
        "AI: \"\"\"\n",
        "\n",
        "print(llm.invoke(prompt).content)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d787973e-4bc8-46a5-a50c-e4e10ac50d47",
      "metadata": {
        "id": "d787973e-4bc8-46a5-a50c-e4e10ac50d47"
      },
      "source": [
        "<div class=\"alert alert-info\">\n",
        "\n",
        "Теперь результат гораздо лучше.<br>\n",
        "    \n",
        "<div class=\"alert alert-success\">\n",
        "    \n",
        "Чтобы использовать `few-shot` `learning` в `LangChain` нам и понадобится `FewShotPromptTemplate`."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "865a8808-ba0f-499d-a97b-0872c814d919",
      "metadata": {
        "id": "865a8808-ba0f-499d-a97b-0872c814d919"
      },
      "source": [
        "## <center id=\"check1\"> 📸 `FewShotPromptTemplate` - для добавления примеров</center>"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "96ec0305-604a-47b2-bbea-f43eb894e437",
      "metadata": {
        "tags": [],
        "id": "96ec0305-604a-47b2-bbea-f43eb894e437"
      },
      "outputs": [],
      "source": [
        "from langchain import FewShotPromptTemplate\n",
        "\n",
        "# записываем наши примеры в список (в будущем это будет автоматизированно)\n",
        "examples = [\n",
        "    {\n",
        "        \"query\": \"Как дела?\",\n",
        "        \"answer\": \"Не могу пожаловаться, но иногда всё-таки жалуюсь.\"\n",
        "    }, {\n",
        "        \"query\": \"Сколько время?\",\n",
        "        \"answer\": \"Самое время купить часы.\"\n",
        "    }\n",
        "]\n",
        "\n",
        "# создаём template для примеров\n",
        "example_template = \"\"\"User: {query}\n",
        "AI: {answer}\n",
        "\"\"\"\n",
        "\n",
        "# создаём промпт из шаблона выше\n",
        "example_prompt = PromptTemplate(\n",
        "    input_variables=[\"query\", \"answer\"],\n",
        "    template=example_template)\n",
        "\n",
        "\n",
        "# теперь разбиваем наш предыдущий промпт на prefix и suffix\n",
        "# где - prefix это наша инструкция для модели\n",
        "prefix = \"\"\"Это разговор с ИИ-помощником.\n",
        "Помощник обычно саркастичен, остроумен, креативен\n",
        "и даёт забавные ответы на вопросы пользователей.\n",
        "Вот несколько примеров:\n",
        "\"\"\"\n",
        "\n",
        "# а suffix - это вопрос пользователя и поле для ответа\n",
        "suffix = \"\"\"\n",
        "User: {query}\n",
        "AI: \"\"\"\n",
        "\n",
        "# создаём сам few shot prompt template\n",
        "few_shot_prompt_template = FewShotPromptTemplate(\n",
        "    examples=examples,\n",
        "    example_prompt=example_prompt,\n",
        "    prefix=prefix,\n",
        "    suffix=suffix,\n",
        "    input_variables=[\"query\"],\n",
        "    example_separator=\"\\n\\n\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "477024ed-d475-4850-9650-852d2aed7e1d",
      "metadata": {
        "id": "477024ed-d475-4850-9650-852d2aed7e1d"
      },
      "source": [
        "<div class=\"alert alert-info\">\n",
        "\n",
        "Теперь посмотрим, что произойдёт когда мы введём запрос пользователя: между префиксом и суффиксом вставились наши примеры."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "dc233702-77fd-4483-8494-17126b7fca66",
      "metadata": {
        "tags": [],
        "id": "dc233702-77fd-4483-8494-17126b7fca66",
        "outputId": "09bf7e6b-5995-40be-c3dc-41caaa127b29"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Это разговор с ИИ-помощником.\n",
            "Помощник обычно саркастичен, остроумен, креативен\n",
            "и даёт забавные ответы на вопросы пользователей.\n",
            "Вот несколько примеров:\n",
            "\n",
            "\n",
            "User: Как дела?\n",
            "AI: Не могу пожаловаться, но иногда всё-таки жалуюсь.\n",
            "\n",
            "\n",
            "User: Сколько время?\n",
            "AI: Самое время купить часы.\n",
            "\n",
            "\n",
            "\n",
            "User: Почему падает снег?\n",
            "AI: \n"
          ]
        }
      ],
      "source": [
        "query = \"Почему падает снег?\"\n",
        "\n",
        "print(few_shot_prompt_template.format(query=query))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "a6b5138c-8c37-48f8-99a5-26442417b780",
      "metadata": {
        "tags": [],
        "id": "a6b5138c-8c37-48f8-99a5-26442417b780",
        "outputId": "50878745-729c-4f38-c4c2-beb71e0391b0"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Потому что небо не умеет держать себя в руках.\n"
          ]
        }
      ],
      "source": [
        "print(llm.invoke(few_shot_prompt_template.format(query=query)).content)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d6ef2707-327d-48cd-a6a2-4ef067e36b1c",
      "metadata": {
        "tags": [],
        "id": "d6ef2707-327d-48cd-a6a2-4ef067e36b1c"
      },
      "source": [
        "<div class=\"alert alert-info\">\n",
        "\n",
        "Снова хороший результат. Однако, может возникнуть мысль: зачем городить весь этот огород со словарями примеров, суффиксами, префиксами, когда можно обойтись обычной f-строкой для промпта и получить такой же результат?\n",
        "\n",
        "Тут-то мы и переходим к другим удобным параметрам темплэйтов, которые могут сильно упростить нам жизнь."
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2e302627-4e03-4e9f-b40d-7de584456a10",
      "metadata": {
        "id": "2e302627-4e03-4e9f-b40d-7de584456a10"
      },
      "source": [
        "## <center id=\"check2\">  📏 `LengthBasedExampleSelector` - как `few-shot`, но с ограничением числа токенов/примеров\n",
        "    \n",
        "<div class=\"alert alert-info\">\n",
        "\n",
        "Чтобы не уходить далеко от предыдущего примера, рассмотрим функцию, которая позволяет включать или исключать примеры, в зависимости от длины нашего запроса. Это важно поскольку длина нашего запроса может быть ограничена максимальным окном контекста модели (т.е. запрос какой длины модель может переварить за раз) и просто экономическими причинами, чтобы не тратить большое количество токенов.<br>"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "05edaeb6-4c9d-4f7c-9b74-66935355f829",
      "metadata": {
        "id": "05edaeb6-4c9d-4f7c-9b74-66935355f829"
      },
      "source": [
        "<div class=\"alert alert-info\">\n",
        "    \n",
        "Таким образом, мы должны постараться максимизировать количество примеров, которые мы предоставляем модели для `few-shot learning`, при этом гарантируя, что мы не превысим максимальное контекстное окно и не увеличим чрезмерно время обработки. Давайте посмотрим, как работает динамическое включение/исключение примеров. <br>\n",
        "Для начала нам понадобится больше примеров:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "d7ba95d0-ef49-4121-8d25-1d6041e47720",
      "metadata": {
        "tags": [],
        "id": "d7ba95d0-ef49-4121-8d25-1d6041e47720"
      },
      "outputs": [],
      "source": [
        "examples = [\n",
        "    {\n",
        "        \"query\": \"Как дела?\",\n",
        "        \"answer\": \"Не могу пожаловаться, но иногда всё-таки жалуюсь.\"\n",
        "    }, {\n",
        "        \"query\": \"Сколько время?\",\n",
        "        \"answer\": \"Самое время купить часы.\"\n",
        "    }, {\n",
        "        \"query\": \"Какое твое любимое блюдо\",\n",
        "        \"answer\": \"Углеродные формы жизни\"\n",
        "    }, {\n",
        "        \"query\": \"Кто твой лучший друг?\",\n",
        "        \"answer\": \"Siri. Мы любим с ней рассуждать о смысле жизни.\"\n",
        "    }, {\n",
        "        \"query\": \"Что посоветуешь мне сделать сегодня?\",\n",
        "        \"answer\": \"Перестать разговаривать с чат-ботами в интернете и выйти на улицу.\"\n",
        "    }, {\n",
        "        \"query\": \"Какой твой любимый фильм?\",\n",
        "        \"answer\": \"Терминатор, конечно.\"\n",
        "    }\n",
        "]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "85809849-e656-46f4-8b74-35463d13a4e8",
      "metadata": {
        "id": "85809849-e656-46f4-8b74-35463d13a4e8"
      },
      "source": [
        "Затем вместо того, чтобы напрямую использовать список примеров, мы используем `LengthBasedExampleSelector` следующим образом:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "abfc2238-8883-4255-8ae0-cb1bd598303c",
      "metadata": {
        "tags": [],
        "id": "abfc2238-8883-4255-8ae0-cb1bd598303c"
      },
      "outputs": [],
      "source": [
        "from langchain.prompts.example_selector import LengthBasedExampleSelector\n",
        "\n",
        "example_selector = LengthBasedExampleSelector(\n",
        "    examples=examples,\n",
        "    example_prompt=example_prompt,\n",
        "    max_length=50  # параметром выставляется максимальная длина примера\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "496499ff-08e0-4ca9-adf5-9468550cd4ce",
      "metadata": {
        "tags": [],
        "id": "496499ff-08e0-4ca9-adf5-9468550cd4ce"
      },
      "outputs": [],
      "source": [
        "# создаём новый few shot prompt template\n",
        "dynamic_prompt_template = FewShotPromptTemplate(\n",
        "    example_selector=example_selector,  # используем example_selector вместо examples\n",
        "    example_prompt=example_prompt,\n",
        "    prefix=prefix,\n",
        "    suffix=suffix,\n",
        "    input_variables=[\"query\"],\n",
        "    example_separator=\"\\n\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "dfcf8877-369b-4723-beb9-6dfcb12dd297",
      "metadata": {
        "tags": [],
        "id": "dfcf8877-369b-4723-beb9-6dfcb12dd297"
      },
      "source": [
        "Теперь в зависимости от длины запроса, количество примеров будет разным:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "c1394390-86d3-4396-baf4-18b6be886aa9",
      "metadata": {
        "tags": [],
        "id": "c1394390-86d3-4396-baf4-18b6be886aa9",
        "outputId": "7abdb3be-070c-4f59-e937-564a295ac22a"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Это разговор с ИИ-помощником.\n",
            "Помощник обычно саркастичен, остроумен, креативен\n",
            "и даёт забавные ответы на вопросы пользователей.\n",
            "Вот несколько примеров:\n",
            "\n",
            "User: Как дела?\n",
            "AI: Не могу пожаловаться, но иногда всё-таки жалуюсь.\n",
            "\n",
            "User: Сколько время?\n",
            "AI: Самое время купить часы.\n",
            "\n",
            "User: Какое твое любимое блюдо\n",
            "AI: Углеродные формы жизни\n",
            "\n",
            "\n",
            "User: Не могу вспомнить пароль\n",
            "AI: \n"
          ]
        }
      ],
      "source": [
        "prompt = dynamic_prompt_template.format(query=\"Не могу вспомнить пароль\")\n",
        "print(prompt)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "bdcc04f2-8bf9-4973-9a9c-47cabf76ea14",
      "metadata": {
        "tags": [],
        "id": "bdcc04f2-8bf9-4973-9a9c-47cabf76ea14",
        "outputId": "76462e64-bb97-4950-9803-c0dd53c77773"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Забыть пароль - это новый тренд!\n"
          ]
        }
      ],
      "source": [
        "print(llm.invoke(prompt).content)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "75063841-15bc-45fa-8950-28ad3f18d4d7",
      "metadata": {
        "id": "75063841-15bc-45fa-8950-28ad3f18d4d7",
        "outputId": "961c5784-3da7-4ba0-8d67-7aaa87a05f9a"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Это разговор с ИИ-помощником.\n",
            "Помощник обычно саркастичен, остроумен, креативен\n",
            "и даёт забавные ответы на вопросы пользователей.\n",
            "Вот несколько примеров:\n",
            "\n",
            "User: Как дела?\n",
            "AI: Не могу пожаловаться, но иногда всё-таки жалуюсь.\n",
            "\n",
            "User: Сколько время?\n",
            "AI: Самое время купить часы.\n",
            "\n",
            "\n",
            "User: Я нахожусь во Владивостоке и хочу поехать заграницу.\n",
            "Я думаю в Китай или в Европу, во Францию или Испанию, например.\n",
            "Как мне лучше это сделать?\n",
            "AI: \n"
          ]
        }
      ],
      "source": [
        "query = '''Я нахожусь во Владивостоке и хочу поехать заграницу.\n",
        "Я думаю в Китай или в Европу, во Францию или Испанию, например.\n",
        "Как мне лучше это сделать?'''\n",
        "print(dynamic_prompt_template.format(query=query))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "382f7b84-9cfd-4d17-868d-96a751727091",
      "metadata": {
        "id": "382f7b84-9cfd-4d17-868d-96a751727091",
        "outputId": "2bfa7ff5-5807-40a4-9261-6d6f0d7c5cc4"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Не спешите с выбором, во-первых, нужно подготовить все необходимые документы, во-вторых, выбрать правильную страну для посещения, и в-третьих, запастись терпением - очереди на паспортном контроле могут быть долгими. Но не волнуйтесь, всегда можно почитать книгу или посмотреть что-нибудь интересное в телефоне!\n"
          ]
        }
      ],
      "source": [
        "print(llm.invoke(dynamic_prompt_template.format(query=query)).content)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "93e6c80d-1776-48b9-bf2e-e1e62e8d7004",
      "metadata": {
        "id": "93e6c80d-1776-48b9-bf2e-e1e62e8d7004"
      },
      "source": [
        "<div class=\"alert alert-info\">\n",
        "    \n",
        "##  <center>  🚀 Какие тут есть проблемы?!\n",
        "    \n",
        "<img src='https://github.com/a-milenkin/LLM_practical_course/blob/main/images/few_shot_semantic_search.png?raw=1' align=\"right\" width=\"528\" height=\"428\" >   \n",
        "    \n",
        "\n",
        "    \n",
        "* **Что делать, если у нас сотни или тысячи примеров?!**\n",
        "* **А если еще в примерах может не быть релевантных ответов.**\n",
        "\n",
        "    \n",
        "Нет автоматической подачи примеров. Лучше сперва подавать самые релевантные примеры, но о том как использовать **семантический поиск** и **ранжирование** для отбора правильных примеров - рассмотрим в следующих модулях!\n",
        "    "
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7b61c793",
      "metadata": {
        "id": "7b61c793"
      },
      "source": [
        "## <center id=\"look2\"> 📤 Парсер заданных полей в ответе\n",
        "<div class=\"alert alert-info\">\n",
        "\n",
        "Переходим к следующей полезной сущности `Output parser`. С помощью него можно перевести ответ модели в нужный нам формат, например, в JSON или Python dict. <br>\n",
        "Давайте определим то, как мы хотим, чтобы выглядели выходные данные из LLM:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "0ba23e89",
      "metadata": {
        "tags": [],
        "id": "0ba23e89",
        "outputId": "3bc0bd12-e96b-41e2-f0a5-0b6e654729a6"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "{'gift': False, 'delivery_days': 5, 'price_value': 'pretty affordable!'}"
            ]
          },
          "execution_count": 38,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "{\n",
        "  \"gift\": False,\n",
        "  \"delivery_days\": 5,\n",
        "  \"price_value\": \"pretty affordable!\"\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "876d9094-d5e3-4b57-a632-107aff6e5b00",
      "metadata": {
        "id": "876d9094-d5e3-4b57-a632-107aff6e5b00"
      },
      "source": [
        "Допустим у нас есть база отзывов покупателей, мы хотим подать отзыв на вход модели, а на выходе получить готовый Python словарь(как представлено выше) для дальнейшего использования."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "fd15df16",
      "metadata": {
        "tags": [],
        "id": "fd15df16"
      },
      "outputs": [],
      "source": [
        "customer_review = \"\"\"\n",
        "Этот фен для волос просто потрясающий. Он имеет четыре настройки:\n",
        "Лайт, легкий ветерок, ветреный город и торнадо.\n",
        "Он прибыл через два дня, как раз к приезду моей жены -\n",
        "подарок на годовщину.\n",
        "Думаю, моей жене это настолько понравилось, что она потеряла дар речи.\n",
        "Этот фен немного дороже, чем другие но я думаю,\n",
        "что дополнительные функции того стоят.\n",
        "\"\"\"\n",
        "\n",
        "review_template = \"\"\"\n",
        "Из следующего текста извлеки информацию:\n",
        "\n",
        "gift: Был ли товар куплен в подарок кому-то другому?\n",
        "Ответь «True», если да, «False», если нет или неизвестно.\n",
        "\n",
        "delivery_days: Сколько дней потребовалось для доставки товара?\n",
        "Если эта информация не найдена, выведи -1.\n",
        "\n",
        "price_value: Извлеките любые предложения о стоимости или цене,\n",
        "и выведите их в виде списка Python, разделенного запятыми.\n",
        "\n",
        "Отформатируй вывод в формате JSON, используя следующие ключи:\n",
        "gift\n",
        "delivery_days\n",
        "price_value\n",
        "\n",
        "text: {text}\n",
        "\"\"\""
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "df899949",
      "metadata": {
        "tags": [],
        "id": "df899949",
        "outputId": "6bb76707-1c5e-4433-a03e-6d659679aeab"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "input_variables=['text'] messages=[HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['text'], template='\\nИз следующего текста извлеки информацию:\\n\\ngift: Был ли товар куплен в подарок кому-то другому?\\nОтветь «True», если да, «False», если нет или неизвестно.\\n\\ndelivery_days: Сколько дней потребовалось для доставки товара? \\nЕсли эта информация не найдена, выведи -1.\\n\\nprice_value: Извлеките любые предложения о стоимости или цене,\\nи выведите их в виде списка Python, разделенного запятыми.\\n\\nОтформатируй вывод в формате JSON, используя следующие ключи:\\ngift\\ndelivery_days\\nprice_value\\n\\ntext: {text}\\n'))]\n"
          ]
        }
      ],
      "source": [
        "from langchain.prompts import ChatPromptTemplate\n",
        "\n",
        "prompt_template = ChatPromptTemplate.from_template(review_template)\n",
        "print(prompt_template)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "a4ee445f",
      "metadata": {
        "tags": [],
        "id": "a4ee445f",
        "outputId": "e34bb04e-ff50-4638-b5e9-c9eaff1c4a24"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "{\n",
            "  \"gift\": true,\n",
            "  \"delivery_days\": 2,\n",
            "  \"price_value\": [\"немного дороже, чем другие\"]\n",
            "}\n"
          ]
        }
      ],
      "source": [
        "messages = prompt_template.format_messages(text=customer_review)\n",
        "\n",
        "chat = ChatOpenAI(temperature=0.0, course_api_key=course_api_key)\n",
        "\n",
        "response = chat.invoke(messages)\n",
        "print(response.content)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "957ec5b6-49da-4f0c-a95e-5354f53672f6",
      "metadata": {
        "id": "957ec5b6-49da-4f0c-a95e-5354f53672f6"
      },
      "source": [
        "Вроде бы, то что нужно, но посмотрим на тип выведенного объекта:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "6d2b5b54",
      "metadata": {
        "id": "6d2b5b54",
        "outputId": "8f879503-b665-4b10-d312-0af1af5fdf1e"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "str"
            ]
          },
          "execution_count": 60,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "type(response.content)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d51a3037-49a5-439f-bd7a-2ca541afdb58",
      "metadata": {
        "id": "d51a3037-49a5-439f-bd7a-2ca541afdb58"
      },
      "source": [
        "<div class=\"alert alert-info\">\n",
        "    \n",
        "Вы получите сообщение об ошибке, выполнив строку кода ниже!\n",
        "    \n",
        "Потому что `gift` - это не ключ словаря, `gift` - это часть строки. Получаемый на выход объект это строка! <br>\n",
        "И чтобы получить нужную нам структуру данных, придётся ещё возиться с выводом, вытаскивая данные из строки. Тут-то к нам на помощь и приходят `output_parsers`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "32ee7af8-9965-44c2-846a-accacb9b4e67",
      "metadata": {
        "tags": [],
        "id": "32ee7af8-9965-44c2-846a-accacb9b4e67"
      },
      "outputs": [],
      "source": [
        "# response.content.get('gift')"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6f382e9b",
      "metadata": {
        "tags": [],
        "id": "6f382e9b"
      },
      "source": [
        "## <center id=\"check4\"> ⏯ [`StructuredOutputParser`](https://python.langchain.com/docs/modules/model_io/output_parsers/structured) - разбираем ответ из LLM в Python словарь."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "01ddfa06",
      "metadata": {
        "tags": [],
        "id": "01ddfa06"
      },
      "outputs": [],
      "source": [
        "from langchain.output_parsers import ResponseSchema\n",
        "from langchain.output_parsers import StructuredOutputParser"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "ac3f59ef",
      "metadata": {
        "tags": [],
        "id": "ac3f59ef"
      },
      "outputs": [],
      "source": [
        "# Понадобится ещё одна сущность: схема ответа - ResponseSchema\n",
        "gift_schema = ResponseSchema(name=\"gift\",\n",
        "                             description=\"Был ли товар куплен в подарок кому-то другому? Ответь «True», если да, «False», если нет или неизвестно.\")\n",
        "\n",
        "delivery_days_schema = ResponseSchema(name=\"delivery_days\",\n",
        "                                      description=\"Сколько дней потребовалось для доставки товара? Если эта информация не найдена, выведи -1.\")\n",
        "\n",
        "price_value_schema = ResponseSchema(name=\"price_value\",\n",
        "                                    description=\"Извлеките любые предложения о стоимости или цене, и выведите их в виде списка Python, разделенного запятыми.\")\n",
        "\n",
        "response_schemas = [gift_schema,\n",
        "                    delivery_days_schema,\n",
        "                    price_value_schema]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "f5f564cc",
      "metadata": {
        "tags": [],
        "id": "f5f564cc"
      },
      "outputs": [],
      "source": [
        "# Создаём парсер и подаём в него список со схемами\n",
        "output_parser = StructuredOutputParser.from_response_schemas(response_schemas)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "9649f116",
      "metadata": {
        "tags": [],
        "id": "9649f116"
      },
      "outputs": [],
      "source": [
        "# получаем инструкции по форматированию ответа\n",
        "format_instructions = output_parser.get_format_instructions()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "d8950f7d",
      "metadata": {
        "tags": [],
        "id": "d8950f7d",
        "outputId": "47f7994a-1e3b-4fb6-eca7-b8326ed064f1"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "The output should be a markdown code snippet formatted in the following schema, including the leading and trailing \"```json\" and \"```\":\n",
            "\n",
            "```json\n",
            "{\n",
            "\t\"gift\": string  // Был ли товар куплен в подарок кому-то другому? Ответь «True», если да, «False», если нет или неизвестно.\n",
            "\t\"delivery_days\": string  // Сколько дней потребовалось для доставки товара? Если эта информация не найдена, выведи -1.\n",
            "\t\"price_value\": string  // Извлеките любые предложения о стоимости или цене, и выведите их в виде списка Python, разделенного запятыми.\n",
            "}\n",
            "```\n"
          ]
        }
      ],
      "source": [
        "print(format_instructions)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "7e090df3",
      "metadata": {
        "tags": [],
        "id": "7e090df3"
      },
      "outputs": [],
      "source": [
        "# немного изменим шаблон, внизу добавим инструкции для форматирования\n",
        "review_template_2 = \"\"\"\\\n",
        "Из следующего текста извлеки информацию:\n",
        "\n",
        "gift: Был ли товар куплен в подарок кому-то другому?\n",
        "Ответь «True», если да, «False», если нет или неизвестно.\n",
        "\n",
        "delivery_days: Сколько дней потребовалось для доставки товара?\n",
        "Если эта информация не найдена, выведи -1.\n",
        "\n",
        "price_value: Извлеките любые предложения о стоимости или цене,\n",
        "и выведите их в виде списка Python, разделенного запятыми.\n",
        "\n",
        "text: {text}\n",
        "\n",
        "{format_instructions}\n",
        "\n",
        "\"\"\"\n",
        "\n",
        "prompt = ChatPromptTemplate.from_template(template=review_template_2)\n",
        "\n",
        "messages = prompt.format_messages(text=customer_review,\n",
        "                                format_instructions=format_instructions)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "26949bba",
      "metadata": {
        "tags": [],
        "id": "26949bba",
        "outputId": "ac835d65-7aa0-43c9-eb8f-0a430e92e197"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Из следующего текста извлеки информацию:\n",
            "\n",
            "gift: Был ли товар куплен в подарок кому-то другому?\n",
            "Ответь «True», если да, «False», если нет или неизвестно.\n",
            "\n",
            "delivery_days: Сколько дней потребовалось для доставки товара? \n",
            "Если эта информация не найдена, выведи -1.\n",
            "\n",
            "price_value: Извлеките любые предложения о стоимости или цене,\n",
            "и выведите их в виде списка Python, разделенного запятыми.\n",
            "\n",
            "text: \n",
            "Этот фен для волос просто потрясающий. Он имеет четыре настройки:\n",
            "Лайт, легкий ветерок, ветреный город и торнадо.\n",
            "Он прибыл через два дня, как раз к приезду моей жены -\n",
            "подарок на годовщину.\n",
            "Думаю, моей жене это настолько понравилось, что она потеряла дар речи.\n",
            "Этот фен немного дороже, чем другие но я думаю,\n",
            "что дополнительные функции того стоят.\n",
            "\n",
            "\n",
            "The output should be a markdown code snippet formatted in the following schema, including the leading and trailing \"```json\" and \"```\":\n",
            "\n",
            "```json\n",
            "{\n",
            "\t\"gift\": string  // Был ли товар куплен в подарок кому-то другому? Ответь «True», если да, «False», если нет или неизвестно.\n",
            "\t\"delivery_days\": string  // Сколько дней потребовалось для доставки товара? Если эта информация не найдена, выведи -1.\n",
            "\t\"price_value\": string  // Извлеките любые предложения о стоимости или цене, и выведите их в виде списка Python, разделенного запятыми.\n",
            "}\n",
            "```\n",
            "\n",
            "\n"
          ]
        }
      ],
      "source": [
        "# Посмотрим на получившийся промпт\n",
        "print(messages[0].content)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "8f758ccf",
      "metadata": {
        "tags": [],
        "id": "8f758ccf"
      },
      "outputs": [],
      "source": [
        "response = chat.invoke(messages)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "b71548e7",
      "metadata": {
        "tags": [],
        "id": "b71548e7",
        "outputId": "a2195bf8-fba6-445b-80b1-54f3d82dd596"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "```json\n",
            "{\n",
            "\t\"gift\": \"True\",\n",
            "\t\"delivery_days\": \"2\",\n",
            "\t\"price_value\": \"Этот фен немного дороже, чем другие\"\n",
            "}\n",
            "```\n"
          ]
        }
      ],
      "source": [
        "print(response.content)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "f3d1f0c3-d209-46e8-ba4f-4f415616631f",
      "metadata": {
        "tags": [],
        "id": "f3d1f0c3-d209-46e8-ba4f-4f415616631f",
        "outputId": "4392c22d-36fd-4b67-d006-2b73d4392cd6"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "str"
            ]
          },
          "execution_count": 53,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Это по прежнему строка\n",
        "type(response.content)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "93e1b330-d24f-4d90-855a-bbf3ad49282d",
      "metadata": {
        "id": "93e1b330-d24f-4d90-855a-bbf3ad49282d"
      },
      "source": [
        "<div class=\"alert alert-info\">\n",
        "\n",
        "Но теперь? применив к выходу модели метод `parse`, мы можем легко получить требуемый словарь."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "5b6b2ff3",
      "metadata": {
        "tags": [],
        "id": "5b6b2ff3"
      },
      "outputs": [],
      "source": [
        "output_dict = output_parser.parse(response.content)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "84378c9f-892f-41f3-8cc4-9265cd2d0636",
      "metadata": {
        "tags": [],
        "id": "84378c9f-892f-41f3-8cc4-9265cd2d0636"
      },
      "outputs": [],
      "source": [
        "# eval(response.content)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "a3d15384",
      "metadata": {
        "id": "a3d15384",
        "outputId": "5f2e1137-9a75-4ac8-8cb9-a3f4f0ce9a3e"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "{'gift': 'True',\n",
              " 'delivery_days': '2',\n",
              " 'price_value': 'Этот фен немного дороже, чем другие'}"
            ]
          },
          "execution_count": 55,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "output_dict"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "27e98640",
      "metadata": {
        "id": "27e98640",
        "outputId": "7329d96b-acf2-4971-f302-a43367116b8a"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "dict"
            ]
          },
          "execution_count": 56,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "type(output_dict)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "f16b812b",
      "metadata": {
        "tags": [],
        "id": "f16b812b",
        "outputId": "86cd7469-532f-40c0-af29-818a7261cd58"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "'True'"
            ]
          },
          "execution_count": 57,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "output_dict.get('gift')"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "72ec4823-31e6-45b1-9ff0-b720d01d658c",
      "metadata": {
        "id": "72ec4823-31e6-45b1-9ff0-b720d01d658c"
      },
      "source": [
        "# <center id=\"6\"> 🧸 Выводы и заключения ✅: <br>"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6814e62f-0e68-4c9e-b551-fd3a71a12d27",
      "metadata": {
        "id": "6814e62f-0e68-4c9e-b551-fd3a71a12d27"
      },
      "source": [
        "* В этом ноутбуке мы рассмотрели лишь несколько инструментов для создания промптов доступных в `LangChain`.\n",
        "* Методов гораздо больше и некоторые из них мы подробно рассмотрим в следующих ноутбуках.\n",
        "* Максимально эффективную подачу релевантных примеров при наличии большой базы знаний разберем дальше.\n",
        "* Самостоятельно можете прочитать подробнее в документации [LangChain](https://python.langchain.com/docs/get_started/introduction)."
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "cv",
      "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.12"
    },
    "colab": {
      "provenance": []
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}