Создание плагина провайдера генерации видео
Плагины провайдеров генерации видео регистрируют бэкенд, который обслуживает каждый вызов инструмента 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 сканирует бэкенды генерации видео в трёх местах:
- Встроенные —
<repo>/plugins/video_gen/<name>/(автозагрузка сkind: backend) - Пользовательские —
~/.vibeos/plugins/video_gen/<name>/(включение черезplugins.enabled) - 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):
- Ключевое слово
model=из вызова инструмента - Переменная окружения
<PROVIDER>_VIDEO_MODEL video_gen.<provider>.modelвconfig.yamlvideo_gen.modelвconfig.yaml(когда это один из ваших ID)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, так и без него, проверить чистые ответы об ошибках при отсутствии аутентификации.