Writing a provider
Every AI-ish stage is a kind with named providers. Which one runs is a string in
config.toml, resolved through the registry in video_review_os/providers.py. No stage
names a vendor, and neither should yours.
Kinds: transcription, diarization, copy, tagging, storyboard, perception.
video-review-os providers # what is selectable right now
video-review-os providers --json # the same, machine-readable
The contract
Two rules, both non-negotiable:
- Never raise out of your public method. Catch everything and return the kind's deterministic result instead, with the failure recorded in the artifact. A provider that throws takes down someone's overnight batch.
- Never widen the schema. Sanitize your output to the known enums before returning it. The pipeline treats provider output as untrusted input, because it is.
Registering
Write a module with a register_providers() function:
from video_review_os import providers
from video_review_os.perception import PROVIDER_KIND, PerceptionProvider
class MyPerception(PerceptionProvider):
name = "my-perception"
def __init__(self, config): # receives the [perception] config section
self.config = config
def perceive(self, clips, context, config):
try:
enriched = my_model(clips)
except Exception:
return {"provider": "fallback", "status": "fallback", "clips": clips,
"errors": ["my-perception failed; used the deterministic reading."]}
return {"provider": self.name, "status": "ok", "clips": enriched}
def register_providers() -> None:
providers.register(PROVIDER_KIND, "my-perception", MyPerception,
summary="What this does", source="plugin-module")
Then either list it in config:
[plugins]
modules = ["my_studio.perception"]
…or ship it as an installed package advertising a video_review_os.providers entry point
whose value is a zero-argument callable:
[project.entry-points."video_review_os.providers"]
my-studio = "my_studio.perception:register_providers"
Select it like any builtin:
[perception]
provider = "my-perception"
Sanitizing perception output
Reuse the shipped sanitizers rather than writing your own — they are what keep an unknown enum out of the artifact:
from video_review_os.perception import sanitize_visual, sanitize_reframe, taxonomy
visual = sanitize_visual(model_answer, clip["visual"], config.perception)
reframe = sanitize_reframe(model_answer.get("reframe"), clip["reframe"])
taxonomy(config.perception) gives the exact vocabulary a model is allowed to answer in —
send it in your prompt so the model has a chance of complying, and sanitize anyway.
Debugging
- Plugin import failures are collected, not raised:
video-review-os providersprints them at the bottom, andproviders.load_errors()returns them. - An unknown provider name silently resolves to the kind's fallback. If your provider
"isn't running", check the spelling against
providers --jsonfirst. - Providers load builtins first, plugins second, so a plugin can deliberately replace a builtin name — check you have not done so by accident.