Pular para o conteúdo principal

Documentação oficial desta página · v0.20.6

Criar um plugin de provedor de busca na web

Os plugins de provedor de busca na web registram um backend que atende às chamadas de ferramenta web_search, web_extract e (opcionalmente) de deep crawl. Os provedores embutidos — Firecrawl, SearXNG, Tavily, Exa, Parallel, Brave Search (camada gratuita), xAI e DDGS — vêm todos como plugins em plugins/web/<nome>/. Você pode adicionar um novo, ou substituir um dos que já vêm juntos, colocando um diretório ao lado deles.

dica

A busca na web é um dos vários plugins de backend que o Hermes suporta. Os outros (cada um com sua própria ABC) são os plugins de provedor de geração de imagem, os plugins de provedor de geração de vídeo, os plugins de provedor de memória, os plugins de motor de contexto e os plugins de provedor de modelo. Os plugins gerais de ferramenta/hook/CLI ficam em Criar um plugin do Hermes.

Como funciona a descoberta​

O Hermes procura backends de busca na web em três lugares:

  1. Embutidos — <repo>/plugins/web/<nome>/ (carregados automaticamente com kind: backend, sempre disponíveis)
  2. Do usuário — ~/.hermes/plugins/web/<nome>/ (opt-in via plugins.enabled ou hermes plugins enable <nome>)
  3. Via pip — pacotes que declaram um entry point hermes_agent.plugins

A função register(ctx) de cada plugin chama ctx.register_web_search_provider(...) — isso coloca a instância no registro em agent/web_search_registry.py. O provedor ativo de cada capacidade é escolhido pela configuração:

CapacidadeChave de configuraçãoCai para
web_searchweb.search_backendweb.backend
web_extractweb.extract_backendweb.backend
Modos de deep crawl dentro de web_extractweb.extract_backendweb.backend

Quando nenhuma das duas chaves está definida, o Hermes detecta o backend sozinho, a partir da chave de API ou URL que estiver presente no ambiente. O hermes tools guia o usuário pela escolha.

Estrutura de diretórios​

plugins/web/meu-backend/
├── __init__.py # entry point register()
├── provider.py # subclasse de WebSearchProvider
└── plugin.yaml # manifesto com kind: backend e provides_web_providers

brave_free/ e ddgs/ são as menores referências dentro do repositório — brave_free para um provedor só de busca protegido por chave de API, ddgs para um provedor sem chave que instala o SDK sob demanda.

A ABC WebSearchProvider​

Faça uma subclasse de agent.web_search_provider.WebSearchProvider. Os únicos membros obrigatórios são name, is_available() e aquele que você implementar entre search() / extract(). (O deep crawl não é um método separado — é um modo do extract().)

# plugins/web/meu-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):
"""Provedor mínimo, só de busca, contra a API HTTP do My Backend."""

@property
def name(self) -> str:
# Id estável usado nas chaves de configuração web.search_backend /
# web.extract_backend / web.backend. Minúsculas, sem espaço; hífen pode.
return "my-backend"

@property
def display_name(self) -> str:
# Rótulo legível exibido no `hermes tools`. O padrão é `name`.
return "My Backend"

def is_available(self) -> bool:
# Checagem barata — variável de ambiente presente, dependência opcional importável etc.
# NÃO PODE fazer chamada de rede (roda a cada repintura do `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)}

# O formato da resposta é fixo — veja "Formato da resposta" abaixo.
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/meu-backend/__init__.py
from plugins.web.my_backend.provider import MyBackendWebSearchProvider


def register(ctx) -> None:
"""Entry point do plugin — chamado uma vez, na hora do carregamento."""
ctx.register_web_search_provider(MyBackendWebSearchProvider())

plugin.yaml​

name: web-my-backend
version: 1.0.0
description: "Busca na web do My Backend — API REST com autenticação Bearer"
author: Seu Nome
kind: backend
provides_web_providers:
- my-backend
requires_env:
- MY_BACKEND_API_KEY
ChavePara que serve
kind: backendRoteia o plugin pelo caminho de carregamento de backend
provides_web_providersLista dos names de provedor que este plugin registra — o carregador usa isso para anunciar o plugin no hermes tools mesmo antes de o register() rodar
requires_envPergunta interativa de credencial durante o hermes plugins install (veja Criar um plugin do Hermes para o formato completo)

Referência da ABC​

O contrato completo está em agent/web_search_provider.py. Métodos que você pode sobrescrever:

MembroObrigatórioPadrãoPara que serve
name✅—Id estável usado na configuração web.*_backend
display_name—nameRótulo exibido no hermes tools
is_available()✅—Portão barato de disponibilidade — variáveis de ambiente, dependências opcionais
supports_search()—TrueSinalizador de capacidade para o roteamento do web_search
supports_extract()—FalseSinalizador de capacidade para o roteamento do web_extract
search(query, limit)condicionallevanta erroObrigatório quando supports_search() devolve True
extract(urls, **kwargs)condicionallevanta erroObrigatório quando supports_extract() devolve True

