Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 42 additions & 77 deletions docs/sdks/client-sdks/javascript/bandits.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,39 +11,42 @@ import ApiOptionRef from '@site/src/components/ApiOptionRef';

Eppo also supports contextual multi-armed bandits. You can read more about them in the [high-level documentation](/contextual-bandits).
Bandit flag configuration--including setting up the flag key, status quo variation, bandit variation, and targeting rules--are configured within
the Eppo application. However, available actions are supplied to the SDK in the code when querying the bandit.
the Eppo application.

To leverage bandits using the JavaScript Client SDK, there are two additional steps over regular feature flags:
1. Add a bandit action logger to the SDK client instance
2. Query the bandit for an action
2. Provide the SDK the available actions for the bandit

### Define a bandit assignment logger
Bandits can only be used with [precomputed assignments](../precomputed-assignments).

In order for the bandit to learn an optimized policy, we need to capture and log the bandit's actions.
This requires defining a bandit logger in addition to an assignment logger.
```ts
// Import Eppo's logger interfaces and client initializer
import { IAssignmentLogger, IBanditLogger, init } from "@eppo/js-client-sdk";

// Define an assignment logger for recording variation assignments
const assignmentLogger: IAssignmentLogger = {
logAssignment(assignment: IAssignmentEvent) {
console.log("TODO: save assignment information to data warehouse", assignment);
}
};

// Define a bandit logger for recording bandit action assignments
const banditLogger: IBanditLogger = {
logBanditAction (banditEvent: IBanditEvent) {
console.log("TODO: save bandit action information to the data warehouse, ensuring the column names are as expected", banditEvent);
}
};

// Initialize the SDK with both loggers provided
await init({
apiKey: "<SDK_KEY>",
assignmentLogger,
banditLogger
await EppoSdk.precomputedInit({
apiKey: 'YOUR_SDK_KEY',
assignmentLogger: {
logAssignment: (assignmentEvent) => {
/* send to warehouse */
}
},
banditLogger: {
logBanditAction: (banditEvent) => {
/* send to warehouse */
}
},
precompute: {
subjectKey: 'test-subject',
subjectAttributes: { attr1: 'value1' },
banditActions: {
'bandit1-key': {
'action1-key': {
numericAttributes: { subjectActionNumericAttr1: 12345 },
categoricalAttributes: { subjectActionCategoricalAttr1: 'value1' }
},
'action2-key': { numericAttributes: {}, categoricalAttributes: {} },
'action3-key': { numericAttributes: {}, categoricalAttributes: {} },
'action4-key': { numericAttributes: {}, categoricalAttributes: {} }
}
}
}
});
```

Expand All @@ -61,57 +64,22 @@ The SDK will invoke the `logBanditAction()` function with an `IBanditEvent` obje
| `actionNumericAttributes` (Attributes) | Metadata about numeric attributes of the assigned action. Map of the name of attributes their provided values | `{"brandAffinity": 0.2}` |
| `actionCategoricalAttributes` (Attributes) | Metadata about non-numeric attributes of the assigned action. Map of the name of attributes their provided values | `{"previouslyPurchased": false}` |
| `actionProbability` (number) | The weight between 0 and 1 the bandit valued the assigned action | 0.25 |
| `optimalityGap` (number) | The difference between the score of the selected action and the highest-scored action | 456 |
| `optimalityGap` (number) | The difference between the score of the selected action and the highest-scored action | 456 |
| `modelVersion` (string) | Unique identifier for the version (iteration) of the bandit parameters used to determine the action probability | "v123" |
| `metaData` Record\<string, unknown\> | Any additional freeform meta data, such as the version of the SDK | `{ "sdkLibVersion": "3.5.1" }` |
| `metaData` Record\<string, unknown\> | Any additional freeform meta data, such as the version of the SDK | `{ "sdkLibVersion": "3.5.1" }` |

### Querying the bandit for an action

To query the bandit for an action, you can use the `getBanditAction()` function. This function takes the following parameters:
- `flagKey` (string): The key of the feature flag corresponding to the bandit
- `subjectKey` (string): The key of the subject or user assigned to the experiment variation
- `subjectAttributes` (Attributes | ContextAttributes): The context of the subject
- `actions` (string\[\] | Record\<string, Attributes | ContextAttributes\>): Available actions, optionally mapped to their respective contexts
- `defaultValue` (string): The default *variation* to return if the flag is not successfully evaluated

