diff --git a/main.py b/main.py index 4beab7d..cc2dd2a 100644 --- a/main.py +++ b/main.py @@ -8,16 +8,16 @@ 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 +from pydantic import BaseModel, Field 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, +BROJS_INFERENCE_URL = "https://platform.brojs.ru/jrnl-bh/api/inference/v1" +DEFAULT_MODEL = "openai/gpt-oss-20b:free" + +DEFAULT_INPUT = ( + "Сдайте к пятнице мини-отчёт по LangChain: 2 страницы, упор на агентов. " + "Оценка: за полноту и за пример кода." ) @@ -37,25 +37,51 @@ class AssignmentCard(BaseModel): ) -parser = PydanticOutputParser(pydantic_object=AssignmentCard) +def _api_key() -> str: + return ( + os.getenv("OPENAI_API_KEY") + or os.getenv("JOURNAL_MCP_PAT") + or os.getenv("JOURNAL_TOKEN") + or "" + ) -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 +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 -DEFAULT_INPUT = ( - "Сдайте к пятнице мини-отчёт по LangChain: 2 страницы, упор на агентов. " - "Оценка: за полноту и за пример кода." -) + +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: @@ -72,7 +98,7 @@ def human_summary(card: AssignmentCard) -> str: def main() -> None: assignment_text = " ".join(sys.argv[1:]).strip() or DEFAULT_INPUT - card: AssignmentCard = chain.invoke({"assignment_text": assignment_text}) + card = parse_assignment_text(assignment_text) print("=== Валидированный объект (model_dump) ===") dumped = card.model_dump()