Экзамен: Планирующий агент: solution.py

This commit is contained in:
2026-05-29 06:39:23 +00:00
parent 53ed987130
commit 4f66fdf7b2
+169
View File
@@ -0,0 +1,169 @@
# solution.py
# Планирующий агент на основе LangGraph, LangChain и questionary
# Запуск: python solution.py
import os
from typing import TypedDict, List, Optional, Dict, Any
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.sqlite import SqliteSaver
from questionary import select, text
# ----------------------------------------------------------------------
# 1. Определяем состояние графа
# ----------------------------------------------------------------------
class AgentState(TypedDict):
# Ввод задачи от пользователя
task: str
# Список шагов, которые LLM распланировал
plan: List[str]
# Текущий индекс шага
step_index: int
# Список результатов выполнения шагов
step_results: List[str]
# Итоговый ответ
final_answer: Optional[str]
# Выбранный вариант (если есть)
choice: Optional[str]
# ----------------------------------------------------------------------
# 2. Инициализируем LLM
# ----------------------------------------------------------------------
# Ожидается, что переменная окружения OPENAI_API_KEY установлена
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.2)
# ----------------------------------------------------------------------
# 3. Определяем функции-узлы
# ----------------------------------------------------------------------
def plan(state: AgentState) -> Dict[str, Any]:
"""
Первый узел: LLM разбивает задачу на шаги.
"""
prompt = f"""
Задача: {state['task']}
Разбей её на последовательные шаги, которые можно выполнить один за другим.
Выведите список шагов, каждый шаг в отдельной строке, без префиксов.
"""
response = llm.invoke(prompt)
steps = [s.strip() for s in response.content.splitlines() if s.strip()]
return {"plan": steps, "step_index": 0, "step_results": []}
def execute_step(state: AgentState) -> Dict[str, Any]:
"""
Выполняем текущий шаг из плана.
"""
idx = state["step_index"]
if idx >= len(state["plan"]):
# План завершён
return {"final_answer": "План выполнен."}
current_step = state["plan"][idx]
prompt = f"""
Выполните следующий шаг задачи:
Шаг {idx + 1}: {current_step}
Предоставьте краткий результат выполнения этого шага.
"""
response = llm.invoke(prompt)
result = response.content.strip()
new_results = state["step_results"] + [result]
return {
"step_results": new_results,
"step_index": idx + 1
}
def ask_choice(state: AgentState) -> Dict[str, Any]:
"""
Если в результате выполнения шага есть варианты выбора,
предлагаем пользователю выбрать один.
"""
last_result = state["step_results"][-1]
# Простейший парсинг: ищем строки вида "1) ... 2) ..."
import re
matches = re.findall(r'\d+\)\s*([^\d]+)', last_result)
if not matches:
# Нет вариантов, просто продолжаем
return {}
# Показываем варианты пользователю
choice = select(
"Выберите вариант:",
choices=[f"{i+1}) {m.strip()}" for i, m in enumerate(matches)]
).ask()
# Сохраняем выбранный вариант
return {"choice": choice}
def finalize(state: AgentState) -> Dict[str, Any]:
"""
После завершения всех шагов формируем итоговый ответ.
"""
prompt = f"""
Задача: {state['task']}
Выполнены шаги:
{chr(10).join(f"{i+1}. {r}" for i, r in enumerate(state['step_results']))}
На основании результата сформируйте окончательный ответ на задачу.
"""
response = llm.invoke(prompt)
return {"final_answer": response.content.strip()}
# ----------------------------------------------------------------------
# 4. Строим граф
# ----------------------------------------------------------------------
builder = StateGraph(AgentState)
# Добавляем узлы
builder.add_node("plan", plan)
builder.add_node("execute_step", execute_step)
builder.add_node("ask_choice", ask_choice)
builder.add_node("finalize", finalize)
# Определяем переходы
builder.set_entry_point("plan")
builder.add_conditional_edges(
"plan",
lambda x: "execute_step",
{"execute_step": "execute_step"}
)
builder.add_conditional_edges(
"execute_step",
lambda x: "finalize" if x["step_index"] >= len(x["plan"]) else "ask_choice",
{"finalize": "finalize", "ask_choice": "ask_choice"}
)
builder.add_conditional_edges(
"ask_choice",
lambda x: "execute_step",
{"execute_step": "execute_step"}
)
builder.add_edge("finalize", END)
# Сохраняем состояние в SQLite (чекпоинтер)
checkpoint = SqliteSaver.from_conn_str("sqlite:///langgraph_checkpoint.db")
graph = builder.compile(checkpointer=checkpoint)
# ----------------------------------------------------------------------
# 5. Запуск агента
# ----------------------------------------------------------------------
def main() -> None:
# Получаем задачу от пользователя
task = text("Введите задачу для агента:").ask()
# Инициализируем состояние
init_state: AgentState = {
"task": task,
"plan": [],
"step_index": 0,
"step_results": [],
"final_answer": None,
"choice": None,
}
# Запускаем граф
for event in graph.stream(init_state):
# event - это обновлённое состояние
if event["final_answer"]:
print("\n=== Итоговый ответ ===")
print(event["final_answer"])
break
if __name__ == "__main__":
main()