From 805fb9546a0c50ab3dd8e4855ac35d661aff065a Mon Sep 17 00:00:00 2001 From: kuzakhmetovartur Date: Mon, 29 Jun 2026 17:40:07 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20solution=20for=20'=D0=9F=D0=BE=D0=B2?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=BD=D1=8B=D0=B9=20=D1=8D=D0=BA=D0=B7=D0=B0?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=20#2:=20=D0=93=D1=80=D0=B0=D1=84=20=D1=81=20?= =?UTF-8?q?=D1=80=D0=B5=D1=84=D0=BB=D0=B5=D0=BA=D1=81=D0=B8=D0=B5=D0=B9=20?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=BA=D0=BE=D0=B4'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 20 +++++------- requirements.txt | 4 +++ src/cli.py | 41 +++++++++++++++++++++++++ src/graph.py | 24 +++++++++++++++ src/main.py | 4 +++ src/nodes.py | 80 ++++++++++++++++++++++++++++++++++++++++++++++++ src/state.py | 10 +++--- 7 files changed, 165 insertions(+), 18 deletions(-) create mode 100644 requirements.txt create mode 100644 src/cli.py create mode 100644 src/graph.py create mode 100644 src/main.py create mode 100644 src/nodes.py diff --git a/README.md b/README.md index 3880ce5..5f284d0 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,15 @@ EN Повторный экзамен #2: Граф с рефлексией на код Зачёт -Версия 1 +Версия 2 Дедлайн сдачи: 31.08.2026 В работе +Требуется доработка + +В работе не обнаружено использования ключевых технологий, указанных в условии задания. Для успешной сдачи необходимо добавить соответствующие импорты и примеры кода. + Редактирование ответа Заполните ответ и отправьте работу на проверку преподавателю. @@ -20,15 +24,5 @@ EN Текст Ссылка Файлы -Текст ответа -Прикреплённые файлы -Загрузить файл -Отправить на проверку -Отменить - -Задание - -Практическое задание: LangGraph с рефлексией на код -Цель - -Реализовать LangGraph-агента, который берёт функцию на Python и п \ No newline at end of file +Ссылка (URL) +Прикреплённ \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..916eeec --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +langgraph +langchain-openai +langchain-core +python-dotenv \ No newline at end of file diff --git a/src/cli.py b/src/cli.py new file mode 100644 index 0000000..a33e0c4 --- /dev/null +++ b/src/cli.py @@ -0,0 +1,41 @@ +import os +from src.graph import build_graph +from src.state import CodeReviewState + +def main(): + # Example function to review + def sort_numbers(arr): + return sorted(arr) + + # Get source code as string + import inspect + code_str = inspect.getsource(sort_numbers) + + # Initial state + state: CodeReviewState = { + "code": code_str, + "draft_review": None, + "criteria_scores": None, + "weakest_criterion": None, + "verdict": None, + "round": 0, + "max_rounds": 2, + } + + graph = build_graph() + # Run the graph + final_state = graph.invoke(state) + + # Print results + print("\n=== Initial Draft Review ===") + print(final_state["draft_review"]) + print("\n=== Scores ===") + for k, v in final_state["criteria_scores"].items(): + print(f"{k}: {v}") + print("\n=== Verdict ===") + print(final_state["verdict"]) + print("\n=== Final Review (after rewrites if any) ===") + print(final_state["draft_review"]) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/graph.py b/src/graph.py new file mode 100644 index 0000000..f7051b0 --- /dev/null +++ b/src/graph.py @@ -0,0 +1,24 @@ +from langgraph.graph import StateGraph, END, START +from src.state import CodeReviewState +from src.nodes import draft_review, reflect, rewrite + +def build_graph() -> StateGraph: + graph = StateGraph(CodeReviewState) + + # Add nodes + graph.add_node("draft_review", draft_review) + graph.add_node("reflect", reflect) + graph.add_node("rewrite", rewrite) + + # Define transitions + graph.set_entry_point("draft_review") + graph.add_edge("draft_review", "reflect") + graph.add_conditional_edges( + "reflect", + lambda state: ( + "rewrite" if state["verdict"] == "needs_revision" and state["round"] < state["max_rounds"] else END + ), + ) + graph.add_edge("rewrite", "reflect") + + return graph \ No newline at end of file diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..1c5ce43 --- /dev/null +++ b/src/main.py @@ -0,0 +1,4 @@ +from src.cli import main + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/nodes.py b/src/nodes.py new file mode 100644 index 0000000..d7c1b06 --- /dev/null +++ b/src/nodes.py @@ -0,0 +1,80 @@ +from typing import Dict, Any +from langgraph.graph import StateGraph, END, START +from langchain_openai import ChatOpenAI +from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder +from langchain_core.output_parsers import JsonOutputParser +from src.state import CodeReviewState + +# LLM instance +llm = ChatOpenAI(temperature=0, model="gpt-4o-mini") + +# Prompt for draft review +draft_prompt = ChatPromptTemplate.from_messages( + [ + ("system", "You are a senior code reviewer. Provide a concise review with 3-6 bullet points."), + ("human", "Here is the Python function:\n\n{code}\n\nWrite your review:"), + ] +) + +# Prompt for reflection (scoring) +reflect_prompt = ChatPromptTemplate.from_messages( + [ + ("system", "You are a code quality critic. Evaluate the following review on four criteria: PEP8, type hints, edge cases, naming. Provide scores 0-10 and a verdict 'ok' or 'needs_revision'. Output JSON with keys: pep8, type_hints, edge_cases, naming, verdict."), + ("human", "Review:\n\n{draft_review}\n\nScores:"), + ] +) + +# Prompt for rewrite +rewrite_prompt = ChatPromptTemplate.from_messages( + [ + ("system", "You are a code reviewer tasked with improving the weakest part of the review. Keep other points unchanged."), + ("human", "Weakest criterion: {weakest_criterion}\n\nOriginal review:\n\n{draft_review}\n\nRewrite the section addressing the weakest criterion:"), + ] +) + +# Output parser for reflection +json_parser = JsonOutputParser() + +def draft_review(state: CodeReviewState) -> CodeReviewState: + """Generate initial draft review.""" + messages = draft_prompt.format_messages(code=state["code"]) + review = llm.invoke(messages).content + state["draft_review"] = review.strip() + state["round"] = 0 + return state + +def reflect(state: CodeReviewState) -> CodeReviewState: + """Score the draft review.""" + messages = reflect_prompt.format_messages(draft_review=state["draft_review"]) + raw_output = llm.invoke(messages).content + try: + scores = json_parser.parse(raw_output) + except Exception as e: + # Fallback: if parsing fails, set default scores + scores = { + "pep8": 0, + "type_hints": 0, + "edge_cases": 0, + "naming": 0, + "verdict": "needs_revision", + } + # Determine weakest criterion + criteria = ["pep8", "type_hints", "edge_cases", "naming"] + weakest = min(criteria, key=lambda c: scores[c]) + state["criteria_scores"] = {c: int(scores[c]) for c in criteria} + state["weakest_criterion"] = weakest + state["verdict"] = scores["verdict"] + return state + +def rewrite(state: CodeReviewState) -> CodeReviewState: + """Rewrite the weakest part of the review.""" + messages = rewrite_prompt.format_messages( + weakest_criterion=state["weakest_criterion"], + draft_review=state["draft_review"], + ) + rewritten = llm.invoke(messages).content + # Replace the section related to weakest criterion + # For simplicity, we just append the rewritten part to the original review + state["draft_review"] = f"{state['draft_review']}\n\nImproved {state['weakest_criterion']} section:\n{rewritten.strip()}" + state["round"] += 1 + return state \ No newline at end of file diff --git a/src/state.py b/src/state.py index 2d5a730..24d1a66 100644 --- a/src/state.py +++ b/src/state.py @@ -1,10 +1,10 @@ -from typing import TypedDict, Dict +from typing import TypedDict, Dict, Any class CodeReviewState(TypedDict): code: str - draft_review: str - criteria_scores: Dict[str, int] - weakest_criterion: str - verdict: str + draft_review: str | None + criteria_scores: Dict[str, int] | None + weakest_criterion: str | None + verdict: str | None # "ok" | "needs_revision" round: int max_rounds: int \ No newline at end of file