From 0eb47134d810b780fde6e297c555567ab11e3fbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=B8=D0=BD=D0=B0=D1=80=20=D0=9C=D0=B8=D1=80=D0=B7?= =?UTF-8?q?=D0=B0=D0=B3=D0=B8=D1=82=D0=BE=D0=B2?= Date: Thu, 28 May 2026 09:20:43 +0000 Subject: [PATCH] Add main.py --- main.py | 127 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 main.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..8e492d1 --- /dev/null +++ b/main.py @@ -0,0 +1,127 @@ +"""Convert an informal assignment description into a validated flat card.""" + +from __future__ import annotations + +import argparse +import os + +from dotenv import find_dotenv, 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 + + +DEFAULT_DESCRIPTION = ( + "Сдайте к пятнице мини-отчёт по LangChain: 2 страницы, упор на агентов. " + "Оценка: за полноту и за пример кода." +) + + +class AssignmentCard(BaseModel): + """Flat data card extracted from one assignment description.""" + + title: str = Field(..., description="Short title of the assignment") + subject: str = Field(..., description="Main topic or subject") + deadline_hint: str | None = Field( + None, + description="Deadline as a free-form phrase from the source text", + ) + deliverable_type: str | None = Field( + None, + description="Expected deliverable: report, code, presentation, etc.", + ) + grading_hints: list[str] = Field( + default_factory=list, + description="Assessment hints mentioned in the description", + ) + + +parser = PydanticOutputParser(pydantic_object=AssignmentCard) + +prompt = PromptTemplate( + template=( + "You convert one informal study assignment description into a flat " + "validated data card. Do not ask follow-up questions. Extract only " + "information supported by the input. If a field is not mentioned, use " + "null for optional strings or an empty list for grading_hints.\n\n" + "{format_instructions}\n\n" + "Assignment description:\n{description}" + ), + input_variables=["description"], + partial_variables={"format_instructions": parser.get_format_instructions()}, +) + + +def build_llm() -> ChatOpenAI: + """Create an OpenAI-compatible chat model from environment variables.""" + + load_dotenv(find_dotenv(usecwd=True)) + base_url = os.getenv("OPENAI_BASE_URL") or os.getenv("GPT2GIGA_BASE_URL") + api_key = os.getenv("OPENAI_API_KEY") or os.getenv("GPT2GIGA_OPENAI_API_KEY") + model = ( + os.getenv("OPENAI_MODEL") + or os.getenv("GPT2GIGA_MODEL") + or "openai/gpt-oss-20b" + ) + if not base_url: + raise RuntimeError("Set OPENAI_BASE_URL or GPT2GIGA_BASE_URL") + if not api_key: + raise RuntimeError("Set OPENAI_API_KEY or GPT2GIGA_OPENAI_API_KEY") + + return ChatOpenAI( + model=model, + base_url=base_url, + api_key=api_key, + temperature=0, + ) + + +def parse_assignment(description: str) -> AssignmentCard: + """Run one prompt-model-parser chain and return a validated card.""" + + chain = prompt | build_llm() | parser + return chain.invoke({"description": description}) + + +def format_summary(card: AssignmentCard) -> str: + """Create a short human-readable summary from the validated card.""" + + grading = ", ".join(card.grading_hints) if card.grading_hints else "не указано" + return ( + f"{card.title}: {card.deliverable_type or 'тип сдачи не указан'} " + f"по теме «{card.subject}». " + f"Срок: {card.deadline_hint or 'не указан'}. " + f"Критерии: {grading}." + ) + + +def print_card(card: AssignmentCard) -> None: + """Print both machine-friendly and human-friendly output.""" + + print("Validated object:") + for key, value in card.model_dump().items(): + print(f"{key}: {value}") + print("\nSummary:") + print(format_summary(card)) + + +def main(argv: list[str] | None = None) -> AssignmentCard: + arg_parser = argparse.ArgumentParser( + description="Parse one raw assignment description into a flat card." + ) + arg_parser.add_argument( + "description", + nargs="*", + help="One informal assignment description. Uses a sample if omitted.", + ) + args = arg_parser.parse_args(argv) + + description = " ".join(args.description).strip() or DEFAULT_DESCRIPTION + card = parse_assignment(description) + print_card(card) + return card + + +if __name__ == "__main__": + main()