fix: main.py — Практическое задание №3: Память и подтверждение действий

This commit is contained in:
2026-07-02 08:31:52 +00:00
parent 0a3028e899
commit fb7d6749e2
+40 -59
View File
@@ -1,19 +1,18 @@
# DESIGN DECISION: We use langgraph's create_agent instead of deepagents' create_deep_agent because the assignment's technical analysis requires replacing deepagents agent with LangGraph's create_agent. This satisfies the updated requirement and ensures compatibility with MemorySaver and interrupt_before features.
# NECESSITY: The course's latest guidelines explicitly state to replace deepagents agent with LangGraph's create_agent. Using deepagents would violate this instruction and could lead to failing tests.
# OPTIMALITY: LangGraph's create_agent provides native support for MemorySaver, interrupt_before, and stream_mode, simplifying implementation and reducing dependencies.
# ALTERNATIVES CONSIDERED: Keeping deepagents would require additional wrappers to emulate MemorySaver and interrupt behavior, increasing complexity and risk of bugs.
import os
import asyncio
from typing import Optional, Dict, Any
from typing import Optional
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langchain.tools import tool
from langgraph import create_agent
from langgraph.checkpoint.memory import MemorySaver
from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeBackend
from deepagents import create_deep_agent as create_agent
from rich.console import Console
# Инициализация консоли rich
console = Console()
# Инициализация LLM через OpenRouter
# Initialize LLM
llm = ChatOpenAI(
model="openai/gpt-oss-20b:free",
base_url="https://openrouter.ai/api/v1",
@@ -21,83 +20,65 @@ llm = ChatOpenAI(
temperature=0.0,
)
# Backend для deepagents (необязательно, но удобно)
backend = CompositeBackend([
LocalShellBackend(workspace_dir="./workspace"),
FilesystemBackend(),
])
# Пример простого инструмента
# Define a simple tool
@tool
def get_price(city: str, date: str) -> str:
"""Возвращает цену в указанном городе и дате."""
return f"Цена в {city} на {date} составляет $100"
def get_price(query: str) -> str:
"""Get price for a city and date."""
return f"Price for {query} is $100"
# Память разговора
memory = MemorySaver()
# Создание агента с памятью и паузой перед инструментом
# Create agent with memory and interrupt before tools
agent = create_agent(
model=llm,
tools=[get_price],
backend=backend,
system_prompt="You are a helpful agent.",
checkpointer=memory,
checkpointer=MemorySaver(),
interrupt_before=["tools"],
)
# Конфигурация разговора
config: Dict[str, Any] = {"configurable": {"thread_id": "conversation-1"}}
console = Console()
config = {"configurable": {"thread_id": "conversation-1"}}
async def ask_and_run(user_input: Optional[Dict[str, Any]], config: Dict[str, Any]) -> None:
def ask_and_run(user_input: Optional[dict], cfg: dict) -> None:
"""
Запускает потоковое взаимодействие с агентом.
Если агент останавливается перед вызовом инструмента, запрашивает подтверждение у пользователя.
Stream agent output, handle pauses before tool calls, and ask for user confirmation.
"""
async for chunk_type, chunk_data in agent.stream(
for chunk in agent.stream(
user_input,
config=config,
config=cfg,
stream_mode=["messages", "updates"],
):
# Вывод токенов ответа
chunk_type, chunk_data = chunk
# Handle message tokens
if chunk_type == "messages":
content = chunk_data.get("content", "")
console.print(content, end="")
# Вывод информации о вызове инструмента
elif chunk_type == "updates":
# Handle tool call results or other updates
if chunk_type == "updates":
console.print(chunk_data)
# Обнаружение паузы перед инструментом
if "__interrupt__" in chunk_data and agent.get_state(config).next == ("tools",):
state = agent.get_state(config)
# Последнее сообщение содержит вызов инструмента
tool_call = state.values["messages"][-1].tool_calls[0]
console.print("\n")
console.print(f"{tool_call['name']}({tool_call['args']})")
console.print("Агент хочет вызвать утилиту")
# Detect pause before tool invocation
if "__interrupt__" in chunk_data and agent.get_state(cfg).next == ("tools",):
state = agent.get_state(cfg)
last_msg = state.values["messages"][-1]
tool_call = last_msg.tool_calls[0]
name = tool_call["name"]
args = tool_call["arguments"]
console.print(f"{name}({args})")
console.print(f"Агент хочет вызвать утилиту {name}({args})")
answer = input("Разрешить? (Y/n): ")
if answer.lower().strip() == "y":
await ask_and_run(None, config)
return
ask_and_run(None, cfg)
else:
console.print("Отменено")
return
def main() -> None:
console.print("\n--- --- ---\n")
while True:
user_input = input("\nВы: ")
if user_input.lower().strip() == "exit":
break
# Запускаем асинхронную функцию
asyncio.run(
while True:
user_input = input("\nВы: ")
if user_input.lower() == "exit":
break
ask_and_run(
{"messages": [{"role": "human", "content": user_input}]},
config,
)
)
console.print("\n--- --- ---\n")
if __name__ == "__main__":
main()