commit 6cd179333aedad5536e7ec8a34247b496832e30f Author: marat Date: Fri May 8 15:57:33 2026 +0300 feat: initial solution for task 2 - structured output with Pydantic and LangChain diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..12f3e22 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +OPENAI_API_KEY=your-api-key-here +OPENAI_BASE_URL=https://api.openai.com/v1 +OPENAI_MODEL=gpt-4o-mini diff --git a/README.md b/README.md new file mode 100644 index 0000000..0ae9bcb --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# Задание 2: Сырой текст задания → плоская карточка + +## Описание + +Модуль разбора одного формулировки задания в компактную структурированную сводку. +Использует LLM-цепочку (LangChain) + PydanticOutputParser для извлечения полей из неструктурированного текста. + +## Стек + +- Python 3.10+ +- langchain-core, langchain-openai, pydantic, python-dotenv + +## Установка + +```bash +pip install -r requirements.txt +cp .env.example .env +# Заполнить OPENAI_API_KEY в .env +``` + +## Запуск + +```bash +python main.py +``` + +## Что делает + +1. Описывает Pydantic-модель `TaskCard` с полями: `title`, `subject`, `deadline_hint`, `deliverable_type`, `grading_hints` +2. Собирает цепочку: шаблон промпта → LLM → PydanticOutputParser +3. В промпте явно просит модель вернуть данные в формате, ожидаемом парсером +4. Выводит валидированный объект (`model_dump()`) и краткую человекочитаемую сводку +5. Один запрос — один проход, без уточняющих вопросов + +## Пример вывода + +``` +📝 Входной текст: + "Сдайте к пятнице мини-отчёт по LangChain: 2 страницы, упор на агентов. Оценка: за полноту и за пример кода." + +=== Карточка задания === +Название: Мини-отчёт по LangChain +Предмет: LangChain +Дедлайн: к пятнице +Тип результата: отчёт +Критерии: полнота, пример кода + +=== JSON (model_dump) === +{'title': 'Мини-отчёт по LangChain', 'subject': 'LangChain', ...} +``` diff --git a/main.py b/main.py new file mode 100644 index 0000000..f931c3a --- /dev/null +++ b/main.py @@ -0,0 +1,128 @@ +""" +Задание 2: Сырой текст задания → плоская карточка + +Скрипт с LangChain + Pydantic, который из неструктурированного текста задания +извлекает структурированную карточку: title, subject, deadline_hint, +deliverable_type, grading_hints. +""" + +import os +from pydantic import BaseModel, Field +from langchain.output_parsers import PydanticOutputParser +from langchain.prompts import PromptTemplate +from langchain_openai import ChatOpenAI + + +# ─── Pydantic-модель карточки ─────────────────────────────────────────────── + +class TaskCard(BaseModel): + """Структурированная карточка задания, извлечённая из сырого текста.""" + + title: str = Field( + description="Краткое название задания (1–10 слов)" + ) + subject: str = Field( + description="Предмет / область знаний, к которой относится задание" + ) + deadline_hint: str = Field( + description="Указание на дедлайн, если есть в тексте; иначе — «не указан»" + ) + deliverable_type: str = Field( + description="Тип результата: например «код», «эссе», «презентация», «отчёт», «тест» и т.д." + ) + grading_hints: str = Field( + description="Подсказки по оцениванию: что будет проверяться, критерии, баллы — кратко" + ) + + +# ─── Настройка парсера и промпта ─────────────────────────────────────────── + +parser = PydanticOutputParser(pydantic_object=TaskCard) + +prompt = PromptTemplate( + template=( + "Ты — ассистент, который превращает неструктурированный текст учебного задания " + "в структурированную карточку.\n\n" + "Извлеки из текста ниже пять полей:\n" + "- title — краткое название задания\n" + "- subject — предмет / область\n" + "- deadline_hint — дедлайн (если указан, иначе «не указан»)\n" + "- deliverable_type — тип результата (код, эссе, презентация, отчёт, тест…)\n" + "- grading_hints — краткие критерии оценивания\n\n" + "Текст задания:\n" + "{raw_text}\n\n" + "{format_instructions}" + ), + input_variables=["raw_text"], + partial_variables={"format_instructions": parser.get_format_instructions()}, +) + + +# ─── Инициализация LLM ────────────────────────────────────────────────────── + +# Переменные окружения (можно задать через .env или export): +# OPENAI_API_BASE — URL API (по умолчанию https://api.openai.com/v1) +# OPENAI_API_KEY — ключ API +# OPENAI_MODEL — модель (по умолчанию gpt-4o-mini) + +api_base = os.environ.get("OPENAI_API_BASE", "https://api.openai.com/v1") +api_key = os.environ.get("OPENAI_API_KEY", "") +model_name = os.environ.get("OPENAI_MODEL", "gpt-4o-mini") + +llm = ChatOpenAI( + model=model_name, + openai_api_base=api_base, + openai_api_key=api_key, + temperature=0, +) + + +# ─── Цепочка (один проход) ────────────────────────────────────────────────── + +chain = prompt | llm | parser + + +# ─── Пример использования ─────────────────────────────────────────────────── + +SAMPLE_TASK = ( + "Напишите Python-скрипт, который парсит CSV-файл с данными о продажах " + "и строит сводный отчёт по месяцам. Код должен быть покрыт тестами (pytest). " + "Дедлайн — 15 марта 2025 года. Оценивается: корректность парсинга (30%), " + "качество тестов (30%), читаемость кода (20%), оформление README (20%). " + "Предмет: Программирование на Python." +) + + +def extract_card(raw_text: str) -> TaskCard: + """Извлечь структурированную карточку из сырого текста задания.""" + result = chain.invoke({"raw_text": raw_text}) + return result + + +def print_card(card: TaskCard) -> None: + """Красивый вывод карточки.""" + print("=" * 60) + print(" СТРУКТУРИРОВАННАЯ КАРТОЧКА ЗАДАНИЯ") + print("=" * 60) + for key, value in card.model_dump().items(): + print(f" {key:20s}: {value}") + print("=" * 60) + print() + print("model_dump():") + print(card.model_dump()) + print() + print("── Краткая сводка ──") + print(f" 📌 Задание : {card.title}") + print(f" 📚 Предмет : {card.subject}") + print(f" ⏰ Дедлайн : {card.deadline_hint}") + print(f" 📦 Результат: {card.deliverable_type}") + print(f" ✅ Критерии : {card.grading_hints}") + + +if __name__ == "__main__": + print("Образец входного текста:") + print(f' "{SAMPLE_TASK}"') + print() + + card = extract_card(SAMPLE_TASK) + print_card(card) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e755400 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +langchain-core>=0.3.0 +langchain-openai>=0.2.0 +pydantic>=2.0.0 +python-dotenv>=1.0.0