-
Notifications
You must be signed in to change notification settings - Fork 4.6k
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
Agents endpoints #449
Agents endpoints #449
Conversation
@PHLF am I correct that you haven't made any changes to the flask endpoints yet? I was curious what it would look like to load a specific agent |
You're right: I didn't do any api changes at the moment. I'd like the actual work to be reviewed first before introducing any breaking changes. And yes loading a specific model for your agent is something that would be great to introduce 😄 |
So without the ability to load a specific model, this would be a major breaking change for us. When we train a model we put it through an evaluation phase, only after the evaluation is done does a user in our system choose whether or not to activate it. The activation is done by maintain our own list of active models and simple routing request approprietly. In the end we can hack it either way by just strictly interacting with agents as we have models. Just food for thought. I'm swamped this week, but may get me or my team involved next week. Either way, we are watching with nervous anticipation. |
You don't have to be nervous as my incentive is not to break anything but to remove the need of hacking things you mentioned. By the way it should be easy to extend the http api to request a specific model for a specific agent. Still this is just a PR with no guarantee to be integrated at all 😉 |
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.
Looking good 👍 some minor remarks
_pytest/test_multitenancy.py
Outdated
@@ -119,19 +115,19 @@ def test_post_parse_invalid_model(client, response_test): | |||
assert response.json.get("error").startswith(response_test.expected_response["error"]) | |||
|
|||
|
|||
if __name__ == '__main__': |
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 still have a main method to easily generate the models as these are models we don't want to retrain during the execution of the tests but rather test if we can still load previously trained models.
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 call the train() function each time because I had an issue where the test_models
trained with python 2 where not loadable with python 3 and vice versa because of pickle (the issue was more specifically related to joblib)
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.
ok 👍
rasa_nlu/data_router.py
Outdated
@staticmethod | ||
def _latest_agent_model(agent_path): | ||
"""Retrieves the latest trained model for an agent""" | ||
agent_models = {model[6:]: model for model in os.listdir(agent_path)} |
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.
need to remove the magic number (e.g. define "model_" as a prefix constant somewhere and take its length 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.
👍
rasa_nlu/data_router.py
Outdated
return { | ||
"trainings_under_this_process": num_trainings, | ||
"available_models": models, | ||
"available_models": agents, |
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.
these aren't models
, right? ;)
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, this was left as is to be matter for discussion: what do we give to the end user?
- Just the list of endpoints
- The list of endpoints with the actual model
- " " + the endpoints status {ready, training...} (would that be really useful)?
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 we actually get the information for the last one for all the endpoints (if easy that would be great if not its not a big deal)?
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.
What do you mean exactly? Are you talking about the endpoint status or the underlying model?
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.
How I see things:
{
"agent A": {
"status": "ready",
"default_model": "model_xxxxxx",
"available_models": [
"model_xxxxxx",
"model_xxxxxx"
]
},
"agent B": {
"status": "training",
"default_model": "model_xxxxxx",
"available_models": [
"model_xxxxxx",
"model_xxxxxx"
]
}
}
@wrathagom Would that be enough for your usecase?
@tmbo Do you have any suggestion regarding some http API changes: can I go for loading a specific model for a specific agent? |
@PHLF sure that sounds very useful. The only thing we need then is a way for the API user to know which model currently is loaded for an agent. |
And there might be threading issues, right? So you could have loaded the recent model within one thread and an older one in another thread, which sounds dangerous. |
|
Checks are erroring: that's OK. That's still very much W.I.P. but at least it is advanced enough to allow you to give some feedback about the current code structure (among other things, I need to take care of multithreading issues). |
Agent -> Projects ? Sounds a bit strange to me. What is the motivation behind such a name ? |
So you might have multiple bots under development (say one restaurant bot and one customer service bot), and each has it's own training data and model. So the project would be e.g. 'restaurants', and you can always call |
Fair enough. Let's do some renaming then. |
Should the logic behind the emulators be changed? As they only allow to query a specific "project" therefore automatically querying the latest model (which is the behaviour of API.ai likes services). |
I think sensible behaviour would be to either specify a project (&rasa uses the latest) or specifying a specific model - @tmbo WDYT? |
Yes, I agree, querying the latest is a sensible default. But being able to use a specific model is quite useful as well - should do both. |
Querying a specific model for a specific project already works, but I'm not sure it works with the emulators as well. |
sounds good. why wouldn't the emulation work? I think they are just post processing the pipeline result |
My bad, it's not related to the emulators but to the way some parameters are normalized. Does this need further changes to also normalize a def normalise_request_json(self, data):
# type: (Dict[Text, Any]) -> Dict[Text, Any]
_data = {}
_data["text"] = data["q"][0] if type(data["q"]) == list else data["q"]
if not data.get("project"):
_data["project"] = "default"
elif type(data["project"]) == list:
_data["project"] = data["project"][0]
else:
_data["project"] = data["project"]
_data['time'] = data["time"] if "time" in data else None
return _data |
yes, that's right, the parameter needs to be added there. |
I made some changes (readers-writer lock) to ease the development of further enhancements like an |
rasa_nlu/project.py
Outdated
@@ -32,23 +34,32 @@ def __init__(self, config=None, component_builder=None, project=None): | |||
self._default_model = self._latest_project_model() or 'fallback' | |||
|
|||
def parse(self, text, time=None, model=None): | |||
# Lazy model loading | |||
# Readers-writer lock simple double mutex implementation | |||
self._reader_lock.acquire() |
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.
let's put the lock handling into a separate function
rasa_nlu/project.py
Outdated
self._lock.release() | ||
response = self._models[model].parse(text, time) | ||
|
||
self._reader_lock.acquire() |
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.
separate function
@PHLF do you think we can get this merged today? |
I have to update the docs, but I think this is technically ready. |
@amn41 @tmbo @wrathagom As I said I think this is technically OK. I did a first pass on the docs. Let me know what you think. |
looks really good - you did a great job with this PR let's get it merged when the Travis build is finished. we'll tackle issues with the documentation along the way. |
Only time will tell 😃 Next thing to add: an HTTP endpoint to unload models from memory. The server side logic is in place, it just requires a new endpoint and a new route handler in the datarouter. Thanks Tom for your support. |
Yes, you are right, I think that is another really good addition. But as I don't want to delay this PR any further (I know that you already needed to resolve a lot of merge conflicts 😉 ) I'd rather see them added separately. |
🎉 |
Proposed changes:
Status:
The idea behind this is to enhance how RASA handles multiple apps to be on par with services like api.ai. Indeed when you have a client sending a training request like this
127.0.0.1:5000/train?name=agent_A
you expect your agent (endpoint) to still be available for parsing requests and updated with the newly trained model once available. So models would be a way to version your agent:Original PR: #426