Создание плагина провайдера генерации видео

Плагины провайдеров генерации видео регистрируют бэкенд, который обслуживает каждый вызов инструмента video_generate. Встроенные провайдеры (xAI, FAL) размещаются в видеоплагах. Чтобы добавить новый или переопределить встроенный, поместите каталог в plugins/video_gen/<name>/.:::совет Генерация видео почти полностью повторяется Плагины поставщиков генерации изображений — если вы создали резервную копию для генерации изображений, вы уже знаете текстуру. Основные различия: метод capabilities(), рекламная модальность/соотношение сторон/длительности и соглашение о маршрутизации (передайте image_url для использования изображения в видео, отключите его для преобразования текста в видео — провайдер сам выбирает подходящую внутреннюю конечную точку).

Единая поверхность (один инструмент, две модальности)

Инструмент video_generate обеспечивает две модальности через один параметр:

Редактирование и расширение намеренно не упоминаются. Для большинства бэкендов их не было, а несовместимость вынудила добавить описание для каждого бэкенда в описание агента инструмента.

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

Гермес сканирует резервные копии генерации видео в трёх местах:

  1. Встроенные<repo>/plugins/video_gen/<name>/ (автоматически загружаются с kind: backend)
  2. Пользовательские~/.hermes/plugins/video_gen/<name>/ (подключаются через plugins.enabled)
  3. Pip — пакеты, объявляющие точку входа hermes_agent.plugins

Функция register(ctx) каждый плагин вызывает ctx.register_video_gen_provider(...). Активный провайдер вы меняете параметр video_gen.provider в config.yaml; hermes Tools → Генерация видео осуществляется пользователем по выбору. В отличие от image_generate, здесь нет закрытого консольного бэкенда — каждый провайдер является плагином.

Структура директории

plugins/video_gen/my-backend/
├── __init__.py      # Подкласс VideoGenProvider + register()
└── plugin.yaml      # Манифест с kind: backend

начало базового класса VideoGenProvider

Создайте подкласс agent.video_gen_provider.VideoGenProvider. Обязательны: свойство name и метод generate().

# plugins/video_gen/my-backend/__init__.py
from typing import Any, Dict, List, Optional
import os

from agent.video_gen_provider import (
    VideoGenProvider,
    error_response,
    success_response,
)


class MyVideoGenProvider(VideoGenProvider):
    @property
    def name(self) -> str:
        return "my-backend"

    @property
    def display_name(self) -> str:
        return "My Backend"

    def is_available(self) -> bool:
        return bool(os.environ.get("MY_API_KEY"))

    def list_models(self) -> List[Dict[str, Any]]:
        # Каждая запись — это СЕМЕЙСТВО моделей — имя, которое пользователь выбирает один раз.
        # Ваш провайдер generate() маршрутизирует внутри семейства на основе того,
        # был ли передан image_url.
        return [
            {
                "id": "fast",
                "display": "Fast",
                "speed": "~30s",
                "strengths": "Самый дешёвый тариф",
                "price": "$0.05/s",
                "modalities": ["text", "image"],  # информационно
            },
        ]

    def default_model(self) -> Optional[str]:
        return "fast"

    def capabilities(self) -> Dict[str, Any]:
        return {
            "modalities": ["text", "image"],
            "aspect_ratios": ["16:9", "9:16"],
            "resolutions": ["720p", "1080p"],
            "min_duration": 1,
            "max_duration": 10,
            "supports_audio": False,
            "supports_negative_prompt": True,
            "max_reference_images": 0,
        }

    def get_setup_schema(self) -> Dict[str, Any]:
        return {
            "name": "My Backend",
            "badge": "paid",
            "tag": "Краткое описание, отображаемое в `hermes tools`",
            "env_vars": [
                {
                    "key": "MY_API_KEY",
                    "prompt": "API-ключ My Backend",
                    "url": "https://mybackend.example.com/keys",
                },
            ],
        }

    def generate(
        self,
        prompt: str,
        *,
        model: Optional[str] = None,
        image_url: Optional[str] = None,
        reference_image_urls: Optional[List[str]] = None,
        duration: Optional[int] = None,
        aspect_ratio: str = "16:9",
        resolution: str = "720p",
        negative_prompt: Optional[str] = None,
        audio: Optional[bool] = None,
        seed: Optional[int] = None,
        **kwargs: Any,  # всегда игнорируйте неизвестные kwargs для обратной совместимости
    ) -> Dict[str, Any]:
        # МАРШРУТИЗАЦИЯ: наличие image_url определяет endpoint.
        if image_url:
            endpoint = "my-backend/image-to-video"
            modality_used = "image"
        else:
            endpoint = "my-backend/text-to-video"
            modality_used = "text"

        #... вызов вашего API...

        return success_response(
            video="https://your-cdn/output.mp4",
            model=model or "fast",
            prompt=prompt,
            modality=modality_used,
            aspect_ratio=aspect_ratio,
            duration=duration or 5,
            provider=self.name,
        )


