diff --git a/solutions/69a474cdc46fd26feae69896_Практическое_задание__3__Память_и_подтве/client.py b/solutions/69a474cdc46fd26feae69896_Практическое_задание__3__Память_и_подтве/client.py new file mode 100644 index 0000000..554814c --- /dev/null +++ b/solutions/69a474cdc46fd26feae69896_Практическое_задание__3__Память_и_подтве/client.py @@ -0,0 +1,118 @@ +# client.py + +""" +Интерактивный клиент для агента, реализованного в agent.py. +Поддерживает команды: + /add – добавить текст в базу Qdrant + /search – выполнить поиск по базе и вывести результаты + /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() \ No newline at end of file