Local annotation¶
Annotates a Signal Dataset snapshot on the machine you run RFGen from,
publishing the annotation set beside it. execution_mode: local is the
default; Annotate an existing
dataset is the task page, and this
one covers the run in detail — subsetting, failures, and resume.
Prepare¶
uv pip install -e '.[gemini]' # omitting this fails with BackendUnavailableError
export GEMINI_API_KEY=... # omitting this fails in the provider SDK: ValueError: No API key was provided
The run is synchronous: it publishes the annotation set or reports an error in your shell. No polling, no Spark session, no Java runtime.
Configure¶
To annotate the whole store, omit requests. That is the ordinary case at any
size:
execution_mode: local
annotation_type: caption
template_id: caption.declared.v1
run_id: caption-v1
model: gemini-3.1-flash-lite
backend: local_concurrent
signal_dataset:
dataset_uri: /absolute/path/rfgen-output # the snapshot root holding root.json
Naming records is for annotating a subset on purpose. Sample IDs are 64-character SHA-256 hex strings, so list real ones rather than inventing them:
rfgen inspect /absolute/path/rfgen-output --sample-size 3 --sample-ids
signal_dataset:
dataset_uri: /absolute/path/rfgen-output
requests:
- {sample_id: 9cd73142fc0a..., annotation_type: caption, template_id: caption.declared.v1, run_id: caption-v1}
For a dataset rather than a smoke test, switch the backend, for many records per provider job, roughly half the price:
backend: gemini_batch
For a local model server or a lower-cost hosted endpoint, switch the provider. Both settings below matter: the default 60-second timeout expires while a large model loads, and one GPU serves one request at a time.
backend: local_concurrent
model: gemma4
inference:
provider: openai_compatible
timeout_s: 600
concurrency: 1
endpoints: [{base_url: "http://localhost:11434/v1", model: gemma4}]
Run¶
rfgen annotate submit --config-dir ./annotation-config --config-name local-caption
Exit |
Meaning |
|---|---|
|
|
|
|
|
Stopped before any terminal result; a JSON |
Read it back¶
The run publishes to <dataset_uri>/annotations/sets/<run_id>/. Read it with
the signal_dataset package against the same root:
import signal_dataset as sd
annotations = sd.open("/absolute/path/rfgen-output").annotations["caption-v1"]
print(annotations[0].status, annotations[0].output)
The set is named by your run_id, not by the template, and is dense: one row
per record in the snapshot, carrying success, failed, or skipped.
Resume¶
Re-submit the same run_id. Records that already have a complete row are
skipped without calling the model again:
rfgen annotate submit --config-dir ./annotation-config --config-name local-caption
A row written by a different provider is refused rather than adopted: treating it as this run’s work would turn a retry into an overwrite.
When records fail¶
A complete_with_errors run lists what it could not annotate under
result.failures, each with sample_id, error_code, and a message. That
message is the provider’s own only when the failure came from outside the
inference client; a failure the client raises itself, transport or schema alike,
repeats its code instead. The list stops after 20; terminal_counts always
carries the exact
total, so a wholesale failure is visible even when the sample is truncated.
Two codes account for nearly every local failure:
|
Means |
Do |
|---|---|---|
|
The call returned no usable reply: refused, unreachable, or retries exhausted against rate limiting. The submission is fine. |
Re-run, subject to the publication rule below. Whole run failing: check |
|
The model replied and the reply did not match the template’s output schema after the client’s retries. A model-capability signal, not a transport one. |
Try a more capable |
|
An SDK exception normalized to its gRPC status, lowercased: |
|
|
The catch-all. An exception carrying no gRPC status, or one whose status is outside the standard set, normalizes to |
The code carries no diagnostic information, so read the driver logs rather than the code. It is not retried in-run, so a transient fault reaches you as terminal. Re-run, subject to the publication rule below. |
A published set cannot be repaired¶
Re-running the same run_id does not retry a failed row on the Signal
Dataset route. A published annotation set is immutable, so an unchanged
configuration is the idempotent no-op: it returns the same counts in seconds,
having made no provider call. That is deliberate, and it is the rule stated
under Annotate an existing
dataset.
Same run_id resumes only a killed attempt, whose work was staged and never
published. Once the set exists, the choice is:
Give the new run a new
run_id. It re-annotates every record, including the ones that succeeded, and publishes a second set beside the first.Leave the failures in place and filter them out downstream on
row.status == "success".
There is no way to repair individual rows inside a published set. Fix the cause
first, so the second run does not reproduce the failure: a provider_error
fraction usually means inference.concurrency is too high for the key.
A failed row carries its code in detail_status and its values are empty,
so the code is all you get from the row itself. Find the affected records by
re-opening the published set and filtering on row.status == "failed". Do that
rather than reading result.failures on a remote run: the
dataproc_serverless route reports failures: null and returns counts only.
result.failures is populated on the local route.