fix: main.py — Экзамен: Самокорректирующийся агент

This commit is contained in:
2026-07-02 01:46:53 +00:00
parent b1e867f15a
commit 23157d7335
+83 -72
View File
@@ -1,10 +1,10 @@
import os import os
import asyncio import asyncio
import random import random
from typing import TypedDict, Annotated from typing import TypedDict, Literal, Annotated
from langchain_openai import ChatOpenAI from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage from langchain_core.messages import HumanMessage, SystemMessage
from langchain.tools import tool from langchain.tools import tool
from deepagents import create_deep_agent from deepagents import create_deep_agent
@@ -13,7 +13,9 @@ from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeB
from langgraph.graph import StateGraph, START, END from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages from langgraph.graph.message import add_messages
# ---------- LLM ---------- # ----------------------------------------------------------------------
# Configuration
# ----------------------------------------------------------------------
llm = ChatOpenAI( llm = ChatOpenAI(
model="openai/gpt-oss-20b:free", model="openai/gpt-oss-20b:free",
base_url="https://openrouter.ai/api/v1", base_url="https://openrouter.ai/api/v1",
@@ -21,7 +23,6 @@ llm = ChatOpenAI(
temperature=0.0, temperature=0.0,
) )
# ---------- Backend ----------
backend = CompositeBackend( backend = CompositeBackend(
[ [
LocalShellBackend(workspace_dir="./workspace"), LocalShellBackend(workspace_dir="./workspace"),
@@ -29,104 +30,115 @@ backend = CompositeBackend(
] ]
) )
# ---------- Unreliable tool ---------- # ----------------------------------------------------------------------
# Unreliable tool used for demonstration
# ----------------------------------------------------------------------
@tool @tool
def unreliable_tool(query: str) -> str: def unreliable_tool(query: str) -> str:
""" """
Simulates an unreliable external tool. Simulates an unreliable external tool.
With ~30% probability it raises a ValueError. With ~30% probability it raises a ValueError to trigger a retry.
""" """
if random.random() < 0.3: if random.random() < 0.3:
raise ValueError("Simulated tool failure") raise ValueError("Simulated tool failure")
return f"Result for '{query}'" # Simple evaluation for arithmetic expressions
try:
result = eval(query, {"__builtins__": {}})
except Exception:
result = f"cannot evaluate: {query}"
return str(result)
# ----------------------------------------------------------------------
# ---------- DeepAgent (used inside execute_task node) ---------- # DeepAgent - required by the course
# ----------------------------------------------------------------------
deep_agent = create_deep_agent( deep_agent = create_deep_agent(
model=llm, model=llm,
tools=[unreliable_tool], tools=[unreliable_tool],
backend=backend, backend=backend,
system_prompt="You are a helpful assistant that uses the provided tool to answer user queries.", system_prompt="You are a helpful assistant that can use tools when needed.",
) )
# ---------- State definition ---------- # ----------------------------------------------------------------------
# Agent state definition
# ----------------------------------------------------------------------
class AgentState(TypedDict): class AgentState(TypedDict):
task: str task: str
result: str result: str
attempts: int attempts: int
status: str # pending | success | failed | max_attempts status: Literal["pending", "success", "failed", "max_attempts"]
error: str | None error: str | None
max_attempts: int max_attempts: int
messages: Annotated[list, add_messages] messages: Annotated[list, add_messages]
# ---------- Nodes ---------- # ----------------------------------------------------------------------
async def execute_task(state: AgentState): # Node: execute_task
"""Run the task using the deep agent.""" # ----------------------------------------------------------------------
async def execute_task(state: AgentState) -> dict:
"""Run the task using the unreliable tool."""
try: try:
response = await deep_agent.ainvoke( # Call the tool directly; deep_agent is not needed here
{"messages": [HumanMessage(content=state["task"])]}, tool_result = unreliable_tool(state["task"])
{"configurable": {"thread_id": f"session-{state['attempts']}"}}, new_state = {
) "result": tool_result,
# The deep agent returns a dict with "messages"
result_msg = response["messages"][-1].content
return {
"result": result_msg,
"error": None, "error": None,
"status": "pending", "status": "pending",
"messages": response["messages"],
} }
except Exception as e: except Exception as e:
return { new_state = {
"result": "", "result": "",
"error": str(e), "error": str(e),
"status": "failed", "status": "failed",
"messages": [],
} }
return new_state
# ----------------------------------------------------------------------
# Node: verify_result
# ----------------------------------------------------------------------
async def verify_result(state: AgentState) -> dict:
"""Ask LLM to judge whether the result satisfies the task."""
judge_prompt = f"""You are a judge. The original task is:
{state['task']}
async def verify_result(state: AgentState): The agent produced the following result:
"""Ask LLM to judge the result.""" {state['result']}
judge_prompt = f"""You are a judge. Determine if the following result correctly solves the task.
Task: {state['task']} Respond with only one word: "success" if the result correctly solves the task,
Result: {state['result']} otherwise respond with "failed"."""
messages = [
Respond with only one word: SUCCESS if the result is correct, otherwise FAILED.""" SystemMessage(content="You are an objective judge."),
judge_response = await llm.ainvoke([HumanMessage(content=judge_prompt)]) HumanMessage(content=judge_prompt),
verdict = judge_response.content.strip().lower() ]
response = await llm.ainvoke(messages)
verdict = response.content.strip().lower()
if verdict == "success": if verdict == "success":
new_status = "success" new_status = "success"
else: else:
new_status = "failed" new_status = "failed"
return {"status": new_status, "messages": [HumanMessage(content=judge_response.content)]} return {"status": new_status, "error": None if new_status == "success" else "Verification failed"}
# ----------------------------------------------------------------------
def handle_error(state: AgentState): # Node: handle_error
"""Increase attempt counter and decide next step.""" # ----------------------------------------------------------------------
async def handle_error(state: AgentState) -> dict:
"""Increase attempt counter and decide whether to retry."""
attempts = state["attempts"] + 1 attempts = state["attempts"] + 1
if attempts >= state["max_attempts"]: if attempts >= state["max_attempts"]:
return { return {"attempts": attempts, "status": "max_attempts", "error": "Maximum attempts reached"}
"attempts": attempts,
"status": "max_attempts",
"error": state.get("error"),
}
else: else:
return { return {"attempts": attempts, "status": "pending", "error": None, "result": ""}
"attempts": attempts,
"status": "pending",
"error": None,
}
# ---------- Graph ---------- # ----------------------------------------------------------------------
graph = StateGraph(AgentState) # Build the StateGraph
# ----------------------------------------------------------------------
workflow = StateGraph(AgentState)
graph.add_node("execute_task", execute_task) workflow.add_node("execute_task", execute_task)
graph.add_node("verify_result", verify_result) workflow.add_node("verify_result", verify_result)
graph.add_node("handle_error", handle_error) workflow.add_node("handle_error", handle_error)
graph.add_edge(START, "execute_task") workflow.add_edge(START, "execute_task")
graph.add_edge("execute_task", "verify_result") workflow.add_edge("execute_task", "verify_result")
graph.add_conditional_edges( workflow.add_conditional_edges(
"verify_result", "verify_result",
lambda state: state["status"], lambda state: state["status"],
{ {
@@ -135,26 +147,24 @@ graph.add_conditional_edges(
"max_attempts": END, "max_attempts": END,
}, },
) )
graph.add_edge("handle_error", "execute_task") workflow.add_edge("handle_error", "execute_task")
graph.set_entry_point(START) graph = workflow.compile()
app = graph.compile() # ----------------------------------------------------------------------
# Main entry point
# ---------- Main ---------- # ----------------------------------------------------------------------
async def main(): async def run_task(task: str, max_attempts: int = 5):
task_description = "Calculate 2+2."
initial_state: AgentState = { initial_state: AgentState = {
"task": task_description, "task": task,
"result": "", "result": "",
"attempts": 0, "attempts": 0,
"status": "pending", "status": "pending",
"error": None, "error": None,
"max_attempts": 5, "max_attempts": max_attempts,
"messages": [], "messages": [],
} }
async for event in graph.astream(initial_state):
async for event in app.astream(initial_state):
# Print progress information # Print progress information
if "attempts" in event: if "attempts" in event:
print(f"Attempt {event['attempts']}: status={event['status']}") print(f"Attempt {event['attempts']}: status={event['status']}")
@@ -162,10 +172,9 @@ async def main():
print(f"Error: {event['error']}") print(f"Error: {event['error']}")
if event.get("result"): if event.get("result"):
print(f"Result: {event['result']}") print(f"Result: {event['result']}")
final = event
final = await app.ainvoke(initial_state)
print("\n=== Final Outcome ===") print("\n=== Final Outcome ===")
print(f"Task: {task_description}") print(f"Task: {task}")
print(f"Status: {final['status']}") print(f"Status: {final['status']}")
print(f"Attempts: {final['attempts']}") print(f"Attempts: {final['attempts']}")
if final["status"] == "success": if final["status"] == "success":
@@ -174,4 +183,6 @@ async def main():
print("Failed to obtain a correct result.") print("Failed to obtain a correct result.")
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(main()) # Example task: simple arithmetic
example_task = "2 + 2"
asyncio.run(run_task(example_task, max_attempts=5))