{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "0dddb850",
      "metadata": {
        "id": "0dddb850"
      },
      "source": [
        "# Лабораторная работа 3\n",
        "# Структурированный вывод больших языковых моделей (LLM)\n",
        "\n",
        "## Практическая задача\n",
        "\n",
        "LLM часто используются не только для генерации текста, но и для извлечения информации:\n",
        "\n",
        "- анализ резюме;\n",
        "- обработка документов;\n",
        "- заполнение баз данных;\n",
        "- создание API на основе естественного языка.\n",
        "\n",
        "В работе реализуется pipeline:\n",
        "\n",
        "```\n",
        "Текст документа\n",
        "        |\n",
        "        v\n",
        "       LLM\n",
        "        |\n",
        "        v\n",
        "     JSON\n",
        "        |\n",
        "        v\n",
        "  Pydantic validation\n",
        "        |\n",
        "        v\n",
        "Структурированные данные\n",
        "```\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "86782725",
      "metadata": {
        "id": "86782725"
      },
      "source": [
        "# 1. Описание структуры данных\n",
        "\n",
        "Создадим модель резюме кандидата.\n",
        "\n",
        "Необходимо описать:\n",
        "\n",
        "- имя кандидата;\n",
        "- контактную информацию;\n",
        "- опыт работы;\n",
        "- список должностей;\n",
        "- навыки.\n",
        "\n",
        "Попробуем извлечь из резюме в свободной форме структурированную информацию. Сначала импортируем библиотеку pydantic и с ее помощью зададим желаемую структуру"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "2cf7cc42",
      "metadata": {
        "id": "2cf7cc42"
      },
      "outputs": [],
      "source": [
        "from typing import List, Optional\n",
        "from pydantic import BaseModel, Field\n",
        "\n",
        "\n",
        "class Resume(BaseModel):\n",
        "    class PositionDescription(BaseModel):\n",
        "        company_name: str = Field(..., description=\"Name of the company where the candidate worked\")\n",
        "        position_title: str = Field(..., description=\"Name of the position the candidate worked in\")\n",
        "        start_date: str = Field(..., description=\"Start date for position\")\n",
        "        end_date: str = Field(..., description=\"End date for position, 'current' if not specified\")\n",
        "\n",
        "    work_experience: List[PositionDescription] = Field(...,\n",
        "                                                       description=\"List of positions in which the candidate worked\")\n",
        "    skills: List[str] = Field(..., description=\"List of skills found on resume\")\n",
        "    foreign_languages: List[str] = Field(...,\n",
        "                                         description=\"Foreign languages spoken by \"\n",
        "                                                     \"the candidate as indicated in the resume\")\n",
        "    native_language: str = Field(..., description=\"Candidate's native language\")\n",
        "    person_name: str = Field(..., description=\"Full name of the candidate\")\n",
        "    telephone_number: str = Field(..., description=\"Formated phone number of the candidate\")\n",
        "    contact_email: str\n",
        "    revision_date: Optional[str]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "83446972",
      "metadata": {
        "id": "83446972"
      },
      "source": [
        "# 2. Получение JSON Schema\n",
        "\n",
        "## Задание 1\n",
        "\n",
        "Преобразуйте модель `Resume` в JSON Schema.\n",
        "\n",
        "Сохраните результат в переменную `resume_schema`.\n",
        "\n",
        "Подумайте:\n",
        "\n",
        "- зачем модели нужен формат ответа?\n",
        "- почему JSON удобнее обычного текста?\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "42f21727",
      "metadata": {
        "id": "42f21727"
      },
      "outputs": [],
      "source": [
        "resume_schema = Resume.model_json_schema()\n",
        "resume_schema"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "84b78ed5",
      "metadata": {
        "id": "84b78ed5"
      },
      "source": [
        "# 3. Извлечение информации с помощью LLM\n",
        "\n",
        "Загрузите инструкционную модель:\n",
        "\n",
        "Например:\n",
        "\n",
        "- Qwen2.5-Instruct;\n",
        "- Hermes;\n",
        "- Mistral-Instruct.\n",
        "\n",
        "Создайте функцию:\n",
        "\n",
        "```python\n",
        "extract_resume(model, tokenizer, text, schema)\n",
        "```\n",
        "\n",
        "Функция должна:\n",
        "\n",
        "1. сформировать prompt;\n",
        "2. передать схему модели;\n",
        "3. получить JSON-ответ.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "Используйте следующий промпт для обращения к модели. Можете поэкспериментировать с ним"
      ],
      "metadata": {
        "id": "r7f2qDVgf6eU"
      },
      "id": "r7f2qDVgf6eU"
    },
    {
      "cell_type": "code",
      "source": [
        "user_prompt = \"\"\"Extract all the necessary information from this resume.\n",
        "\n",
        "Resume:\n",
        "<resume>\n",
        "{raw_resume}\n",
        "</resume>\n",
        "You must answer strictly in JSON. Here's the json schema you must adhere to:\n",
        "<schema>\n",
        "{resume_schema}\n",
        "</schema>\"\"\""
      ],
      "metadata": {
        "id": "yKkfqDU7f7M2"
      },
      "id": "yKkfqDU7f7M2",
      "execution_count": 1,
      "outputs": []
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "80469dd0",
      "metadata": {
        "id": "80469dd0"
      },
      "outputs": [],
      "source": [
        "def extract_resume(model, tokenizer, resume_text, schema):\n",
        "    # Реализуйте генерацию ответа LLM\n",
        "    pass\n"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "Примените созданную функцию к имеющимся резюме"
      ],
      "metadata": {
        "id": "gZtygs6qjSEa"
      },
      "id": "gZtygs6qjSEa"
    },
    {
      "cell_type": "code",
      "source": [],
      "metadata": {
        "id": "PUAK8K7cjlhh"
      },
      "id": "PUAK8K7cjlhh",
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "id": "74ade143",
      "metadata": {
        "id": "74ade143"
      },
      "source": [
        "# 4. Валидация результата\n",
        "\n",
        "LLM может вернуть:\n",
        "\n",
        "- неправильный JSON;\n",
        "- отсутствующие поля;\n",
        "- неверные типы данных.\n",
        "\n",
        "## Задание 2\n",
        "\n",
        "Создайте функцию:\n",
        "\n",
        "```python\n",
        "validate_output(json_string, model_class)\n",
        "```\n",
        "\n",
        "Функция должна возвращать:\n",
        "\n",
        "```\n",
        "(valid, result, error_message)\n",
        "```\n",
        "\n",
        "где:\n",
        "\n",
        "- valid=True если данные корректны;\n",
        "- result содержит объект Pydantic;\n",
        "- error_message содержит описание ошибки.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "60b4dcc0",
      "metadata": {
        "id": "60b4dcc0"
      },
      "outputs": [],
      "source": [
        "def validate_output(json_string, model_class):\n",
        "    try:\n",
        "        result = model_class.model_validate_json(json_string)\n",
        "        return True, result, \"\"\n",
        "    except Exception as e:\n",
        "        return False, None, str(e)\n"
      ]
    },
    {
      "cell_type": "code",
      "source": [],
      "metadata": {
        "id": "iejC85pWldSH"
      },
      "id": "iejC85pWldSH",
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "id": "c251f49e",
      "metadata": {
        "id": "c251f49e"
      },
      "source": [
        "# 5. Расширение структуры\n",
        "\n",
        "## Задание 3\n",
        "\n",
        "Добавьте информацию об образовании.\n",
        "\n",
        "Создайте класс:\n",
        "\n",
        "```python\n",
        "EducationInfo\n",
        "```\n",
        "\n",
        "с полями:\n",
        "\n",
        "- institution_name;\n",
        "- degree_name;\n",
        "- education_level (необязательно).\n",
        "\n",
        "Добавьте поле:\n",
        "\n",
        "```python\n",
        "education: List[EducationInfo]\n",
        "```\n",
        "\n",
        "в модель резюме.\n",
        "\n",
        "После изменения схемы проверьте работу извлечения данных заново.\n"
      ]
    },
    {
      "cell_type": "code",
      "source": [],
      "metadata": {
        "id": "m0Jl6oMSgT7L"
      },
      "id": "m0Jl6oMSgT7L",
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "id": "051a9bbf",
      "metadata": {
        "id": "051a9bbf"
      },
      "source": [
        "# 6. Анализ научных статей\n",
        "\n",
        "Создайте структуру:\n",
        "\n",
        "- авторы;\n",
        "- название;\n",
        "- метод;\n",
        "- результаты;\n",
        "- ключевые выводы."
      ]
    },
    {
      "cell_type": "code",
      "source": [],
      "metadata": {
        "id": "bpNG7tw0lelm"
      },
      "id": "bpNG7tw0lelm",
      "execution_count": null,
      "outputs": []
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "colab": {
      "provenance": []
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}