From 86daf8c3df117c638f03f5cc6798873e475ccc6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=B8=D1=80=D0=B8=D0=BB=D0=BB=20=D0=9A=D1=83=D1=82?= =?UTF-8?q?=D0=BB=D0=B0=D1=85=D0=BC=D0=B5=D1=82=D0=BE=D0=B2?= Date: Thu, 4 Jun 2026 16:16:02 +0000 Subject: [PATCH] add graph.py --- graph.py | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 graph.py diff --git a/graph.py b/graph.py new file mode 100644 index 0000000..1378eb7 --- /dev/null +++ b/graph.py @@ -0,0 +1,121 @@ +"""LangGraph с узлом рефлексии: draft_answer -> reflect -> rewrite (при необходимости).""" +import os +from typing import Literal +from typing_extensions import TypedDict +from langchain_openai import ChatOpenAI +from langchain_core.messages import HumanMessage, SystemMessage +from langgraph.graph import StateGraph, START, END + +llm = ChatOpenAI( + model="openai/gpt-oss-20b:free", + base_url="https://platform.brojs.ru/jrnl-bh/api/inference/v1", + api_key=os.getenv("JOURNAL_MCP_PAT"), + temperature=0.5, +) + +class ReflectState(TypedDict): + """Состояние графа рефлексии.""" + question: str + draft: str + critique: str + verdict: str # ok | needs_revision + round: int + max_rounds: int + + +def draft_answer(state: ReflectState) -> dict: + """Узел 1: генерирует первичный ответ на вопрос (5-10 предложений).""" + response = llm.invoke([ + SystemMessage(content=( + "Ты эксперт. Напиши развёрнутый ответ на вопрос в 5-10 предложений. " + "Будь конкретен, приводи примеры, избегай воды." + )), + HumanMessage(content=state["question"]), + ]) + print(f"\n[draft_answer] Черновик:\n{response.content}\n") + return {"draft": response.content, "round": 0} + + +def reflect(state: ReflectState) -> dict: + """Узел 2: LLM-критик оценивает качество ответа и выставляет вердикт.""" + prompt = ( + f'''Оцени следующий ответ на вопрос: "{state["question"]}" + +Ответ: +{state["draft"]} + +Оцени по трём критериям: +1. Полнота — охвачены ли все аспекты вопроса? +2. Конкретика — есть ли конкретные примеры? +3. Отсутствие воды — нет ли лишних слов? + +Выведи строго в формате: +VERDICT: ok +или +VERDICT: needs_revision +CRITIQUE: <2-3 конкретных замечания>''' + ) + response = llm.invoke([HumanMessage(content=prompt)]) + text = response.content + + verdict = "needs_revision" + if "VERDICT: ok" in text or "verdict: ok" in text.lower(): + verdict = "ok" + + critique = "" + if "CRITIQUE:" in text: + critique = text.split("CRITIQUE:")[-1].strip() + + print(f"[reflect] Вердикт: {verdict}") + if critique: + print(f"[reflect] Замечания: {critique[:200]}") + + return {"verdict": verdict, "critique": critique} + + +def rewrite(state: ReflectState) -> dict: + """Узел 3: улучшает черновик с учётом замечаний критика.""" + prompt = ( + f'''Улучши следующий ответ на вопрос: "{state["question"]}" + +Текущий ответ: +{state["draft"]} + +Замечания критика: +{state["critique"]} + +Напиши улучшенный ответ в 5-10 предложений, устранив все замечания.''' + ) + response = llm.invoke([HumanMessage(content=prompt)]) + new_round = state["round"] + 1 + print(f"\n[rewrite] Раунд {new_round}:\n{response.content}\n") + return {"draft": response.content, "round": new_round} + + +def route_after_reflect(state: ReflectState) -> Literal["rewrite", "__end__"]: + """Роутер после узла reflect: продолжить или завершить.""" + if state["verdict"] == "ok": + print("[route] Вердикт ok — завершаем.") + return "__end__" + if state["round"] >= state["max_rounds"]: + print(f"[route] Достигнут лимит раундов ({state['max_rounds']}) — завершаем.") + return "__end__" + print(f"[route] needs_revision, раунд {state['round']} < {state['max_rounds']} — rewrite.") + return "rewrite" + +# ── Сборка графа ────────────────────────────────────────────────────────────── +builder = StateGraph(ReflectState) +builder.add_node("draft_answer", draft_answer) +builder.add_node("reflect", reflect) +builder.add_node("rewrite", rewrite) + +builder.add_edge(START, "draft_answer") +builder.add_edge("draft_answer", "reflect") +builder.add_conditional_edges( + "reflect", + route_after_reflect, + {"rewrite": "rewrite", "__end__": END}, +) +builder.add_edge("rewrite", "reflect") + +graph = builder.compile()