feat: solution for 'Практическое задание №3: Память и подтверждение действий'
This commit is contained in:
@@ -1,33 +1,48 @@
|
|||||||
# Практическое задание №3: Память и подтверждение действий
|
# Interactive LangGraph Agent with Memory and Tool Confirmation
|
||||||
|
|
||||||
Главная
|
This project demonstrates a simple interactive agent built with LangGraph that:
|
||||||
Мои задания
|
- Maintains conversation memory across turns.
|
||||||
Практическое задание №3: Память и подтверждение действий
|
- Pauses before invoking tools and asks the user for confirmation.
|
||||||
5Д
|
- Uses the `rich` library for pretty console output.
|
||||||
EN
|
|
||||||
Практическое задание №3: Память и подтверждение действий
|
|
||||||
Зачёт
|
|
||||||
Версия 4
|
|
||||||
Дедлайн сдачи: 31.08.2026
|
|
||||||
|
|
||||||
В работе
|
## Prerequisites
|
||||||
|
|
||||||
Редактирование ответа
|
- Python 3.10 or newer
|
||||||
|
- An OpenAI API key (set as the environment variable `OPENAI_API_KEY`)
|
||||||
|
|
||||||
Заполните ответ и отправьте работу на проверку преподавателю.
|
## Installation
|
||||||
|
|
||||||
Тип ответа
|
```bash
|
||||||
Текст
|
# Clone the repository
|
||||||
Ссылка
|
git clone https://github.com/yourusername/langgraph-agent.git
|
||||||
Файлы
|
cd langgraph-agent
|
||||||
Ссылка (URL)
|
|
||||||
Прикреплённые файлы
|
|
||||||
Загрузить файл
|
|
||||||
Отправить на проверку
|
|
||||||
Отменить
|
|
||||||
|
|
||||||
Задание
|
# Create a virtual environment (optional but recommended)
|
||||||
|
python -m venv venv
|
||||||
|
source venv/bin/activate # On Windows: venv\Scripts\activate
|
||||||
|
|
||||||
Цель
|
# Install dependencies
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
Доработать агента из предыдущих заданий: добавить память разговора и механизм подтверждения каждо
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ensure your OpenAI API key is set
|
||||||
|
export OPENAI_API_KEY="sk-..."
|
||||||
|
|
||||||
|
# Run the agent
|
||||||
|
python src/main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
You will see a prompt where you can type messages. The agent will respond, and if it needs to use the calculator tool, it will pause and ask for your confirmation before executing the tool.
|
||||||
|
|
||||||
|
Type `exit` to quit the program.
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
- **Memory**: `MemorySaver` stores the conversation history, allowing the agent to remember previous messages.
|
||||||
|
- **Interrupt Before Tool Calls**: The agent is configured with `interrupt_before=['tools']`, causing it to pause before calling any tool.
|
||||||
|
- **User Confirmation**: When the agent pauses, it prints the pending tool call and asks you whether to proceed. If you confirm, the agent resumes; otherwise, the tool call is cancelled.
|
||||||
|
|
||||||
|
Feel free to extend the toolset or modify the system prompt to suit your needs.
|
||||||
+4
-5
@@ -1,5 +1,4 @@
|
|||||||
langgraph==0.0.41
|
langgraph
|
||||||
langchain==0.1.12
|
langchain-openai
|
||||||
langchain-openai==0.0.7
|
rich
|
||||||
rich==13.7.1
|
openai
|
||||||
openai==1.12.0
|
|
||||||
+55
-101
@@ -1,131 +1,85 @@
|
|||||||
#!/usr/bin/env python
|
from langgraph import create_agent
|
||||||
# -*- coding: utf-8 -*-
|
from langgraph.tools import tool
|
||||||
|
|
||||||
"""
|
|
||||||
A simple LangGraph agent with conversation memory and tool usage confirmation.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
from typing import Any, Dict, Iterable, Tuple
|
|
||||||
|
|
||||||
from langgraph.checkpoint.memory import MemorySaver
|
from langgraph.checkpoint.memory import MemorySaver
|
||||||
from langgraph.prebuilt import create_agent
|
|
||||||
from langgraph.graph import END
|
|
||||||
from langchain_openai import ChatOpenAI
|
from langchain_openai import ChatOpenAI
|
||||||
from langchain.tools import tool
|
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
|
|
||||||
# Initialize Rich console
|
|
||||||
console = Console()
|
console = Console()
|
||||||
|
|
||||||
# Ensure OpenAI API key is set
|
# Define a simple calculator tool
|
||||||
if "OPENAI_API_KEY" not in os.environ:
|
@tool(name="calculator", description="Evaluates an arithmetic expression.")
|
||||||
console.print("[red]Error:[/red] OPENAI_API_KEY environment variable not set.")
|
def calculator(expression: str) -> str:
|
||||||
console.print("Please set it before running the script.")
|
try:
|
||||||
exit(1)
|
# Use eval in a safe context
|
||||||
|
result = eval(expression, {"__builtins__": None}, {})
|
||||||
|
return str(result)
|
||||||
|
except Exception as e:
|
||||||
|
return f"Error evaluating expression: {e}"
|
||||||
|
|
||||||
# Define a simple echo tool
|
# Initialize LLM
|
||||||
@tool
|
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
|
||||||
def echo(text: str) -> str:
|
|
||||||
"""
|
|
||||||
Echo the input text back to the user.
|
|
||||||
"""
|
|
||||||
return f"Echo: {text}"
|
|
||||||
|
|
||||||
# Initialize the LLM
|
# Memory for conversation
|
||||||
llm = ChatOpenAI(temperature=0)
|
|
||||||
|
|
||||||
# Initialize memory saver
|
|
||||||
memory = MemorySaver()
|
memory = MemorySaver()
|
||||||
|
|
||||||
# Create the agent with interrupt_before to pause before tool calls
|
# Create agent with interrupt_before to pause before tool calls
|
||||||
agent = create_agent(
|
agent = create_agent(
|
||||||
model=llm,
|
llm=llm,
|
||||||
tools=[echo],
|
tools=[calculator],
|
||||||
system_prompt=(
|
system_prompt="You are a helpful assistant. Use the calculator tool to evaluate expressions.",
|
||||||
"You are a helpful assistant. "
|
|
||||||
"When you need to use a tool, you will be paused for confirmation."
|
|
||||||
),
|
|
||||||
checkpointer=memory,
|
checkpointer=memory,
|
||||||
interrupt_before=["tools"],
|
interrupt_before=["tools"],
|
||||||
)
|
)
|
||||||
|
|
||||||
def ask_and_run(user_input: str | None, config: Dict[str, Any]) -> None:
|
def ask_and_run(user_input: str | None, config: dict):
|
||||||
"""
|
"""
|
||||||
Send user input to the agent, handle tool call interruptions,
|
Run the agent with optional user input. Handles pauses before tool calls
|
||||||
and recursively resume or cancel based on user confirmation.
|
and asks the user for confirmation before executing a tool.
|
||||||
"""
|
"""
|
||||||
# Prepare the input payload
|
# Prepare input for the agent
|
||||||
payload = {"messages": []}
|
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
payload["messages"].append({"role": "user", "content": user_input})
|
input_dict = {"messages": [{"role": "user", "content": user_input}]}
|
||||||
|
else:
|
||||||
|
input_dict = {}
|
||||||
|
|
||||||
# Stream the agent's response
|
for chunk_type, chunk_data in agent.stream(
|
||||||
for chunk in agent.stream(
|
input_dict, config=config, stream_mode=["messages", "updates"]
|
||||||
payload,
|
|
||||||
config=config,
|
|
||||||
stream_mode=["messages", "updates"],
|
|
||||||
):
|
):
|
||||||
state = agent.get_state(config)
|
|
||||||
chunk_type, chunk_data = chunk
|
|
||||||
|
|
||||||
# Handle message chunks
|
# Handle message chunks
|
||||||
if chunk_type == "messages":
|
if chunk_type == "messages":
|
||||||
for msg in chunk_data:
|
# Check for interrupt before tool call
|
||||||
role = msg.get("role")
|
if "__interrupt__" in chunk_data and agent.get_state(config).next == ("tools",):
|
||||||
content = msg.get("content", "")
|
# Retrieve the pending tool call
|
||||||
if role == "assistant":
|
state = agent.get_state(config)
|
||||||
console.print(f"[bold green]Assistant:[/bold green] {content}")
|
last_msg = state.values["messages"][-1]
|
||||||
elif role == "user":
|
tool_call = last_msg.tool_calls[0]
|
||||||
console.print(f"[bold blue]User:[/bold blue] {content}")
|
console.print(
|
||||||
|
f"[bold yellow]Tool call requested:[/bold yellow] {tool_call['name']}({tool_call['args']})"
|
||||||
|
)
|
||||||
|
answer = input("Разрешить? (Y/n): ")
|
||||||
|
if answer.lower().strip() == "y":
|
||||||
|
# Resume the agent from the same state
|
||||||
|
ask_and_run(None, config)
|
||||||
|
else:
|
||||||
|
console.print("[red]Tool call cancelled.[/red]")
|
||||||
|
break # exit the current stream loop
|
||||||
|
|
||||||
# Detect interruption before tool call
|
# Print the messages
|
||||||
if "__interrupt__" in chunk_data and state.next == ("tools",):
|
for msg in chunk_data.get("messages", []):
|
||||||
# Extract the pending tool call
|
console.print(f"[{msg['role']}] {msg['content']}")
|
||||||
last_msg = state.values["messages"][-1]
|
|
||||||
tool_calls = last_msg.get("tool_calls", [])
|
|
||||||
if not tool_calls:
|
|
||||||
console.print("[red]Error:[/red] No tool call found during interruption.")
|
|
||||||
return
|
|
||||||
|
|
||||||
tool_call = tool_calls[0]
|
# Handle updates (optional)
|
||||||
tool_name = tool_call.get("name")
|
elif chunk_type == "updates":
|
||||||
tool_args = tool_call.get("args", {})
|
# For simplicity, we ignore updates in this example
|
||||||
|
|
||||||
console.print(
|
|
||||||
f"[yellow]Agent wants to call tool:[/yellow] {tool_name}({tool_args})"
|
|
||||||
)
|
|
||||||
answer = console.input("Разрешить? (Y/n): ")
|
|
||||||
|
|
||||||
if answer.lower().strip() in ("", "y", "yes"):
|
|
||||||
# Resume the agent from the interruption point
|
|
||||||
ask_and_run(None, config)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
console.print("[red]Отменено[/red]")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Handle updates (e.g., tool results) if needed
|
|
||||||
if chunk_type == "updates":
|
|
||||||
# In this simple example we don't process updates separately
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# If the agent has finished, exit the loop
|
def main():
|
||||||
if state.next == END:
|
config = {"configurable": {"thread_id": "conversation-1"}}
|
||||||
return
|
console.print("[bold green]Welcome to the interactive agent. Type 'exit' to quit.[/bold green]")
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
"""
|
|
||||||
Main chat loop.
|
|
||||||
"""
|
|
||||||
thread_id = "thread-1"
|
|
||||||
config = {"configurable": {"thread_id": thread_id}}
|
|
||||||
|
|
||||||
console.print("[bold cyan]Start chat. Type 'exit' to quit.[/bold cyan]")
|
|
||||||
while True:
|
while True:
|
||||||
user_input = console.input("[bold blue]You:[/bold blue] ")
|
user_input = input("> ")
|
||||||
if user_input.lower() in ("exit", "quit"):
|
if user_input.lower() == "exit":
|
||||||
console.print("[bold magenta]Goodbye![/bold magenta]")
|
console.print("[bold blue]Goodbye![/bold blue]")
|
||||||
break
|
break
|
||||||
ask_and_run(user_input, config)
|
ask_and_run(user_input, config)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user