async def get_model_by_name(request, project_id, model): model = _model_service(request).get_model_by_name(project_id, model) if not model: return rasa_x_utils.error( http.HTTPStatus.NOT_FOUND, "ModelNotFound", f"Model '{model}' not found for project '{project_id}'.", ) model_hash = model["hash"] try: if model_hash == request.headers.get("If-None-Match"): return response.text("", http.HTTPStatus.NO_CONTENT) return await response.file_stream( location=model["path"], headers={ "ETag": model_hash, "filename": os.path.basename(model["path"]), }, mime_type="application/gzip", ) except FileNotFoundError as e: logger.exception(e) return rasa_x_utils.error( http.HTTPStatus.NOT_FOUND, "ModelDownloadFailed", f"Failed to download file.\n{e}", )
async def get_story(request, story_id): from rasa.core.domain import Domain story = _story_service(request).fetch_story(story_id) content_type = request.headers.get("Accept") if content_type == "text/vnd.graphviz": project_id = rasa_x_utils.default_arg(request, "project_id", config.project_name) domain_dict = _domain_service(request).get_or_create_domain( project_id) domain = Domain.from_dict(domain_dict) visualization = await _story_service(request).visualize_stories( [story], domain) if visualization: return response.text(visualization) else: return rasa_x_utils.error( HTTPStatus.NOT_ACCEPTABLE, "VisualizationNotAvailable", "Cannot produce a visualization for the requested story", ) else: if story: return response.json(story) return rasa_x_utils.error( HTTPStatus.NOT_FOUND, "StoryNotFound", f"Story for id {story_id} could not be found", )
async def get_model_for_tag(request, project_id, tag): model = _model_service(request).model_for_tag(project_id, tag) if not model: return rasa_x_utils.error( 404, "TagNotFound", f"Tag '{tag}' not found for project '{project_id}'.") model_hash = model["hash"] try: if model_hash == request.headers.get("If-None-Match"): return response.text("", http.HTTPStatus.NO_CONTENT) return await response.file_stream( location=model["path"], headers={ "ETag": model_hash, "filename": os.path.basename(model["path"]), }, mime_type="application/gzip", ) except FileNotFoundError: logger.warning("Tried to download model file '{}', " "but file does not exist.".format(model["path"])) return rasa_x_utils.error( 404, "ModelDownloadFailed", "Failed to find model file '{}'.".format(model["path"]), )
async def update_conversation_evaluations(request): event_service = _event_service(request) conversation_ids = event_service.conversation_ids() for _id in conversation_ids: story = event_service.story_for_conversation_id(_id) try: content = await _stack_service(request, "worker").evaluate_story(story) event_service.update_evaluation(_id, content) except ClientError as e: return rasa_x_utils.error( HTTPStatus.BAD_REQUEST, "CoreEvaluationFailed", f"Failed to create evaluation for conversation with ID '{_id}'.", details=e, ) except ValueError as e: return rasa_x_utils.error( HTTPStatus.NOT_FOUND, "StoringEvaluationFailed", f"Failed to store evaluation for conversation_id '{_id}'. Conversation not found.", details=e, ) return response.text("", HTTPStatus.NO_CONTENT)
async def post_event(request, conversation_id, user): _event = request.json try: _ = await _stack_service( request, RASA_PRODUCTION_ENVIRONMENT).append_events_to_tracker( conversation_id, _event) if tracker_utils.is_user_event(_event): # add annotated user messages to training data data_service = DataService(request[REQUEST_DB_SESSION_KEY]) data_service.save_user_event_as_example( user, config.project_name, _event) telemetry.track_message_annotated( telemetry.MESSAGE_ANNOTATED_INTERACTIVE_LEARNING) return response.json(_event) except ClientError as e: logger.warning( f"Creating event for sender '{conversation_id}' failed. Error: {e}" ) return rasa_x_utils.error( HTTPStatus.BAD_REQUEST, "EventCreationError", f"Error when creating event for sender '{conversation_id}'.", details=e, ) except ValueError as e: return rasa_x_utils.error(HTTPStatus.NOT_FOUND, "ServiceNotFound", details=e)
async def chat_to_bot(request, conversation_id=None): message_object = request.json if conversation_id: message_object["conversation_id"] = conversation_id environment = rasa_x_utils.deployment_environment_from_request( request, DEFAULT_RASA_ENVIRONMENT) service = _stack_service(request, DEFAULT_RASA_ENVIRONMENT) if not service: return rasa_x_utils.error( HTTPStatus.NOT_FOUND, "ServiceNotFound", f"Service for requested environment '{environment}' not found.", ) try: message_response = await service.send_message( message_object, token=request.headers.get("Authorization")) return response.json(message_response) except ClientError as e: logger.error( f"Failed to send message to Rasa Chat webhook. Error: {e}") return rasa_x_utils.error( HTTPStatus.NOT_FOUND, "MessageSendFailed", "Message '{}' could not be sent to environment '{}'.".format( message_object["message"], environment), )
async def put_evaluation(request, conversation_id): event_service = _event_service(request) story = event_service.story_for_conversation_id(conversation_id) if not story: return rasa_x_utils.error( HTTPStatus.NOT_FOUND, "ClientNotFound", f"Client for conversation_id {conversation_id} could not be found", ) try: content = await _stack_service(request, "worker").evaluate_story(story) event_service.update_evaluation(conversation_id, content) return response.json(content) except ClientError as e: return rasa_x_utils.error( HTTPStatus.BAD_REQUEST, "CoreEvaluationFailed", f"Failed to create evaluation for conversation_id {conversation_id}", details=e, ) except ValueError as e: return rasa_x_utils.error( HTTPStatus.NOT_FOUND, "StoringEvaluationFailed", f"Failed to store evaluation for conversation_id '{conversation_id}'.", details=e, )
async def create_commit(request: Request, project_id: Text, repository_id: int, branch_name: Text) -> HTTPResponse: """Commit and push the current local changes.""" git_service = _git(request, project_id, repository_id) try: commit = await git_service.commit_and_push_changes_to(branch_name) return response.json(commit, 201) except ValueError as e: logger.debug(e) return error( 404, "RepositoryNotFound", f"Branch '{branch_name}' for repository with ID '{repository_id}' " f"could not be found.", ) except GitCommitError as e: logger.debug(e) return error( 403, "BranchIsProtected", f"Branch '{branch_name}' is protected. Please add your changes to a " f"different branch.", )
async def get_conversation_tracker_impl(request: Request, conversation_id: Text, user: Dict[Text, Any] = None): event_service = _event_service(request) if not _has_access_to_conversation(event_service, conversation_id, user): return rasa_x_utils.error(HTTPStatus.UNAUTHORIZED, "NoPermission", "Access denied") until_time = rasa_x_utils.float_arg(request, "until", None) since_time = rasa_x_utils.float_arg(request, "since", None) rasa_environment_query = rasa_x_utils.default_arg( request, "rasa_environment", DEFAULT_RASA_ENVIRONMENT) event_verbosity = _event_verbosity_from_request(request) exclude_leading_action_session_start = rasa_x_utils.bool_arg( request, "exclude_leading_action_session_start", False) tracker = event_service.get_tracker_with_message_flags( conversation_id, until_time, since_time, event_verbosity, rasa_environment_query, exclude_leading_action_session_start, ) if not tracker: return rasa_x_utils.error( HTTPStatus.NOT_FOUND, "ClientNotFound", f"Client for conversation_id '{conversation_id}' could not be found", ) requested_format = request.headers.get("Accept") if requested_format == "application/json": dispo = f"attachment;filename={conversation_id}-dump.json" return response.json( tracker, content_type="application/json", headers={"Content-Disposition": dispo}, ) elif requested_format == "text/markdown": _events = events.deserialise_events(tracker["events"]) story = Story.from_events(_events) exported = story.as_story_string(flat=True) return response.text( exported, content_type="text/markdown", headers={ "Content-Disposition": f"attachment;filename={conversation_id}-story.md" }, ) else: return response.json(tracker, headers={"Content-Disposition": "inline"})
async def change_password(request: Request) -> HTTPResponse: rjs = request.json user_service = UserService(request[REQUEST_DB_SESSION_KEY]) try: user = user_service.change_password(rjs) if user is None: return rasa_x_utils.error(404, "UserNotFound", "user not found") return response.json(user) except MismatchedPasswordsException: return rasa_x_utils.error(403, "WrongPassword", "wrong password")
async def save_model_config(request, project_id, user=None): settings_service = SettingsService(request[REQUEST_DB_SESSION_KEY]) try: config_yaml = settings_service.inspect_and_save_yaml_config_from_request( request.body, user["team"], project_id) return response.text(config_yaml) except YAMLError as e: return rasa_x_utils.error( 400, "InvalidConfig", f"Failed to read configuration file. Error: {e}") except InvalidConfigError as e: return rasa_x_utils.error(http.HTTPStatus.UNPROCESSABLE_ENTITY, "ConfigMissingKeys", f"Error: {e}")
async def train_model(request, project_id): stack_services = SettingsService( request[REQUEST_DB_SESSION_KEY]).stack_services(project_id) environment = utils.deployment_environment_from_request( request, "worker") stack_service = stack_services[environment] try: training_start = time.time() content = await stack_service.start_training_process() telemetry.track(telemetry.MODEL_TRAINED_EVENT) model_name = await _model_service(request).save_trained_model( project_id, content) nlg_service = NlgService(request[REQUEST_DB_SESSION_KEY]) nlg_service.mark_responses_as_used(training_start) return response.json({ "info": "New model trained.", "model": model_name }) except FileExistsError as e: logger.error(e) return response.json( { "info": "Model already exists.", "path": str(e) }, http.HTTPStatus.CREATED, ) except ValueError as e: logger.error(e) return rasa_x_utils.error( http.HTTPStatus.BAD_REQUEST, "StackTrainingFailed", "Unable to train on data containing retrieval intents.", details=e, ) except ClientError as e: logger.error(e) return rasa_x_utils.error( http.HTTPStatus.INTERNAL_SERVER_ERROR, "StackTrainingFailed", "Failed to train a Rasa model.", details=e, )
async def update_intent(request, intent_id, project_id): intent = request.json intent_service = _intent_service(request) existing_intents = intent_service.get_permanent_intents(project_id) if intent[INTENT_NAME_KEY] not in existing_intents: intent_service.update_temporary_intent(intent_id, intent, project_id) user_goal = intent.get(INTENT_USER_GOAL_KEY) user_goal_service = _user_goal_service(request) try: if user_goal: user_goal_service.add_intent_to_user_goal( user_goal, intent[INTENT_NAME_KEY], project_id) elif INTENT_USER_GOAL_KEY in intent.keys(): user_goal_service.remove_intent_from_user_goal( user_goal, intent[INTENT_NAME_KEY], project_id) except ValueError as e: logger.error(e) return error(400, "UpdateIntentFailed", "Failed to update user goal.", details=e) return response.text(f"Intent '{intent_id}' updated.", 200)
async def add_conversation_tags( request: Request, conversation_id: Text ) -> HTTPResponse: """Assign tags to <conversation_id>. If tags don't exist, they will be created. Args: request: Incoming HTTP request. conversation_id: Id of conversation to assign tags to. Returns: List of assigned tags in format of JSON schema `conversation_tags`. """ try: tags = _event_service(request).add_conversation_tags( conversation_id, request.json ) telemetry.track_conversation_tagged(len(tags)) return response.json(tags, headers={"X-Total-Count": len(tags)}) except ValueError as e: return rasa_x_utils.error( HTTPStatus.BAD_REQUEST, "Failed to add tags to conversation", str(e) )
async def update_user( request: Request, username: Text, user: Optional[Dict[Text, Any]] = None) -> HTTPResponse: """Update properties of a `User`.""" if username != user["username"]: return rasa_x_utils.error( 403, "UserUpdateError", "Users can only update their own propeties.") try: _user_service(request).update_user(user["username"], request.json) return response.text("", 204) except UserException as e: return rasa_x_utils.error(404, "UserUpdateError", details=e)
async def create_conversation(request: Request, user: Dict[Text, Any] = None) -> HTTPResponse: payload = request.json or {} conversation_id = payload.get("sender_id") conversation_id_to_copy_from = payload.get( "conversation_id_to_copy_from") events_until = payload.get("until") or time.time() try: event_service = _event_service(request) conversation = event_service.create_new_conversation( conversation_id, interactive=True, created_by=user.get(USERNAME_KEY)) # Commit changes right away to make sure the conversation is not created # by Rasa Open Source sending the events to the `EventService` event_service.commit() stack_service = _stack_service(request, RASA_PRODUCTION_ENVIRONMENT) if conversation_id_to_copy_from: _events = event_service.copy_events_from_conversation( conversation_id_to_copy_from, events_until) else: initial_tracker = await stack_service.tracker_json( conversation.sender_id) _events = initial_tracker["events"] tracker = await stack_service.append_events_to_tracker( conversation.sender_id, _events) return response.json(tracker, HTTPStatus.CREATED) except ClientError as e: return rasa_x_utils.error( HTTPStatus.UNPROCESSABLE_ENTITY, "ConversationCreationFailed", "Error while trying to create conversation.", details=e, ) except ValueError as e: return rasa_x_utils.error(HTTPStatus.NOT_FOUND, "ConversationEventCopyError", details=e)
async def delete_story(request: Request, story_id: Text) -> HTTPResponse: deleted = _story_service(request).delete_story(story_id) if deleted: return response.text("", HTTPStatus.NO_CONTENT) return rasa_x_utils.error( HTTPStatus.NOT_FOUND, "StoryNotFound", f"Failed to delete story with story with id '{story_id}'.", )
async def get_domain(request: Request) -> HTTPResponse: domain_service = DomainService(request[REQUEST_DB_SESSION_KEY]) domain = domain_service.get_domain() if domain is None: return rasa_x_utils.error(400, "DomainNotFound", "Could not find domain.") domain_service.remove_domain_edited_states(domain) return response.text(domain_service.dump_cleaned_domain_yaml(domain))
async def get_domain_warnings(request: Request) -> HTTPResponse: domain_service = DomainService(request[REQUEST_DB_SESSION_KEY]) domain_warnings = await domain_service.get_domain_warnings() if domain_warnings is None: return rasa_x_utils.error(400, "DomainNotFound", "Could not find domain.") return response.json(domain_warnings[0], headers={"X-Total-Count": domain_warnings[1]})
async def delete_model(request, project_id, model): deleted = _model_service(request).delete_model(project_id, model) if deleted: return response.text("", http.HTTPStatus.NO_CONTENT) return rasa_x_utils.error( http.HTTPStatus.NOT_FOUND, "ModelDeleteFailed", f"Failed to delete model '{model}'.", )
async def delete_user(request: Request, username: Text, user: Dict) -> HTTPResponse: user_service = UserService(request[REQUEST_DB_SESSION_KEY]) try: deleted = user_service.delete_user( username, requesting_user=user["username"]) return response.json(deleted) except UserException as e: return rasa_x_utils.error(404, "UserDeletionError", e)
async def delete_response(request: Request, response_id: int) -> HTTPResponse: deleted = _nlg_service(request).delete_response(response_id) if deleted: background_dump_service.add_domain_change() return response.text("", http.HTTPStatus.NO_CONTENT) return rasa_x_utils.error( http.HTTPStatus.NOT_FOUND, "ResponseNotFound", "Response could not be found.", )
async def create_new_action(request: Request, project_id: Text) -> HTTPResponse: domain_service = DomainService(request[REQUEST_DB_SESSION_KEY]) try: created = domain_service.add_new_action(request.json, project_id) return response.json(created, status=201) except ValueError as e: return rasa_x_utils.error(400, "ActionCreationError", "Action already exists.", details=e)
async def get_model_config(request, project_id, user=None): settings_service = SettingsService(request[REQUEST_DB_SESSION_KEY]) stack_config = settings_service.get_config(user["team"], project_id) if not stack_config: return rasa_x_utils.error(http.HTTPStatus.NOT_FOUND, "SettingsFailed", "Could not find settings.") yaml_config = rasa_x_utils.dump_yaml(stack_config) return response.text(yaml_config)
async def untag(request, project_id, model, tag): try: _model_service(request).delete_tag(project_id, model, tag) return response.text("", http.HTTPStatus.NO_CONTENT) except ValueError as e: return rasa_x_utils.error( http.HTTPStatus.NOT_FOUND, "TagDeletionFailed", "Failed to delete model tag", details=e, )
async def issue_signed_jwt(request): id_object = request.json chat_token = _domain_service(request).get_token() if not chat_token: return rasa_x_utils.error( HTTPStatus.NOT_FOUND, "ChatTokenNotFound", "Could not find valid chat_token.", ) if id_object["chat_token"] != chat_token["chat_token"]: return rasa_x_utils.error(HTTPStatus.UNAUTHORIZED, "InvalidChatToken", "Chat token is not valid.") if _domain_service(request).has_token_expired(id_object["chat_token"]): return rasa_x_utils.error(HTTPStatus.UNAUTHORIZED, "ChatTokenExpired", "Chat token has expired.") # set JWT expiration to one day from current time or chat token # expiration time, whichever is lower expires = min(int(time.time()) + 24 * 60 * 60, chat_token["expires"]) conversation_id = uuid.uuid4().hex jwt_object = { "username": conversation_id, "exp": expires, "user": { "username": conversation_id, "roles": [user_service.GUEST], }, "scopes": normalise_permissions(guest_permissions()), } encoded = rasax.community.jwt.encode_jwt(jwt_object, config.jwt_private_key) return response.json({ "access_token": encoded, "conversation_id": conversation_id })
async def upload_model(request, project_id): model_service = _model_service(request) try: tpath = model_service.save_model_to_disk(request) except (FileNotFoundError, ValueError, TypeError) as e: return rasa_x_utils.error( http.HTTPStatus.BAD_REQUEST, "ModelSaveError", f"Could not save model.\n{e}", ) minimum_version = await model_service.minimum_compatible_version() if not model_service.is_model_compatible(minimum_version, fpath=tpath): return rasa_x_utils.error( http.HTTPStatus.BAD_REQUEST, "ModelVersionError", f"Model version unsupported.\n" f"The minimum compatible version is {minimum_version}.", ) try: filename: Text = os.path.basename(tpath) model_name = filename.split(".tar.gz")[0] saved = await model_service.save_uploaded_model( project_id, model_name, tpath) if saved: telemetry.track(telemetry.MODEL_UPLOADED_EVENT) return response.json(saved, http.HTTPStatus.CREATED) return rasa_x_utils.error( http.HTTPStatus.BAD_REQUEST, "ModelSaveError", "Could not save model.\nModel name '{}'." "File path '{}'.".format(model_name, tpath), ) except FileExistsError: return rasa_x_utils.error( http.HTTPStatus.CONFLICT, "ModelExistsError", "A model with that name already exists.", )
async def delete_message_correction( request: Request, conversation_id: Text, message_timestamp: float) -> HTTPResponse: try: project_id = rasa_x_utils.default_arg(request, "project_id", config.project_name) _event_service(request).delete_message_correction( conversation_id, message_timestamp, project_id) return response.text("", HTTPStatus.OK) except ValueError as e: return rasa_x_utils.error(HTTPStatus.BAD_REQUEST, "MessageRelabellingError.", e)
async def get_runtime_config(_: Request) -> HTTPResponse: config_dict, errors = config_service.get_runtime_config_and_errors() if errors: return rasa_x_utils.error( 400, "FileNotFoundError", rasa_x_utils.add_plural_suffix( "Could not find runtime config file{}.", errors), details=errors, ) return response.json(config_dict)
async def get_messages(request, conversation_id, user=None): event_service = _event_service(request) if not _has_access_to_conversation(event_service, conversation_id, user): return rasa_x_utils.error(HTTPStatus.UNAUTHORIZED, "NoPermission", "Access denied") until_time = rasa_x_utils.float_arg(request, "until", None) event_verbosity = _event_verbosity_from_request(request) tracker = event_service.get_messages_for(conversation_id, until_time, event_verbosity) if not tracker: return rasa_x_utils.error( HTTPStatus.NOT_FOUND, "ClientNotFound", f"Client for conversation_id '{conversation_id}' could not be found.", ) return response.json( tracker, headers={"X-Total-Count": len(tracker.get("messages"))})