Перейти к основному содержимому

Создание плагина провайдера веб-поиска

Плагины провайдеров веб-поиска регистрируют бэкенд, который обслуживает вызовы инструментов web_search, web_extract и (опционально) глубокого обхода. Встроенные провайдеры — Firecrawl, SearXNG, Tavily, Exa, Parallel, Brave Search (бесплатный тариф), xAI и DDGS — поставляются как плагины в plugins/web/<name>/. Вы можете добавить новый или переопределить встроенный, поместив рядом с ними свою директорию.

подсказка

Веб-поиск — один из нескольких бэкенд-плагинов, которые поддерживает VibeOS. Другие (со своими ABC) — это Плагины провайдеров генерации изображений, Плагины провайдеров генерации видео, Плагины провайдеров памяти, Плагины контекстных движков и Плагины провайдеров моделей. Общие плагины инструментов/хуков/CLI описаны в разделе Создание плагина VibeOS.

Сегодняшние встроенные веб-бэкенды: Firecrawl, SearXNG, Tavily, Exa, Parallel, Brave Search (бесплатный тариф), xAI, DDGS и Yandex Search.

Как работает обнаружение​

VibeOS сканирует веб-бэкенды в трёх местах:

  1. Встроенные — <repo>/plugins/web/<name>/ (автозагрузка с kind: backend, всегда доступны)
  2. Пользовательские — ~/.vibeos/plugins/web/<name>/ (включаются через plugins.enabled или vibeos plugins enable <name>)
  3. Pip — пакеты, объявляющие точку входа vibeos_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_extractweb.extract_backendweb.backend

Если ни один ключ не установлен, VibeOS автоматически определяет бэкенд по тому, какой API-ключ/URL присутствует в окружении. vibeos 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.

ABC WebSearchProvider​

Создайте подкласс 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):
"""Минимальный провайдер только поиска через HTTP API My Backend."""

@property
def name(self) -> str:
# Стабильный идентификатор, используемый в ключах конфига
# web.search_backend / web.extract_backend / web.backend.
# Нижний регистр, без пробелов; дефисы разрешены.
return "my-backend"

@property
def display_name(self) -> str:
# Человеческое название, показываемое в `vibeos tools`. По умолчанию — `name`.
return "My Backend"

def is_available(self) -> bool:
# Дешёвая проверка — наличие переменной окружения, импортируемость опциональной зависимости и т.д.
# НЕ ДОЛЖНА делать сетевые вызовы (выполняется при каждой отрисовке `vibeos 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)}

# Формат ответа фиксирован — см. «Формат ответа» ниже.
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 — REST API с Bearer-аутентификацией"
author: Ваше Имя
kind: backend
provides_web_providers:
- my-backend
requires_env:
- MY_BACKEND_API_KEY
КлючНазначение
kind: backendНаправляет плагин через путь загрузки бэкендов
provides_web_providersСписок name провайдеров, которые регистрирует этот плагин — используется загрузчиком для рекламы плагина в vibeos tools ещё до выполнения register()
requires_envИнтерактивный запрос учётных данных во время vibeos plugins install (см. Создание плагина VibeOS → Проверка переменных окружения для полного формата)

Справочник по ABC​

Полный контракт в agent/web_search_provider.py. Методы, которые вы можете переопределить:

ЧленОбязателенПо умолчаниюНазначение
name✅—Стабильный идентификатор, используемый в конфиге web.*_backend
display_name—nameНазвание, показываемое в vibeos 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, # опционально
"error": str, # опционально, только при ошибке для конкретного URL
},
...
],
}

Любая возможность, при ошибке:

{"success": False, "error": "понятное человеку сообщение"}
}

Оба метода `search()` и `extract()` могут быть `async def` — диспетчер определяет функции-корутины через `inspect.iscoroutinefunction` и ожидает их соответственно. Синхронные реализации, выполняющие блокирующий ввод-вывод (HTTP, вызовы SDK), подходят для небольших бэкендов; диспетчер обрабатывает многопоточность.

## Флаги возможностей

VibeOS направляет вызовы к нужному провайдеру на основе флагов `supports_*`. Типичная многопровайдерная настройка:

```yaml
# ~/.vibeos/config.yaml
web:
search_backend: "brave-free" # только поиск, быстро, бесплатно 2k/мес
extract_backend: "firecrawl" # извлечение + обход, платная квота

Когда web.search_backend или web.extract_backend не заданы, оба используют web.backend как запасной. Если и он не задан, VibeOS выбирает первый доступный провайдер, поддерживающий запрошенную возможность, на основе наличия переменных окружения.

Если ваш провайдер поддерживает только одну возможность, оставьте другие флаги по умолчанию (False), и реестр пропустит его для этого инструмента — пользователи не увидят вводящих в заблуждение ошибок «провайдер X не сработал», когда они используют X только для поиска, а просят агента выполнить извлечение.

Как VibeOS подключает это к инструментам​

Инструменты web_search и web_extract находятся в tools/web_tools.py. Во время вызова они:

  1. Читают соответствующий ключ конфига (web.search_backend для web_search, web.extract_backend для web_extract)
  2. Запрашивают у реестра провайдера с этим name
  3. Проверяют is_available() и соответствующий флаг supports_*()
  4. Выполняют search() / extract() (глубокий обход работает как режим внутри extract()), ожидая, если метод является корутиной
  5. Сериализуют ответную структуру в JSON и передают её LLM

Ошибки отображаются как результат инструмента; LLM решает, как их объяснить. Если ни один провайдер не зарегистрирован (или каждый доступный не проходит проверку возможности), инструмент возвращает полезную ошибку со ссылкой на vibeos tools.

Ленивая установка опциональных зависимостей​

Если ваш провайдер оборачивает сторонний SDK (как DDGS делает с пакетом ddgs), не импортируйте его на верхнем уровне модуля. Используйте tools.lazy_deps.ensure(...) внутри is_available() или search() — VibeOS установит пакет при первом использовании, под контролем security.allow_lazy_installs. См. Создание плагина VibeOS → Ленивая установка для модели безопасности.

Эталонные реализации​

  • plugins/web/brave_free/ — маленький HTTP-провайдер только поиска с API-ключом. Хороший стартовый шаблон.
  • plugins/web/ddgs/ — провайдер без ключа, лениво устанавливающий свой SDK. Полезный паттерн для бэкендов, оборачивающих Python-пакет.
  • plugins/web/firecrawl/ — полноценный многозадачный провайдер (поиск + извлечение + обход) с несколькими режимами форматирования.
  • plugins/web/searxng/ — самостоятельно размещаемый бэкенд, настраиваемый через URL, без аутентификации.
  • plugins/web/xai/ — поиск на основе LLM через серверный инструмент web_search от Grok. Показывает, как повторно использовать существующую поверхность учётных данных OAuth/переменных окружения (tools/xai_http.py) без добавления новых переменных, и как написать дешёвый is_available(), соблюдающий контракт без сетевых вызовов.

Распространение через pip​

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

my_backend_web_package должен предоставлять функцию register на верхнем уровне. См. Распространение через pip в общем руководстве по плагинам для полной настройки.

Связанные страницы​