构建网页搜索提供者插件
网页搜索提供者插件注册一个后端,服务于 web_search、web_extract 和(可选)深度爬取工具调用。内置提供者——Firecrawl、SearXNG、Tavily、Exa、Parallel、Brave Search(免费层级)和 DDGS——均作为插件位于 plugins/web/<name>/ 下。你可以通过在旁边放入目录来添加新插件或覆盖内置插件。
:::tip 网页搜索是 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 的提供者。
WebSearchProvider ABC
继承 agent.web_search_provider.WebSearchProvider。唯一必需的成员是 name、is_available(),以及你实现的 search() / extract() / crawl() 中的任意方法。
# 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):
"""基于 My Backend HTTP API 的最小搜索专用提供者。"""
@property
def name(self) -> str:
# 用于 web.search_backend / web.extract_backend / web.backend
# 配置键的稳定 ID。小写,无空格;允许连字符。
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 supports_crawl(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 列表——加载器使用此信息在 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 路由的能力标志 |
supports_crawl() | — | False | 深度爬取模式的能力标志 |
search(query, limit) | 条件 | 抛出异常 | 当 supports_search() 返回 True 时必需 |
extract(urls, **kwargs) | 条件 | 抛出异常 | 当 supports_extract() 返回 True 时必需 |
crawl(url, **kwargs) | 条件 | 抛出异常 | 当 supports_crawl() 返回 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" # 仅搜索,快速,免费每月 2000 次
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使用web.search_backend,web_extract使用web.extract_backend) - 向注册表请求具有该
name的提供者 - 检查
is_available()和匹配的supports_*()标志 - 分发到
search()/extract()/crawl(),如果方法是协程则 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 插件 — 通用工具/钩子/斜杠命令指南