""" Обработчики инструментов research-agent плагина. Каждый обработчик: 1. Собирает аргументы командной строки из параметров вызова 2. Запускает соответствующий Python-скрипт через subprocess 3. Захватывает stdout/stderr 4. Возвращает результат как JSON Скрипты лежат в scripts/ рядом с плагином — плагин самодостаточен. """ import json import os import subprocess import sys from pathlib import Path # ─── Пути к скриптам ────────────────────────────────────────────────────────── _PLUGIN_DIR = Path(__file__).parent _SCRIPTS_DIR = _PLUGIN_DIR / "scripts" DEEP_RESEARCH_SCRIPT = _SCRIPTS_DIR / "deep_research.py" PERPLEXITY_SEARCH_SCRIPT = _SCRIPTS_DIR / "perplexity_search.py" YOUTUBE_ANALYZER_SCRIPT = _SCRIPTS_DIR / "youtube_analyzer.py" # ─── Общие настройки ────────────────────────────────────────────────────────── # Таймауты (секунд) — скрипты долгие из-за LLM-синтеза TIMEOUT_DEEP_RESEARCH = 420 # 7 минут TIMEOUT_PERPLEXITY = 120 # 2 минуты TIMEOUT_YOUTUBE = 180 # 3 минуты def _run_script(script_path: Path, args: list[str], timeout: int) -> dict: """ Запустить Python-скрипт и вернуть результат. Args: script_path: абсолютный путь к .py файлу args: аргументы командной строки (без имени скрипта) timeout: таймаут в секундах Returns: dict с ключами: success, report (текст отчёта), error (если ошибка), exit_code, stderr """ if not script_path.exists(): return { "success": False, "error": f"Скрипт не найден: {script_path}\n" f"Установите плагин полностью: hermes plugins install research-agent", "report": "", "exit_code": -1, "stderr": "", } cmd = [sys.executable, str(script_path)] + args try: result = subprocess.run( cmd, capture_output=True, text=True, timeout=timeout, cwd=str(script_path.parent), env={**os.environ, "PYTHONUNBUFFERED": "1"}, ) # Скрипт пишет отчёт в stdout report = result.stdout.strip() stderr = result.stderr.strip() if result.returncode != 0: # Частичный отчёт может быть на stdout даже при ошибке if not report and stderr: report = f"[Ошибка выполнения]\n{stderr[-2000:]}" return { "success": False, "error": stderr[-2000:] if stderr else f"Exit code {result.returncode}", "report": report, "exit_code": result.returncode, "stderr": stderr[-3000:], } return { "success": True, "report": report, "error": "", "exit_code": 0, "stderr": stderr[-1000:] if stderr else "", } except subprocess.TimeoutExpired: return { "success": False, "error": f"Таймаут ({timeout}с). Исследование заняло слишком много времени.", "report": "", "exit_code": -1, "stderr": f"Timeout after {timeout}s", } except Exception as e: return { "success": False, "error": f"Ошибка запуска: {e}", "report": "", "exit_code": -1, "stderr": str(e), } # ─── Обработчики ────────────────────────────────────────────────────────────── def handle_deep_research(params: dict, **kwargs) -> str: """ Запустить deep_research.py с параметрами из tool call. Ожидаемые параметры: query (str): тема исследования lang (str, опционально): 'ru' или 'en' (default 'ru') """ query = params.get("query", "") if not query: return json.dumps({"success": False, "error": "Параметр 'query' обязателен"}) args = [query] lang = params.get("lang", "ru") if lang: args.extend(["--lang", lang]) # Опциональные параметры из env или переопределения model = params.get("model") or os.environ.get("LLM_MODEL") temperature = params.get("temperature") or os.environ.get("LLM_TEMPERATURE") if model: args.extend(["--model", model]) if temperature: args.extend(["--temperature", str(temperature)]) result = _run_script(DEEP_RESEARCH_SCRIPT, args, TIMEOUT_DEEP_RESEARCH) return json.dumps(result, ensure_ascii=False) def handle_perplexity_search(params: dict, **kwargs) -> str: """ Запустить perplexity_search.py с параметрами из tool call. Ожидаемые параметры: query (str): поисковый запрос category (str, опционально): 'general', 'news', 'it', 'science' results (int, опционально): 1-5 (default 3) """ query = params.get("query", "") if not query: return json.dumps({"success": False, "error": "Параметр 'query' обязателен"}) args = [query] category = params.get("category", "general") if category and category != "general": args.extend(["--category", category]) results = params.get("results", 3) args.extend(["--results", str(results)]) # Опциональные параметры model = params.get("model") or os.environ.get("LLM_MODEL") temperature = params.get("temperature") or os.environ.get("LLM_TEMPERATURE") if model: args.extend(["--model", model]) if temperature: args.extend(["--temperature", str(temperature)]) result = _run_script(PERPLEXITY_SEARCH_SCRIPT, args, TIMEOUT_PERPLEXITY) return json.dumps(result, ensure_ascii=False) def handle_youtube_analyze(params: dict, **kwargs) -> str: """ Запустить youtube_analyzer.py с параметрами из tool call. Ожидаемые параметры: url (str, опционально): URL видео (взаимоисключается с search) search (str, опционально): поисковый запрос (взаимоисключается с url) lang (str, опционально): 'ru' или 'en' limit (int, опционально): 1-5 compare (bool, опционально): сравнить несколько видео """ url = params.get("url", "") search = params.get("search", "") if not url and not search: return json.dumps({ "success": False, "error": "Укажите 'url' или 'search' параметр", }) args = [] if url: args.extend(["--url", url]) else: args.extend(["--search", search]) lang = params.get("lang", "ru") if lang: args.extend(["--lang", lang]) limit = params.get("limit") if limit and search: args.extend(["--limit", str(limit)]) if params.get("compare") and search: args.append("--compare") result = _run_script(YOUTUBE_ANALYZER_SCRIPT, args, TIMEOUT_YOUTUBE) return json.dumps(result, ensure_ascii=False)