""" Парсер задания: сырой текст → плоская карточка TaskCard. Пример: python main.py python main.py "Сдайте до понедельника презентацию по RAG: 10 слайдов. Оценка: за полноту и визуализацию." """ import json import os 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 pydantic import BaseModel, Field load_dotenv() # --------------------------------------------------------------------------- # Pydantic-модель карточки задания # --------------------------------------------------------------------------- class TaskCard(BaseModel): """Плоская карточка задания — все поля одного уровня вложенности.""" 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 = 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.3, ) # --------------------------------------------------------------------------- # Цепочка: PromptTemplate → LLM → PydanticOutputParser # --------------------------------------------------------------------------- parser = PydanticOutputParser(pydantic_object=TaskCard) _TEMPLATE = """Ты — ассистент, который извлекает структурированную информацию из описания учебного задания. Верни ответ строго в формате JSON согласно инструкции ниже — без лишних пояснений. {format_instructions} Описание задания: {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__": text = " ".join(sys.argv[1:]) if len(sys.argv) > 1 else _EXAMPLE print(f"Вход:\n {text}") card = parse_task(text) print_card(card)