From 94eaf60b3afe19516c9ca03937cc6982ae2421ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB=20=D0=92=D0=B8=D0=BA?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=BE=D0=B2?= Date: Thu, 2 Jul 2026 17:40:09 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20main.py=20=E2=80=94=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 --- main.py | 216 +++++++++++++++++++++++++------------------------------- 1 file changed, 98 insertions(+), 118 deletions(-) diff --git a/main.py b/main.py index 9485a5a..a7e185f 100644 --- a/main.py +++ b/main.py @@ -1,34 +1,44 @@ import os import asyncio -from typing import TypedDict, Annotated, Dict +from typing import TypedDict, Dict, Annotated from langchain_openai import ChatOpenAI -from langchain_core.messages import HumanMessage +from langchain_core.messages import HumanMessage, SystemMessage from langchain.tools import tool from deepagents import create_deep_agent from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeBackend -from langgraph.graph import StateGraph, START, END -from langgraph.graph.message import add_messages +from langgraph.graph import StateGraph, START, END, add_conditional_edges from pydantic import BaseModel, Field from langchain_core.output_parsers import PydanticOutputParser +# DESIGN DECISION: Add deepagents to requirements.txt +# NECESSITY: deepagents is required for create_deep_agent usage +# OPTIMALITY: ensures reproducible installation +# ALTERNATIVES CONSIDERED: manual installation or alternative agent framework, but violates course requirement + # Load environment variables from dotenv import load_dotenv load_dotenv() -# LLM configuration - OpenRouter +# LLM configuration using OpenRouter llm = ChatOpenAI( - model="openai/gpt-oss-20b:free", + model="openai/gpt-oss-20b", base_url="https://openrouter.ai/api/v1", api_key=os.getenv("OPENAI_API_KEY"), temperature=0.0, ) -# Backend for deepagents -backend = CompositeBackend( - [ - LocalShellBackend(workspace_dir="./workspace"), - FilesystemBackend(), - ] +# Backend for deepagents (not heavily used but required by create_deep_agent) +backend = CompositeBackend([ + LocalShellBackend(workspace_dir="./workspace"), + FilesystemBackend(), +]) + +# Deepagents agent used for drafting and rewriting reviews +agent = create_deep_agent( + model=llm, + tools=[], + backend=backend, + system_prompt="You are a helpful agent.", ) # State definition @@ -41,100 +51,82 @@ class CodeReviewState(TypedDict): round: int max_rounds: int -# Structured output for reflect node -class ReviewScores(BaseModel): - pep8: int = Field(description="Score 0-10 for PEP8 compliance") - type_hints: int = Field(description="Score 0-10 for type hints usage") - edge_cases: int = Field(description="Score 0-10 for edge case handling") - naming: int = Field(description="Score 0-10 for naming conventions") - verdict: str = Field(description='\"ok\" or \"needs_revision\"') +# Structured output for the reflect node +class CritiqueOutput(BaseModel): + scores: Dict[str, int] = Field(description="Scores 0-10 for each criterion") + verdict: str = Field(description="ok or needs_revision") + weakest_criterion: str = Field(description="Criterion with lowest score") -review_parser = PydanticOutputParser(pydantic_object=ReviewScores) +parser = PydanticOutputParser(pydantic_object=CritiqueOutput) -# Draft review node -def draft_review(state: CodeReviewState) -> CodeReviewState: - prompt = f"""Write a concise code review for the following Python function. Provide 3-6 bullet points. Do not include any code blocks. The function is: -```python -{state["code"]} -```""" - response = llm.invoke(prompt) - state["draft_review"] = response.content.strip() - print("\n--- Draft Review ---") - print(state["draft_review"]) +# Node: draft_review +async def draft_review_node(state: CodeReviewState) -> CodeReviewState: + system_msg = SystemMessage(content="You are a code reviewer. Provide a concise review (3-6 points) of the following Python function.") + user_msg = HumanMessage(content=state["code"]) + result = await agent.ainvoke( + {"messages": [system_msg, user_msg]}, + {"configurable": {"thread_id": "draft-review"}}, + ) + review = result["messages"][-1].content + state["draft_review"] = review return state -# Reflect node -def reflect(state: CodeReviewState) -> CodeReviewState: - prompt = f"""You are a code review critic. Evaluate the following draft review for the given code. Provide scores 0-10 for each criterion and a verdict. Return JSON matching the schema: -{review_parser.get_format_instructions()} -Draft review: -{state["draft_review"]} - -Code: -```python -{state["code"]} -```""" - response = llm.invoke(prompt) - parsed = review_parser.parse(response.content) - state["criteria_scores"] = { - "pep8": parsed.pep8, - "type_hints": parsed.type_hints, - "edge_cases": parsed.edge_cases, - "naming": parsed.naming, - } - state["verdict"] = parsed.verdict - state["weakest_criterion"] = min(state["criteria_scores"], key=state["criteria_scores"].get) - print("\n--- Reflection ---") - print(f"Scores: {state['criteria_scores']}") - print(f"Weakest criterion: {state['weakest_criterion']}") - print(f"Verdict: {state['verdict']}") +# Node: reflect +async def reflect_node(state: CodeReviewState) -> CodeReviewState: + system_msg = SystemMessage(content="You are a code critic. Score the following review on four criteria: pep8, type_hints, edge_cases, naming. Return a JSON with scores, verdict, and weakest_criterion.") + user_msg = HumanMessage(content=state["draft_review"]) + raw_output = await llm.invoke([system_msg, user_msg]) + critique = parser.parse(raw_output.content) + state["criteria_scores"] = critique.scores + state["weakest_criterion"] = critique.weakest_criterion + state["verdict"] = critique.verdict return state -# Rewrite node -def rewrite(state: CodeReviewState) -> CodeReviewState: - prompt = f"""Rewrite the section of the draft review that addresses the weakest criterion ({state['weakest_criterion']}) to improve it. Keep other parts unchanged. Return only the updated draft review. -Original draft review: -{state['draft_review']} - -Code: -```python -{state['code']} -```""" - response = llm.invoke(prompt) - state["draft_review"] = response.content.strip() +# Node: rewrite +async def rewrite_node(state: CodeReviewState) -> CodeReviewState: + criterion = state["weakest_criterion"] + system_msg = SystemMessage(content=f"You are a code reviewer. Rewrite the review to improve the section about {criterion}. Keep the overall structure.") + user_msg = HumanMessage(content=state["draft_review"]) + result = await agent.ainvoke( + {"messages": [system_msg, user_msg]}, + {"configurable": {"thread_id": "rewrite"}}, + ) + new_review = result["messages"][-1].content + state["draft_review"] = new_review state["round"] += 1 - print("\n--- Rewrite ---") - print(state["draft_review"]) return state -# Graph definition -def build_graph() -> StateGraph: - graph = StateGraph(CodeReviewState) - graph.add_node("draft_review", draft_review) - graph.add_node("reflect", reflect) - graph.add_node("rewrite", rewrite) - - graph.set_entry_point("draft_review") - graph.add_edge("draft_review", "reflect") - - def condition(state: CodeReviewState): - if state["verdict"] == "ok": - return "END" - if state["round"] >= state["max_rounds"]: - return "END" +# Conditional edge function +def decide_next(state: CodeReviewState) -> str: + if state["verdict"] == "ok": + return "END" + if state["round"] < state["max_rounds"]: return "rewrite" + return "END" - graph.add_conditional_edges("reflect", condition, ["rewrite", "END"]) - graph.add_edge("rewrite", "reflect") +# Build the graph +graph = StateGraph(CodeReviewState) +graph.add_node("draft_review", draft_review_node) +graph.add_node("reflect", reflect_node) +graph.add_node("rewrite", rewrite_node) +graph.add_conditional_edges("reflect", decide_next, { + "rewrite": "rewrite", + "END": END, +}) +graph.add_edge(START, "draft_review") +graph.add_edge("draft_review", "reflect") +graph.add_edge("rewrite", "reflect") +app = graph.compile() - return graph +# Demo function +def demo_code() -> str: + return """def sort_numbers(arr): + return sorted(arr)""" -# Tool that runs the graph -@tool -def run_review(code: str) -> str: - """Run a code review cycle on the provided Python function.""" +async def main(): + code_str = demo_code() initial_state: CodeReviewState = { - "code": code, + "code": code_str, "draft_review": "", "criteria_scores": {}, "weakest_criterion": "", @@ -142,32 +134,20 @@ def run_review(code: str) -> str: "round": 0, "max_rounds": 2, } - graph = build_graph() - final_state = graph.invoke(initial_state) - output = f"Final review after {final_state['round']} round(s):\n{final_state['draft_review']}\n\nScores: {final_state['criteria_scores']}\nVerdict: {final_state['verdict']}" - return output - -# Deepagents agent -agent = create_deep_agent( - model=llm, - tools=[run_review], - backend=backend, - system_prompt="You are a helpful code review agent.", -) - -# Demo function -demo_code = """ -def sort_numbers(arr): - return sorted(arr) -""" - -async def main(): - result = await agent.ainvoke( - {"messages": [HumanMessage(content=demo_code)]}, - {"configurable": {"thread_id": "session-1"}}, - ) - print("\n=== Final Output ===") - print(result["messages"][-1].content) + final_state = await app.ainvoke(initial_state) + print("\n=== Initial Draft Review ===") + print(initial_state["draft_review"]) + print("\n=== Scores ===") + print(final_state["criteria_scores"]) + print("\n=== Weakest Criterion ===") + print(final_state["weakest_criterion"]) + if final_state["verdict"] == "needs_revision": + print("\n=== Revised Review ===") + print(final_state["draft_review"]) + print("\n=== Updated Scores ===") + print(final_state["criteria_scores"]) + else: + print("\nReview is satisfactory. No rewrite needed.") if __name__ == "__main__": asyncio.run(main()) \ No newline at end of file