-
Notifications
You must be signed in to change notification settings - Fork 23
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
Pass original request headers merged with enricher response headers to Pyfunc ensembler #217
Pass original request headers merged with enricher response headers to Pyfunc ensembler #217
Conversation
def predict(self, inputs: Dict[str, Any]) -> List[Any]: | ||
logging.info(f"Input request payload: {inputs}") | ||
output = self._ensembler.predict(inputs) | ||
def predict(self, body: Dict[str, Any], headers: Dict[str, str]) -> List[Any]: |
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.
This call's MLFlow Pyfunc model's predict which is eventually received here at runtime. The self.ensemble
call from _ensemble_request
finally calls the implementation provided by the user.
@@ -20,19 +20,42 @@ | |||
|
|||
|
|||
@pytest.mark.parametrize( | |||
"inputs,expected", [ | |||
"ensembler_uri,inputs,headers,expected", [ |
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.
Changes to test old and new ensemblers (created with and without **kwargs
)
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 🚀 , left some small comments!
sdk/turing/ensembler.py
Outdated
@@ -1,4 +1,6 @@ | |||
import abc | |||
import deprecation |
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.
I don't think this is being used right now, would using warnings
library and raising a warning at except clause be better?
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 catching, and the suggestion - removed the deprecation
import and added a warning log.
logging.info(f"Input request payload: {inputs}") | ||
output = self._ensembler.predict(inputs) | ||
def predict(self, body: Dict[str, Any], headers: Dict[str, str]) -> List[Any]: | ||
logging.info(f"Input request payload: {body}") |
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.
Is there any particular reason/benefit to log only the body but not headers (guessing it's because there might be auth details here)?
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.
Right now, the default logger uses INFO level and it's not possible to change that. So I left out the header log, to not bloat the logs further. Will capture an item in the backlog to make logging configurable.
@@ -26,7 +26,7 @@ It is not possible to select as the fallback response a route that has traffic r | |||
{% endhint %} | |||
|
|||
## Docker | |||
Turing will deploy specified image as a post-processor and will send the original request, responses from all routes, and the treatment configuration (if a Experiment Engine is selected, in Configure Experiment Engine), for ensembling. To configure a Docker ensembler, there are 3 sections to be filled. | |||
Turing will deploy the specified image as a post-processor and will send the in the request payload the original request, responses from all routes, and the treatment configuration (if an Experiment Engine is selected, in Configure Experiment Engine), for ensembling. The ensembler's request headers will contain the original request headers sent to Turing, merged with the enricher's response headers (if there are duplicates, the value in the enricher's response headers will take precedence). To configure a Docker ensembler, there are 3 sections to be filled. |
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.
"will send in the request payload to the original request" -> extra "the", missing "to"
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! Rephrased for clarity.
@@ -6,6 +6,22 @@ | |||
|
|||
class TestEnsembler(PyFunc): | |||
|
|||
def initialize(self, artifacts: dict): |
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.
Didn't notice this previously, but perhaps we should standardize the typings? I see different options - dict
, Dict
, Dict[with types]
.
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.
Ideally yes. But there are also other inconsistencies like double vs single quote usage. We can later put in a linter to catch these everywhere and adjust styling. For now, I just changed this example of the test ensemblers to Dict
to match theensemble
method.
except TypeError as e: | ||
if "got an unexpected keyword argument 'headers'" in str(e): | ||
# This handles the legacy ensemblers | ||
# TODO: Deprecate support for legacy ensemblers |
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.
Should perhaps specify what future SDK version would we be deprecating the legacy ensemblers.
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.
I think deprecating this depends on users moving away as well. So we need to do it at some point, but I'm not sure when. We will also need to remove some of the default_route_id related stuff. Will also track this in the backlog.
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! 🚀 Thanks for cleaning up some of the stuff in the SDK files; @terryyylim also raised some interesting points which are worth thinking about so I just "+1"-ed some of them :)
Thanks for the quick review, both! I'll be merging this shortly if no further comments / questions. |
This PR makes 2 user-facing changes:
engines/router/missionctl/handlers/http_handler.go
- Make the Enricher's response headers available to the Ensembler in the request headers. The merging of the headers was introduced in Add steps to merge original request and enricher response headers #176, intended to be used for the routing step and we hadn't propagated it down to the Ensembler. However, allowing the Ensembler access to those values would help the Ensembler make decisions based on values computed by the Enricher.Passing Ensembler Request Headers to the Pyfunc Ensembler
To support this, the optional
**kwargs
argument has been added to theEnsemblerBase.ensemble
method, meant to take in contextual information. This will be used by the Pyfunc Ensembler Service to propagate request headers. The main changes are below.engines/pyfunc-ensembler-service/pyfunc_ensembler_runner/handler.py
- Nest request headers and body under a single dict, when calling the MlFlow Pyfunc model's predict.sdk/turing/ensembler.py
- Pick up the headers from the model input and pass them to theensemble()
method. The signature of theensemble
method now takes in**kwargs
which should be included in the custom implementation to take advantage of the headers. However, since some users may be on the older Pyfunc ensemblers, atry-except
has been added to issue the call without headers if there is an associated exception. This special handling can be removed in a few releases, when users have started to include**kwargs
in their implementation.Tests
The following scenarios have been tested to be working with the new Pyfunc service as of this PR:
**kwargs