feat: initial solution for task 2 - structured output with Pydantic and LangChain

This commit is contained in:
2026-05-08 15:57:33 +03:00
commit 6cd179333a
4 changed files with 185 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
OPENAI_API_KEY=your-api-key-here
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_MODEL=gpt-4o-mini
+50
View File
@@ -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', ...}
```
+128
View File
@@ -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)
+4
View File
@@ -0,0 +1,4 @@
langchain-core>=0.3.0
langchain-openai>=0.2.0
pydantic>=2.0.0
python-dotenv>=1.0.0