Um provedor pode anunciar várias capacidades a partir de uma única classe — Firecrawl, Tavily, Exa e Parallel implementam busca e extração. Brave Search e DDGS são só de busca; o SearXNG é só de busca, com um fluxo documentado de "combine comigo um provedor de extração".

Formato da resposta​

O wrapper da ferramenta espera um envelope fixo, para não ter que traduzir entre backends.

Busca bem-sucedida:

{
"success": True,
"data": {
"web": [
{"title": str, "url": str, "description": str, "position": int},
...
],
},
}

Extração bem-sucedida:

{
"success": True,
"data": [
{
"url": str,
"title": str,
"content": str,
"raw_content": str,
"metadata": dict, # opcional
"error": str, # opcional, só em caso de falha por URL
},
...
],
}

Qualquer uma das capacidades, em caso de falha:

{"success": False, "error": "mensagem legível para humanos"}

Tanto search() quanto extract() podem ser async def — o dispatcher detecta funções corrotina via inspect.iscoroutinefunction e faz o await na medida. Implementações síncronas que fazem E/S bloqueante (HTTP, chamadas de SDK) servem para backends pequenos; o dispatcher cuida das threads.

Sinalizadores de capacidade​

O Hermes roteia as chamadas para o provedor certo com base nos sinalizadores supports_*. Uma configuração comum com vários provedores:

# ~/.hermes/config.yaml
web:
search_backend: "brave-free" # só busca, rápido, 2 mil grátis por mês
extract_backend: "firecrawl" # extração + crawl, cota paga

Quando web.search_backend ou web.extract_backend não estão definidos, os dois caem para web.backend. Quando esse também não está definido, o Hermes escolhe o primeiro provedor disponível que suporte a capacidade pedida, com base na presença das variáveis de ambiente.

Se o seu provedor só suporta uma capacidade, deixe os outros sinalizadores no padrão (False) e o registro vai ignorá-lo naquela ferramenta — os usuários não vão ver erros enganosos de "o provedor X falhou" quando estiverem usando o X só para busca e pedirem uma extração ao agente.

Como o Hermes liga isso às ferramentas​

As ferramentas web_search e web_extract ficam em tools/web_tools.py. Na hora da chamada, elas:

  1. Leem a chave de configuração relevante (web.search_backend para o web_search, web.extract_backend para o web_extract)
  2. Pedem ao registro o provedor com aquele name
  3. Conferem is_available() e o sinalizador supports_*() correspondente
  4. Despacham para search() / extract() (o deep crawl roda como um modo dentro do extract()), fazendo await se o método for uma corrotina
  5. Serializam o envelope de resposta em JSON e devolvem para o LLM

Os erros aparecem como resultado da ferramenta; o LLM decide como explicá-los. Se nenhum provedor estiver registrado (ou se todos os disponíveis falharem no portão de capacidade), a ferramenta devolve um erro útil apontando para o hermes tools.

Instalar dependências opcionais sob demanda​

Se o seu provedor embrulha um SDK de terceiros (como o DDGS faz com o pacote ddgs), não faça o import no topo do módulo. Use tools.lazy_deps.ensure(...) dentro de is_available() ou de search() — o Hermes instala o pacote no primeiro uso, controlado por security.allow_lazy_installs. Veja Criar um plugin do Hermes → instalação sob demanda para o modelo de segurança.

Implementações de referência​

  • plugins/web/brave_free/ — provedor HTTP pequeno, protegido por chave de API, só de busca. Bom modelo para começar.
  • plugins/web/ddgs/ — provedor sem chave que instala o SDK sob demanda. Padrão útil para backends que embrulham um pacote Python.
  • plugins/web/firecrawl/ — provedor completo, com várias capacidades (busca + extração + crawl) e vários modos de formato.
  • plugins/web/searxng/ — backend auto-hospedado, configurado por URL e sem autenticação.
  • plugins/web/xai/ — busca apoiada em LLM pela ferramenta web_search do lado do servidor do Grok. Mostra como reaproveitar uma superfície de credencial OAuth/variável de ambiente já existente (tools/xai_http.py) sem acrescentar variáveis novas, e como escrever um is_available() barato que respeita o contrato de não fazer rede.

Distribuir via pip​

# pyproject.toml
[project.entry-points."hermes_agent.plugins"]
my-backend-web = "my_backend_web_package"

O my_backend_web_package precisa expor uma função register no nível de topo. Veja Distribuir via pip no guia geral de plugins para a configuração completa.