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

This commit is contained in:
2026-07-02 08:31:52 +00:00
parent 0a3028e899
commit fb7d6749e2
+37 -56
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 os
import asyncio from typing import Optional
from typing import Optional, Dict, Any
from langchain_openai import ChatOpenAI from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langchain.tools import tool from langchain.tools import tool
from langgraph import create_agent
from langgraph.checkpoint.memory import MemorySaver 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 from rich.console import Console
# Инициализация консоли rich # Initialize LLM
console = Console()
# Инициализация LLM через OpenRouter
llm = ChatOpenAI( llm = ChatOpenAI(
model="openai/gpt-oss-20b:free", model="openai/gpt-oss-20b:free",
base_url="https://openrouter.ai/api/v1", base_url="https://openrouter.ai/api/v1",
@@ -21,83 +20,65 @@ llm = ChatOpenAI(
temperature=0.0, temperature=0.0,
) )
# Backend для deepagents (необязательно, но удобно) # Define a simple tool
backend = CompositeBackend([
LocalShellBackend(workspace_dir="./workspace"),
FilesystemBackend(),
])
# Пример простого инструмента
@tool @tool
def get_price(city: str, date: str) -> str: def get_price(query: str) -> str:
"""Возвращает цену в указанном городе и дате.""" """Get price for a city and date."""
return f"Цена в {city} на {date} составляет $100" return f"Price for {query} is $100"
# Память разговора # Create agent with memory and interrupt before tools
memory = MemorySaver()
# Создание агента с памятью и паузой перед инструментом
agent = create_agent( agent = create_agent(
model=llm, model=llm,
tools=[get_price], tools=[get_price],
backend=backend,
system_prompt="You are a helpful agent.", system_prompt="You are a helpful agent.",
checkpointer=memory, checkpointer=MemorySaver(),
interrupt_before=["tools"], interrupt_before=["tools"],
) )
# Конфигурация разговора console = Console()
config: Dict[str, Any] = {"configurable": {"thread_id": "conversation-1"}} 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, user_input,
config=config, config=cfg,
stream_mode=["messages", "updates"], stream_mode=["messages", "updates"],
): ):
# Вывод токенов ответа chunk_type, chunk_data = chunk
# Handle message tokens
if chunk_type == "messages": if chunk_type == "messages":
content = chunk_data.get("content", "") content = chunk_data.get("content", "")
console.print(content, end="") console.print(content, end="")
# Вывод информации о вызове инструмента # Handle tool call results or other updates
elif chunk_type == "updates": if chunk_type == "updates":
console.print(chunk_data) console.print(chunk_data)
# Обнаружение паузы перед инструментом # Detect pause before tool invocation
if "__interrupt__" in chunk_data and agent.get_state(config).next == ("tools",): if "__interrupt__" in chunk_data and agent.get_state(cfg).next == ("tools",):
state = agent.get_state(config) state = agent.get_state(cfg)
# Последнее сообщение содержит вызов инструмента last_msg = state.values["messages"][-1]
tool_call = state.values["messages"][-1].tool_calls[0] tool_call = last_msg.tool_calls[0]
console.print("\n") name = tool_call["name"]
console.print(f"{tool_call['name']}({tool_call['args']})") args = tool_call["arguments"]
console.print("Агент хочет вызвать утилиту") console.print(f"{name}({args})")
console.print(f"Агент хочет вызвать утилиту {name}({args})")
answer = input("Разрешить? (Y/n): ") answer = input("Разрешить? (Y/n): ")
if answer.lower().strip() == "y": if answer.lower().strip() == "y":
await ask_and_run(None, config) ask_and_run(None, cfg)
return
else: else:
console.print("Отменено") console.print("Отменено")
return break
def main() -> None:
console.print("\n--- --- ---\n")
while True: while True:
user_input = input("\nВы: ") user_input = input("\nВы: ")
if user_input.lower().strip() == "exit": if user_input.lower() == "exit":
break break
# Запускаем асинхронную функцию
asyncio.run(
ask_and_run( ask_and_run(
{"messages": [{"role": "human", "content": user_input}]}, {"messages": [{"role": "human", "content": user_input}]},
config, config,
) )
)
console.print("\n--- --- ---\n")
if __name__ == "__main__":
main()