From 4c199f14af2e9bbfb9ec6d54a3a640257e31e5ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=B8=D1=80=D0=B8=D0=BB=D0=BB=20=D0=9A=D1=83=D1=82?= =?UTF-8?q?=D0=BB=D0=B0=D1=85=D0=BC=D0=B5=D1=82=D0=BE=D0=B2?= Date: Tue, 26 May 2026 12:21:10 +0000 Subject: [PATCH] feat: full task card parser with notes field and CLI --- main.py | 128 ++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 106 insertions(+), 22 deletions(-) diff --git a/main.py b/main.py index f96e573..5c4cd78 100644 --- a/main.py +++ b/main.py @@ -1,46 +1,130 @@ +""" +Парсер задания: сырой текст → плоская карточка TaskCard. + +Пример: + python main.py + python main.py "Сдайте до понедельника презентацию по RAG: 10 слайдов. Оценка: за полноту и визуализацию." +""" + +import json 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_openai import ChatOpenAI -from langchain_core.output_parsers import PydanticOutputParser +from pydantic import BaseModel, Field + +load_dotenv() + +# --------------------------------------------------------------------------- +# Pydantic-модель карточки задания +# --------------------------------------------------------------------------- + class TaskCard(BaseModel): - title: str = Field(..., description="Краткое название задачи") - subject: str = Field(..., description="Область знаний, к которой относится задача") - deadline_hint: str = Field(..., description="Краткая подсказка о сроке выполнения") - deliverable_type: str = Field(..., description="Тип сдачи: отчёт, код, презентация и т.п.") - grading_hints: list[str] = Field(..., description="Список ключевых критериев оценки") + """Плоская карточка задания — все поля одного уровня вложенности.""" + + title: str = Field( + ..., + 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( model="openai/gpt-oss-20b:free", base_url="https://platform.brojs.ru/jrnl-bh/api/inference/v1", api_key=os.getenv("JOURNAL_MCP_PAT"), - temperature=0.5, + temperature=0.3, ) +# --------------------------------------------------------------------------- +# Цепочка: PromptTemplate → LLM → PydanticOutputParser +# --------------------------------------------------------------------------- + parser = PydanticOutputParser(pydantic_object=TaskCard) -prompt = PromptTemplate( - template=""" -Ниже приведено описание задания от преподавателя. Нужно извлечь из него структуру задачи в виде JSON, соответствующую модели TaskCard. +_TEMPLATE = """Ты — ассистент, который извлекает структурированную информацию из описания учебного задания. +Верни ответ строго в формате JSON согласно инструкции ниже — без лишних пояснений. {format_instructions} Описание задания: -{task_description} -""", - input_variables=["task_description"], +{task_text} +""" + +prompt = PromptTemplate( + template=_TEMPLATE, + input_variables=["task_text"], partial_variables={"format_instructions": parser.get_format_instructions()}, ) 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__": - # Пример входа - example = "Сдайте к пятнице мини-отчёт по LangChain: 2 страницы, упор на агентов. Оценка: за полноту и за пример кода." - result: TaskCard = chain.invoke({"task_description": example}) - print("\n--- Валидированный объект ---") - 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)}") + text = " ".join(sys.argv[1:]) if len(sys.argv) > 1 else _EXAMPLE + print(f"Вход:\n {text}") + card = parse_task(text) + print_card(card)