qbrixqbrix

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

ScenarioWithout ContextWith Context
Homepage heroSame best image for all usersDifferent image by user segment
Pricing pageOne price for everyonePersonalized pricing by geography/behavior
RecommendationsGlobal best itemPer-user recommendations
Ad placementSame ad for allTargeted 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:

FieldTypeRequiredDescription
idstringNoIdentifies the request source (e.g., user ID, session ID). Used by feature gates for deterministic rollout.
vectorfloat[]For contextual policiesNumeric feature vector. Must match context_dim configured on the experiment.
metadataobjectNoArbitrary 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},
)

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"},
    ),
)
Warning

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

QuestionStochasticContextual
Do different user types prefer different variants?No / Don't knowYes
Do you have user features available at request time?NoYes
How many features?N/A3-10 is ideal
Data volume?Works with little dataNeeds more data to learn per-context patterns
Complexity?Simple — no feature engineeringRequires feature pipeline
Tip

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