Add a custom annotator¶
Goal¶
Add a Python annotator class that produces inference-grounded text from
already-labeled Phase 1 records. The shipped extension path is a
MetadataAnnotator subclass with a custom
Templater and optional package entry
point.
When to use this¶
Use this when you need a new prompt family, provider wiring, or packaging unit
around the implemented AnnotationType
values. The current enum values are CAPTION, QA, REASONING,
SCENE_REPORT, and CONTRASTIVE. Adding a new top-level annotation type is a
framework enum change, not a plugin-only change.
If you only need a new task string for an existing annotation type, see
Add a custom annotation template. That path
can pass a Templater directly without declaring a new class.
Prerequisites¶
Read Concepts / Annotations, particularly the Grounding Contract. The annotator may only consume metadata fields present on the StoredRecord. It must not read IQ, spectrograms, or raw samples.
Inference calls go through BaseInferenceClient, which abstracts Gemini, Anthropic, OpenAI, vLLM, and offline test clients behind one interface. Do not instantiate provider SDKs inside an annotator.
Minimal implementation¶
Subclass MetadataAnnotator and declare a stable name. The inherited
annotate(...) method already matches the shipped public contract:
annotate(record: StoredRecord, *, annotation_type: AnnotationType, template_id: str, run_id: str) -> StoredRecord.
from typing import ClassVar
from rfgen.annotators import MetadataAnnotator, Templater
from rfgen.enums import AnnotationType
class EventSummaryAnnotator(MetadataAnnotator):
name: ClassVar[str] = "event_summary"
templater = Templater(
templates={
"scene_report.event_summary.v1": (
"Write a short scene report from the whitelisted FACTS. "
"Summarize emitter count, class names, occupied bandwidth, and "
"SNR only when those facts are present."
)
}
)
annotator = EventSummaryAnnotator.from_components(
client=client,
templater=templater,
allowed_annotation_types=[AnnotationType.SCENE_REPORT],
)
Run it on an IQ-stripped stored record:
from rfgen.enums import AnnotationType
annotated = annotator.annotate(
stored_record,
annotation_type=AnnotationType.SCENE_REPORT,
template_id="scene_report.event_summary.v1",
run_id="event-summary-v1",
)
annotated is a new StoredRecord
whose text field contains the annotation row. The row is keyed by
annotation_type, template_id, and run_id; it includes the validated JSON
output, model id, request id, token usage, optional PAES score, and schema
version.
Register¶
For an in-tree call path, import the class directly and construct it with
from_components(...) or from_config(...).
For a third-party package, declare the entry-point group rfgen.annotators in
your package metadata. The value points at the annotator class.
[project.entry-points."rfgen.annotators"]
event_summary = "my_pkg.annotators:EventSummaryAnnotator"
The package can also declare a top-level PLUGIN: PluginMetadata attribute
with plugin_kind="annotator"; see
Reference / Plugin Metadata / Plugin registration
for the full schema.
Configure¶
The shipped AnnotatorConfig schema
accepts enabled, types, bulk_llm, verifier_llm, and
verifier_subset_pct. Template routing and plugin-specific construction are
passed through Python.
from rfgen.config.annotation import AnnotatorConfig, LLMConfig
from rfgen.enums import AnnotationType
config = AnnotatorConfig(
enabled=True,
types=[AnnotationType.SCENE_REPORT],
bulk_llm=LLMConfig(provider="openai", model="gpt-4o-mini"),
)
annotator = EventSummaryAnnotator.from_config(
config,
bulk_client=client,
templater=templater,
)
Verify¶
Confirm the behavior through public outputs:
annotator.name == "event_summary".Calling
annotate(...)returns aStoredRecord, not a dict.The returned
StoredRecord.text["scene_report"]["scene_report.event_summary.v1"]["event-summary-v1"]row hasannotation_type == "scene_report".The prompt contains only whitelisted metadata facts from the
StoredRecord.Re-running with the same
(annotation_type, template_id, run_id)returns the existing row unchanged.
For plugin discovery, construct an EntryPointRegistry("rfgen.annotators"),
call discover(), then get("event_summary") and instantiate the returned
class through from_config(...) or from_components(...).
Troubleshoot¶
Symptom |
Fix |
|---|---|
Plugin discovery does not find the annotator |
Check the |
Inference response fails to parse |
Use |
Annotator references unwhitelisted fields |
Restrict the template to fields present after |
Re-runs duplicate text outputs |
Reuse the same |
See Also¶
Concepts / Annotations: grounding contract, two-phase generation, append-only text runs.
Add a custom annotation template: pass a custom
Templaterwithout defining a new class.Reference / API /
rfgen.annotators: full BaseAnnotator and BaseInferenceClient contracts.