docs/getting-started/concepts/label-view.md
A label view is a Feast primitive for storing judgments about entities — reward signals, safety scores, human reviews, ground-truth answers — separately from the immutable observations stored in feature views.
| Feature View | Label View | |
|---|---|---|
| Stores | What was observed | What was judged |
| Example | Agent prompt and response | response_quality: "poor", is_safe: 0 |
| Writers | Usually one source | Multiple labelers (human, LLM judge, code) |
| Changes over time | Append-only | Updated by new label writes |
Use a label view when you need governed, training-ready labels that multiple sources can write, disagree on, and resolve — not when you need append-only feature data.
Before using label views, you need:
EntityPushSource (or batch DataSource) for label ingestionfeast apply run after defining label viewsSeparate features from judgments. Mixing reward labels into feature views blurs two different lifecycles — observations are append-only; labels are overwritten, corrected, and debated.
Support multiple labelers. Human reviewers, safety scanners, and LLM judges can all label the same entity. Feast tracks who wrote what via labeler_field and resolves conflicts via ConflictPolicy.
Generate training datasets. Compose label views with feature views in a FeatureService and retrieve features + labels together with point-in-time correctness.
Data label in the UI. Configure data labeling profiles (annotation profiles) so data scientists can label data directly in the Feast UI — entity forms, document spans, bulk review, or active learning.
Not all labels serve the same purpose. Feast distinguishes two common types — both stored in a label view, modeled with field names and tags:
| Feedback | Expectation | |
|---|---|---|
| Question | How good was the actual output? | What is the correct answer? |
| Example | response_quality: "poor", relevance: "irrelevant" | ground_truth: "relevant", is_default: 1 |
| Typical writers | Human, LLM judge, automated code | Human experts (gold standard) |
| Training use | Reward signal, quality filter, active-learning queue | Supervised target column |
| Conflict handling | Common — use ConflictPolicy | Rare — usually one authoritative source |
Feast does not require separate primitives for feedback and expectations. Model them with field names and tags:
tags={
"feast.io/field-role:response_quality": "feedback", # judgment about output
"feast.io/field-role:ground_truth": "expectation", # correct answer
}
Practical pattern:
ConflictPolicy, fields like response_quality, safety_score, relevanceground_truth, expected_answer, is_defaultrelevance = feedback, ground_truth = expectation)Label views accept labels from any source. Track the writer in labeler_field:
| Source | labeler example | Typical role |
|---|---|---|
| Human reviewer | [email protected] | Feedback or expectation |
| LLM judge | gpt-4-evaluator | Feedback (quality scores) |
| Automated scanner | nemo-guardrails | Feedback (safety signals) |
| Batch import | risk-ops-team | Expectation (historical outcomes) |
labels_df = pd.DataFrame({
"user_id": ["user-001"],
"response_quality": ["poor"],
"is_safe": [0],
"labeler": ["[email protected]"],
"event_timestamp": [pd.Timestamp.now()],
})
store.push("agent_feedback_labels_push_source", labels_df)
| Use a FeatureView when… | Use a LabelView when… |
|---|---|
| Data is observational and append-only | Data is a judgment or data label (annotation) |
| One source writes the data | Multiple labelers may disagree |
| No conflict resolution needed | You need governed conflict resolution |
| No labeling UI needed | You want structured data labeling workflows (annotation) |
from datetime import timedelta
from feast import Entity, FeatureService, Field, FileSource, PushSource
from feast.labeling import ConflictPolicy, LabelView
from feast.types import Float32, Int64, String
user = Entity(name="user_id", join_keys=["user_id"])
agent_feedback_labels = LabelView(
name="agent_feedback_labels",
entities=[user],
schema=[
Field(name="response_quality", dtype=String),
Field(name="is_safe", dtype=Int64),
Field(name="reviewer_notes", dtype=String),
Field(name="labeler", dtype=String),
],
source=PushSource(
name="agent_feedback_labels_push_source",
batch_source=FileSource(
name="agent_feedback_labels_batch",
path="data/agent_feedback.parquet",
timestamp_field="event_timestamp",
),
),
labeler_field="labeler",
conflict_policy=ConflictPolicy.LAST_WRITE_WINS,
reference_feature_view="user_profile",
description="Human and automated feedback on agent responses.",
tags={
"feast.io/labeling-method": "entity-form",
"feast.io/field-role:response_quality": "feedback",
"feast.io/field-role:is_safe": "feedback",
"feast.io/field-role:reviewer_notes": "metadata",
"feast.io/label-values:response_quality": "excellent,good,acceptable,poor,harmful",
"feast.io/label-values:is_safe": "1,0",
"feast.io/label-widget:response_quality": "enum",
"feast.io/label-widget:is_safe": "binary",
"feast.io/label-widget:reviewer_notes": "text",
},
)
Run feast apply to register the label view.
Labels are written with FeatureStore.push():
import pandas as pd
from feast import FeatureStore
store = FeatureStore(repo_path="feature_repo/")
labels_df = pd.DataFrame({
"user_id": ["user-001", "user-002"],
"response_quality": ["good", "harmful"],
"is_safe": [1, 0],
"reviewer_notes": ["Accurate summary", "Unsafe medical advice"],
"labeler": ["human-reviewer", "nemo-guardrails"],
"event_timestamp": pd.to_datetime(["2025-01-15", "2025-01-15"]),
})
store.push("agent_feedback_labels_push_source", labels_df)
Each push appends to the offline store (full history retained) and updates the online store (latest value per key).
Open the label view in the Feast UI Data Labeling tab (Annotate tab). The UI reads data labeling tags (annotation tags) and shows the right workflow:
PushSourcetraining_service = FeatureService(
name="agent_training_service",
features=[
user_profile_fv, # immutable features
agent_feedback_labels, # mutable labels
],
)
training_df = store.get_historical_features(
entity_df=entities_df,
features=training_service,
).to_df()
Training pipelines get features and resolved labels in one retrieval call.
Data labeling profiles (annotation profiles) configure how labels are created in the UI. Set them via tags — no schema changes required.
| Profile | Best for | UI experience |
|---|---|---|
entity-form | RLHF, safety review, per-entity feedback | Form — one entity at a time |
document-span | RAG chunk labeling, span labeling (annotation) | Load document, label chunks |
table | Bulk review, correcting existing labels | Editable table with dropdowns |
active-learning | Label high-value unlabeled entities | Queue from a reference feature view |
Answer one question:
entity-formdocument-spantableactive-learning (requires reference_feature_view)The Data Labeling tab (Annotate tab) shows only relevant data labeling methods (annotation methods) per profile:
| Profile | Methods shown |
|---|---|
document-span | Document Span, Review & Edit |
entity-form | Entity Form, Review & Edit, Active Learning |
table | Review & Edit, Active Learning, Entity Form |
active-learning | Active Learning, Entity Form, Review & Edit |
| Tag | Purpose | Example values |
|---|---|---|
feast.io/labeling-method | Primary data labeling method (annotation method) | entity-form, document-span, table |
feast.io/field-role:<field> | Semantic role of a field | feedback, expectation, label, metadata, content, span_start, span_end |
feast.io/label-values:<field> | Allowed label values | relevant,irrelevant |
feast.io/label-widget:<field> | Input widget type | enum, binary, text, number |
Feedback from human reviewers and automated safety layers on agent responses.
agent_feedback_labels = LabelView(
name="agent_feedback_labels",
entities=[user],
schema=[
Field(name="response_quality", dtype=String),
Field(name="is_safe", dtype=Int64),
Field(name="reviewer_notes", dtype=String),
Field(name="labeler", dtype=String),
],
source=feedback_push_source,
labeler_field="labeler",
conflict_policy=ConflictPolicy.LAST_WRITE_WINS,
reference_feature_view="user_profile",
tags={"feast.io/labeling-method": "entity-form", ...},
)
One view can hold both a retrieval judgment and ground truth:
rag_chunk_labels = LabelView(
name="rag_chunk_labels",
entities=[chunk],
schema=[
Field(name="source_document", dtype=String),
Field(name="chunk_text", dtype=String),
Field(name="relevance", dtype=String), # feedback
Field(name="ground_truth", dtype=String), # expectation
Field(name="chunk_start", dtype=Int64),
Field(name="chunk_end", dtype=Int64),
Field(name="labeler", dtype=String),
],
source=rag_push_source,
labeler_field="labeler",
conflict_policy=ConflictPolicy.MAJORITY_VOTE,
tags={
"feast.io/labeling-method": "document-span",
"feast.io/field-role:relevance": "feedback",
"feast.io/field-role:ground_truth": "expectation",
"feast.io/field-role:chunk_text": "content",
"feast.io/label-values:relevance": "relevant,irrelevant",
},
)
Pre-existing label tables loaded via feast materialize:
loan_default_labels = LabelView(
name="loan_default_labels",
entities=[loan],
schema=[
Field(name="is_default", dtype=Int64),
Field(name="delinquency_days", dtype=Int64),
Field(name="loss_severity", dtype=Float32),
Field(name="labeler", dtype=String),
],
source=PushSource(
name="loan_default_labels_push_source",
batch_source=FileSource(
path="data/loan_defaults.parquet",
timestamp_field="event_timestamp",
),
),
labeler_field="labeler",
conflict_policy=ConflictPolicy.LABELER_PRIORITY,
tags={
"feast.io/labeling-method": "table",
"feast.io/field-role:is_default": "expectation",
},
)
When multiple labelers write different values for the same entity, ConflictPolicy picks one value for offline store reads (training, UI browse):
| Policy | When to use |
|---|---|
LAST_WRITE_WINS | Default. Most recent write wins. |
LABELER_PRIORITY | Trusted labelers override others (e.g. human over LLM judge). |
MAJORITY_VOTE | Consensus labeling (e.g. multiple labelers on RAG chunks). |
{% hint style="info" %} Conflict policies apply to the offline store (training). The online store always uses last-write-wins. Full label history is always retained in the offline store. {% endhint %}
Name fields by intent. Use response_quality (feedback) and ground_truth (expectation) — not generic score or label.
Tag field roles. Set feast.io/field-role:<field> to feedback or expectation so your team and UI know what each field means.
Match conflict policy to label type. Use LABELER_PRIORITY when humans correct automated judges. Use MAJORITY_VOTE for multi-labeler consensus. Use LAST_WRITE_WINS for simple feedback streams.
Link to features. Set reference_feature_view so the UI and documentation show which feature view the labels apply to.
Separate noisy feedback from stable ground truth. When possible, put expectations in dedicated fields or views with stricter writer conventions (human-only).
LABELER_PRIORITY requires explicit labeler ordering configuration.