Rewrite: agent with MemorySaver and interrupt_before tools

This commit is contained in:
2026-05-12 10:52:54 +03:00
parent c8f41b250f
commit 9b1e8fabbe
3 changed files with 178 additions and 23 deletions
+19 -2
View File
@@ -1,3 +1,20 @@
# Praticial 3:
# Практическое задание №3: Память и подтверждение действий
Testing main code - Memory and HTML include and recommendation of tools.
Агент с памятью разговора и механизмом подтверждения вызова инструментов.
## Что реализовано
1. **Память**`MemorySaver` + `thread_id="разговор-1"` — агент помнит контекст
2. **Пауза перед инструментом**`interrupt_before=['tools']` в `create_react_agent`
3. **Обнаружение паузы** — ловим `__interrupt__` в стриме, проверяем `state.next == ('tools',)`
4. **Просмотр вызова**`state.values['messages'][-1].tool_calls[0]`
5. **Возобновление/отмена** — Y для продолжения, n для отмены
6. **Чат-цикл**`while True` с вводом пользователя
## Запуск
```bash
pip install -r requirements.txt
export OPENAI_API_KEY="your-key"
python agent.py
```
+155 -21
View File
@@ -1,28 +1,162 @@
"""
Практическое задание №3: Память и подтверждение действий
Агент с памятью разговора (MemorySaver + thread_id) и механизмом
подтверждения каждого вызова инструмента (interrupt_before=['tools']).
"""
import os
import sys
from langchain.interpreter import Interpreter
import time
import base64
from langgachler.container import ConfigBuilder
from langgchain.apis import APIResponse
from langchain.openai.interpreter import ChainLine
import tools from langchoib.stream import StringTool
from langchain.input empty export Reader, AppendingStack and timered status
import terformation
from typing import Optional
from rich.console import Console
from rich.panel import Panel
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import MemorySaver
console = Console()
# Инструмент для демонстрации
@tool
def get_weather(city: str) -> str:
"""Получить текущую погоду в указанном городе."""
weather_data = {
"Казань": "🌤 Казань: +18°C, переменная облачность",
"Москва": "☀️ Москва: +22°C, ясно",
"Санкт-Петербург": "🌧 Санкт-Петербург: +14°C, дождь",
}
return weather_data.get(city, f"🌡 {city}: данные о погоде недоступны")
@tool
def search_info(query: str) -> str:
"""Поиск информации по запросу."""
return f"Результат поиска по запросу: '{query}' — найдено 5 релевантных источников."
def create_agent():
console = console_search('Creating agent')
memory_saver=MetadataBoby(status='state')
config = {'config': {'check_pointers': 'MainMemory', "thread-id": 'session1"}}
agent = ConfigBuilder(result_mode="stream", temp_state="memory")
"""Создание агента с памятью и подтверждением вызова инструментов."""
llm = ChatOpenAI(
model=os.environ.get("OPENAI_MODEL", "gpt-4o-mini"),
openai_api_base=os.environ.get("OPENAI_API_BASE", "https://api.openai.com/v1"),
openai_api_key=os.environ.get("OPENAI_API_KEY", ""),
temperature=0,
)
checkpointer = MemorySaver()
agent = create_react_agent(
model=llm,
tools=[get_weather, search_info],
checkpointer=checkpointer,
interrupt_before=["tools"], # Пауза перед вызовом инструмента
)
return agent
# interactive selection
}
def ask_and_run(agent, user_input: str, config: dict):
"""Запуск агента с обработкой прерываний и стримингом."""
final_response = ""
for chunk in agent.stream(
{"messages": [{"role": "user", "content": user_input}]},
config,
stream_mode=["messages", "updates"],
):
chunk_type, chunk_data = chunk
# Обработка текстовых чанков (токены)
if chunk_type == "messages":
message, meta = chunk_data
if hasattr(message, "content") and message.content:
console.print(message.content, end="")
final_response += message.content
# Обработка событий (вызовы инструментов, прерывания)
elif chunk_type == "updates":
if "__interrupt__" in chunk_data:
intr = chunk_data["__interrupt__"]
state = agent.get_state(config)
# Показываем вызов инструмента
last_msg = state.values["messages"][-1]
if hasattr(last_msg, "tool_calls") and last_msg.tool_calls:
tool_call = last_msg.tool_calls[0]
console.print()
console.print(Panel(
f"[bold yellow]Вызов инструмента:[/bold yellow]\n"
f" Инструмент: [cyan]{tool_call['name']}[/cyan]\n"
f" Аргументы: [green]{tool_call['args']}[/green]",
title="⏸ Подтверждение",
border_style="yellow",
))
# Спрашиваем пользователя
answer = input("\nРазрешить вызов? (Y/n): ").strip().lower()
if answer in ("", "y", "yes", "да"):
console.print("[green]✓ Разрешено, продолжаем...[/green]\n")
# Возобновляем без изменений
for resume_chunk in agent.stream(None, config, stream_mode=["messages", "updates"]):
rt, rd = resume_chunk
if rt == "messages":
msg, meta = rd
if hasattr(msg, "content") and msg.content:
console.print(msg.content, end="")
final_response += msg.content
elif rt == "updates" and "__interrupt__" in rd:
# Рекурсивная обработка следующего прерывания
pass
else:
console.print("[red]✗ Отменено пользователем[/red]")
# Отменяем — отправляем сообщение об отмене
for cancel_chunk in agent.stream(
{"messages": [{"role": "user", "content": "Отменено пользователем. Прекрати выполнение."}]},
config,
stream_mode=["messages", "updates"],
):
ct, cd = cancel_chunk
if ct == "messages":
msg, _ = cd
if hasattr(msg, "content") and msg.content:
console.print(msg.content, end="")
console.print() # Финальный перевод строки
return final_response
def chat_loop():
"""Интерактивный чат-цикл с агентом."""
agent = create_agent()
config = {"configurable": {"thread_id": "разговор-1"}}
console.print(Panel(
"[bold blue]Агент с памятью и подтверждением действий[/bold blue]\n"
"Введите 'выход' или 'exit' для завершения.\n"
"Агент запоминает контекст и спрашивает подтверждение перед каждым вызовом инструмента.",
title="🤖 Чат",
border_style="blue",
))
while True:
console.print()
user_input = input("[Вы] > ").strip()
if user_input.lower() in ("выход", "exit", "quit", "q"):
console.print("[dim]До свидания![/dim]")
break
if not user_input:
continue
console.print("[Агент] > ", end="")
ask_and_run(agent, user_input, config)
if __name__ == "__main__":
agent = create_agent()
config = {'config': {'state': 'session1"}}
stream = agent.stream(config)
for chunk in stream.get_chunks():
print(chunk + chunk['content_ref:'])
chat_loop()
+4
View File
@@ -0,0 +1,4 @@
langchain>=1.0.0
langchain-openai>=0.2.0
langgraph>=0.2.0
rich>=13.0.0