Conversation
typotter
left a comment
There was a problem hiding this comment.
Thanks for continuing to tackle the missing features in this SDK, Lisa.
eppo_client/client.py
Outdated
| ) | ||
| return BanditResult(variation, None) | ||
|
|
||
| # if no actions are given - a valid use case - return the default value |
There was a problem hiding this comment.
We don't want to compute the flag string assignment (because it will log the flag assignment) and then return the default value if no actions are passed. If no actions are passed and the flag is not associated with bandits, we can compute and return the string assignment.
This requires reading the bandits field of the UFC response.
There was a problem hiding this comment.
This comment was correct at the time, but has since changed. See this Slack thread.
I've implemented the new plan, and have re-requesting a review
…ed categorical attributes to strings.
ea4c247 to
4441e50
Compare
| ) | ||
| if result.action is None: | ||
| do_variation(result.variation) | ||
| if result.action: |
There was a problem hiding this comment.
I think this is more representative of how we expect users to use our bandits for now (with the two variation template)
| to their context of actions with their contexts. | ||
| If supplying an ActionAttributes, it gets converted to an ActionContexts instance. | ||
| default (str): The default variation to use if the subject is not part of the bandit. | ||
| default (str): The default variation to use if an error is encountered retrieving the |
There was a problem hiding this comment.
Would you mind updating the types in the doc block at line 259 for subject_context? => type should be Union[ContextAttributes, Attributes]
eppo_client/client.py
Outdated
| or the assignment if they are not. The BanditResult includes: | ||
| - variation (str): The assignment key indicating the subject's variation. | ||
| - action (str): The key of the selected action if the subject is part of the bandit. | ||
| - action (str | null): The key of the selected action if the subject was assigned one |
There was a problem hiding this comment.
returned actions can be None
There was a problem hiding this comment.
Nit: should say None instead of null, or better: Optional[str] to keep it Pythonic
eppo_client/client.py
Outdated
| action = None | ||
| try: | ||
| return self.get_bandit_action_detail( | ||
| subject_attributes = convert_context_attributes_to_attributes(subject_context) |
There was a problem hiding this comment.
get_bandit_action_detail was ensuring the attributes were passed in with context and then disaggregating them back down to Attributes.
Now that we pass these in before we try to ensure context, we want to do the opposite, which is to make sure they are disaggregated.
| else: | ||
| raise e | ||
|
|
||
| return BanditResult(variation, action) |
There was a problem hiding this comment.
By having this be the only return statement, if we got a variation but hit an error later on evaluating the bandit, we'll still return the variation (to match our logging of it)
| stringified_categorical_attributes = { | ||
| key: str(value) for key, value in subject_context.categorical_attributes.items() | ||
| } |
There was a problem hiding this comment.
@lisaah's original fix, which is to stringify the values of all explicitly passed in categorical attributes so they can be evaluated correctly against the model
There was a problem hiding this comment.
Should we also ensure conversion to string when subject_context is a dictionary?
There was a problem hiding this comment.
I guess in that case we don't know what values should be converted, so bugs can still arise but we have no way to fix.
There was a problem hiding this comment.
ContextAttributes.from_dict() handles this... bucketing all numeric values numeric attributes and then the string values of strings and bools as categorical attributes
|
|
||
|
|
||
| def test_get_bandit_action_flag_without_bandit(): | ||
| def test_get_bandit_action_flag_has_no_bandit(): |
There was a problem hiding this comment.
have this use a real flag and get assigned a non-default (but non-bandit) variation
|
|
||
|
|
||
| def test_get_bandit_action_bandit_does_not_exist(): | ||
| def test_get_bandit_action_flag_not_exist(): |
There was a problem hiding this comment.
this is really what the test is testing
| assert result == BanditResult("control", None) | ||
|
|
||
| @patch.object(BanditEvaluator, 'evaluate_bandit', side_effect=Exception("Mocked Exception")) | ||
| def test_get_bandit_action_bandit_error(mock_bandit_evaluator): |
There was a problem hiding this comment.
Tests save lives! (part I)
| ) | ||
|
|
||
| @patch.object(MockAssignmentLogger, 'log_bandit_action', side_effect=Exception("Mocked Exception")) | ||
| def test_get_bandit_action_bandit_logger_error(patched_mock_assignment_logger): |
There was a problem hiding this comment.
Tests save lives! (part II)
There was a problem hiding this comment.
💯
feel free to file issues against the other SDKs to ensure we've got protection around logging in each.
There was a problem hiding this comment.
💯
feel free to file issues against the other SDKs to ensure we've got protection around logging in each.
schmit
left a comment
There was a problem hiding this comment.
Thanks for the changes, this looks good to me. I don't feel great that we can have 'null' actions after working so hard to removing the null variation in the UFC, but I also don't know how to better handle the empty actions list case.
eppo_client/client.py
Outdated
| or the assignment if they are not. The BanditResult includes: | ||
| - variation (str): The assignment key indicating the subject's variation. | ||
| - action (str): The key of the selected action if the subject is part of the bandit. | ||
| - action (str | null): The key of the selected action if the subject was assigned one |
There was a problem hiding this comment.
Nit: should say None instead of null, or better: Optional[str] to keep it Pythonic
| stringified_categorical_attributes = { | ||
| key: str(value) for key, value in subject_context.categorical_attributes.items() | ||
| } |
There was a problem hiding this comment.
Should we also ensure conversion to string when subject_context is a dictionary?
| stringified_categorical_attributes = { | ||
| key: str(value) for key, value in subject_context.categorical_attributes.items() | ||
| } |
There was a problem hiding this comment.
I guess in that case we don't know what values should be converted, so bugs can still arise but we have no way to fix.
| ) | ||
| if result.action is None: | ||
| do_variation(result.variation) | ||
| if result.action: |
| to their context of actions with their contexts. | ||
| If supplying an ActionAttributes, it gets converted to an ActionContexts instance. | ||
| default (str): The default variation to use if the subject is not part of the bandit. | ||
| default (str): The default variation to use if an error is encountered retrieving the |
There was a problem hiding this comment.
Would you mind updating the types in the doc block at line 259 for subject_context? => type should be Union[ContextAttributes, Attributes]
| assert result == BanditResult("control", None) | ||
|
|
||
| @patch.object(BanditEvaluator, 'evaluate_bandit', side_effect=Exception("Mocked Exception")) | ||
| def test_get_bandit_action_bandit_error(mock_bandit_evaluator): |
| ) | ||
|
|
||
| @patch.object(MockAssignmentLogger, 'log_bandit_action', side_effect=Exception("Mocked Exception")) | ||
| def test_get_bandit_action_bandit_logger_error(patched_mock_assignment_logger): |
There was a problem hiding this comment.
💯
feel free to file issues against the other SDKs to ensure we've got protection around logging in each.
| ) | ||
|
|
||
| @patch.object(MockAssignmentLogger, 'log_bandit_action', side_effect=Exception("Mocked Exception")) | ||
| def test_get_bandit_action_bandit_logger_error(patched_mock_assignment_logger): |
There was a problem hiding this comment.
💯
feel free to file issues against the other SDKs to ensure we've got protection around logging in each.
Fixes: #FF-2573
Motivation and Context
Tests were modified, catching some edge cases.
We want to return the default value for a bandit flag with no actionsReturn the assigned variation, butNonefor action, if there were no actions provided or there was an error evaluating the banditDescription
tryblockHow has this been tested?
New unit tests in
client_bandit_test.py