Self‑correcting LangGraph agent: update main.py
This commit is contained in:
@@ -1,16 +1,25 @@
|
|||||||
"""
|
"""
|
||||||
Self‑correcting LangGraph agent demo.
|
Self‑correcting LangGraph agent.
|
||||||
|
|
||||||
Run with:
|
Run with:
|
||||||
python main.py
|
python main.py "Вычисли 2+2"
|
||||||
|
|
||||||
Requires:
|
The agent will execute the task, verify the result via an LLM judge, and retry up to max_attempts.
|
||||||
pip install langgraph langchain-openai
|
|
||||||
"""
|
"""
|
||||||
import random
|
import os
|
||||||
from typing import TypedDict, Dict
|
from typing import TypedDict, Dict, Any
|
||||||
|
|
||||||
|
from langgraph.graph import StateGraph
|
||||||
|
from langchain_openai import ChatOpenAI
|
||||||
|
from langchain_core.messages import HumanMessage
|
||||||
|
|
||||||
|
MAX_ATTEMPTS = 5
|
||||||
|
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
||||||
|
if not OPENAI_API_KEY:
|
||||||
|
raise RuntimeError("Please set OPENAI_API_KEY environment variable.")
|
||||||
|
|
||||||
|
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
|
||||||
|
|
||||||
# ---------- State definition ----------
|
|
||||||
class AgentState(TypedDict):
|
class AgentState(TypedDict):
|
||||||
task: str
|
task: str
|
||||||
result: str | None
|
result: str | None
|
||||||
@@ -19,97 +28,75 @@ class AgentState(TypedDict):
|
|||||||
error: str | None
|
error: str | None
|
||||||
max_attempts: int
|
max_attempts: int
|
||||||
|
|
||||||
# ---------- Unreliable tool ----------
|
async def execute_task(state: AgentState) -> Dict[str, Any]:
|
||||||
class UnreliableTool:
|
|
||||||
def run(self, input_: str) -> str:
|
|
||||||
if random.random() < 0.3:
|
|
||||||
raise ValueError("Simulated tool failure")
|
|
||||||
# simple eval for demo purposes
|
|
||||||
try:
|
|
||||||
return str(eval(input_))
|
|
||||||
except Exception as e:
|
|
||||||
raise ValueError(f"Eval error: {e}")
|
|
||||||
|
|
||||||
# ---------- LangGraph imports ----------
|
|
||||||
from langgraph.graph import StateGraph, END
|
|
||||||
|
|
||||||
from langchain_openai import ChatOpenAI
|
|
||||||
|
|
||||||
# ---------- Nodes ----------
|
|
||||||
def execute_task(state: AgentState) -> AgentState:
|
|
||||||
tool = UnreliableTool()
|
|
||||||
try:
|
try:
|
||||||
result = tool.run(state["task"])
|
import random
|
||||||
state["result"] = result
|
if random.random() < 0.3:
|
||||||
state["error"] = None
|
raise ValueError("Simulated tool error")
|
||||||
|
result = f"Result for: {state['task']}"
|
||||||
|
return {"result": result, "error": None, "status": "pending"}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
state["result"] = None
|
return {"result": None, "error": str(e), "status": "failed"}
|
||||||
state["error"] = str(e)
|
|
||||||
return state
|
|
||||||
|
|
||||||
def verify_result(state: AgentState) -> AgentState:
|
async def verify_result(state: AgentState) -> Dict[str, Any]:
|
||||||
# Ask LLM to judge success or failed based on result and error
|
if state.get("error"):
|
||||||
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
|
return {"status": "failed"}
|
||||||
prompt = (
|
prompt = (
|
||||||
f"Task: {state['task']}\n"
|
f"You are a judge. The task was: {state['task']}\n"
|
||||||
f"Result: {state.get('result')}\n"
|
f"The result returned by the tool is: {state['result']}\n"
|
||||||
f"Error: {state.get('error')}\n"
|
"Determine if this result satisfies the task. Respond with only one word: 'success' or 'failed'."
|
||||||
"Respond with only 'success' or 'failed'."
|
|
||||||
)
|
)
|
||||||
resp = llm.invoke(prompt)
|
response = await llm.agenerate([HumanMessage(content=prompt)])
|
||||||
verdict = resp.content.strip().lower()
|
verdict = response.generations[0][0].content.strip().lower()
|
||||||
if verdict not in ("success", "failed"):
|
return {"status": verdict if verdict in {"success", "failed"} else "failed"}
|
||||||
verdict = "failed"
|
|
||||||
state["verdict"] = verdict
|
|
||||||
return state
|
|
||||||
|
|
||||||
def handle_error(state: AgentState) -> AgentState:
|
async def handle_error(state: AgentState) -> Dict[str, Any]:
|
||||||
state["attempts"] += 1
|
new_attempts = state["attempts"] + 1
|
||||||
if state["attempts"] >= state["max_attempts"]:
|
if new_attempts >= state["max_attempts"]:
|
||||||
state["status"] = "max_attempts"
|
return {"attempts": new_attempts, "status": "max_attempts"}
|
||||||
else:
|
return {"attempts": new_attempts, "status": "pending", "error": None, "result": None}
|
||||||
state["status"] = "pending"
|
|
||||||
return state
|
|
||||||
|
|
||||||
# ---------- Graph ----------
|
|
||||||
builder = StateGraph(AgentState)
|
builder = StateGraph(AgentState)
|
||||||
builder.add_node("execute_task", execute_task)
|
builder.add_node("execute_task", execute_task)
|
||||||
builder.add_node("verify_result", verify_result)
|
builder.add_node("verify_result", verify_result)
|
||||||
builder.add_node("handle_error", handle_error)
|
builder.add_node("handle_error", handle_error)
|
||||||
|
|
||||||
builder.set_entry_point("execute_task")
|
builder.set_entry_point("execute_task")
|
||||||
builder.add_edge("execute_task", "verify_result")
|
builder.add_edge("execute_task", "verify_result")
|
||||||
# From verify_result, branch on verdict
|
|
||||||
builder.add_conditional_edges(
|
builder.add_conditional_edges(
|
||||||
"verify_result",
|
"verify_result",
|
||||||
lambda state: state.get("verdict"),
|
lambda x: x["status"],
|
||||||
{
|
{"success": "END_success", "failed": "handle_error"},
|
||||||
"success": END,
|
|
||||||
"failed": "handle_error",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
builder.add_edge("handle_error", "execute_task")
|
builder.add_edge("handle_error", "execute_task")
|
||||||
|
builder.add_conditional_edges(
|
||||||
|
"handle_error",
|
||||||
|
lambda x: x["status"],
|
||||||
|
{"max_attempts": "END_max"},
|
||||||
|
)
|
||||||
|
builder.set_finish_nodes(["END_success", "END_max"])
|
||||||
graph = builder.compile()
|
graph = builder.compile()
|
||||||
|
|
||||||
# ---------- Demo runner ----------
|
async def run_task(task: str) -> AgentState:
|
||||||
if __name__ == "__main__":
|
|
||||||
initial_state: AgentState = {
|
initial_state: AgentState = {
|
||||||
"task": "2+2",
|
"task": task,
|
||||||
"result": None,
|
"result": None,
|
||||||
"attempts": 0,
|
"attempts": 0,
|
||||||
"status": "pending",
|
"status": "pending",
|
||||||
"error": None,
|
"error": None,
|
||||||
"max_attempts": 5,
|
"max_attempts": MAX_ATTEMPTS,
|
||||||
}
|
}
|
||||||
for attempt in range(1, initial_state["max_attempts"] + 1):
|
return await graph.ainvoke(initial_state)
|
||||||
print(f"Attempt {attempt}:")
|
|
||||||
result = graph.invoke(initial_state)
|
if __name__ == "__main__":
|
||||||
if result.get("verdict") == "success":
|
import sys, asyncio
|
||||||
print(f"Success: {result['result']}")
|
if len(sys.argv) < 2:
|
||||||
break
|
print("Usage: python main.py '<task>'")
|
||||||
else:
|
sys.exit(1)
|
||||||
print(f"Failed, error: {result.get('error')}")
|
task_text = sys.argv[1]
|
||||||
|
final = asyncio.run(run_task(task_text))
|
||||||
|
print(f"Task: {final['task']}")
|
||||||
|
print(f"Attempts: {final['attempts']}\n")
|
||||||
|
if final.get("status") == "success":
|
||||||
|
print(f"Result: {final['result']}\nStatus: success")
|
||||||
else:
|
else:
|
||||||
print("Reached max attempts without success.")
|
print(f"Failed after {final['attempts']} attempts. Status: {final.get('status')}\nError: {final.get('error')}")
|
||||||
""
|
|
||||||
|
|||||||
Reference in New Issue
Block a user