Repository navigation
Contextual bandit support - #134
Merged
Merged
Conversation
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains from the previously reported contextual-bandit weight, identifier, or ranges-metadata issues. Reviews (6): Last reviewed commit: "validate rule.tracks bandit metadata wit..." | Re-trigger Greptile |
Contributor
Author
Contributor
Author
Contributor
Author
Contributor
Author
Contributor
Author
4 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Contextual bandit support for both clients (
GrowthBook,GrowthBookClient), at behavioral parity with the JS SDK (source of truth: growthbook/growthbook#6479, payload design #6274).Backward-compatible feature on top of 3.0.0 → minor release (3.1.0).
How it's implemented
All learning is server-side. The server fits a decision tree over context attributes, runs Thompson sampling per leaf, and ships per-leaf weight vectors in a new top-level payload section:
{ "features": { "my-feature": { "rules": [{ "contextualBanditRef": "cb_abc123", "contextualVariations": ["control", "treatment"], "weights": [0.5, 0.5] // server-computed marginal (fallback) weights }]}}, "contextualBandits": { // or encryptedContextualBandits "cb_abc123": { "banditVersion": 7, "contexts": [ { "leafId": 0, "condition": { "country": { "$in": ["US"] } }, "weights": [0.82, 0.18] }, { "leafId": 1, "condition": {}, "weights": [0.31, 0.69] } ]} } }The SDK does no model evaluation:
_build_contextual_bandit_experiment(core.py)contextualBanditRef,conditionmatches via the existingevalConditionengine,No leaf match / empty contexts / errored selection → bucketing keeps the rule's marginal weights and reports the fallback
leafId: -1; a dangling ref runs as a plain experiment with no bandit metadata.Payloads can also be seeded out-of-band with the new
set_payloadon both clients (JSsetPayloadparity, including encrypted sections):Behavioral Changes
Result(and to the result youron_experiment_viewedcallback receives):leafId,variationWeights, andbanditVersion. Log them to your warehouse along with the user attributes — bandit training needs them. They are only set for real hashed exposures, never for forced variations, QA mode, or coverage misses.user_contextpassed to callbacks is a snapshot taken at evaluation time, so what you log is exactly what was used for leaf routing.Perf
contextualBanditRef. The one shared change:getBucketRangesnow normalizes malformed weight vectors (negative, boolean, non-finite, non-numeric, or float-overflowing entries) to equal weights for every experiment, where the JS SDK checks only length and sum. No valid payload is affected (the conformance corpus exercises no such vectors).Test plan
contextualBanditcorpus (35 cases) + feature-section backwards-compat cases green, CB skiplist entries removedtests/test_contextual_bandit.py(31: ingestion in all refresh paths, encryption,set_payloadsemantics, snapshot atomicity, malformed-payload degradation, tracking metadata) and exposure-context plugin tests intests/test_plugins.pyFollow-ups
Experiment Viewedsends onlyexperimentId/variationId/hashAttribute/hashValue);