Add main.py
This commit is contained in:
@@ -0,0 +1,177 @@
|
|||||||
|
"""
|
||||||
|
Self‑correcting LangGraph agent demo.
|
||||||
|
|
||||||
|
Run:
|
||||||
|
python main.py
|
||||||
|
|
||||||
|
Requires an OpenAI API key in the environment variable `OPENAI_API_KEY`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import random
|
||||||
|
from typing import TypedDict, Optional
|
||||||
|
|
||||||
|
from langchain_openai import ChatOpenAI
|
||||||
|
from langgraph.graph import StateGraph, END, START
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 1. State definition
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
class AgentState(TypedDict):
|
||||||
|
task: str
|
||||||
|
result: str
|
||||||
|
attempts: int
|
||||||
|
status: str # pending | success | failed | max_attempts
|
||||||
|
error: Optional[str]
|
||||||
|
max_attempts: int
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 2. Unreliable tool
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def unreliable_addition(a: int, b: int) -> str:
|
||||||
|
"""Adds two numbers but fails with 30 % probability.
|
||||||
|
|
||||||
|
Returns the sum as a string.
|
||||||
|
"""
|
||||||
|
if random.random() < 0.3:
|
||||||
|
raise ValueError("Random failure in unreliable tool")
|
||||||
|
return str(a + b)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 3. LLM initialization
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# The OpenAI key must be set in the environment.
|
||||||
|
llm = ChatOpenAI(temperature=0)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 4. Node implementations
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def execute_task(state: AgentState) -> AgentState:
|
||||||
|
"""Executes the task using the unreliable tool.
|
||||||
|
|
||||||
|
The task is expected to be a string of the form "<a> + <b>".
|
||||||
|
"""
|
||||||
|
# Increment attempt counter at the start of each execution
|
||||||
|
state["attempts"] += 1
|
||||||
|
|
||||||
|
task = state["task"].strip()
|
||||||
|
try:
|
||||||
|
a_str, b_str = task.split("+")
|
||||||
|
a = int(a_str.strip())
|
||||||
|
b = int(b_str.strip())
|
||||||
|
result = unreliable_addition(a, b)
|
||||||
|
state["result"] = result
|
||||||
|
state["error"] = None
|
||||||
|
except Exception as e:
|
||||||
|
state["result"] = ""
|
||||||
|
state["error"] = str(e)
|
||||||
|
return state
|
||||||
|
|
||||||
|
def verify_result(state: AgentState) -> AgentState:
|
||||||
|
"""LLM checks if the result is correct.
|
||||||
|
|
||||||
|
The LLM is asked to output only "success" or "failed".
|
||||||
|
"""
|
||||||
|
task = state["task"].strip()
|
||||||
|
result = state["result"].strip()
|
||||||
|
# Compute expected result for comparison
|
||||||
|
try:
|
||||||
|
a_str, b_str = task.split("+")
|
||||||
|
expected = str(int(a_str.strip()) + int(b_str.strip()))
|
||||||
|
except Exception:
|
||||||
|
expected = ""
|
||||||
|
|
||||||
|
prompt = (
|
||||||
|
f"Task: {task}\n"
|
||||||
|
f"Result: {result}\n"
|
||||||
|
f"Expected: {expected}\n"
|
||||||
|
"Is the result correct? Respond with only 'success' or 'failed'."
|
||||||
|
)
|
||||||
|
response = llm.invoke(prompt).content.strip().lower()
|
||||||
|
if response == "success":
|
||||||
|
state["status"] = "success"
|
||||||
|
else:
|
||||||
|
state["status"] = "failed"
|
||||||
|
return state
|
||||||
|
|
||||||
|
def handle_error(state: AgentState) -> AgentState:
|
||||||
|
"""Decide whether to retry or stop based on the number of attempts."""
|
||||||
|
if state["attempts"] >= state["max_attempts"]:
|
||||||
|
state["status"] = "max_attempts"
|
||||||
|
else:
|
||||||
|
state["status"] = "pending"
|
||||||
|
return state
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 5. Graph construction
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
graph = StateGraph(AgentState)
|
||||||
|
|
||||||
|
graph.add_node("execute_task", execute_task)
|
||||||
|
|
||||||
|
graph.add_node("verify_result", verify_result)
|
||||||
|
|
||||||
|
graph.add_node("handle_error", handle_error)
|
||||||
|
|
||||||
|
# Define edges
|
||||||
|
# Start -> execute_task
|
||||||
|
graph.add_edge(START, "execute_task")
|
||||||
|
# execute_task -> verify_result
|
||||||
|
graph.add_edge("execute_task", "verify_result")
|
||||||
|
# verify_result -> END on success, else -> handle_error on failed
|
||||||
|
|
||||||
|
graph.add_conditional_edges(
|
||||||
|
"verify_result",
|
||||||
|
lambda state: "END" if state["status"] == "success" else "handle_error" if state["status"] == "failed" else "END",
|
||||||
|
)
|
||||||
|
# handle_error -> execute_task if pending, else END
|
||||||
|
|
||||||
|
graph.add_conditional_edges(
|
||||||
|
"handle_error",
|
||||||
|
lambda state: "execute_task" if state["status"] == "pending" else "END",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Compile the graph
|
||||||
|
app = graph.compile()
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# 6. Demo execution
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def main():
|
||||||
|
task = "2 + 2"
|
||||||
|
initial_state: AgentState = {
|
||||||
|
"task": task,
|
||||||
|
"result": "",
|
||||||
|
"attempts": 0,
|
||||||
|
"status": "pending",
|
||||||
|
"error": None,
|
||||||
|
"max_attempts": 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"Task: {task}")
|
||||||
|
|
||||||
|
# Run the graph
|
||||||
|
for event in app.stream(initial_state):
|
||||||
|
state = event["state"]
|
||||||
|
attempts = state["attempts"]
|
||||||
|
if state["status"] == "pending":
|
||||||
|
print(f"Попытка {attempts}: выполняется...")
|
||||||
|
elif state["status"] == "failed":
|
||||||
|
print(f"Попытка {attempts}: ошибка -> {state['error']}")
|
||||||
|
elif state["status"] == "success":
|
||||||
|
print(f"Попытка {attempts}: результат {state['result']} → verify: success")
|
||||||
|
elif state["status"] == "max_attempts":
|
||||||
|
print(f"Попытка {attempts}: достигнут лимит попыток → verify: failed")
|
||||||
|
|
||||||
|
final_state = app.get_state()
|
||||||
|
print("\nИтог:")
|
||||||
|
if final_state["status"] == "success":
|
||||||
|
print(f"success за {final_state['attempts']} попыток")
|
||||||
|
else:
|
||||||
|
print(f"failed после {final_state['attempts']} попыток")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user