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

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

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

подсказка

Генерация видео почти полностью повторяет Плагины провайдеров генерации изображений — если вы уже создавали бэкенд для генерации изображений, вы знакомы с шаблоном. Основные отличия: метод capabilities(), который объявляет модальности/соотношения сторон/длительность, и соглашение о маршрутизации (передайте image_url для использования image-to-video, опустите для text-to-video — провайдер сам выбирает нужный эндпоинт).

Единый интерфейс (один инструмент, две модальности)​

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

  • Text-to-video — вызов только с prompt. Провайдер направляет запрос на свой эндпоинт text-to-video.
  • Image-to-video — вызов с prompt + image_url. Провайдер направляет запрос на свой эндпоинт image-to-video.

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

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

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

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

Каждый плагин вызывает функцию register(ctx), которая, в свою очередь, вызывает ctx.register_video_gen_provider(...). Активный провайдер выбирается параметром video_gen.provider в config.yaml; vibeos tools → Video Generation проводит пользователя через выбор. В отличие от image_generate, здесь нет встроенного устаревшего бэкенда — каждый провайдер является плагином.

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

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

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

```python
# 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/с",
"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": "Краткое описание, отображаемое в `vibeos 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 определяет эндпоинт.
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​

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

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

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

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

Когда ваш бэкенд имеет несколько эндпоинтов на «модель» — как FAL, где каждое семейство (Veo 3.1, Pixverse v6, Kling O3) имеет как /text-to-video, так и /image-to-video URL — представляйте каждое семейство как одну запись в каталоге. Ваш 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"]
# ... построить полезную нагрузку из объявленных флагов возможностей семейства, вызвать эндпоинт ...

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

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

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

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

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

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

Ключи успеха: success, video (URL или абсолютный путь), model, prompt, modality ("text" или "image"), aspect_ratio, duration, provider, плюс extra.

Ключи ошибки: success, video (None), error, error_type, model, prompt, aspect_ratio, provider.

Где сохранять артефакты​

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

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

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