fix: main.py — Экзамен: Самокорректирующийся агент
This commit is contained in:
@@ -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))
|
||||||
Reference in New Issue
Block a user