feat: initial solution for task 2 - structured output with Pydantic and LangChain
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
OPENAI_API_KEY=your-api-key-here
|
||||||
|
OPENAI_BASE_URL=https://api.openai.com/v1
|
||||||
|
OPENAI_MODEL=gpt-4o-mini
|
||||||
@@ -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', ...}
|
||||||
|
```
|
||||||
@@ -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)
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
langchain-core>=0.3.0
|
||||||
|
langchain-openai>=0.2.0
|
||||||
|
pydantic>=2.0.0
|
||||||
|
python-dotenv>=1.0.0
|
||||||
Reference in New Issue
Block a user