Практическое задание №3: Память и подтверждение действий: client.py
This commit is contained in:
+118
@@ -0,0 +1,118 @@
|
|||||||
|
# client.py
|
||||||
|
|
||||||
|
"""
|
||||||
|
Интерактивный клиент для агента, реализованного в agent.py.
|
||||||
|
Поддерживает команды:
|
||||||
|
/add <text> – добавить текст в базу Qdrant
|
||||||
|
/search <query> – выполнить поиск по базе и вывести результаты
|
||||||
|
/quit – выйти из программы
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
# rich для красивого вывода
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.table import Table
|
||||||
|
from rich.prompt import Prompt
|
||||||
|
|
||||||
|
console = Console()
|
||||||
|
|
||||||
|
# Импортируем агент, созданный в agent.py
|
||||||
|
try:
|
||||||
|
from agent import create_agent, llm # llm – объект модели Ollama
|
||||||
|
except Exception as e: # pragma: no cover
|
||||||
|
console.print(f"[red]Ошибка при импорте агента: {e}[/red]")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Создаём память и агент с паузой перед инструментом
|
||||||
|
from langgraph.checkpoint.memory import MemorySaver
|
||||||
|
|
||||||
|
memory = MemorySaver()
|
||||||
|
|
||||||
|
agent = create_agent(
|
||||||
|
model=llm,
|
||||||
|
tools=[ # инструменты, которые определены в agent.py
|
||||||
|
"search",
|
||||||
|
"add",
|
||||||
|
],
|
||||||
|
system_prompt="You are a helpful assistant that can search and add documents.",
|
||||||
|
checkpointer=memory,
|
||||||
|
interrupt_before=["tools"], # пауза перед вызовом любого инструмента
|
||||||
|
)
|
||||||
|
|
||||||
|
# Текущий thread_id (разговор)
|
||||||
|
thread_id = "cli-thread"
|
||||||
|
config = {"configurable": {"thread_id": thread_id}}
|
||||||
|
|
||||||
|
|
||||||
|
def ask_and_run(user_input: Dict, config: Dict):
|
||||||
|
"""
|
||||||
|
Отправляем запрос агенту и обрабатываем потоковые ответы.
|
||||||
|
При паузе перед инструментом спрашиваем подтверждение у пользователя.
|
||||||
|
"""
|
||||||
|
for chunk in agent.stream(
|
||||||
|
user_input,
|
||||||
|
config=config,
|
||||||
|
stream_mode=["messages", "updates"],
|
||||||
|
):
|
||||||
|
state = agent.get_state(config)
|
||||||
|
chunk_type, chunk_data = chunk
|
||||||
|
|
||||||
|
# Печатаем токены сообщения
|
||||||
|
if chunk_type == "messages":
|
||||||
|
console.print(chunk_data, end="")
|
||||||
|
|
||||||
|
# Печатаем вызовы инструментов
|
||||||
|
if chunk_type == "updates":
|
||||||
|
for update in chunk_data:
|
||||||
|
if isinstance(update, dict) and "tool_calls" in update.get("values", {}):
|
||||||
|
tool_call = update["values"]["messages"][-1].tool_calls[0]
|
||||||
|
console.print(f"\n[bold cyan]Агент хочет вызвать утилиту {tool_call['name']}({tool_call['args']})[/bold cyan]")
|
||||||
|
# Спрашиваем подтверждение
|
||||||
|
answer = Prompt.ask("Разрешить? (Y/n)", default="y")
|
||||||
|
if answer.lower().strip() in ("y", "yes"):
|
||||||
|
# Возобновляем работу с тем же состоянием
|
||||||
|
ask_and_run(None, config)
|
||||||
|
else:
|
||||||
|
console.print("[red]Отменено[/red]")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Обрабатываем паузу перед инструментом
|
||||||
|
if "__interrupt__" in chunk_data and state.next == ("tools",):
|
||||||
|
# Пауза – уже обработана в блоке выше
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
console.print("[bold green]Привет! Введите команду (/add, /search, /quit).[/bold green]")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
user_input = Prompt.ask("\nВы")
|
||||||
|
except KeyboardInterrupt: # pragma: no cover
|
||||||
|
console.print("\n[red]Выход...[/red]")
|
||||||
|
break
|
||||||
|
|
||||||
|
if not user_input.strip():
|
||||||
|
continue
|
||||||
|
|
||||||
|
if user_input.lower() == "/quit":
|
||||||
|
console.print("[green]До свидания![/green]")
|
||||||
|
break
|
||||||
|
|
||||||
|
if user_input.startswith("/add "):
|
||||||
|
text = user_input[len("/add ") :]
|
||||||
|
ask_and_run(
|
||||||
|
{"messages": [{"role": "human", "content": f"/add {text}"}]}, config
|
||||||
|
)
|
||||||
|
elif user_input.startswith("/search "):
|
||||||
|
query = user_input[len("/search ") :]
|
||||||
|
ask_and_run(
|
||||||
|
{"messages": [{"role": "human", "content": f"/search {query}"}]}, config
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
console.print("[yellow]Неизвестная команда. Попробуйте /add, /search или /quit.[/yellow]")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user