Argilla datasets
Opt a protobuf message into Argilla codegen with (pgml.argilla).enable = true.
py-gen-ml emits:
- Pydantic models with
Field(description=...)from proto comments build_*_settings()for ArgillaSettings(UI fields + questions)to_*_record/from_*_recordmappers- Optional alias helpers when
(pgml.mapper_config).enableis set
Use message kinds (FEATURE_ROW, LABEL, FEEDBACK, …) to
document the contract. Kind does not replace the Argilla opt-in.
Field vs Question vs Metadata
Every field on an Argilla-enabled message must set
(pgml.argilla_field).slot. Missing slots fail at codegen.
| Slot | Argilla artifact | Record mapping |
|---|---|---|
FIELD |
TextField / ChatField / ImageField / … |
record.fields[name] |
QUESTION |
LabelQuestion / MultiLabelQuestion / RatingQuestion / TextQuestion / … |
suggestions / responses |
METADATA |
(none in Settings) | record.metadata[name] |
Typical pattern for classification HITL:
- FIELD — the text (or multimodal content) annotators read
- QUESTION — the label they assign (
positive/negative, …) - METADATA — ids, provenance (
imdbvssynthetic), timestamps
string id = 1 [(pgml.argilla_field) = { slot: METADATA }];
string text = 2 [(pgml.argilla_field) = {
slot: FIELD; field_type: "text"; required: true
}];
string sentiment = 3 [(pgml.argilla_field) = {
slot: QUESTION; question_type: "label";
labels: ["negative", "positive"]; required: true
}];
ArgillaField knobs
| Field | Applies to | Meaning |
|---|---|---|
slot |
all | FIELD / QUESTION / METADATA (required) |
name |
all | Optional UI/API name (default: proto field name) |
field_type |
FIELD | text (default), chat, image, … |
question_type |
QUESTION | label, multi_label, rating, text, … |
labels |
label questions | Allowed class names |
required |
FIELD / QUESTION | Passed through to Argilla |
Install the extra
The Argilla extra also pins datasets>=3 so imports work with modern pyarrow
(versions that removed PyExtensionType, commonly pulled in with LanceDB).
Annotate
Instruction–response style demo:
syntax = "proto3";
package demo;
import "py_gen_ml/extensions.proto";
// A single instruction–response example for LLM synthesis and Argilla review.
message ReviewExample {
option (pgml.kind) = FEATURE_ROW;
option (pgml.pydantic_ai) = {
enable: true;
};
option (pgml.argilla) = {
enable: true;
dataset_name: "review_examples";
};
option (pgml.mapper_config) = {
enable: true;
};
// Stable identifier stored as Argilla metadata (not a primary UI field).
string id = 1 [(pgml.argilla_field) = {
slot: METADATA;
}];
// User-facing task prompt the model should answer. Clear comments become
// JSON Schema descriptions used by PydanticAI NativeOutput.
string instruction = 2 [(pgml.argilla_field) = {
slot: FIELD;
field_type: "text";
required: true;
}];
// Complete answer or generation for the instruction.
string generation = 3 [(pgml.argilla_field) = {
slot: FIELD;
field_type: "text";
required: true;
}];
// Human quality judgment for the generation (Argilla LabelQuestion).
string quality = 4 [(pgml.argilla_field) = {
slot: QUESTION;
question_type: "label";
labels: ["bad", "good"];
required: true;
}];
}
Sentiment / IMDB flywheel contract:
syntax = "proto3";
package demo;
import "py_gen_ml/extensions.proto";
// Labeled movie-review example for sentiment classification.
// Field comments become JSON Schema descriptions for PydanticAI NativeOutput.
message SentimentExample {
option (pgml.kind) = FEATURE_ROW;
option (pgml.pydantic_ai) = {
enable: true;
};
option (pgml.argilla) = {
enable: true;
dataset_name: "imdb_sentiment";
};
option (pgml.lancedb) = {
enable: true;
table_name: "sentiment_examples";
};
option (pgml.mapper_config) = {
enable: true;
};
// Stable row id (Argilla metadata; not a primary UI field).
string id = 1 [
(pgml.argilla_field) = { slot: METADATA; },
(pgml.lancedb_field) = { merge_key: true }
];
// Provenance of the row: "imdb" for seed reviews, "synthetic" for LLM-generated ones.
string source = 2 [(pgml.argilla_field) = {
slot: METADATA;
}];
// Full movie-review text in the style of IMDB user reviews (may include spoilers,
// informal tone, and mixed praise/criticism). Used as the classifier input.
string text = 3 [(pgml.argilla_field) = {
slot: FIELD;
field_type: "text";
required: true;
}];
// Binary sentiment of the review: "negative" or "positive".
string sentiment = 4 [(pgml.argilla_field) = {
slot: QUESTION;
question_type: "label";
labels: ["negative", "positive"];
required: true;
}];
}
// Hyperparameters for a sentiment flywheel training run.
message SentimentTrainConfig {
option (pgml.kind) = RUN_CONFIG;
option (pgml.cli) = {
enable: true;
};
option (pgml.mlflow) = {
enable: true;
experiment_name: "imdb_sentiment";
run_name_field: "run_name";
};
// Human-readable run name (logged as a tracker tag).
string run_name = 1 [
(pgml.default).string = "sentiment",
(pgml.tracking_field) = { slot: TAG }
];
// Number of synthetic examples requested per diversify round.
int32 synthesize_count = 2 [(pgml.default).int32 = 4];
// Extra synthesis rounds that feed prior outputs back as examples.
int32 diversify_rounds = 3 [(pgml.default).int32 = 1];
// OpenAI / Azure deployment name used for synthesis.
string openai_model = 4 [(pgml.default).string = "gpt-4o"];
// TF-IDF + logistic regression holdout fraction.
float test_size = 5 [(pgml.default).float = 0.25];
}
// Holdout metrics from the sentiment classifier.
message SentimentMetrics {
option (pgml.kind) = METRIC_SET;
option (pgml.mlflow) = {
enable: true;
};
float accuracy = 1;
int32 n_train = 2;
int32 n_test = 3;
int32 n_labeled = 4;
}
// Inference request for the online sentiment classifier.
message SentimentPredictRequest {
option (pgml.kind) = FEATURE_ROW;
// Stable sample id (links prediction and feedback rows).
string id = 1;
// Review text to classify.
string text = 2;
}
// Model output for one scored review (separate from the labeled training row).
message SentimentPrediction {
option (pgml.kind) = PREDICTION;
option (pgml.lancedb) = {
enable: true;
table_name: "sentiment_predictions";
};
// Sample id matching SentimentPredictRequest.id.
string sample_id = 1 [(pgml.lancedb_field) = { merge_key: true }];
// Review text that was scored.
string text = 2;
// Predicted sentiment: "negative" or "positive".
string sentiment = 3;
// Model confidence for the predicted class.
float score = 4;
// Deployed model / pipeline version tag.
string model_version = 5;
}
// Human correction / re-label for a scored review.
message SentimentFeedback {
option (pgml.kind) = FEEDBACK;
option (pgml.argilla) = {
enable: true;
dataset_name: "imdb_sentiment_feedback";
};
option (pgml.lancedb) = {
enable: true;
table_name: "sentiment_feedback";
};
// Sample id matching SentimentPrediction.sample_id.
string sample_id = 1 [
(pgml.argilla_field) = { slot: METADATA; },
(pgml.lancedb_field) = { merge_key: true }
];
// Review text shown to the annotator.
string text = 2 [(pgml.argilla_field) = {
slot: FIELD;
field_type: "text";
required: true;
}];
// Model-predicted sentiment (for comparison; not the Argilla question).
string predicted_sentiment = 3 [(pgml.argilla_field) = {
slot: METADATA;
}];
// Corrected sentiment after review: "negative" or "positive".
// When logging a draft from a prediction, set this to the predicted label
// so Argilla records it as a Suggestion.
string sentiment = 4 [(pgml.argilla_field) = {
slot: QUESTION;
question_type: "label";
labels: ["negative", "positive"];
required: true;
}];
// Provenance: "model" for suggestion drafts, "human" after correction.
string source = 5 [(pgml.argilla_field) = {
slot: METADATA;
}];
}
// LitServe / client settings for SentimentClassifier.
message SentimentServeConfig {
option (pgml.kind) = RUN_CONFIG;
option (pgml.litserve_config) = {
enable: true;
service: "SentimentClassifier";
};
string url = 1 [(pgml.default) = {string: "http://localhost:8000"}];
float timeout_s = 2 [(pgml.default) = {float: 30}];
int32 workers_per_device = 3 [(pgml.default) = {int32: 1}];
string accelerator = 4 [(pgml.default) = {string: "cpu"}];
}
// Online-loop metrics (predictions vs human feedback).
message SentimentOnlineMetrics {
option (pgml.kind) = METRIC_SET;
option (pgml.mlflow) = {
enable: true;
};
int32 n_predictions = 1;
int32 n_feedback = 2;
float agreement_rate = 3;
}
// Unary sentiment inference service (LitServe).
service SentimentClassifier {
option (pgml.litserve) = {
enable: true;
};
rpc Predict(SentimentPredictRequest) returns (SentimentPrediction) {
option (pgml.litserve_method) = {
api_path: "/predict";
};
}
}
| Option | Where | Meaning |
|---|---|---|
(pgml.argilla).enable |
message | Required to emit Argilla helpers. |
(pgml.argilla).dataset_name |
message | Hint returned by *_dataset_name() (default: snake_case message name). |
(pgml.argilla_field).* |
field | Slot + type/labels (see above). |
(pgml.mapper_config).enable |
message | Emit *_ALIASES / *_to_row_dict / *_from_row_dict. |
Generated module
For SentimentExample, enabling the generator writes *_argilla.py with:
SentimentExamplePydantic modelsentiment_example_dataset_name()— e.g."imdb_sentiment"build_sentiment_example_settings(*, client=None)to_sentiment_example_record/from_sentiment_example_record- Optional mapper aliases when configured
Offline Settings construction
Argilla 2.x field/question constructors may try to resolve a default client. Pass an explicit client (or a test double) to avoid requiring API credentials at Settings build time:
from unittest.mock import MagicMock
from pgml_out.sentiment_demo_argilla import build_sentiment_example_settings
settings = build_sentiment_example_settings(client=MagicMock())
assert {f.name for f in settings.fields} == {"text"}
assert {q.name for q in settings.questions} == {"sentiment"}
Records and suggestions
to_*_record places FIELD values in record.fields, METADATA in
record.metadata, and non-null QUESTION values as Suggestions (model or
weak labels for annotators to confirm). After humans respond in the Argilla UI,
map responses back with from_*_record (best-effort for suggestions) or your
own response parsing before training.
Suggested flow for classification:
- Synthesize or load seeds → full models
to_*_record→ log to Argilla (labels as suggestions)- Humans submit responses
- Export / fetch records → prefer responses over suggestions for train labels
- Validate back into the same Pydantic / Lance models
Pushing to a live Argilla server
import argilla as rg
from pgml_out.sentiment_demo_argilla import (
build_sentiment_example_settings,
sentiment_example_dataset_name,
to_sentiment_example_record,
)
client = rg.Argilla() # uses ARGILLA_API_URL / ARGILLA_API_KEY
settings = build_sentiment_example_settings(client=client)
dataset = rg.Dataset(
name=sentiment_example_dataset_name(),
settings=settings,
client=client,
)
dataset.create()
dataset.records.log([to_sentiment_example_record(row) for row in rows])
Or via the bridge:
from py_gen_ml.bridges import synthetic_rows_to_argilla_records
records = synthetic_rows_to_argilla_records(rows, to_record=to_sentiment_example_record)
dataset.records.log(records)
Keep dataset create/log in your module. Do not edit the generated *_argilla.py.
HITL tips
- Put free-text the annotator must read in
FIELD, never only in metadata. - Keep class names in
labels:stable—changing them mid-project breaks the UI. - Use
METADATAfor provenance (source=imdb|synthetic) so you can filter training sets later. - For end-to-end sentiment + training wiring, see the Sentiment flywheel example.