Annotate an existing dataset¶
RFGen has one annotation lifecycle: rfgen annotate submit. It selects a
validated source capability rather than exposing a separate direct-annotator
command.
The annotation template¶
One product ships: scene_report.declared.v1, served by the reference
annotator at rfgen.annotation.reference.scene_report:SceneReportAnnotator. A
run names its annotator by import path, so a new domain ships its own without
editing anything here. Its evidence is built
from generator-declared metadata (see declared-metadata annotation
evidence): the annotation
path reads stored record metadata only, never I/Q, and a scene with N declared
emitters is annotated as exactly N components, up to the configured
component_cap. An annotator_options: {...} block is optional and defaults
to the full registered field set; set it only to narrow what is surfaced. The
provider receives the sanitized JSON evidence report, never raw I/Q or a
spectrogram.
rfgen generate writes a Signal Dataset snapshot, and one field names it, on
disk or in a bucket, because a snapshot is addressed by path alone and the
reader never lists a directory:
dataset_uri: /abs/path/rfgen-output # or gs://bucket/prefix
A gs:// snapshot needs the GCS extra and application-default credentials on
whatever machine reads it, including the local_concurrent backend, which
happily annotates a snapshot in a bucket from your laptop:
uv pip install -e '.[gemini,gcs]'
gcloud auth application-default login
A complete configuration¶
Copy this to annotation-config/caption.yaml and run it. Every field it omits
has a default that works:
dataset_uri: /absolute/path/rfgen-output
annotation_set: scene_reports
publication_id: scene_reports_v1
template_id: scene_report.declared.v1
annotator: rfgen.annotation.reference.scene_report:SceneReportAnnotator
run_id: caption-v1
model: gemini-3.5-flash-lite
inference:
backend: local_concurrent
provider: gemini
concurrency: 4
uv pip install -e '.[gemini]'
export GEMINI_API_KEY=...
rfgen annotate submit --config-dir ./annotation-config --config-name caption
Point dataset_uri at a gs:// root to annotate a snapshot in a bucket;
nothing else in the file changes.
That run publishes a dense annotation set beside the snapshot, never rewriting a signal shard. Every record gets a row:
Row status |
Means |
|---|---|
|
Annotated; the row carries the result |
|
Inference or validation failed for that record |
|
The annotator found nothing it could truthfully describe |
A skipped row is not a failure. An annotator raises RecordNotAnnotatable
for a record with nothing to say – a quiet epoch, an empty dwell – and that
record is never sent to a provider. Filing it as an error would make a real
outage and an honest silence indistinguishable.
Nothing is visible to a reader until the whole run publishes. There is no resume: a killed run is rerun, and publishing the same content under an existing publication id is refused rather than overwritten.
Prompt assembly reads declared metadata only. That is convention rather than an enforced boundary – an annotator is handed the record – and no shipped path reads a waveform.
A failed row’s detail_status carries the failure code for that record; see
When records fail.
inference.backend selects where the run executes: local_concurrent calls
the provider directly from this process, and vertex_batch stages the whole run
as one batch. Both assemble byte-identical requests, so the cheap one is
evidence about the expensive one.
Read the annotation set back¶
A set is named by its annotation_set, not by the run_id and not by the
template. signal_dataset.open takes the dataset root either way, on disk or in
a bucket, so replace the path with gs://my-bucket/rfgen-output to read a
snapshot a remote run annotated. A gs:// root needs application-default
credentials, whether the annotation ran locally or on Dataproc:
gcloud auth application-default login
import signal_dataset
dataset = signal_dataset.open("/absolute/path/rfgen-output")
print(list(dataset.annotations)) # every published set, by annotation_set
rows = dataset.annotations["scene_reports"]
row = rows[0]
print(row.source_record_id, row.status, row.detail_status)
print(row.values["output"]["expert_summary"]) # the prose, unverified
print(row.values["claims"]) # the claims, exact
print(row.provenance["prose_verification"]) # always "not_performed"
row.values carries exactly two keys, and they come from different places.
output is what the provider returned, verbatim and unchecked. claims is
projected from the same evidence the prompt was built from, in the annotating
process, before the request was sent — so nothing in it was authored by a model.
The evidence report itself is not on the row. claims is a deliberately
lossy projection of it: the bookkeeping saying how the generator knew each fact
is dropped, because a model deployed on a live capture has no such generator to
name. If you need the evidence, rebuild it from the record.
A caption may contradict the claims beside it, and nothing detects that. See what a row contains.
A failed row has empty values and a detail_status naming the failure. A
skipped row means the annotator found nothing it could truthfully describe.
rfgen inspect --annotations reads the retained fixed-IQ overlay only and does
not read an annotation set. See Signal Dataset annotation
sets
for the layout.
Re-running¶
There is no resume and no staged-work reuse. rfgen annotate submit is
synchronous and prepares the whole corpus before it sends anything, so a killed
run is simply run again. wait, cancel and resume do not exist; submit is
the only annotation subcommand.
Once a run has published, its publication_id is taken. Publishing different
content under that id is refused rather than overwritten, because a published
set is immutable and cannot be repaired in place — give the new configuration a
new publication_id. Publishing identical content under it again is the
idempotent no-op.
A vertex_batch run is driver-only: this process renders every prompt, stages
them as one batch, and collects the result. The remote side runs no rfgen code,
which is why model eligibility there is a question about the batch service
rather than about this repository — see
the Vertex Batch guide.