def register(ctx) -> None:
    ctx.register_video_gen_provider(MyVideoGenProvider())

Плагин манифеста

# plugins/video_gen/my-backend/plugin.yaml
name: my-backend
version: 1.0.0
description: "Мой бэкенд генерации видео"
author: Ваше Имя
kind: backend
requires_env:
  - MY_API_KEY

Схема video_generate

Инструмент обеспечивает одну схему для всех бэкендов. Провайдеры исключают параметры, которые не рекомендуются.

Параметр Назначение
подсказка Текстовая инструкция (обязательно)
image_url Если задано → изображение-видео; если опущен → преобразование текста в видео
reference_image_urls Ссылки на стиль/персонажей (зависит от провайдера)
продолжительность Секунды — обеспечение ограничений
aspect_ratio "16:9", "9:16", "1:1",... — обеспечивает ограничение
резолюция "480p" / "540p" / "720p" / "1080p" — провайдер ограничивает
negative_prompt Контент, который следует слушать (только Pixverse/Kling)
аудио Встроенное аудио (Veo3 / тариф Pixverse)
семя Воспроизводимость
модель Переопределение активных моделей/семейства

Поставщик рекламирует метод capabilities(), который из этих параметров применяется. Агент видит возможности активного бэкенда в описанном инструменте, который оказывает воздействие при смене бэкенда пользователя через hermestools.

Семейства моделей и маршрутизация endpoint'ов (шаблон FAL)

Когда ваш бэкенд имеет несколько endpoint'ов по "модели" - например, FAL, где каждое семейство (Veo 3.1, Pixverse v6, Kling O3) имеет как URL /text-to-video, так и /image-to-video - предписывается каждое семейство как одну запись в каталоге. Ваш generate() выбирает правильную конечную точку на основе того, что было передано image_url:

FAMILIES = {
    "veo3.1": {
        "text_endpoint": "fal-ai/veo3.1",
        "image_endpoint": "fal-ai/veo3.1/image-to-video",
        #... флаги возможностей, специфичные для семейства...
    },
}

def generate(self, prompt, *, image_url=None, model=None, **kwargs):
    family_id, family = _resolve_family(model)
    endpoint = family["image_endpoint"] if image_url else family["text_endpoint"]
    #... сформировать полезную нагрузку из объявленных флагов возможностей семейства, вызвать endpoint...

Пользователь выбирает veo3.1 один раз в hermes Tools. Агент никогда не думает об конечных точках — он просто передаёт (или не передаёт) image_url.

Приоритет выбора

Для настроек модели на уровне экземпляра (см. plugins/video_gen/fal/__init__.py):

  1. Ключевое слово model= из вызова инструмента
  2. Переменная окружения <PROVIDER>_VIDEO_MODEL
  3. video_gen.<провайдер>.model в config.yaml
  4. video_gen.model в config.yaml (когда это один из ваших ID)
  5. провайдер default_model()

Формат ответа

success_response() и error_response() создают формат словаря, который возвращает каждый бэкенд. Используйте их — не создавайте словарь вручную.

Ключи успеха: «успех», «видео» (URL или общий путь), «модель», «подсказка», «модальность» («текст» или «изображение»), «aspect_ratio», «длительность», «поставщик», плюс «дополнительно».

Ключевые ошибки: «успех», «видео» (нет), «ошибка», «тип_ошибки», «модель», «подсказка», «соотношение сторон», «поставщик».

Где хранятся документы

Если ваш бэкенд получает base64, воспользуйтесь save_b64_video() для записи в $HERMES_HOME/cache/videos/. Для сырых байтов из последующего HTTP-запроса используйте save_bytes_video(). В противном случае возвращайте URL-адрес вышестоящего сервиса напрямую — шлюз разрешает удаленные URL-адреса при доставке.

Тестирование

Поместите дымовой тест в tests/plugins/video_gen/test_<name>_plugin.py. Тесты xAI и FAL показывают шаблон — зарегистрировать, проверить каталог, протестировать маршрутизацию как с image_url, так и без него, проверить корректные ответы на ошибках при отсутствии аутентификации.