From f86dbb1232151713931f0f73af83673a4b33ed53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BB=D0=B5=D0=B1=20=D0=9D=D0=B8=D0=BA=D0=B8=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Thu, 28 May 2026 17:39:58 +0000 Subject: [PATCH] add main.py --- main.py | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 main.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..6ebda8a --- /dev/null +++ b/main.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +"""Self‑correcting LangGraph agent. + +Run with: + python main.py "Вычисли 2+2" +""" +import os +import random +import sys +from typing import TypedDict, Dict, Any + +from langchain_openai import ChatOpenAI +from langgraph.graph import StateGraph, END +from langgraph.checkpoint.memory import InMemorySaver + +# ---------- LLM ---------- +llm = ChatOpenAI( + model="openai/gpt-oss-20b:free", + base_url="https://openrouter.ai/api/v1", + api_key=os.getenv("OPENAI_API_KEY"), + temperature=0.0, +) + +# ---------- State ---------- +class AgentState(TypedDict): + task: str + result: str + attempts: int + status: str # pending | success | failed | max_attempts + error: str | None + max_attempts: int + +# ---------- Tool ---------- +class UnreliableTool: + """Tool that fails ~30% of the time.""" + + def __call__(self, input: str) -> str: + if random.random() < 0.3: + raise ValueError("Simulated tool failure") + # Simple arithmetic evaluator for demo purposes + try: + return str(eval(input)) + except Exception as e: + raise ValueError(f"Evaluation error: {e}") + +unreliable_tool = UnreliableTool() + +# ---------- Nodes ---------- +async def execute_task(state: AgentState) -> Dict[str, Any]: + """Run the task using the unreliable tool.""" + try: + result = unreliable_tool(state["task"]) + return {"result": result, "error": None, "status": "pending"} + except Exception as e: + return {"result": "", "error": str(e), "status": "failed"} + +async def verify_result(state: AgentState) -> Dict[str, Any]: + """LLM judge: success or failed.""" + prompt = ( + f"Task: {state['task']}\n" + f"Result: {state['result']}\n" + f"Error: {state['error']}\n" + "Is the result correct? Answer with only 'success' or 'failed'." + ) + response = await llm.ainvoke(prompt) + verdict = response.content.strip().lower() + if verdict not in {"success", "failed"}: + verdict = "failed" + return {"status": verdict} + +async def handle_error(state: AgentState) -> Dict[str, Any]: + """Increment attempts and decide whether to retry.""" + attempts = state["attempts"] + 1 + if attempts >= state["max_attempts"]: + return {"attempts": attempts, "status": "max_attempts"} + return {"attempts": attempts, "status": "pending", "error": None, "result": ""} + +# ---------- Graph ---------- +builder = StateGraph(AgentState) +builder.add_node("execute_task", execute_task) +builder.add_node("verify_result", verify_result) +builder.add_node("handle_error", handle_error) + +builder.set_entry_point("execute_task") + +builder.add_edge("execute_task", "verify_result") +builder.add_conditional_edges( + "verify_result", + lambda x: x["status"], + { + "success": END, + "failed": "handle_error", + "max_attempts": END, + }, +) +builder.add_edge("handle_error", "execute_task") + +graph = builder.compile(checkpointer=InMemorySaver()) + +# ---------- Runner ---------- +async def run(task: str, max_attempts: int = 5): + initial_state: AgentState = { + "task": task, + "result": "", + "attempts": 0, + "status": "pending", + "error": None, + "max_attempts": max_attempts, + } + async for event in graph.astream_events(initial_state, version="1"): + if "update" in event: + state = event["update"] + if state["status"] == "pending" and state["attempts"] > 0: + print(f"Попытка {state['attempts']}: {state['error'] or state['result']}") + final_state = graph.get_state() + print("\nИтог:", final_state["status"], f"за {final_state['attempts']} попытки(и)") + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: python main.py ''") + sys.exit(1) + task = sys.argv[1] + import asyncio + asyncio.run(run(task))