Contexts
Contexts let you pass per-request information to qbrix so that contextual policies (LinUCB, LinTS) can personalize selections. Instead of finding a single best arm for everyone, contextual bandits learn which arm works best for each type of request.
When to Use Contexts
| Scenario | Without Context | With Context |
|---|---|---|
| Homepage hero | Same best image for all users | Different image by user segment |
| Pricing page | One price for everyone | Personalized pricing by geography/behavior |
| Recommendations | Global best item | Per-user recommendations |
| Ad placement | Same ad for all | Targeted ads by user features |
Use contexts when different users (or different situations) should see different variants. If you just want to find the single best variant overall, stochastic policies without context are simpler and sufficient.
Context Object
A context has three fields:
| Field | Type | Required | Description |
|---|---|---|---|
id | string | No | Identifies the request source (e.g., user ID, session ID). Used by feature gates for deterministic rollout. |
vector | float[] | For contextual policies | Numeric feature vector. Must match context_dim configured on the experiment. |
metadata | object | No | Arbitrary key-value pairs. Used by feature gate rules for targeting. |
Using Context with Contextual Policies
Contextual policies (LinUCB, LinTS) require a vector field — a numeric array of features that describe the request.
Step 1: Create an experiment with context_dim
experiment = client.experiment.create(
name="personalized-hero",
pool_id="<pool-id>",
policy="LinUCBPolicy",
policy_params={"alpha": 1.0, "context_dim": 4},
)curl -X POST $QBRIX_URL/api/v1/experiments \
-H "X-API-Key: $QBRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "personalized-hero",
"pool_id": "<pool-id>",
"policy": "LinUCBPolicy",
"policy_params": {"alpha": 1.0, "context_dim": 4}
}'Step 2: Pass context vector on each selection
from qbrix import Context
result = client.agent.select(
experiment_id=experiment.id,
context=Context(
id="user-42",
vector=[0.8, 0.2, 1.0, 0.0],
metadata={"country": "US", "device": "mobile"},
),
)curl -X POST $QBRIX_URL/api/v1/agent/select \
-H "X-API-Key: $QBRIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"experiment_id": "<experiment-id>",
"context": {
"id": "user-42",
"vector": [0.8, 0.2, 1.0, 0.0],
"metadata": {"country": "US", "device": "mobile"}
}
}'The vector length must exactly match context_dim. A mismatch will return an error.
Designing Context Vectors
The context vector is the numeric representation of what you know about the current request. The policy uses this to learn which arms perform well for which contexts.
What to include
Good features are ones that correlate with arm preference:
# e-commerce: user behavior features
vector = [
days_since_signup, # 0: tenure
avg_order_value / 100, # 1: spending level (normalized)
sessions_last_7d / 10, # 2: activity (normalized)
is_mobile, # 3: device type (0 or 1)
]
# content: user interest features
vector = [
tech_affinity, # 0: interest score 0-1
design_affinity, # 1: interest score 0-1
business_affinity, # 2: interest score 0-1
experience_years / 20, # 3: seniority (normalized)
]Best practices for context vectors
Normalize features to similar scales (e.g., 0-1 or -1 to 1). Large differences in scale can cause the policy to overweight certain features.
# good: normalized
vector = [age / 100, income / 200000, clicks / 50]
# bad: raw values with wildly different scales
vector = [25, 85000, 12]Keep dimensions small. LinUCB and LinTS learn a matrix per arm of size context_dim x context_dim. More dimensions means slower learning. Start with 3-10 features and expand if needed.
Be consistent. Every selection request for the same experiment must use the same feature encoding. Feature 0 should always mean the same thing.
Avoid sparse features. If a feature is missing for most requests, it adds noise without value. Either fill with a default or remove it.
Context ID
The id field identifies the request source. It's optional but useful for:
- Feature gates: Rollout percentage uses a hash of the context ID for deterministic assignment. The same user always gets the same gate decision.
- Debugging: Trace which user saw which arm.
- Deduplication: Identify repeated requests from the same source.
# use a stable user identifier
context = Context(id="user-42")
# or a session ID if users aren't authenticated
context = Context(id="session-abc123")Context Metadata
Metadata is a free-form JSON object for feature gate targeting. It's not used by the bandit policy itself — only by the rules engine.
context = Context(
id="user-42",
vector=[0.8, 0.2, 1.0, 0.0],
metadata={
"country": "US",
"plan": "pro",
"device": "mobile",
},
)Feature gate rules can then target based on these fields:
{
"key": "country",
"operator": "in",
"value": ["US", "GB"],
"arm_id": "<arm-id>"
}See the Features page for the full rules engine reference.
Using Context with Stochastic Policies
Stochastic policies (BetaTS, UCB1Tuned, etc.) ignore the context vector — they don't use per-request features. However, you can still pass id and metadata for feature gate functionality:
# BetaTSPolicy ignores the vector, but gates can use id + metadata
result = client.agent.select(
experiment_id="<experiment-id>",
context={"id": "user-42", "metadata": {"country": "US"}},
)Choosing Between Stochastic and Contextual
| Question | Stochastic | Contextual |
|---|---|---|
| Do different user types prefer different variants? | No / Don't know | Yes |
| Do you have user features available at request time? | No | Yes |
| How many features? | N/A | 3-10 is ideal |
| Data volume? | Works with little data | Needs more data to learn per-context patterns |
| Complexity? | Simple — no feature engineering | Requires feature pipeline |
Start with a stochastic policy (BetaTSPolicy) to validate your setup. Once you confirm the feedback loop works, switch to a contextual policy to unlock personalization.
What's Next
- Policies — detailed reference for LinUCB, LinTS, and all 12 algorithms
- Pools & Experiments — the data model
- Feedback & Rewards — how the learning loop works