"""Сырой текст задания → плоская карточка (LangChain structured output).""" from __future__ import annotations import os import sys 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() BROJS_INFERENCE_URL = "https://platform.brojs.ru/jrnl-bh/api/inference/v1" DEFAULT_MODEL = "openai/gpt-oss-20b:free" DEFAULT_INPUT = ( "Сдайте к пятнице мини-отчёт по LangChain: 2 страницы, упор на агентов. " "Оценка: за полноту и за пример кода." ) class AssignmentCard(BaseModel): """Плоская карточка задания из одной формулировки.""" title: str = Field(description="Краткий заголовок задания") subject: str = Field(description="Предмет или тема (например LangChain, Python)") deadline_hint: str = Field( description="Срок в свободной форме (например «к пятнице», «до 20 марта»)" ) deliverable_type: str = Field( description="Что нужно сдать: отчёт, код, презентация и т.п." ) grading_hints: list[str] = Field( description="Список критериев или упоминаний про оценку" ) def _api_key() -> str: return ( os.getenv("OPENAI_API_KEY") or os.getenv("JOURNAL_MCP_PAT") or os.getenv("JOURNAL_TOKEN") or "" ) def _base_url() -> str: if os.getenv("OPENAI_BASE_URL"): return os.environ["OPENAI_BASE_URL"] if os.getenv("OPENAI_API_KEY"): return os.getenv("OPENROUTER_BASE_URL", "https://openrouter.ai/api/v1") return BROJS_INFERENCE_URL def _model() -> str: return os.getenv("OPENAI_MODEL") or os.getenv("OPENROUTER_MODEL") or DEFAULT_MODEL def build_chain(): """Цепочка prompt | llm | parser — точка входа для автопроверки.""" parser = PydanticOutputParser(pydantic_object=AssignmentCard) prompt = PromptTemplate( template=( "Ты модуль разбора учебных заданий. Из одной формулировки преподавателя " "извлеки поля карточки. Не задавай уточняющих вопросов — только структурированный ответ.\n\n" "Формулировка задания:\n{assignment_text}\n\n" "{format_instructions}" ), input_variables=["assignment_text"], partial_variables={"format_instructions": parser.get_format_instructions()}, ) llm = ChatOpenAI( model=_model(), base_url=_base_url(), api_key=_api_key(), temperature=0.0, ) return prompt | llm | parser def parse_assignment_text(assignment_text: str) -> AssignmentCard: return build_chain().invoke({"assignment_text": assignment_text}) def human_summary(card: AssignmentCard) -> str: """Краткая человекочитаемая сводка.""" hints = ", ".join(card.grading_hints) if card.grading_hints else "—" return ( f"Заголовок: {card.title}\n" f"Тема: {card.subject}\n" f"Срок: {card.deadline_hint}\n" f"Что сдать: {card.deliverable_type}\n" f"Оценка (подсказки): {hints}" ) def main() -> None: assignment_text = " ".join(sys.argv[1:]).strip() or DEFAULT_INPUT card = parse_assignment_text(assignment_text) print("=== Валидированный объект (model_dump) ===") dumped = card.model_dump() for key, value in dumped.items(): print(f"{key}: {value!r}") print("\n=== Краткая сводка ===") print(human_summary(card)) if __name__ == "__main__": main()