Update agent.py

This commit is contained in:
2026-06-02 16:17:26 +00:00
parent c2adb7e9fb
commit 6bdbc87a5c
+113 -109
View File
@@ -1,29 +1,23 @@
""" """
Selfcorrecting LangGraph agent. Selfcorrecting LangGraph agent.
The agent: The agent takes a user task, executes it with an unreliable tool, asks an LLM to judge the result, and retries until success or a maximum number of attempts.
1. Takes a user task.
2. Executes it via an unreliable tool.
3. Asks an LLM to judge the result (success / failed).
4. Retries until success or max_attempts.
Run with: Usage:
python agent.py python agent.py
Requires an OpenAI API key in the environment variable `OPENAI_API_KEY`. The agent will prompt for a task and print the outcome.
""" """
from __future__ import annotations
import os import os
import random import random
import time import time
from typing import Dict, TypedDict from typing import TypedDict, Any
from langchain_openai import ChatOpenAI from langgraph.graph import StateGraph, END, START
from langgraph.graph import StateGraph, END
from langgraph.prebuilt import ToolNode
from langgraph.checkpoint.memory import InMemorySaver from langgraph.checkpoint.memory import InMemorySaver
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# 1. State definition # 1. State definition
@@ -32,158 +26,168 @@ class AgentState(TypedDict):
task: str task: str
result: str result: str
attempts: int attempts: int
status: str # pending | success | failed | max_attempts status: str # "pending" | "success" | "failed" | "max_attempts"
error: str | None error: str | None
max_attempts: int max_attempts: int
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# 2. Unreliable tool # 2. Unreliable tool 30% chance of raising ValueError
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def unreliable_tool(task: str) -> str: def unreliable_tool(task: str) -> str:
"""Simulates a tool that fails 30% of the time. """Simulate a tool that fails 30% of the time.
Args: Args:
task: The task string. task: The task string.
Returns: Returns:
A string result (here we just echo the task for demo). A fabricated result string.
Raises: Raises:
ValueError: Simulated failure. ValueError: Simulated failure.
""" """
if random.random() < 0.3: if random.random() < 0.3:
raise ValueError("Simulated tool failure") raise ValueError("Simulated tool failure")
# Simulate some work # Simulate some processing time
time.sleep(0.5) time.sleep(0.5)
return f"Result for task: {task}" return f"Result for task: {task}"
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# 3. LLM judge node # 3. Nodes
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
JUDGE_PROMPT = ( def execute_task(state: AgentState) -> AgentState:
"You are a judge that evaluates the result of a task. " """Execute the task using the unreliable tool.
"Given the task and the result, reply with either 'success' or 'failed'. "
"Do not add any other text."
)
async def verify_result(state: AgentState) -> Dict[str, str]: Updates ``result`` and ``status``.
"""LLM judge that returns status.
Returns a dict with key 'status' set to 'success' or 'failed'.
"""
task = state["task"]
result = state["result"]
prompt = f"Task: {task}\nResult: {result}\n{JUDGE_PROMPT}"
response = await llm.ainvoke(prompt)
status = response.content.strip().lower()
if status not in {"success", "failed"}:
# Fallback to failed if LLM is uncertain
status = "failed"
return {"status": status}
# ---------------------------------------------------------------------------
# 4. Handle error / retry node
# ---------------------------------------------------------------------------
async def handle_error(state: AgentState) -> Dict[str, int | str | None]:
"""Increment attempts and decide whether to retry or stop.
Returns updated attempts and status.
"""
attempts = state["attempts"] + 1
max_attempts = state["max_attempts"]
status = "max_attempts" if attempts >= max_attempts else "pending"
return {"attempts": attempts, "status": status}
# ---------------------------------------------------------------------------
# 5. Execute task node
# ---------------------------------------------------------------------------
async def execute_task(state: AgentState) -> Dict[str, str | None]:
"""Runs the unreliable tool and captures result or error.
Returns updated result and error.
""" """
task = state["task"] task = state["task"]
try: try:
result = unreliable_tool(task) result = unreliable_tool(task)
return {"result": result, "error": None} state["result"] = result
state["status"] = "pending"
state["error"] = None
except Exception as e: except Exception as e:
return {"result": "", "error": str(e)} state["result"] = ""
state["status"] = "failed"
state["error"] = str(e)
return state
# LLM for judging the result
llm = ChatOpenAI(temperature=0, model="gpt-4o-mini")
def verify_result(state: AgentState) -> AgentState:
"""Ask the LLM to judge whether the result is correct.
The LLM must respond with only "success" or "failed".
"""
task = state["task"]
result = state["result"]
# If tool failed, we skip LLM and mark as failed
if state["status"] == "failed":
return state
prompt = (
f"You are a judge. Given the task: {task}\n"
f"And the result: {result}\n"
"Decide if the result is correct. Respond with only "success" or "failed"."
)
try:
msg = llm([HumanMessage(content=prompt)])
verdict = msg.content.strip().lower()
if verdict.startswith("success"):
state["status"] = "success"
else:
state["status"] = "failed"
except Exception as e:
state["status"] = "failed"
state["error"] = f"LLM error: {e}"
return state
def handle_error(state: AgentState) -> AgentState:
"""Increment attempts and prepare for a retry."""
state["attempts"] += 1
# If we hit max attempts, set status accordingly
if state["attempts"] >= state["max_attempts"]:
state["status"] = "max_attempts"
else:
state["status"] = "pending"
return state
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# 6. Build the graph # 4. Build the graph
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
builder = StateGraph(AgentState)
# Register nodes graph = StateGraph(AgentState)
builder.add_node("execute_task", execute_task)
builder.add_node("verify_result", verify_result)
builder.add_node("handle_error", handle_error)
# Define edges graph.add_node("execute_task", execute_task)
builder.set_entry_point("execute_task")
# After executing, verify graph.add_node("verify_result", verify_result)
builder.add_edge("execute_task", "verify_result")
graph.add_node("handle_error", handle_error)
# Entry point
graph.set_entry_point("execute_task")
# Conditional transitions
def verify_cond(state: AgentState) -> str:
return state["status"]
# After verification # After verification
builder.add_conditional_edges( # success -> END
# failed -> handle_error (if attempts < max)
# max_attempts -> END
graph.add_conditional_edges(
"verify_result", "verify_result",
lambda x: x["status"], verify_cond,
{ {
"success": END, "success": END,
"failed": "handle_error", "failed": "handle_error",
"max_attempts": END, # safety, though not expected here
},
)
# After error handling, either retry or end
builder.add_conditional_edges(
"handle_error",
lambda x: x["status"],
{
"pending": "execute_task",
"max_attempts": END, "max_attempts": END,
}, },
) )
# Compile graph # After error handling, go back to execute_task
graph = builder.compile(checkpointer=InMemorySaver())
graph.add_edge("handle_error", "execute_task")
# Build the graph
flow = graph.compile(checkpointer=InMemorySaver())
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# 7. CLI # 5. CLI
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def main() -> None:
def main():
print("Selfcorrecting LangGraph agent demo") print("Selfcorrecting LangGraph agent demo")
task = input("Enter a task: ") while True:
max_attempts = 5 task = input("Enter a task (or 'exit' to quit): ")
if task.strip().lower() == "exit":
initial_state: AgentState = { break
# Initialize state
state: AgentState = {
"task": task, "task": task,
"result": "", "result": "",
"attempts": 0, "attempts": 0,
"status": "pending", "status": "pending",
"error": None, "error": None,
"max_attempts": max_attempts, "max_attempts": 5,
} }
# Run the flow
# Run the graph result = flow(state)
result = graph.invoke(initial_state) final_state = result["states"][-1]
# Extract final status
final_status = result["status"]
attempts = result["attempts"]
final_result = result["result"]
error = result["error"]
print("\n--- Result ---") print("\n--- Result ---")
print(f"Status: {final_status}") print(f"Status: {final_state['status']}")
print(f"Attempts: {attempts}") print(f"Attempts: {final_state['attempts']}")
if error: print(f"Result: {final_state['result']}")
print(f"Last error: {error}") if final_state["error"]:
print(f"Result: {final_result}") print(f"Error: {final_state['error']}")
print("\n")
if __name__ == "__main__": if __name__ == "__main__":
main() main()