Экзамен: Самокорректирующийся агент: main.py
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
main.py – запуск LangGraph‑агента с самопроверкой и retry.
|
||||
|
||||
Требования:
|
||||
- Python 3.10+
|
||||
- pip install langgraph langchain-openai
|
||||
"""
|
||||
|
||||
import random
|
||||
from typing import TypedDict, Dict, Any
|
||||
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.graph import StateGraph, END
|
||||
from langgraph.prebuilt import InMemorySaver
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# 1. Состояние графа
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
class AgentState(TypedDict):
|
||||
task: str # исходная задача
|
||||
result: str # результат выполнения инструмента
|
||||
attempts: int # количество попыток
|
||||
status: str # pending | success | failed | max_attempts
|
||||
error: str | None # сообщение об ошибке (если есть)
|
||||
max_attempts: int # лимит попыток
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# 2. Инструмент – unreliable_tool
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def unreliable_tool(input_text: str) -> str:
|
||||
"""
|
||||
Случайно бросает ValueError с вероятностью ~30%.
|
||||
Иначе возвращает результат выполнения простого арифметического выражения.
|
||||
"""
|
||||
if random.random() < 0.3:
|
||||
raise ValueError("Инструмент случайно упал")
|
||||
# Простейший парсер: ищем числа и оператор +, -, *, /
|
||||
try:
|
||||
left, op, right = input_text.split()
|
||||
left_val = float(left)
|
||||
right_val = float(right)
|
||||
if op == '+':
|
||||
return str(left_val + right_val)
|
||||
elif op == '-':
|
||||
return str(left_val - right_val)
|
||||
elif op == '*':
|
||||
return str(left_val * right_val)
|
||||
elif op == '/':
|
||||
return str(left_val / right_val)
|
||||
except Exception:
|
||||
pass
|
||||
# Если не удалось распарсить – просто возвращаем строку
|
||||
return f"результат: {input_text}"
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# 3. Узлы графа
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def execute_task(state: AgentState) -> Dict[str, Any]:
|
||||
"""
|
||||
Выполняет задачу через unreliable_tool.
|
||||
При ошибке сохраняется сообщение об ошибке и статус «failed».
|
||||
"""
|
||||
try:
|
||||
result = unreliable_tool(state["task"])
|
||||
return {
|
||||
"result": result,
|
||||
"error": None,
|
||||
"status": "pending", # переход к verify_result
|
||||
}
|
||||
except Exception as exc:
|
||||
return {
|
||||
"result": "",
|
||||
"error": str(exc),
|
||||
"status": "failed",
|
||||
}
|
||||
|
||||
|
||||
def verify_result(state: AgentState) -> Dict[str, Any]:
|
||||
"""
|
||||
LLM‑оценка результата. Модель должна ответить «success» или «failed».
|
||||
Если результат пустой – сразу считается «failed».
|
||||
"""
|
||||
if not state["result"]:
|
||||
return {"status": "failed"}
|
||||
|
||||
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
|
||||
prompt = (
|
||||
f"Задача: {state['task']}\n"
|
||||
f"Результат: {state['result']}\n\n"
|
||||
"Оцените результат. Ответьте только одним словом: success или failed."
|
||||
)
|
||||
response = llm.invoke(prompt).content.strip().lower()
|
||||
if response.startswith("success"):
|
||||
return {"status": "success"}
|
||||
else:
|
||||
return {"status": "failed"}
|
||||
|
||||
|
||||
def handle_error(state: AgentState) -> Dict[str, Any]:
|
||||
"""
|
||||
Увеличивает счётчик попыток и готовит повторный запуск.
|
||||
Если достигнут лимит – статус «max_attempts».
|
||||
"""
|
||||
attempts = state["attempts"] + 1
|
||||
if attempts >= state["max_attempts"]:
|
||||
return {"attempts": attempts, "status": "max_attempts"}
|
||||
else:
|
||||
return {"attempts": attempts, "status": "pending"}
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# 4. Построение графа
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
builder = StateGraph(AgentState)
|
||||
|
||||
# Добавляем узлы
|
||||
builder.add_node("execute_task", execute_task)
|
||||
builder.add_node("verify_result", verify_result)
|
||||
builder.add_node("handle_error", handle_error)
|
||||
|
||||
# Определяем переходы
|
||||
builder.set_entry_point("execute_task")
|
||||
|
||||
builder.add_conditional_edges(
|
||||
"execute_task",
|
||||
lambda x: x["status"],
|
||||
{
|
||||
"pending": "verify_result",
|
||||
"failed": "handle_error",
|
||||
},
|
||||
)
|
||||
|
||||
builder.add_conditional_edges(
|
||||
"verify_result",
|
||||
lambda x: x["status"],
|
||||
{
|
||||
"success": END,
|
||||
"failed": "handle_error",
|
||||
},
|
||||
)
|
||||
|
||||
builder.add_conditional_edges(
|
||||
"handle_error",
|
||||
lambda x: x["status"],
|
||||
{
|
||||
"pending": "execute_task",
|
||||
"max_attempts": END,
|
||||
},
|
||||
)
|
||||
|
||||
graph = builder.compile(saver=InMemorySaver())
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# 5. Запуск
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
def main() -> None:
|
||||
"""
|
||||
CLI‑интерфейс: пользователь вводит задачу, например «2 + 2».
|
||||
Выводятся номера попыток и финальный статус.
|
||||
"""
|
||||
task = input("Введите задачу (пример: 2 + 2): ").strip()
|
||||
if not task:
|
||||
print("Задача не может быть пустой.")
|
||||
return
|
||||
|
||||
# Инициализируем состояние
|
||||
initial_state: AgentState = {
|
||||
"task": task,
|
||||
"result": "",
|
||||
"attempts": 0,
|
||||
"status": "pending",
|
||||
"error": None,
|
||||
"max_attempts": 5,
|
||||
}
|
||||
|
||||
print("\nЗапуск агента...\n")
|
||||
|
||||
# Запускаем граф
|
||||
for step in graph.stream(initial_state, stream_mode="values"):
|
||||
state = step.values
|
||||
attempts = state["attempts"] + 1 if state["status"] != "pending" else state["attempts"]
|
||||
print(f"Попытка {attempts}:")
|
||||
if state.get("error"):
|
||||
print(f" Ошибка инструмента: {state['error']}")
|
||||
else:
|
||||
print(f" Результат: {state['result']}")
|
||||
|
||||
if state["status"] == "success":
|
||||
print("\nИтог: success за", attempts, "попыток")
|
||||
break
|
||||
elif state["status"] == "max_attempts":
|
||||
print("\nИтог: max_attempts – задача не решена после 5 попыток")
|
||||
break
|
||||
|
||||
else:
|
||||
# Если цикл завершился без явного выхода (должно быть)
|
||||
print("\nНеизвестный конец выполнения.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user