@@ -462,99 +462,116 @@ export default class EppoClient {
462
462
actions : BanditActions ,
463
463
defaultValue : string ,
464
464
) : IAssignmentDetails < string > {
465
- const flagEvaluationDetailsBuilder = this . flagEvaluationDetailsBuilder ( flagKey ) ;
466
- const defaultResult = { variation : defaultValue , action : null } ;
467
465
let variation = defaultValue ;
468
466
let action : string | null = null ;
469
- try {
470
- const banditVariations = this . banditVariationConfigurationStore ?. get ( flagKey ) ;
471
- if ( banditVariations && ! Object . keys ( actions ) . length ) {
472
- // No actions passed for a flag known to have an active bandit, so we just return the default values so that
473
- // we don't log a variation or bandit assignment
474
- return {
475
- ...defaultResult ,
476
- evaluationDetails : flagEvaluationDetailsBuilder . buildForNoneResult (
477
- 'NO_ACTIONS_SUPPLIED_FOR_BANDIT' ,
478
- 'No bandit actions passed for a flag known to have an active bandit' ,
479
- ) ,
480
- } ;
481
- }
482
467
468
+ // Initialize with a generic evaluation details. This will mutate as the function progresses.
469
+ let evaluationDetails : IFlagEvaluationDetails = this . flagEvaluationDetailsBuilder (
470
+ flagKey ,
471
+ ) . buildForNoneResult (
472
+ 'ASSIGNMENT_ERROR' ,
473
+ 'Unexpected error getting assigned variation for bandit action' ,
474
+ ) ;
475
+ try {
483
476
// Get the assigned variation for the flag with a possible bandit
484
477
// Note for getting assignments, we don't care about context
485
478
const nonContextualSubjectAttributes =
486
479
this . ensureNonContextualSubjectAttributes ( subjectAttributes ) ;
487
- const { variation : _variation , evaluationDetails } = this . getStringAssignmentDetails (
488
- flagKey ,
489
- subjectKey ,
490
- nonContextualSubjectAttributes ,
491
- defaultValue ,
492
- ) ;
493
- variation = _variation ;
480
+ const { variation : assignedVariation , evaluationDetails : assignmentEvaluationDetails } =
481
+ this . getStringAssignmentDetails (
482
+ flagKey ,
483
+ subjectKey ,
484
+ nonContextualSubjectAttributes ,
485
+ defaultValue ,
486
+ ) ;
487
+ variation = assignedVariation ;
488
+ evaluationDetails = assignmentEvaluationDetails ;
494
489
495
490
// Check if the assigned variation is an active bandit
496
491
// Note: the reason for non-bandit assignments include the subject being bucketed into a non-bandit variation or
497
492
// a rollout having been done.
493
+ const banditVariations = this . banditVariationConfigurationStore ?. get ( flagKey ) ;
498
494
const banditKey = banditVariations ?. find (
499
495
( banditVariation ) => banditVariation . variationValue === variation ,
500
496
) ?. key ;
501
497
502
498
if ( banditKey ) {
503
- // Retrieve the model parameters for the bandit
504
- const banditParameters = this . banditModelConfigurationStore ?. get ( banditKey ) ;
505
-
506
- if ( ! banditParameters ) {
507
- throw new Error ( 'No model parameters for bandit ' + banditKey ) ;
508
- }
509
-
510
- const banditModelData = banditParameters . modelData ;
511
- const contextualSubjectAttributes =
512
- this . ensureContextualSubjectAttributes ( subjectAttributes ) ;
513
- const actionsWithContextualAttributes = this . ensureActionsWithContextualAttributes ( actions ) ;
514
- const banditEvaluation = this . banditEvaluator . evaluateBandit (
499
+ evaluationDetails . banditKey = banditKey ;
500
+ action = this . evaluateBanditAction (
515
501
flagKey ,
516
502
subjectKey ,
517
- contextualSubjectAttributes ,
518
- actionsWithContextualAttributes ,
519
- banditModelData ,
503
+ subjectAttributes ,
504
+ actions ,
505
+ banditKey ,
506
+ evaluationDetails ,
520
507
) ;
521
- action = banditEvaluation . actionKey ;
522
508
evaluationDetails . banditAction = action ;
523
- evaluationDetails . banditKey = banditKey ;
524
-
525
- const banditEvent : IBanditEvent = {
526
- timestamp : new Date ( ) . toISOString ( ) ,
527
- featureFlag : flagKey ,
528
- bandit : banditKey ,
529
- subject : subjectKey ,
530
- action,
531
- actionProbability : banditEvaluation . actionWeight ,
532
- optimalityGap : banditEvaluation . optimalityGap ,
533
- modelVersion : banditParameters . modelVersion ,
534
- subjectNumericAttributes : contextualSubjectAttributes . numericAttributes ,
535
- subjectCategoricalAttributes : contextualSubjectAttributes . categoricalAttributes ,
536
- actionNumericAttributes : actionsWithContextualAttributes [ action ] . numericAttributes ,
537
- actionCategoricalAttributes :
538
- actionsWithContextualAttributes [ action ] . categoricalAttributes ,
539
- metaData : this . buildLoggerMetadata ( ) ,
540
- evaluationDetails,
541
- } ;
542
- this . logBanditAction ( banditEvent ) ;
543
509
}
544
- return { variation, action, evaluationDetails } ;
545
510
} catch ( err ) {
546
- logger . error ( 'Error evaluating bandit action' , err ) ;
511
+ logger . error ( 'Error determining bandit action' , err ) ;
547
512
if ( ! this . isGracefulFailureMode ) {
548
513
throw err ;
549
514
}
550
- return {
551
- ...defaultResult ,
552
- evaluationDetails : flagEvaluationDetailsBuilder . buildForNoneResult (
553
- 'ASSIGNMENT_ERROR' ,
554
- `Error evaluating bandit action: ${ err . message } ` ,
555
- ) ,
556
- } ;
515
+ if ( variation ) {
516
+ // If we have a variation, the assignment succeeded and the error was with the bandit part.
517
+ // Update the flag evaluation code to indicate that
518
+ evaluationDetails . flagEvaluationCode = 'BANDIT_ERROR' ;
519
+ }
520
+ evaluationDetails . flagEvaluationDescription = `Error evaluating bandit action: ${ err . message } ` ;
521
+ }
522
+ return { variation, action, evaluationDetails } ;
523
+ }
524
+
525
+ private evaluateBanditAction (
526
+ flagKey : string ,
527
+ subjectKey : string ,
528
+ subjectAttributes : BanditSubjectAttributes ,
529
+ actions : BanditActions ,
530
+ banditKey : string ,
531
+ evaluationDetails : IFlagEvaluationDetails ,
532
+ ) : string | null {
533
+ // If no actions, there is nothing to do
534
+ if ( ! Object . keys ( actions ) . length ) {
535
+ return null ;
536
+ }
537
+ // Retrieve the model parameters for the bandit
538
+ const banditParameters = this . banditModelConfigurationStore ?. get ( banditKey ) ;
539
+
540
+ if ( ! banditParameters ) {
541
+ throw new Error ( 'No model parameters for bandit ' + banditKey ) ;
557
542
}
543
+
544
+ const banditModelData = banditParameters . modelData ;
545
+ const contextualSubjectAttributes = this . ensureContextualSubjectAttributes ( subjectAttributes ) ;
546
+ const actionsWithContextualAttributes = this . ensureActionsWithContextualAttributes ( actions ) ;
547
+ const banditEvaluation = this . banditEvaluator . evaluateBandit (
548
+ flagKey ,
549
+ subjectKey ,
550
+ contextualSubjectAttributes ,
551
+ actionsWithContextualAttributes ,
552
+ banditModelData ,
553
+ ) ;
554
+ const action = banditEvaluation . actionKey ;
555
+
556
+ const banditEvent : IBanditEvent = {
557
+ timestamp : new Date ( ) . toISOString ( ) ,
558
+ featureFlag : flagKey ,
559
+ bandit : banditKey ,
560
+ subject : subjectKey ,
561
+ action,
562
+ actionProbability : banditEvaluation . actionWeight ,
563
+ optimalityGap : banditEvaluation . optimalityGap ,
564
+ modelVersion : banditParameters . modelVersion ,
565
+ subjectNumericAttributes : contextualSubjectAttributes . numericAttributes ,
566
+ subjectCategoricalAttributes : contextualSubjectAttributes . categoricalAttributes ,
567
+ actionNumericAttributes : actionsWithContextualAttributes [ action ] . numericAttributes ,
568
+ actionCategoricalAttributes : actionsWithContextualAttributes [ action ] . categoricalAttributes ,
569
+ metaData : this . buildLoggerMetadata ( ) ,
570
+ evaluationDetails,
571
+ } ;
572
+ this . logBanditAction ( banditEvent ) ;
573
+
574
+ return action ;
558
575
}
559
576
560
577
private ensureNonContextualSubjectAttributes (
0 commit comments