fix: SEARXNG_URL no longer includes /search suffix (provider adds it), sync all files
This commit is contained in:
1 parent
e5e9b5c664
commit
d4731bee20
11 files changed
+2891
No files matched your search
@@ -0,0 +1,220 @@
|
||||
"""
|
||||
Обработчики инструментов 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)
|
||||
Reference in new issue
Block a user