diff --git a/main.py b/main.py index 191e822..1af4e01 100644 --- a/main.py +++ b/main.py @@ -1,22 +1,21 @@ -"""Разбор сырого текста задания в плоскую карточку (Pydantic + LangChain + OpenRouter).""" +"""Разбор сырого текста задания в плоскую карточку (LangGraph + Pydantic + OpenRouter).""" from __future__ import annotations import os +from typing import Any, TypedDict from dotenv import load_dotenv from langchain_core.output_parsers import PydanticOutputParser from langchain_core.prompts import PromptTemplate from langchain_openai import ChatOpenAI +from langgraph.constants import END, START +from langgraph.graph import StateGraph from pydantic import BaseModel, Field load_dotenv() OPENROUTER_BASE_URL = os.getenv("OPENROUTER_BASE_URL", "https://openrouter.ai/api/v1") -# gpt-oss-20b:free часто в 429 — fallback на рабочую free-модель OpenRouter -OPENROUTER_MODEL = os.getenv( - "OPENROUTER_MODEL", - "poolside/laguna-m.1:free", -) +OPENROUTER_MODEL = os.getenv("OPENROUTER_MODEL", "poolside/laguna-m.1:free") SAMPLE_TASK_TEXT = ( "Сдайте к пятнице мини-отчёт по LangChain: 2 страницы, упор на агентов. " @@ -40,6 +39,11 @@ class TaskCard(BaseModel): ) +class GraphState(TypedDict): + task_text: str + verdict_row: dict[str, Any] | None + + def build_chain(): parser = PydanticOutputParser(pydantic_object=TaskCard) prompt = PromptTemplate( @@ -61,6 +65,30 @@ def build_chain(): return prompt | llm | parser +def parse_task_text(task_text: str) -> TaskCard: + chain = build_chain() + return chain.invoke({"task_text": task_text}) + + +def parse_to_verdict_node(state: GraphState) -> dict[str, Any]: + """Узел графа: заполняет verdict_row для автопроверки.""" + card = parse_task_text(state["task_text"]) + return {"verdict_row": card.model_dump()} + + +def build_graph(): + """Собирает и компилирует граф (ожидается автопроверкой).""" + builder = StateGraph(GraphState) + builder.add_node("parse", parse_to_verdict_node) + builder.add_edge(START, "parse") + builder.add_edge("parse", END) + return builder.compile() + + +# Экспорт для автопроверки: graph.invoke(...) -> {"verdict_row": {...}} +graph = build_graph() + + def print_summary(card: TaskCard) -> None: print("\n--- Краткая сводка ---") print(f"title: {card.title}") @@ -70,16 +98,18 @@ def print_summary(card: TaskCard) -> None: print(f"grading_hints: {card.grading_hints}") -def parse_task_text(task_text: str) -> TaskCard: - chain = build_chain() - return chain.invoke({"task_text": task_text}) - - def main() -> None: print(f"Модель OpenRouter: {OPENROUTER_MODEL}") print(f"\nВход:\n{SAMPLE_TASK_TEXT}\n") - card = parse_task_text(SAMPLE_TASK_TEXT) + result = graph.invoke( + {"task_text": SAMPLE_TASK_TEXT, "verdict_row": None}, + ) + + if not result.get("verdict_row"): + raise RuntimeError("Граф не вернул verdict_row") + + card = TaskCard.model_validate(result["verdict_row"]) print("\n--- Валидированный объект (model_dump) ---") print(card.model_dump())