139 lines
4.2 KiB
Python
139 lines
4.2 KiB
Python
import os
|
||
import asyncio
|
||
import random
|
||
from typing import TypedDict, Annotated
|
||
|
||
from langchain_openai import ChatOpenAI
|
||
from langchain_core.messages import HumanMessage
|
||
from langchain.tools import tool
|
||
from deepagents import create_deep_agent
|
||
from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeBackend
|
||
from langgraph.graph import StateGraph, START, END
|
||
from langgraph.graph.message import add_messages
|
||
|
||
# ---------- LLM ----------
|
||
llm = ChatOpenAI(
|
||
model="openai/gpt-oss-20b:free",
|
||
base_url="https://openrouter.ai/api/v1",
|
||
api_key=os.getenv("OPENAI_API_KEY"),
|
||
temperature=0.0,
|
||
)
|
||
|
||
# ---------- Backend ----------
|
||
backend = CompositeBackend([
|
||
LocalShellBackend(workspace_dir="./workspace"),
|
||
FilesystemBackend(),
|
||
])
|
||
|
||
# ---------- Test tool (unreliable) ----------
|
||
@tool
|
||
def unreliable_tool(query: str) -> str:
|
||
"""Tool that succeeds 70% of the time, otherwise raises ValueError."""
|
||
if random.random() < 0.3:
|
||
raise ValueError("Simulated tool failure")
|
||
return f"Result for '{query}'"
|
||
|
||
# ---------- Agent ----------
|
||
agent = create_deep_agent(
|
||
model=llm,
|
||
tools=[unreliable_tool],
|
||
backend=backend,
|
||
system_prompt="You are a helpful agent that can execute tasks and self‑check the result.",
|
||
)
|
||
|
||
# ---------- State definition ----------
|
||
class AgentState(TypedDict):
|
||
task: str
|
||
result: str
|
||
attempts: int
|
||
status: str # pending | success | failed | max_attempts
|
||
error: str | None
|
||
max_attempts: int
|
||
|
||
# ---------- Nodes ----------
|
||
async def execute_task(state: AgentState) -> AgentState:
|
||
task = state["task"]
|
||
try:
|
||
# Use the agent to run the task via the tool
|
||
response = await agent.ainvoke(
|
||
{"messages": [HumanMessage(content=task)]},
|
||
{"configurable": {"thread_id": "session-1"}},
|
||
)
|
||
result = response["messages"][-1].content
|
||
state["result"] = result
|
||
state["error"] = None
|
||
except Exception as e:
|
||
state["result"] = ""
|
||
state["error"] = str(e)
|
||
return state
|
||
|
||
async def verify_result(state: AgentState) -> AgentState:
|
||
# Ask LLM to judge the result
|
||
prompt = (
|
||
f"Task: {state['task']}\n"
|
||
f"Result: {state['result']}\n"
|
||
f"Error: {state['error']}\n"
|
||
"Is the result correct? Respond with only 'success' or 'failed'."
|
||
)
|
||
judge = await llm.ainvoke([HumanMessage(content=prompt)])
|
||
verdict = judge.content.strip().lower()
|
||
if verdict == "success":
|
||
state["status"] = "success"
|
||
else:
|
||
state["status"] = "failed"
|
||
return state
|
||
|
||
async def handle_error(state: AgentState) -> AgentState:
|
||
state["attempts"] += 1
|
||
if state["attempts"] >= state["max_attempts"]:
|
||
state["status"] = "max_attempts"
|
||
else:
|
||
state["status"] = "pending"
|
||
return state
|
||
|
||
# ---------- Graph ----------
|
||
graph = StateGraph(AgentState)
|
||
graph.add_node("execute_task", execute_task)
|
||
graph.add_node("verify_result", verify_result)
|
||
graph.add_node("handle_error", handle_error)
|
||
|
||
graph.set_entry_point("execute_task")
|
||
graph.add_conditional_edges(
|
||
"execute_task",
|
||
lambda x: "verify_result",
|
||
)
|
||
graph.add_conditional_edges(
|
||
"verify_result",
|
||
lambda x: "handle_error" if x["status"] == "failed" else "END",
|
||
)
|
||
graph.add_conditional_edges(
|
||
"handle_error",
|
||
lambda x: "execute_task" if x["status"] == "pending" else "END",
|
||
)
|
||
|
||
flow = graph.compile()
|
||
|
||
# ---------- Runner ----------
|
||
async def run_task(task: str, max_attempts: int = 5):
|
||
initial_state: AgentState = {
|
||
"task": task,
|
||
"result": "",
|
||
"attempts": 0,
|
||
"status": "pending",
|
||
"error": None,
|
||
"max_attempts": max_attempts,
|
||
}
|
||
async for event in flow.astream(initial_state):
|
||
if event.get("type") == "state":
|
||
state = event["data"]
|
||
print(f"Попытка {state['attempts'] + 1}: status={state['status']}")
|
||
if state["error"]:
|
||
print(f" Error: {state['error']}")
|
||
if state["result"]:
|
||
print(f" Result: {state['result']}")
|
||
final_state = event["data"]
|
||
print("\nИтог:", final_state["status"], "за", final_state["attempts"] + 1, "попытки")
|
||
|
||
if __name__ == "__main__":
|
||
asyncio.run(run_task("Вычисли 2+2", max_attempts=5))
|