Экзамен: Самокорректирующийся агент: main.py
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
main.py
|
||||
|
||||
Самокорректирующийся агент на LangGraph.
|
||||
- AgentState содержит task, result, attempts, status, error, max_attempts.
|
||||
- Узлы: execute_task, verify_result, handle_error.
|
||||
- Инструмент unreliable_tool с ~30% вероятностью бросает ValueError.
|
||||
- verify_result использует LLM-as-judge (OpenAI Ollama) и отвечает строго "success" или "failed".
|
||||
- Граф реализует цикл retry до исчерпания max_attempts.
|
||||
- При запуске выводит номера попыток и финальный статус.
|
||||
"""
|
||||
|
||||
import os
|
||||
import random
|
||||
from typing import TypedDict, Optional
|
||||
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
from langchain_openai import ChatOpenAI # требует OPENAI_API_KEY в окружении
|
||||
from langchain_core.messages import HumanMessage
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# 1. Состояние агента
|
||||
# ----------------------------------------------------------------------
|
||||
class AgentState(TypedDict):
|
||||
task: str
|
||||
result: str
|
||||
attempts: int
|
||||
status: str # pending | success | failed | max_attempts
|
||||
error: Optional[str]
|
||||
max_attempts: int
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# 2. Ненадёжный инструмент
|
||||
# ----------------------------------------------------------------------
|
||||
def unreliable_tool(task: str) -> str:
|
||||
"""
|
||||
Выполняет простую арифметическую задачу (eval) с вероятностью ~30% бросить ValueError.
|
||||
Для демонстрации retry.
|
||||
"""
|
||||
if random.random() < 0.3:
|
||||
raise ValueError("Случайная ошибка инструмента")
|
||||
# Безопасный eval только для простых арифметических выражений.
|
||||
# В реальном коде следует использовать более строгую валидацию.
|
||||
return str(eval(task, {"__builtins__": {}}))
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# 3. LLM‑judge (OpenAI). Если ключ не задан – fallback на простую проверку.
|
||||
# ----------------------------------------------------------------------
|
||||
def get_llm():
|
||||
api_key = os.getenv("OPENAI_API_KEY")
|
||||
if api_key:
|
||||
return ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo")
|
||||
return None
|
||||
|
||||
|
||||
LLM = get_llm()
|
||||
|
||||
|
||||
def judge_with_llm(task: str, result: str) -> str:
|
||||
"""
|
||||
Запрашивает у LLM оценку результата.
|
||||
Ожидается ответ exatamente "success" или "failed".
|
||||
"""
|
||||
if LLM is None:
|
||||
# Fallback: просто сравниваем с правильным ответом через eval
|
||||
try:
|
||||
correct = str(eval(task, {"__builtins__": {}}))
|
||||
return "success" if result.strip() == correct else "failed"
|
||||
except Exception:
|
||||
return "failed"
|
||||
|
||||
prompt = (
|
||||
"You are a judge. Determine if the result correctly answers the task.\n"
|
||||
f"Task: {task}\n"
|
||||
f"Result: {result}\n"
|
||||
"Respond with exactly one word: 'success' if the result is correct, otherwise 'failed'."
|
||||
)
|
||||
msg = [HumanMessage(content=prompt)]
|
||||
response = LLM.invoke(msg)
|
||||
verdict = response.content.strip().lower()
|
||||
# Приводим к одному из допустимых вариантов
|
||||
if "success" in verdict:
|
||||
return "success"
|
||||
return "failed"
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# 4. Узлы графа
|
||||
# ----------------------------------------------------------------------
|
||||
def execute_task(state: AgentState) -> AgentState:
|
||||
"""Выполняет задачу через unreliable_tool."""
|
||||
state["attempts"] += 1
|
||||
state["error"] = None
|
||||
try:
|
||||
state["result"] = unreliable_tool(state["task"])
|
||||
except Exception as e:
|
||||
state["result"] = ""
|
||||
state["error"] = str(e)
|
||||
return state
|
||||
|
||||
|
||||
def verify_result(state: AgentState) -> AgentState:
|
||||
"""LLM‑as‑judge: ставит status = success/failed."""
|
||||
if state["error"] is not None:
|
||||
# Если инструмент уже упал – считаем failed без вызова LLM
|
||||
state["status"] = "failed"
|
||||
return state
|
||||
|
||||
verdict = judge_with_llm(state["task"], state["result"])
|
||||
state["status"] = verdict
|
||||
return state
|
||||
|
||||
|
||||
def handle_error(state: AgentState) -> AgentState:
|
||||
"""Подготовка к повторной попытке."""
|
||||
# Ставим статус pending, чтобы граф вернулся к execute_task
|
||||
state["status"] = "pending"
|
||||
# Ошибку оставляем для логов, но не очищаем – можно очистить, если нужно
|
||||
return state
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# 5. Построение графа
|
||||
# ----------------------------------------------------------------------
|
||||
def build_graph() -> StateGraph:
|
||||
workflow = StateGraph(AgentState)
|
||||
|
||||
# Регистрация узлов
|
||||
workflow.add_node("execute_task", execute_task)
|
||||
workflow.add_node("verify_result", verify_result)
|
||||
workflow.add_node("handle_error", handle_error)
|
||||
|
||||
# Входная точка
|
||||
workflow.add_edge(START, "execute_task")
|
||||
# После выполнения всегда идём к проверке
|
||||
workflow.add_edge("execute_task", "verify_result")
|
||||
|
||||
# Условные переходы из verify_result
|
||||
def should_continue(state: AgentState) -> str:
|
||||
if state["status"] == "success":
|
||||
return END
|
||||
if state["status"] == "failed" and state["attempts"] < state["max_attempts"]:
|
||||
return "handle_error"
|
||||
# Либо max_attempts исчерпан, либо иной статус – завершаем
|
||||
return END
|
||||
|
||||
workflow.add_conditional_edges(
|
||||
"verify_result",
|
||||
should_continue,
|
||||
{
|
||||
"handle_error": "handle_error",
|
||||
END: END,
|
||||
},
|
||||
)
|
||||
|
||||
# После handle_error повторяем выполнение задачи
|
||||
workflow.add_edge("handle_error", "execute_task")
|
||||
|
||||
return workflow.compile()
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# 6. CLI‑запуск
|
||||
# ----------------------------------------------------------------------
|
||||
def main():
|
||||
# Пример задачи; можно переопределить через аргумент командной строки
|
||||
import sys
|
||||
task = sys.argv[1] if len(sys.argv) > 1 else "Вычисли 2+2"
|
||||
|
||||
initial_state: AgentState = {
|
||||
"task": task,
|
||||
"result": "",
|
||||
"attempts": 0,
|
||||
"status": "pending",
|
||||
"error": None,
|
||||
"max_attempts": 5, # можно изменить
|
||||
}
|
||||
|
||||
app = build_graph()
|
||||
final_state = app.invoke(initial_state)
|
||||
|
||||
print(f"Задача: {final_state['task']}")
|
||||
print(f"Итоговый статус: {final_state['status']}")
|
||||
print(f"Попыток сделано: {final_state['attempts']}")
|
||||
if final_state["result"]:
|
||||
print(f"Результат: {final_state['result']}")
|
||||
if final_state["error"]:
|
||||
print(f"Последняя ошибка: {final_state['error']}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user