Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ class VeoVersion(StrEnum):

VEO_2_0 = 'veo-2.0-generate-001'
VEO_2_0_EXP = 'veo-2.0-generate-exp'
VEO_3_0 = 'veo-3.0-generate-001'
VEO_3_0_FAST = 'veo-3.0-fast-generate-001'
VEO_3_1_PREVIEW = 'veo-3.1-generate-preview'
VEO_3_1_FAST_PREVIEW = 'veo-3.1-fast-generate-preview'
VEO_3_1 = 'veo-3.1-generate-001'
VEO_3_1_FAST = 'veo-3.1-fast-generate-001'

Expand Down Expand Up @@ -134,6 +138,10 @@ class VeoConfigSchema(BaseModel):
duration_seconds: int | None = Field(
default=None, alias='durationSeconds', description='Length of video in seconds.'
)
resolution: str | None = Field(
default=None, alias='resolution', description='Desired output resolution (e.g. "720p").'
)
seed: int | None = Field(default=None, alias='seed', description='Random seed for deterministic generation.')
enhance_prompt: bool | None = Field(default=None, alias='enhancePrompt', description='Enable prompt enhancement.')


Expand Down
26 changes: 26 additions & 0 deletions py/plugins/google-genai/tests/veo_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
path where the SDK returns a model instance).
"""

import pytest
from google.genai import types as genai_types

from genkit.plugins.google_genai.models.veo import (
VeoConfigSchema,
VeoVersion,
_from_veo_operation,
_to_veo_parameters,
is_veo_model,
Expand All @@ -47,6 +49,23 @@ def test_non_veo_model(self) -> None:
assert is_veo_model('gemini-2.0-flash') is False


class TestVeoVersion:
"""Tests for VeoVersion enum convenience constants."""

@pytest.mark.parametrize(
'version',
[
VeoVersion.VEO_3_1_PREVIEW,
VeoVersion.VEO_3_1_FAST_PREVIEW,
VeoVersion.VEO_3_0,
VeoVersion.VEO_3_0_FAST,
],
)
def test_new_googleai_models_are_recognized(self, version: VeoVersion) -> None:
"""New Veo 3.0/3.1 model constants map to valid Veo names."""
assert is_veo_model(version.value) is True


class TestToVeoParameters:
"""Tests for _to_veo_parameters."""

Expand All @@ -67,6 +86,13 @@ def test_schema_config(self) -> None:
assert result['aspectRatio'] == '16:9'
assert result['durationSeconds'] == 5

def test_schema_config_includes_new_fields(self) -> None:
"""VeoConfigSchema includes newer Veo parameters."""
config = VeoConfigSchema(resolution='1080p', seed=7)
result = _to_veo_parameters(config)
assert result['resolution'] == '1080p'
assert result['seed'] == 7


class TestFromVeoOperation:
"""Tests for _from_veo_operation.
Expand Down
12 changes: 12 additions & 0 deletions py/samples/google-genai-media/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,15 @@ genkit start -- uv run src/main.py
```

Flows: `generate_speech`, `generate_image`, `generate_video`.

`generate_video` supports testing Veo models by setting `model` in flow input, for example:

- `googleai/veo-3.1-generate-preview`
- `googleai/veo-3.1-fast-generate-preview`
- `googleai/veo-3.0-generate-001`
- `googleai/veo-3.0-fast-generate-001`
- `googleai/veo-3.1-generate-001`
- `googleai/veo-3.1-fast-generate-001`
- `googleai/veo-2.0-generate-001`

The flow input includes Veo config fields such as `aspect_ratio`, `duration_seconds`, `resolution`, and `seed`.
42 changes: 30 additions & 12 deletions py/samples/google-genai-media/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

from pydantic import BaseModel, Field

from genkit import Genkit, ModelConfig
from genkit import Genkit
from genkit._core._background import lookup_background_action
from genkit._core._typing import Operation, Part, Role, TextPart
from genkit.model import Message, ModelRequest
Expand All @@ -47,12 +47,22 @@ class ImageInput(BaseModel):
class VideoInput(BaseModel):
"""Input for Veo."""

model: str = Field(
default='googleai/veo-3.1-generate-preview',
description=(
'Veo model for generation, e.g. '
'googleai/veo-3.1-generate-preview, googleai/veo-3.1-fast-generate-preview, '
'googleai/veo-3.0-generate-001, googleai/veo-3.0-fast-generate-001'
),
)
prompt: str = Field(
default='A paper airplane gliding through a bright classroom, cinematic slow motion',
description='Video prompt',
)
aspect_ratio: str = Field(default='16:9', description='Video aspect ratio')
duration_seconds: int = Field(default=5, description='Video duration in seconds')
resolution: str | None = Field(default='720p', description='Output resolution (for supported models)')
seed: int | None = Field(default=None, description='Optional RNG seed')


def _first_media_url(response: Any) -> str | None:
Expand Down Expand Up @@ -92,12 +102,12 @@ async def imagen_image_generator(input: ImageInput) -> dict[str, str | None]:
return {'model': 'googleai/imagen-3.0-generate-002', 'image_url': _first_media_url(response)}


async def _poll_video(operation: Operation) -> Operation:
async def _poll_video(operation: Operation, model_name: str) -> Operation:
"""Wait for a background video operation to finish."""

action = await lookup_background_action(ai.registry, '/background-model/googleai/veo-2.0-generate-001')
action = await lookup_background_action(ai.registry, f'/background-model/{model_name}')
if action is None:
raise ValueError('Veo background model not found')
raise ValueError(f'Veo background model not found: {model_name}')

started_at = time.monotonic()
while not operation.done:
Expand All @@ -108,24 +118,32 @@ async def _poll_video(operation: Operation) -> Operation:
return operation


def _video_config(input: VideoInput) -> dict[str, Any]:
"""Build Veo config while omitting unset optional fields."""
config = {
'aspect_ratio': input.aspect_ratio,
'duration_seconds': input.duration_seconds,
'resolution': input.resolution,
'seed': input.seed,
}
return {k: v for k, v in config.items() if v is not None}


@ai.flow(name='generate_video')
async def veo_video_generator(input: VideoInput) -> dict[str, str | int | None]:
"""Generate one video by starting and polling a background model."""

action = await lookup_background_action(ai.registry, '/background-model/googleai/veo-2.0-generate-001')
action = await lookup_background_action(ai.registry, f'/background-model/{input.model}')
if action is None:
raise ValueError('Veo background model not found')
raise ValueError(f'Veo background model not found: {input.model}')

operation = await action.start(
ModelRequest(
messages=[Message(role=Role.USER, content=[Part(root=TextPart(text=input.prompt))])],
config=ModelConfig.model_validate({
'aspect_ratio': input.aspect_ratio,
'duration_seconds': input.duration_seconds,
}),
config=_video_config(input),
)
)
operation = await _poll_video(operation)
operation = await _poll_video(operation, input.model)

video_url = None
if isinstance(operation.output, dict):
Expand All @@ -136,7 +154,7 @@ async def veo_video_generator(input: VideoInput) -> dict[str, str | int | None]:
video_url = media.get('url')

return {
'model': 'googleai/veo-2.0-generate-001',
'model': input.model,
'operation_id': operation.id,
'video_url': video_url,
'duration_seconds': input.duration_seconds,
Expand Down
Loading