Files
hermes-agent/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/developer-guide/web-search-provider-plugin.md
T
2026-06-14 14:30:48 -04:00

260 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
sidebar_position: 12
title: "网页搜索提供商插件"
description: "如何为 Hermes Agent 构建网页搜索/提取/爬取后端插件"
---
# 构建网页搜索提供商插件
网页搜索提供商插件注册一个后端,用于处理 `web_search``web_extract` 以及(可选的)深度爬取工具调用。内置提供商——Firecrawl、SearXNG、Tavily、Exa、Parallel、Brave Search(免费层)和 DDGS——均以插件形式存放于 `plugins/web/<name>/` 目录下。你可以在该目录旁新建一个目录来添加新提供商,或覆盖已有的内置提供商。
:::tip
网页搜索是 Hermes 支持的多种**后端插件**之一。其他插件(各有其 ABC)包括:[图像生成提供商插件](/developer-guide/image-gen-provider-plugin)、[视频生成提供商插件](/developer-guide/video-gen-provider-plugin)、[记忆提供商插件](/developer-guide/memory-provider-plugin)、[上下文引擎插件](/developer-guide/context-engine-plugin)和[模型提供商插件](/developer-guide/model-provider-plugin)。通用工具/hook/CLI 插件请参阅[构建 Hermes 插件](/guides/build-a-hermes-plugin)。
:::
## 发现机制
Hermes 在三个位置扫描网页搜索后端:
1. **内置**`<repo>/plugins/web/<name>/`(以 `kind: backend` 自动加载,始终可用)
2. **用户**`~/.hermes/plugins/web/<name>/`(通过 `plugins.enabled``hermes plugins enable <name>` 按需启用)
3. **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 key/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 key 的纯搜索提供商,`ddgs` 是无需 key 且懒加载 SDK 的提供商。
## WebSearchProvider ABC
继承 `agent.web_search_provider.WebSearchProvider`。唯一必须实现的成员是 `name``is_available()`,以及你所实现的 `search()` / `extract()` / `crawl()` 中的相应方法。
```python
# 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", []))
],
},
}
```
```python
# 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
```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 插件](/guides/build-a-hermes-plugin#gate-on-environment-variables) |
## 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 也仅支持搜索,并有文档说明的"与提取提供商配对使用"工作流。
## 响应格式
工具包装器期望固定的响应信封(envelope),以避免在不同后端之间进行转换。
**搜索成功:**
```python
{
"success": True,
"data": {
"web": [
{"title": str, "url": str, "description": str, "position": int},
...
],
},
}
```
**提取成功:**
```python
{
"success": True,
"data": [
{
"url": str,
"title": str,
"content": str,
"raw_content": str,
"metadata": dict, # optional
"error": str, # optional, only on per-URL failure
},
...
],
}
```
**任意能力,失败时:**
```python
{"success": False, "error": "human-readable message"}
```
`search()``extract()` 均可定义为 `async def`——调度器通过 `inspect.iscoroutinefunction` 检测协程函数并相应地进行 await。对于小型后端,执行阻塞 I/O(HTTP、SDK 调用)的同步实现也完全可行;调度器会处理线程调度。
## 能力标志
Hermes 根据 `supports_*` 标志将调用路由至正确的提供商。一种常见的多提供商配置:
```yaml
# ~/.hermes/config.yaml
web:
search_backend: "brave-free" # 纯搜索,速度快,每月免费 2k 次
extract_backend: "firecrawl" # 提取 + 爬取,付费配额
```
`web.search_backend``web.extract_backend` 未设置时,均回退至 `web.backend`。若该项也未设置,Hermes 将根据环境变量的存在情况,选取第一个支持所请求能力的可用提供商。
如果你的提供商只支持一种能力,将其他标志保持默认值(`False`)即可,注册表会在对应工具调用时跳过它——当用户仅将 X 用于搜索而要求 agent 进行提取时,不会看到误导性的"提供商 X 失败"错误。
## Hermes 如何将其接入工具
`web_search``web_extract` 工具位于 `tools/web_tools.py`。调用时执行以下步骤:
1. 读取相关配置键(`web_search` 对应 `web.search_backend``web_extract` 对应 `web.extract_backend`
2. 向注册表查询具有该 `name` 的提供商
3. 检查 `is_available()` 及对应的 `supports_*()` 标志
4. 调度至 `search()` / `extract()` / `crawl()`,若方法为协程则进行 await
5. 将响应信封 JSON 序列化后返回给 LLM
错误以工具结果的形式呈现;LLM 决定如何解释。若没有提供商被注册(或所有可用提供商均未通过能力检查),工具将返回一条指向 `hermes tools` 的友好错误信息。
## 懒加载可选依赖
如果你的提供商封装了第三方 SDK(如 DDGS 封装了 `ddgs` 包),请勿在模块顶层 `import`。在 `is_available()``search()` 内部使用 `tools.lazy_deps.ensure(...)` ——Hermes 将在首次使用时安装该包,并受 `security.allow_lazy_installs` 控制。安全模型详见[构建 Hermes 插件 → 懒加载](/guides/build-a-hermes-plugin#lazy-install-optional-python-dependencies)。
## 参考实现
- **`plugins/web/brave_free/`** — 小型、需要 API key 的纯搜索 HTTP 提供商。适合作为起始模板。
- **`plugins/web/ddgs/`** — 无需 key、懒加载 SDK 的提供商。适用于封装 Python 包的后端。
- **`plugins/web/firecrawl/`** — 完整的多能力提供商(搜索 + 提取 + 爬取),支持多种格式模式。
- **`plugins/web/searxng/`** — 自托管、通过 URL 配置、无需认证的后端。
- **`plugins/web/xai/`** — 通过 Grok 服务端 `web_search` 工具实现的 LLM 驱动搜索。展示了如何复用现有的 OAuth/环境变量凭据(`tools/xai_http.py`)而无需新增环境变量,以及如何编写遵守无网络调用约定的轻量 `is_available()`
## 通过 pip 分发
```toml
# pyproject.toml
[project.entry-points."hermes_agent.plugins"]
my-backend-web = "my_backend_web_package"
```
`my_backend_web_package` 必须暴露顶层 `register` 函数。完整配置说明参见通用插件指南中的[通过 pip 分发](/guides/build-a-hermes-plugin#distribute-via-pip)。
## 相关页面
- [网页搜索](/user-guide/features/web-search) — 面向用户的功能文档及各后端配置说明
- [插件概览](/user-guide/features/plugins) — 所有插件类型一览
- [构建 Hermes 插件](/guides/build-a-hermes-plugin) — 通用工具/hook/斜杠命令指南