Documentação oficial desta página · v0.20.6
Plugins de Geração de Vídeo
Construir um plugin de provedor de geração de vídeo
Plugins de provedor de geração de vídeo registram um backend que atende toda chamada da ferramenta video_generate. Os provedores embutidos (xAI, FAL, DeepInfra) são distribuídos como plugins. Para adicionar um novo, ou substituir um dos que vêm no pacote, basta colocar um diretório em plugins/video_gen/<nome>/.
A geração de vídeo espelha os Plugins de Geração de Imagem quase linha por linha — se você já construiu um backend de geração de imagem, o formato já é conhecido. As diferenças principais: um método capabilities() que anuncia modalidades, proporções e durações, e uma convenção de roteamento (passe image_url para usar imagem-para-vídeo, omita para usar texto-para-vídeo — o provedor escolhe o endpoint certo internamente).
A superfície unificada (uma ferramenta, duas modalidades)
A ferramenta video_generate expõe duas modalidades por meio de um único parâmetro:
- Texto-para-vídeo — chame só com
prompt. O provedor encaminha para o endpoint de texto-para-vídeo. - Imagem-para-vídeo — chame com
prompt+image_url. O provedor encaminha para o endpoint de imagem-para-vídeo.
Editar e estender ficaram de fora de propósito. A maioria dos backends não dá suporte a isso, e a inconsistência obrigaria a enfiar prosa específica de cada backend na descrição da ferramenta que o agente lê.
Como funciona a descoberta
O Hermes procura backends de geração de vídeo em três lugares:
- Empacotados —
<repo>/plugins/video_gen/<nome>/(carregados automaticamente comkind: backend) - Do usuário —
~/.hermes/plugins/video_gen/<nome>/(habilitação explícita viaplugins.enabled) - Pip — pacotes que declaram um entry point
hermes_agent.plugins
A função register(ctx) de cada plugin chama ctx.register_video_gen_provider(...). O provedor ativo é escolhido por video_gen.provider no config.yaml; hermes tools → Video Generation conduz o usuário pela seleção. Diferente de image_generate, não existe backend legado dentro da árvore do projeto — todo provedor é um plugin.
Estrutura de diretórios
plugins/video_gen/meu-backend/
├── __init__.py # subclasse de VideoGenProvider + register()
└── plugin.yaml # manifesto com kind: backend
A ABC VideoGenProvider
Herde de agent.video_gen_provider.VideoGenProvider. Obrigatórios: a propriedade name e o método generate().
# plugins/video_gen/meu-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 "meu-backend"
@property
def display_name(self) -> str:
return "Meu Backend"
def is_available(self) -> bool:
return bool(os.environ.get("MY_API_KEY"))
def list_models(self) -> List[Dict[str, Any]]:
# Cada entrada é uma FAMÍLIA de modelos — um nome que o usuário
# escolhe uma vez. O generate() do seu provedor roteia dentro da
# família conforme image_url tenha sido passado ou não.
return [
{
"id": "fast",
"display": "Fast",
"speed": "~30s",
"strengths": "Faixa mais barata",
"price": "$0.05/s",
"modalities": ["text", "image"], # informativo
},
]
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": "Meu Backend",
"badge": "paid",
"tag": "Descrição curta exibida em `hermes tools`",
"env_vars": [
{
"key": "MY_API_KEY",
"prompt": "Chave de API do Meu 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, # sempre ignore kwargs desconhecidos, por compatibilidade futura
) -> Dict[str, Any]:
# ROTEAMENTO: a presença de image_url escolhe o endpoint.
if image_url:
endpoint = "my-backend/image-to-video"
modality_used = "image"
else:
endpoint = "my-backend/text-to-video"
modality_used = "text"
# ... chame a sua API ...
return success_response(
video="https://sua-cdn/saida.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())
O manifesto do plugin
# plugins/video_gen/meu-backend/plugin.yaml
name: my-backend
version: 1.0.0
description: "Meu backend de geração de vídeo"
author: Seu Nome
kind: backend
requires_env:
- MY_API_KEY
O schema de video_generate
A ferramenta expõe um único schema para todos os backends. Cada provedor ignora os parâmetros que não suporta.
| Parâmetro | O que faz |
|---|---|
prompt | Instrução em texto (obrigatório) |
image_url | Quando definido → imagem-para-vídeo; quando omitido → texto-para-vídeo |
reference_image_urls | Referências de estilo/personagem (depende do provedor) |
duration | Segundos — o provedor limita |
aspect_ratio | "16:9", "9:16", "1:1", ... — o provedor limita |
resolution | "480p" / "540p" / "720p" / "1080p" — o provedor limita |
negative_prompt | Conteúdo a evitar (só Pixverse/Kling) |
audio | Áudio nativo (faixa de preço do Veo3 / Pixverse) |
seed | Reprodutibilidade |
model | Sobrescreve o modelo/família ativo |
O capabilities() do provedor anuncia quais desses parâmetros são respeitados. O agente vê as capacidades do backend ativo na descrição da ferramenta, reconstruída dinamicamente quando o usuário troca de backend pelo hermes tools.
Famílias de modelos e roteamento de endpoint (o padrão FAL)
Quando o seu backend tem vários endpoints por "modelo" — como o FAL, em que cada família (Veo 3.1, Pixverse v6, Kling O3) tem uma URL /text-to-video e outra /image-to-video — represente cada família como uma entrada de catálogo. O seu generate() escolhe o endpoint certo conforme image_url tenha sido passado ou não:
FAMILIES = {
"veo3.1": {
"text_endpoint": "fal-ai/veo3.1",
"image_endpoint": "fal-ai/veo3.1/image-to-video",
# ... flags de capacidade específicas da família ...
},
}
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"]
# ... monte o payload a partir das flags de capacidade da família e chame o endpoint ...
O usuário escolhe veo3.1 uma vez no hermes tools. O agente nunca pensa em endpoints — ele apenas passa (ou deixa de passar) image_url.
Precedência de seleção
Para ajustes de modelo por instância (veja plugins/video_gen/fal/__init__.py):
- Palavra-chave
model=vinda da chamada de ferramenta - Variável de ambiente
<PROVIDER>_VIDEO_MODEL video_gen.<provider>.modelnoconfig.yamlvideo_gen.modelnoconfig.yaml(quando for um dos seus IDs)- O
default_model()do provedor
Formato da resposta
success_response() e error_response() produzem o formato de dicionário que todo backend devolve. Use essas funções — não monte o dicionário na mão.
Chaves de sucesso: success, video (URL ou caminho absoluto), model, prompt, modality ("text" ou "image"), aspect_ratio, duration, provider, além de extra.
Chaves de erro: success, video (None), error, error_type, model, prompt, aspect_ratio, provider.
Onde salvar os artefatos
Se o seu backend devolve base64, use save_b64_video() para gravar em $HERMES_HOME/cache/videos/. Para bytes crus vindos de um download HTTP posterior, use save_bytes_video(). Caso contrário, devolva a URL de origem diretamente — o gateway resolve URLs remotas na hora da entrega.
Testes
Coloque um teste de fumaça em tests/plugins/video_gen/test_<nome>_plugin.py. Os testes do xAI e do FAL mostram o padrão — registrar, conferir o catálogo, exercitar o roteamento com e sem image_url, e verificar respostas de erro limpas quando falta autenticação.