"""Сырой текст задания → плоская карточка (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, SecretStr load_dotenv() # Модель: LM Studio локально или любой OpenAI-совместимый API через .env llm = ChatOpenAI( model=os.getenv("OPENAI_MODEL", "local-model"), base_url=os.getenv("OPENAI_BASE_URL", "http://localhost:1234/v1"), api_key=SecretStr(os.getenv("OPENAI_API_KEY", "fake")), temperature=0.0, ) 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="Список критериев или упоминаний про оценку" ) 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()}, ) chain = prompt | llm | parser DEFAULT_INPUT = ( "Сдайте к пятнице мини-отчёт по LangChain: 2 страницы, упор на агентов. " "Оценка: за полноту и за пример кода." ) 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: AssignmentCard = chain.invoke({"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()