跳到主要內容

構建 Web 搜索提供商插件

Web 搜索提供商插件註冊一個後端,用於處理 web_searchweb_extract 以及(可選的)深度爬取工具調用。內置提供商——Firecrawl、SearXNG、Tavily、Exa、Parallel、Brave Search(免費層)、xAI 和 DDGS——均作為插件位於 plugins/web/<name>/ 下。你可以通過在它們旁邊放置一個目錄來添加新的提供商,或覆蓋 bundled 的提供商。

提示

Web 搜索是 Hermes 支持的幾種後端插件之一。其他插件(擁有各自的抽象基類 ABC)包括 圖像生成提供商插件視頻生成提供商插件記憶提供商插件上下文引擎插件模型提供商插件。通用的工具/hook/CLI 插件請參閱 構建 Hermes 插件

發現機制的工作原理

Hermes 會在以下三個位置掃描 Web 搜索後端:

  1. Bundled(內置)<repo>/plugins/web/<name>/(隨 kind: backend 自動加載,始終可用)
  2. User(用戶)~/.hermes/plugins/web/<name>/(通過 plugins.enabledhermes plugins enable <name> 選擇啟用)
  3. Pip — 聲明瞭 hermes_agent.plugins 入口點的包

每個插件的 register(ctx) 函數都會調用 ctx.register_web_search_provider(...)——這會將實例放入 agent/web_search_registry.py 中的註冊表。每種能力的活動提供商由配置決定:

能力配置鍵回退至
web_searchweb.search_backendweb.backend
web_extractweb.extract_backendweb.backend
web_extract 內的深度爬取模式web.extract_backendweb.backend

當兩個鍵均未設置時,Hermes 會根據環境中存在的 API 密鑰/URL 自動檢測後端。hermes tools 會引導用戶進行選擇。

目錄結構

plugins/web/my-backend/
├── __init__.py # register() entry point
├── provider.py # WebSearchProvider subclass
└── plugin.yaml # Manifest with kind: backend and provides_web_providers

brave_free/ddgs/ 是代碼庫中最小的參考示例——brave_free 是一個需要 API 密鑰且僅支持搜索的提供商,ddgs 是一個無需密鑰且會懶安裝其 SDK 的提供商。

WebSearchProvider 抽象基類 (ABC)

繼承 agent.web_search_provider.WebSearchProvider。唯一必需的成員是 nameis_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):
"""Minimal search-only provider against the My Backend HTTP API."""

@property
def name(self) -> str:
# Stable id used in web.search_backend / web.extract_backend / web.backend
# config keys. Lowercase, no spaces; hyphens permitted.
return "my-backend"

@property
def display_name(self) -> str:
# Human label shown in `hermes tools`. Defaults to `name`.
return "My Backend"

def is_available(self) -> bool:
# Cheap check — env var present, optional dep importable, etc.
# MUST NOT make network calls (runs on every `hermes tools` paint).
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)}

# Response shape is fixed — see "Response shape" below.
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:
"""Plugin entry point — called once at load time."""
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 列表——加載器使用它在 register() 運行之前在 hermes tools 中宣傳該插件
requires_envhermes plugins install 期間交互式提示憑證(參見 構建 Hermes 插件 瞭解豐富格式)

ABC 參考

完整契約位於 agent/web_search_provider.py。你可以重寫的方法:

成員必需默認值用途
nameweb.*_backend 配置中使用的穩定 ID
display_namenamehermes tools 中顯示的標籤
is_available()輕量級的可用性檢查——環境變量、可選依賴
supports_search()Trueweb_search 路由的能力標誌
supports_extract()Falseweb_extract 路由的能力標誌
search(query, limit)條件性拋出異常supports_search() 返回 True 時為必需
extract(urls, **kwargs)條件性拋出異常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, # optional
"error": str, # optional, only on per-URL failure
},
...
],
}

任一能力,失敗時:

{"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" # search-only, fast, free 2k/mo
extract_backend: "firecrawl" # extract + crawl, paid quota

當未設置 web.search_backendweb.extract_backend 時,兩者都會回退到 web.backend。當後者也未設置時,Hermes 會根據環境變量的存在情況,選擇第一個支持所請求能力的可用提供商。

如果你的提供商僅支持一種能力,請將其他標誌保留為默認值(False),註冊表將針對該工具跳過這些能力——當用戶僅使用 X 進行搜索並要求代理執行提取時,不會看到誤導性的“provider X failed”錯誤。

Hermes 如何將其接入工具

web_searchweb_extract 工具位於 tools/web_tools.py 中。在調用時,它們會:

  1. 讀取相關的配置鍵(web_search 對應 web.search_backendweb_extract 對應 web.extract_backend
  2. 向註冊表請求具有該 name 的提供商
  3. 檢查 is_available() 和匹配的 supports_*() 標誌
  4. 分派到 search() / extract()(深度爬取作為 extract() 內部的一種模式運行),如果方法是協程則進行 await
  5. 對響應信封進行 JSON 序列化並將其返回給 LLM

錯誤會作為工具結果呈現;由 LLM 決定如何解釋這些錯誤。如果沒有註冊提供商(或者所有可用的提供商都未通過能力檢查),工具將返回一個指向 hermes tools 的幫助性錯誤。

懶安裝可選依賴項

如果你的提供商封裝了第三方 SDK(例如 DDGS 使用 ddgs 包),不要在模塊頂層 import 它。請在 is_available()search() 中使用 tools.lazy_deps.ensure(...) —— Hermes 將在首次使用時安裝該包,並受 security.allow_lazy_installs 控制。有關安全模型,請參閱 構建 Hermes 插件 → 懶安裝

參考實現

  • plugins/web/brave_free/ — 小型、需 API 密鑰、僅支持搜索的 HTTP 提供商。良好的起始模板。
  • plugins/web/ddgs/ — 無需密鑰且懶安裝其 SDK 的提供商。對於封裝 Python 包的後端而言,這是一種有用的模式。
  • plugins/web/firecrawl/ — 功能完整的多能力提供商(搜索 + 提取 + 爬取),支持多種格式模式。
  • plugins/web/searxng/ — 自託管、通過 URL 配置且無需認證的後端。
  • plugins/web/xai/ — 通過 Grok 的服務端 web_search 工具實現的基於 LLM 的搜索。展示瞭如何複用現有的 OAuth/環境變量憑據表面(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 分發