-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add bandits configuration to offline bootstrap in precomputed client … #195
Conversation
if (options.requestParameters) { | ||
// Online-mode | ||
this.requestParameters = options.requestParameters; | ||
} else { | ||
// Offline-mode | ||
|
||
// Offline mode depends on pre-populated IConfigurationStores (flags and bandits) to source configuration. | ||
if (!this.precomputedFlagStore.isInitialized()) { | ||
logger.error( | ||
'[Eppo SDK] EppoPrecomputedClient requires an initialized precomputedFlagStore if requestParameters are not provided', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use logger constant [Eppo SDK]
const promises: Promise<void>[] = []; | ||
promises.push( | ||
hydrateConfigurationStore(this.precomputedFlagStore, { | ||
entries: precomputedResponse.flags, | ||
environment: precomputedResponse.environment ?? { name: UNKNOWN_ENVIRONMENT_NAME }, | ||
createdAt: precomputedResponse.createdAt, | ||
format: precomputedResponse.format, | ||
salt: precomputedResponse.salt, | ||
}), | ||
); | ||
if (this.precomputedBanditsStore) { | ||
promises.push( | ||
hydrateConfigurationStore(this.precomputedBanditsStore, { | ||
entries: precomputedResponse.bandits, | ||
environment: precomputedResponse.environment ?? { name: UNKNOWN_ENVIRONMENT_NAME }, | ||
createdAt: precomputedResponse.createdAt, | ||
format: precomputedResponse.format, | ||
salt: precomputedResponse.salt, | ||
}), | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when a bandit store is present - hydrate it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirming both stores use the same salt
js-sdk-common/src/configuration.ts
Lines 139 to 142 in 8a51588
this.salt = salt; | |
this.bandits = obfuscatePrecomputedBanditMap(this.salt, bandits); | |
this.flags = obfuscatePrecomputedFlags(this.salt, flags); | |
} |
export interface IPrecomputedBandit { | ||
banditKey: string; | ||
action: string; | ||
export type BasePrecomputedBandit = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since we haven't published these interfaces publically, i took a moment to rewrite them to more explicitly mention which are obfuscated.
@@ -382,6 +488,40 @@ export default class EppoPrecomputedClient { | |||
} | |||
} | |||
|
|||
private logBanditAction(banditEvent: IBanditEvent): void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
directly copied from local client
// An evaluation details object is needed for the bandit event, but it is not used for anything else. | ||
const evaluationDetails: IFlagEvaluationDetails = { | ||
environmentName: '', | ||
flagEvaluationCode: 'MATCH', | ||
flagEvaluationDescription: 'Bandit action assigned', | ||
variationKey: '', | ||
variationValue: '', | ||
banditKey: banditEvaluation.banditKey, | ||
banditAction: banditEvaluation.action, | ||
configFetchedAt: '', | ||
configPublishedAt: '', | ||
matchedRule: null, | ||
matchedAllocation: null, | ||
unmatchedAllocations: [], | ||
unevaluatedAllocations: [], | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unfortunately an evaluation detail is required for the callback even though we don't support details at this time
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless there's some dependency preventing this, it would be more elegant to make evaluationDetails
optional in IBanditEvent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for mentioning that - the assignment callback allows it to be null so I will normalize this.
export interface IAssignmentEvent {
/**
* The flag evaluation details. Null if the flag was precomputed.
*/
evaluationDetails: IFlagEvaluationDetails | null;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lgtm. Please take a pass at the linter warnings before merging
// Offline-mode | ||
|
||
// Offline mode depends on pre-populated IConfigurationStores (flags and bandits) to source configuration. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can combine these comments
// Offline-mode | |
// Offline mode depends on pre-populated IConfigurationStores (flags and bandits) to source configuration. | |
// Offline-mode: depends on pre-populated IConfigurationStores (flags and bandits) to source configuration. |
// An evaluation details object is needed for the bandit event, but it is not used for anything else. | ||
const evaluationDetails: IFlagEvaluationDetails = { | ||
environmentName: '', | ||
flagEvaluationCode: 'MATCH', | ||
flagEvaluationDescription: 'Bandit action assigned', | ||
variationKey: '', | ||
variationValue: '', | ||
banditKey: banditEvaluation.banditKey, | ||
banditAction: banditEvaluation.action, | ||
configFetchedAt: '', | ||
configPublishedAt: '', | ||
matchedRule: null, | ||
matchedAllocation: null, | ||
unmatchedAllocations: [], | ||
unevaluatedAllocations: [], | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless there's some dependency preventing this, it would be more elegant to make evaluationDetails
optional in IBanditEvent
const promises: Promise<void>[] = []; | ||
promises.push( | ||
hydrateConfigurationStore(this.precomputedFlagStore, { | ||
entries: precomputedResponse.flags, | ||
environment: precomputedResponse.environment ?? { name: UNKNOWN_ENVIRONMENT_NAME }, | ||
createdAt: precomputedResponse.createdAt, | ||
format: precomputedResponse.format, | ||
salt: precomputedResponse.salt, | ||
}), | ||
); | ||
if (this.precomputedBanditsStore) { | ||
promises.push( | ||
hydrateConfigurationStore(this.precomputedBanditsStore, { | ||
entries: precomputedResponse.bandits, | ||
environment: precomputedResponse.environment ?? { name: UNKNOWN_ENVIRONMENT_NAME }, | ||
createdAt: precomputedResponse.createdAt, | ||
format: precomputedResponse.format, | ||
salt: precomputedResponse.salt, | ||
}), | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirming both stores use the same salt
js-sdk-common/src/configuration.ts
Lines 139 to 142 in 8a51588
this.salt = salt; | |
this.bandits = obfuscatePrecomputedBanditMap(this.salt, bandits); | |
this.flags = obfuscatePrecomputedFlags(this.salt, flags); | |
} |
}, | ||
}); | ||
|
||
const client = new EppoPrecomputedClient({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused variable
6503800
to
be58b32
Compare
…(FF-3796)
labels: mergeable
Fixes: #issue
Motivation and Context
The precomputed clients needs to have two bandit evaluation functionalities:
In both cases the SDK initializes in offline mode and is able to perform:
Not all bandit APIs are at parity with the local-evaluation client: specifically this PR only addeds
getBanditAction
, not details since those are not available in the API. Nor does it addgetBestAction
, which was previously developed for coinbase, since that method is not supported in the server.Description
bandit_actions
parameter optionally. its signature matches the server API (https://github.com/Eppo-exp/eppo/blob/main/packages/edge-assignments/src/handlers/handler_assignments.rs#L30-L31). I am explicitly requiring aRecord<string, ContextAttributes>
instead of the more open-endedBanditActions
getBanditAction
API methodHow has this been tested?
Our test sample data (https://github.com/Eppo-exp/sdk-test-data/blob/main/configuration-wire/precomputed-v1.json) needs precomputed bandits to be able to support testing against the shared data suite. For now adding just a simple case and in-parallel adding these.