:::note
The `ContextAttributes` type represents attributes which have already been explicitly bucketed into categorical and numeric attributes (`{ numericAttributes: Attributes,
categoricalAttributes: Attributes }`). There is more detail on this in the Subject Context section.
:::

The following code queries the bandit for an action:

```ts
import { getInstance as getEppoSdkInstance } from "@eppo/js-client-sdk";
import { Attributes, BanditActions } from "@eppo/js-client-sdk-common";

const flagKey = "shoe-bandit";
const subjectKey = "user123";
const subjectAttributes: Attributes = { age: 25, country: "GB" };
const defaultValue = "default";
const actions: BanditActions = {
nike: {
numericAttributes: { brandAffinity: 2.3 },
categoricalAttributes: { imageAspectRatio: "16:9" }
},
adidas: {
numericAttributes: { brandAffinity: 0.2 },
categoricalAttributes: { imageAspectRatio: "16:9" }
}
};
const { variation, action } = getEppoSdkInstance().getBanditAction(
flagKey,
subjectKey,
subjectAttributes,
actions,
defaultValue
);

if (action) {
renderShoeAd(action);
} else {
renderDefaultShoeAd();
}
const banditResult = EppoSdk.getPrecomputedInstance().getBanditAction('bandit1-key', 'bandit1-default');
const variant = banditResult.variant;
const action = banditResult.action;
```

#### Subject context
Expand All @@ -122,7 +90,7 @@ For example, the subject's age or country.
The subject context can be provided as `Attributes`, which will then assume anything that is number is a numeric
attribute, and everything else is a categorical attribute.

You can also explicitly bucket the attribute types by providing the context as `ContextAttributes`. For example, you may have an attribute named `priority`, with
You can also explicitly bucket the attribute types by providing the context as `ContextAttributes`. For example, you may have an attribute named `priority`, with
possible values `0`, `1`, and `2` that you want to be treated categorically rather than numeric. `ContextAttributes` have two nested sets of attributes:
- `numericAttributes` (Attributes): A mapping of attribute names to their numeric values (e.g., `age: 30`)
- `categoricalAttributes` (Attributes): A mapping of attribute names to their categorical values (e.g., `country`)
Expand All @@ -138,23 +106,20 @@ just like with non-bandit assignment methods.

#### Action contexts

The action context contains contextual information about each action. They can be provided as a mapping of attribute names
to their contexts.
The action context contains contextual information about each action. They can be provided as a mapping of attribute names
to their contexts.

Similar to subject context, action contexts can be provided as `Attributes`--which will then assume anything that is number is a numeric
attribute, and everything else is a categorical attribute--or as `ContextAttributes`, which have explicit bucketing into `numericAttributes`
Action contexts must be provided as `ContextAttributes`, which have explicit bucketing into `numericAttributes`
and `categoricalAttributes`.

Note that relevant action contexts are subject-action interactions. For example, there could be a "brand-affinity" model
that computes brand affinities of users to brands, and scores of that model can be added to the action context to provide
additional context for the bandit.

If there is no action context, an array of strings comprising only the actions names can also be passed in.

If the subject is assigned to the variation associated with the bandit, the bandit selects one of the supplied actions.
All actions supplied are considered to be valid. If an action should not be available to a subject, do not include it for that call.
All actions supplied are considered to be valid. If an action should not be available to a subject, do not include it in initialization.

Like attributes, actions are case-sensitive.
Like attributes, actions and their attributes are case-sensitive.

#### Result

Expand Down Expand Up @@ -187,7 +152,7 @@ When you create an analysis allocation for the bandit and the returned `action`

## Debugging

You may encounter a situation where a flag assignment produces a value that you did not expect. There are functions [detailed here](/sdks/sdk-features/debugging-flag-assignment/) to help you understand how flags are assigned, which will allow you to take corrective action on potential configuration issues.
You may encounter a situation where a flag assignment produces a value that you did not expect. There are functions [detailed here](/sdks/sdk-features/debugging-flag-assignment/) to help you understand how flags are assigned, which will allow you to take corrective action on potential configuration issues.