構建 Web 搜索提供商插件
Web 搜索提供商插件註冊一個後端,用於處理 web_search、web_extract 以及(可選的)深度爬取工具調用。內置提供商——Firecrawl、SearXNG、Tavily、Exa、Parallel、Brave Search(免費層)、xAI 和 DDGS——均作為插件位於 plugins/web/<name>/ 下。你可以通過在它們旁邊放置一個目錄來添加新的提供商,或覆蓋 bundled 的提供商。
發現機制的工作原理
Hermes 會在以下三個位置掃描 Web 搜索後端:
- Bundled(內置) —
<repo>/plugins/web/<name>/(隨kind: backend自動加載,始終可用) - User(用戶) —
~/.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() 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。唯一必需的成員是 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):
"""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_env | 在 hermes plugins install 期間交互式提示憑證(參見 構建 Hermes 插件 瞭解豐富格式) |
ABC 參考
完整契約位於 agent/web_search_provider.py。你可以重寫的方法:
| 成員 | 必需 | 默認值 | 用途 |
|---|---|---|---|
name | ✅ | — | 在 web.*_backend 配置中使用的穩定 ID |
display_name | — | name | 在 hermes tools 中顯示的標籤 |
is_available() | ✅ | — | 輕量級的可用性檢查——環境變量、可選依賴 |
supports_search() | — | True | web_search 路由的能力標誌 |
supports_extract() | — | False | web_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_backend 或 web.extract_backend 時,兩者都會回退到 web.backend。當後者也未設置時,Hermes 會根據環境變量的存在情況,選擇第一個支持所請求能力的可用提供商。
如果你的提供商僅支持一種能力,請將其他標誌保留為默認值(False),註冊表將針對該工具跳過這些能力——當用戶僅使用 X 進行搜索並要求代理執行提取時,不會看到誤導性的“provider X failed”錯誤。
Hermes 如何將其接入工具
web_search 和 web_extract 工具位於 tools/web_tools.py 中。在調用時,它們會:
- 讀取相關的配置鍵(
web_search對應web.search_backend,web_extract對應web.extract_backend) - 向註冊表請求具有該
name的提供商 - 檢查
is_available()和匹配的supports_*()標誌 - 分派到
search()/extract()(深度爬取作為extract()內部的一種模式運行),如果方法是協程則進行 await - 對響應信封進行 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 分發。
相關頁面
- 網頁搜索 — 面向用戶的功能文檔和各後端配置
- 插件概覽 — 所有插件類型一覽
- 構建 Hermes 插件 — 通用工具/hooks/斜槓命令指南