Создание плагина провайдера веб-поиска
Плагины провайдеров веб-поиска регистрируют бэкенд, который обслуживает вызовы инструментов web_search, web_extract и (опционально) глубокого краулинга. Встроенные провайдеры — Firecrawl, SearXNG, Tavily, Exa, Parallel, Brave Search (бесплатный тариф), xAI и DDGS — поставляются как плагины в директории plugins/web/<name>/. Чтобы добавить новый или переопределить встроенный, достаточно положить рядом с ними свою директорию.
Веб-поиск — один из нескольких бэкенд-плагинов, которые поддерживает Hermes. Остальные (со своими ABC) — это Плагины провайдеров генерации изображений, Плагины провайдеров генерации видео, Плагины провайдеров памяти, Плагины движка контекста и Плагины провайдеров модели. Обычные плагины инструментов, хуков и CLI описаны в Создание плагина для Hermes.
Как работает обнаружение плагинов
Hermes ищет бэкенды веб-поиска в трёх местах:
- Встроенные —
<repo>/plugins/web/<name>/(загружаются автоматически приkind: backend, доступны всегда) - Пользовательские —
~/.hermes/plugins/web/<name>/(подключаются черезplugins.enabledили командойhermes plugins enable <name>) - Pip — пакеты, объявляющие точку входа
hermes_agent.plugins
Функция register(ctx) каждого плагина вызывает ctx.register_web_search_provider(...) и кладёт экземпляр в реестр agent/web_search_registry.py. Какой провайдер активен для каждой возможности, решает конфигурация:
| Возможность | Ключ конфигурации | Фолбэк |
|---|---|---|
web_search | web.search_backend | web.backend |
web_extract | web.extract_backend | web.backend |
Режимы глубокого краулинга внутри web_extract | web.extract_backend | web.backend |
Если ни один ключ не задан, Hermes сам определяет бэкенд по тому, какой API-ключ или URL присутствует в переменных окружения. Команда hermes tools проведёт пользователя через выбор провайдера.
Структура директории
plugins/web/my-backend/
├── __init__.py # точка входа register()
├── provider.py # подкласс WebSearchProvider
└── plugin.yaml # манифест с kind: backend и provides_web_providers
brave_free/ и ddgs/ — самые компактные эталонные реализации в репозитории: brave_free — провайдер только для поиска, доступ к которому открывает API-ключ, а ddgs — провайдер без ключа, лениво доставляющий свой SDK.
ABC WebSearchProvider
Создайте подкласс agent.web_search_provider.WebSearchProvider. Обязательны только name, is_available() и хотя бы один из методов search() / extract(). (Глубокий краулинг — это режим внутри extract(), а не отдельный метод.)
# plugins/web/my-backend/provider.py
from __future__ import annotations
import os
from typing import Any, Dict, List
from agent.web_search_provider import WebSearchProvider
class MyBackendWebSearchProvider(WebSearchProvider):
"""Минимальный провайдер только для поиска через HTTP API My Backend."""
@property
def name(self) -> str:
# Стабильный идентификатор, используемый в ключах конфигурации
# web.search_backend / web.extract_backend / web.backend.
# Строчные буквы, без пробелов; дефисы допустимы.
return "my-backend"
@property
def display_name(self) -> str:
# Метка, отображаемая в `hermes tools`. По умолчанию равна `name`.
return "My Backend"
def is_available(self) -> bool:
# Лёгкая проверка — наличие переменной окружения, импортируемость зависимости и т. п.
# ЗАПРЕЩЕНЫ сетевые вызовы (метод вызывается при каждой отрисовке `hermes tools`).
return bool(os.getenv("MY_BACKEND_API_KEY", "").strip())
def supports_search(self) -> bool:
return True
def supports_extract(self) -> bool:
return False
def search(self, query: str, limit: int = 5) -> Dict[str, Any]:
import httpx
api_key = os.environ["MY_BACKEND_API_KEY"]
try:
resp = httpx.get(
"https://api.example.com/search",
params={"q": query, "count": max(1, min(int(limit), 20))},
headers={"Authorization": f"Bearer {api_key}"},
timeout=15,
)
resp.raise_for_status()
data = resp.json()
except httpx.HTTPError as exc:
return {"success": False, "error": str(exc)}
# Структура ответа фиксирована — см. раздел «Структура ответа» ниже.
return {
"success": True,
"data": {
"web": [
{
"title": item.get("title", ""),
"url": item.get("url", ""),
"description": item.get("snippet", ""),
"position": idx + 1,
}
for idx, item in enumerate(data.get("results", []))
],
},
}
# plugins/web/my-backend/__init__.py
from plugins.web.my_backend.provider import MyBackendWebSearchProvider
def register(ctx) -> None:
"""Точка входа плагина — вызывается один раз при загрузке."""
ctx.register_web_search_provider(MyBackendWebSearchProvider())
plugin.yaml
name: web-my-backend
version: 1.0.0
description: "My Backend web search — Bearer-auth REST API"
author: Your Name
kind: backend
provides_web_providers:
- my-backend
requires_env:
- MY_BACKEND_API_KEY
| Ключ | Назначение |
|---|---|
kind: backend | Направляет плагин по пути загрузки бэкендов |
provides_web_providers | Список name провайдеров, которые регистрирует плагин — по нему загрузчик анонсирует плагин в hermes tools ещё до вызова register() |
requires_env | Интерактивный запрос учётных данных во время hermes plugins install (расширенный формат описан в Создание плагина для Hermes) |
Справочник по ABC
Полный контракт — в agent/web_search_provider.py. Методы, доступные для переопределения:
| Член | Обязательный | Значение по умолчанию | Назначение |
|---|---|---|---|
name | ✅ | — | Стабильный идентификатор для ключей конфигурации web.*_backend |
display_name | — | name | Метка в hermes tools |
is_available() | ✅ | — | Лёгкая проверка доступности — переменные окружения, опциональные зависимости |
supports_search() | — | True | Флаг возможности для маршрутизации web_search |
supports_extract() | — | False | Флаг возможности для маршрутизации web_extract |
search(query, limit) | условно | raises | Требуется, когда supports_search() возвращает True |
extract(urls, **kwargs) | условно | raises | Требуется, когда supports_extract() возвращает True |
Один класс может объявлять сразу несколько возможностей — Firecrawl, Tavily, Exa и Parallel реализуют и поиск, и извлечение. Brave Search и DDGS работают только на поиск; SearXNG — тоже только поиск, но с задокументированным сценарием «свяжите меня с провайдером извлечения».
Структура ответа
Обёртка инструмента ждёт конверт фиксированной формы, чтобы не переводить формат с одного бэкенда на другой.
Успешный поиск:
{
"success": True,
"data": {
"web": [
{"title": str, "url": str, "description": str, "position": int},
...
],
},
}
Успешное извлечение:
{
"success": True,
"data": [
{
"url": str,
"title": str,
"content": str,
"raw_content": str,
"metadata": dict, # опционально
"error": str, # опционально, только при ошибке на уровне URL
},
...
],
}
Любая возможность, при ошибке:
{"success": False, "error": "human-readable message"}
Оба метода — search() и extract() — можно объявить через async def: диспетчер распознаёт корутины через inspect.iscoroutinefunction и вызывает их с await. Синхронные реализации с блокирующим I/O (HTTP, SDK-вызовы) вполне годятся для небольших бэкендов — потоки диспетчер берёт на себя.
Флаги возможностей
Hermes направляет вызовы к нужному провайдеру по флагам supports_*. Типичная конфигурация с несколькими провайдерами:
# ~/.hermes/config.yaml
web:
search_backend: "brave-free" # только поиск, быстро, 2к запросов/мес бесплатно
extract_backend: "firecrawl" # извлечение + краулинг, платная квота
Если web.search_backend или web.extract_backend не заданы, оба ключа откатываются на web.backend. Если не задан и он, Hermes выбирает первый доступный провайдер с нужной возможностью, ориентируясь на наличие переменных окружения.
Когда провайдер поддерживает только одну возможность, оставьте остальные флаги со значением по умолчанию (False) — для неподдерживаемого инструмента реестр просто пропустит его. Тогда пользователи не увидят сбивающих с толку ошибок «провайдер X недоступен» в ситуации, когда X задействован только для поиска, а агент просят что-то извлечь.
Как Hermes подключает провайдер к инструментам
Инструменты web_search и web_extract описаны в tools/web_tools.py. На каждом вызове они:
- Читают соответствующий ключ конфигурации (
web.search_backendдляweb_search,web.extract_backendдляweb_extract) - Запрашивают у реестра провайдера с нужным
name - Проверяют
is_available()и соответствующий флагsupports_*() - Вызывают
search()/extract()(глубокий краулинг выполняется как режим внутриextract()), добавляяawait, если метод — корутина - JSON-сериализуют конверт ответа и передают его LLM
Ошибки приходят как результат инструмента — а уже LLM решает, как их объяснить. Если не зарегистрирован ни один провайдер (или все доступные не проходят проверку возможности), инструмент возвращает понятную ошибку со ссылкой на hermes tools.
Ленивая установка опциональных зависимостей
Если провайдер оборачивает сторонний SDK (как DDGS — пакет ddgs), не делайте import на верхнем уровне модуля. Вызывайте tools.lazy_deps.ensure(...) внутри is_available() или search() — Hermes установит пакет при первом использовании, если включён параметр security.allow_lazy_installs. Модель безопасности описана в Создание плагина для Hermes → Ленивая установка.
Эталонные реализации
plugins/web/brave_free/— компактный HTTP-провайдер только для поиска, с доступом по API-ключу. Хорошая отправная точка.plugins/web/ddgs/— провайдер без ключа с ленивой установкой SDK. Полезный паттерн для бэкендов, которые оборачивают Python-пакет.plugins/web/firecrawl/— полноценный многофункциональный провайдер (поиск + извлечение + краулинг) с несколькими режимами форматирования.plugins/web/searxng/— самохостируемый бэкенд с настройкой через URL, без аутентификации.plugins/web/xai/— поиск на базе LLM через серверный инструментweb_searchот Grok. Показывает, как переиспользовать готовую поверхность OAuth/env-var (tools/xai_http.py), не заводя новых переменных окружения, и как написать дешёвыйis_available(), который соблюдает контракт отсутствия сетевых вызовов.
Распространение через pip
# pyproject.toml
[project.entry-points."hermes_agent.plugins"]
my-backend-web = "my_backend_web_package"
my_backend_web_package должен предоставлять функцию register на верхнем уровне. Полная инструкция по настройке — в разделе Распространение через pip общего руководства по плагинам.
Связанные страницы
- Веб-поиск — пользовательская документация функции и настройка каждого бэкенда
- Обзор плагинов — все типы плагинов в одном месте
- Создание плагина для Hermes — руководство по плагинам инструментов, хуков и slash-команд