diff --git a/cli.py b/cli.py index 5a7cc70..8a2b4d1 100644 --- a/cli.py +++ b/cli.py @@ -1,131 +1,106 @@ #!/usr/bin/env python3 """ -CLI для исследовательского агента с web search через Tavily -Строит бриф: план → пошаговый сбор данных → финальная сводка +CLI для агента с retry через try/except +Демонстрирует цикл: generate → валидация → (retry при ошибке) → END """ import argparse import sys -from dotenv import load_dotenv -from graph import build_research_graph -from state import BriefState +from graph import build_retry_graph +from state import ReflectState -# Загружаем переменные окружения (.env) -load_dotenv() +DEMO_QUESTION = "Объясни студенту разницу между tool и resource в MCP" -# Демо-тема по умолчанию -DEMO_TOPIC = "Как студенту безопасно подключать MCP к LangChain" - -def run_researcher(topic: str, verbose: bool = True): - """Запускает исследовательского агента""" +def run_agent(question: str, max_rounds: int = 3, verbose: bool = True): + """Запускает агента с retry-логикой""" if verbose: print("\n" + "="*80) - print(f"🔬 ИССЛЕДОВАТЕЛЬСКИЙ АГЕНТ") - print(f"📚 Тема: {topic}") + print(f"🤖 ЗАПУСК АГЕНТА С RETRY (TRY/EXCEPT)") + print(f"📋 Вопрос: {question}") + print(f"🔄 Макс. попыток: {max_rounds}") print("="*80) # Создаём граф - app = build_research_graph() + app = build_retry_graph(max_rounds=max_rounds) - # Начальное состояние - initial_state: BriefState = { - "topic": topic, - "outline": None, - "step_index": 0, - "notes": [], - "final_brief": None + # Состояние + initial_state: ReflectState = { + "question": question, + "draft": "", + "error": None, + "round": 1, + "max_rounds": max_rounds } - # Запуск (с потоковым выводом для прогресса) - if verbose: - print("\n🚀 Начинаю исследование...") - + # Запуск final_state = app.invoke(initial_state) + # Вывод if verbose: print("\n" + "="*80) - print("✅ ИССЛЕДОВАНИЕ ЗАВЕРШЕНО") - print("="*80) + if final_state["error"] is None: + print("✅ ФИНАЛЬНЫЙ РЕЗУЛЬТАТ") + print("="*80) + print(f"📝 Итоговый ответ (попытка {final_state['round']}):") + print("-"*80) + print(final_state["draft"]) + print("-"*80) + print(f"🎉 Успешно сгенерировано за {final_state['round']} попыток!") + else: + print("❌ ОШИБКА ГЕНЕРАЦИИ") + print("="*80) + print(f"Достигнут лимит попыток ({max_rounds})") + print(f"Последняя ошибка: {final_state['error']}") return final_state -def save_brief_to_file(brief: str, filename: str = "research_brief.md"): - """Сохраняет бриф в Markdown файл""" - try: - with open(filename, 'w', encoding='utf-8') as f: - f.write(brief) - print(f"📁 Бриф сохранён в файл: {filename}") - except Exception as e: - print(f"⚠️ Ошибка сохранения: {e}") +def interactive_mode(): + """Интерактивный режим""" + print("\n🎮 Интерактивный режим агента с retry (try/except)") + print("Введите 'exit' для выхода\n") + + while True: + question = input("Ваш вопрос: ").strip() + if question.lower() == 'exit': + break + if not question: + continue + + max_rounds = input("Макс. попыток (по умолч. 3): ").strip() + max_rounds = int(max_rounds) if max_rounds.isdigit() else 3 + + run_agent(question, max_rounds, verbose=True) + print("\n" + "─"*80 + "\n") def main(): parser = argparse.ArgumentParser( - description="Исследовательский агент для создания брифа с web search", + description="LangGraph агент с retry через try/except", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Примеры: - python cli.py --demo # Демо-тема про MCP и LangChain - python cli.py -t "Экологичные технологии 2024" # Своя тема - python cli.py -t "..." --save report.md # Сохранить в файл - python cli.py --interactive # Интерактивный режим + python cli.py --demo # Запуск на демо-вопросе + python cli.py -q "Что такое AI?" # Свой вопрос + python cli.py -q "..." --max-rounds 5 # 5 попыток + python cli.py --interactive # Интерактивный режим """ ) - parser.add_argument("-t", "--topic", help="Тема исследования") - parser.add_argument("--demo", action="store_true", help="Запустить на демо-теме") + parser.add_argument("-q", "--question", help="Вопрос для агента") + parser.add_argument("--max-rounds", type=int, default=3, help="Макс. попыток (по умолч. 3)") + parser.add_argument("--demo", action="store_true", help="Запустить демо-вопрос") parser.add_argument("-i", "--interactive", action="store_true", help="Интерактивный режим") - parser.add_argument("-s", "--save", metavar="FILE", help="Сохранить бриф в файл") - parser.add_argument("--quiet", action="store_true", help="Тихий режим (только итоговый бриф)") + parser.add_argument("--quiet", action="store_true", help="Тихий режим (только ответ)") args = parser.parse_args() - # Определяем тему - topic = None if args.interactive: - print("\n🎓 ИНТЕРАКТИВНЫЙ РЕЖИМ") - topic = input("Введите тему исследования: ").strip() - if not topic: - print("❌ Тема не может быть пустой") - sys.exit(1) - elif args.demo or not args.topic: - topic = DEMO_TOPIC - if not args.quiet: - print(f"\n🎯 Запуск на демо-теме: {topic}") + interactive_mode() + elif args.demo or (not args.question): + run_agent(DEMO_QUESTION, args.max_rounds, verbose=not args.quiet) + elif args.question: + run_agent(args.question, args.max_rounds, verbose=not args.quiet) else: - topic = args.topic - - # Запуск исследования - try: - result = run_researcher(topic, verbose=not args.quiet) - - # Вывод брифа - if args.quiet: - print(result["final_brief"]) - else: - print("\n" + result["final_brief"]) - - # Сохранение в файл - if args.save: - save_brief_to_file(result["final_brief"], args.save) - elif not args.quiet and not args.save: - # Предлагаем сохранить - save_option = input("\n💾 Сохранить бриф в файл? (y/n): ").strip().lower() - if save_option == 'y': - filename = input("Имя файла (по умолч. research_brief.md): ").strip() - if not filename: - filename = "research_brief.md" - save_brief_to_file(result["final_brief"], filename) - - except KeyboardInterrupt: - print("\n\n⚠️ Исследование прервано пользователем") - sys.exit(0) - except Exception as e: - print(f"\n❌ Ошибка: {e}") - print("\nУбедитесь, что:") - print("1. Установлен TAVILY_API_KEY в .env файле") - print("2. Установлен OPENAI_API_KEY в .env файле") - print("3. Установлены зависимости: pip install -r requirements.txt") - sys.exit(1) + parser.print_help() -if __name__ "__main__": +if __name__ == "__main__": main() \ No newline at end of file