Rewrite: agent with MemorySaver and interrupt_before tools
This commit is contained in:
@@ -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
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,28 +1,162 @@
|
|||||||
|
"""
|
||||||
|
Практическое задание №3: Память и подтверждение действий
|
||||||
|
|
||||||
|
Агент с памятью разговора (MemorySaver + thread_id) и механизмом
|
||||||
|
подтверждения каждого вызова инструмента (interrupt_before=['tools']).
|
||||||
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
from typing import Optional
|
||||||
from langchain.interpreter import Interpreter
|
from rich.console import Console
|
||||||
import time
|
from rich.panel import Panel
|
||||||
import base64
|
from langchain_core.tools import tool
|
||||||
from langgachler.container import ConfigBuilder
|
from langchain_openai import ChatOpenAI
|
||||||
from langgchain.apis import APIResponse
|
from langgraph.prebuilt import create_react_agent
|
||||||
from langchain.openai.interpreter import ChainLine
|
from langgraph.checkpoint.memory import MemorySaver
|
||||||
import tools from langchoib.stream import StringTool
|
|
||||||
from langchain.input empty export Reader, AppendingStack and timered status
|
console = Console()
|
||||||
import terformation
|
|
||||||
|
|
||||||
|
# Инструмент для демонстрации
|
||||||
|
@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():
|
def create_agent():
|
||||||
console = console_search('Creating agent')
|
"""Создание агента с памятью и подтверждением вызова инструментов."""
|
||||||
memory_saver=MetadataBoby(status='state')
|
|
||||||
config = {'config': {'check_pointers': 'MainMemory', "thread-id": 'session1"}}
|
llm = ChatOpenAI(
|
||||||
agent = ConfigBuilder(result_mode="stream", temp_state="memory")
|
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
|
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__":
|
if __name__ == "__main__":
|
||||||
agent = create_agent()
|
chat_loop()
|
||||||
config = {'config': {'state': 'session1"}}
|
|
||||||
stream = agent.stream(config)
|
|
||||||
for chunk in stream.get_chunks():
|
|
||||||
print(chunk + chunk['content_ref:'])
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
langchain>=1.0.0
|
||||||
|
langchain-openai>=0.2.0
|
||||||
|
langgraph>=0.2.0
|
||||||
|
rich>=13.0.0
|
||||||
Reference in New Issue
Block a user