feat: full task card parser with notes field and CLI
This commit is contained in:
@@ -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)}")
|
|
||||||
|
|||||||
Reference in New Issue
Block a user