비디오 생성 제공자 플러그인 구축
비디오 생성 제공자 플러그인은 모든 video_generate 도구 호출을 서비스하는 백엔드를 등록합니다. 내장 제공자(xAI, FAL)는 플러그인으로 제공됩니다. plugins/video_gen/<name>/에 디렉터리를 놓아 새 항목을 추가하거나 번들된 항목을 재정의합니다.
Video-gen은 이미지 생성 제공자 플러그인을 거의 한 줄씩 미러링합니다. image-gen 백엔드를 구축했다면 이미 그 모양을 알고 있을 것입니다. 주요 차이점: capabilities() 메소드 광고 형식/종횡비/기간 및 라우팅 규칙(이미지-비디오를 사용하려면 image_url을 전달하고, 텍스트-비디오를 사용하려면 이를 생략합니다. 제공자는 내부적으로 올바른 엔드포인트를 선택합니다).
통합된 표면(하나의 도구, 두 가지 양식)
video_generate 도구는 하나의 매개변수를 통해 두 가지 양식을 노출합니다.
- 텍스트-비디오 —
prompt로만 통화하세요. 제공자는 텍스트-비디오 엔드포인트로 라우팅합니다. - 이미지-비디오 —
prompt+image_url로 호출합니다. 제공자는 이미지-비디오 엔드포인트로 라우팅합니다.
편집 및 확장은 의도적으로 범위를 벗어났습니다. 대부분의 백엔드는 이를 지원하지 않으며 불일치로 인해 백엔드별 구문이 에이전트의 도구 설명에 포함됩니다.
검색 작동 방식
Hermes는 세 곳에서 video-gen 백엔드를 검색합니다.
- 번들 —
<repo>/plugins/video_gen/<name>/(kind: backend로 자동 로드됨) - 사용자 —
~/.hermes/plugins/video_gen/<name>/(plugins.enabled을 통해 선택) - Pip —
hermes_agent.plugins진입점을 선언하는 패키지
각 플러그인의 register(ctx) 함수는 ctx.register_video_gen_provider(...)을 호출합니다. 활성 제공자는 config.yaml의 video_gen.provider에 의해 선택됩니다. hermes tools → 비디오 생성은 사용자에게 선택 과정을 안내합니다. image_generate과 달리 트리 내 레거시 백엔드가 없습니다. 모든 제공자는 플러그인입니다.
디렉토리 구조
plugins/video_gen/my-backend/
├── __init__.py # VideoGenProvider subclass + register()
└── plugin.yaml # Manifest with kind: backend
VideoGenProvider ABC
하위 클래스 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]]:
# Each entry is a model FAMILY — a name the user picks once.
# Your provider's generate() routes within the family based on
# whether image_url was passed.
return [
{
"id": "fast",
"display": "Fast",
"speed": "~30s",
"strengths": "Cheapest tier",
"price": "$0.05/s",
"modalities": ["text", "image"], # advisory
},
]
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": "Short description shown in `hermes tools`",
"env_vars": [
{
"key": "MY_API_KEY",
"prompt": "My Backend API key",
"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, # always ignore unknown kwargs for forward-compat
) -> Dict[str, Any]:
# ROUTE: image_url presence picks the endpoint.
if image_url:
endpoint = "my-backend/image-to-video"
modality_used = "image"
else:
endpoint = "my-backend/text-to-video"
modality_used = "text"
#... call your 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: "My video generation backend"
author: Your Name
kind: backend
requires_env:
- MY_API_KEY
video_generate 스키마
이 도구는 모든 백엔드에서 하나의 스키마를 노출합니다. 제공자는 지원하지 않는 매개변수를 무시합니다.
| 매개변수 | 기능 |
|---|---|
prompt | 텍스트 안내(필수) |
image_url | 설정 시 → 이미지-비디오; 생략 시 → 텍스트-비디오 |
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()은 이들 중 어느 것이 인정되는지 광고합니다. 에이전트는 도구 설명에서 활성 백엔드의 기능을 확인하며, 사용자가 hermes 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",
#... family-specific capability flags...
},
}
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"]
#... build payload from family's declared capability flags, call endpoint...
사용자는 hermes tools에서 veo3.1을 한 번 선택합니다. 에이전트는 엔드포인트에 대해 전혀 생각하지 않습니다. 단지 image_url을 전달합니다(또는 전달하지 않습니다).
선택 우선순위
인스턴스별 모델 손잡이의 경우(plugins/video_gen/fal/__init__.py 참조):
- 도구 호출의
model=키워드 <PROVIDER>_VIDEO_MODEL환경 변수video_gen.<provider>.model(config.yaml)video_gen.modelinconfig.yaml(ID 중 하나인 경우)- 제공업체의
default_model()
응답 형태
success_response() 및 error_response()은 모든 백엔드가 반환하는 사전 형태를 생성합니다. 이를 사용하세요. 딕셔너리를 직접 작성하지 마세요.
성공 키: success, video(URL 또는 절대 경로), model, prompt, modality("text" 또는 "image"), aspect_ratio, duration, provider 및 extra.
오류 키: success, video (없음), error, error_type, model, prompt, aspect_ratio, provider.
유물을 저장할 위치
백엔드가 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 유무에 관계없이 라우팅 실행, 인증 누락에 대한 명확한 오류 응답 주장.