feat: full task card parser with notes field and CLI

This commit is contained in:
2026-05-26 12:21:10 +00:00
parent 1099219e83
commit 4c199f14af
+106 -22
View File
@@ -1,46 +1,130 @@
"""
Парсер задания: сырой текст → плоская карточка TaskCard.
Пример:
python main.py
python main.py "Сдайте до понедельника презентацию по RAG: 10 слайдов. Оценка: за полноту и визуализацию."
"""
import json
import os import os
from pydantic import BaseModel, Field import sys
from typing import Optional
from dotenv import load_dotenv
from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import PromptTemplate from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import PydanticOutputParser from pydantic import BaseModel, Field
load_dotenv()
# ---------------------------------------------------------------------------
# Pydantic-модель карточки задания
# ---------------------------------------------------------------------------
class TaskCard(BaseModel): class TaskCard(BaseModel):
title: str = Field(..., description="Краткое название задачи") """Плоская карточка задания — все поля одного уровня вложенности."""
subject: str = Field(..., description="Область знаний, к которой относится задача")
deadline_hint: str = Field(..., description="Краткая подсказка о сроке выполнения") title: str = Field(
deliverable_type: str = Field(..., description="Тип сдачи: отчёт, код, презентация и т.п.") ...,
grading_hints: list[str] = Field(..., description="Список ключевых критериев оценки") description="Краткое название задачи (2–6 слов)",
)
subject: str = Field(
...,
description="Область знаний или технология (LangChain, Python, ML и т.п.)",
)
deadline_hint: str = Field(
...,
description="Срок сдачи в свободной форме, пустая строка если не указан",
)
deliverable_type: str = Field(
...,
description="Что нужно сдать: отчёт, код, презентация, видео, ссылка и т.п.",
)
grading_hints: list[str] = Field(
default_factory=list,
description="Список критериев оценки, упомянутых в тексте задания",
)
notes: Optional[str] = Field(
default=None,
description="Любые дополнительные заметки или уточнения из текста",
)
# ---------------------------------------------------------------------------
# LLM — BroJS
# ---------------------------------------------------------------------------
# LLM configuration BroJS
llm = ChatOpenAI( llm = ChatOpenAI(
model="openai/gpt-oss-20b:free", model="openai/gpt-oss-20b:free",
base_url="https://platform.brojs.ru/jrnl-bh/api/inference/v1", base_url="https://platform.brojs.ru/jrnl-bh/api/inference/v1",
api_key=os.getenv("JOURNAL_MCP_PAT"), api_key=os.getenv("JOURNAL_MCP_PAT"),
temperature=0.5, temperature=0.3,
) )
# ---------------------------------------------------------------------------
# Цепочка: PromptTemplate → LLM → PydanticOutputParser
# ---------------------------------------------------------------------------
parser = PydanticOutputParser(pydantic_object=TaskCard) parser = PydanticOutputParser(pydantic_object=TaskCard)
prompt = PromptTemplate( _TEMPLATE = """Ты — ассистент, который извлекает структурированную информацию из описания учебного задания.
template=""" Верни ответ строго в формате JSON согласно инструкции ниже — без лишних пояснений.
Ниже приведено описание задания от преподавателя. Нужно извлечь из него структуру задачи в виде JSON, соответствующую модели TaskCard.
{format_instructions} {format_instructions}
Описание задания: Описание задания:
{task_description} {task_text}
""", """
input_variables=["task_description"],
prompt = PromptTemplate(
template=_TEMPLATE,
input_variables=["task_text"],
partial_variables={"format_instructions": parser.get_format_instructions()}, partial_variables={"format_instructions": parser.get_format_instructions()},
) )
chain = prompt | llm | parser chain = prompt | llm | parser
# ---------------------------------------------------------------------------
# Публичный интерфейс
# ---------------------------------------------------------------------------
def parse_task(task_text: str) -> TaskCard:
"""Парсит текст задания и возвращает валидированный объект TaskCard."""
return chain.invoke({"task_text": task_text})
def print_card(card: TaskCard) -> None:
"""Выводит карточку в двух форматах: JSON-объект и читаемая сводка."""
print("\n─── Валидированный объект (model_dump) ─────────────────────")
print(json.dumps(card.model_dump(), ensure_ascii=False, indent=2))
print("\n─── Человекочитаемая сводка ─────────────────────────────────")
print(f" Задача: {card.title}")
print(f" Тема: {card.subject}")
print(f" Срок: {card.deadline_hint or ''}")
print(f" Тип сдачи: {card.deliverable_type}")
if card.grading_hints:
print(f" Критерии: {', '.join(card.grading_hints)}")
if card.notes:
print(f" Заметки: {card.notes}")
# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------
_EXAMPLE = (
"Сдайте к пятнице мини-отчёт по LangChain: 2 страницы, упор на агентов. "
"Оценка: за полноту и за пример кода."
)
if __name__ == "__main__": if __name__ == "__main__":
# Пример входа text = " ".join(sys.argv[1:]) if len(sys.argv) > 1 else _EXAMPLE
example = "Сдайте к пятнице мини-отчёт по LangChain: 2 страницы, упор на агентов. Оценка: за полноту и за пример кода." print(f"Вход:\n {text}")
result: TaskCard = chain.invoke({"task_description": example}) card = parse_task(text)
print("\n--- Валидированный объект ---") print_card(card)
print(result.model_dump())
print("\n--- Человекочитаемая сводка ---")
print(f"Задача: {result.title}\nТема: {result.subject}\nСрок: {result.deadline_hint}\nТип сдачи: {result.deliverable_type}\nКритерии оценки: {', '.join(result.grading_hints)}")