Skip to content

Commit

Permalink
Merge pull request #8141 from RasaHQ/improved-startup-time
Browse files Browse the repository at this point in the history
Improved startup time
  • Loading branch information
wochinge committed Mar 16, 2021
2 parents f7490fe + 830e331 commit 2dd3fe9
Show file tree
Hide file tree
Showing 45 changed files with 484 additions and 369 deletions.
1 change: 1 addition & 0 deletions changelog/4280.improvement.md
@@ -0,0 +1 @@
Improved CLI startup time.
3 changes: 3 additions & 0 deletions changelog/8141.misc.md
@@ -0,0 +1,3 @@
The following modules were renamed:
* `rasa.train` -> `rasa.model_training`
* `rasa.test` -> `rasa.model_testing`
6 changes: 6 additions & 0 deletions changelog/8141.removal.md
@@ -0,0 +1,6 @@
The following import abbreviations were removed:
* `rasa.core.train`: Please use `rasa.core.train.train` instead.
* `rasa.core.visualize`: Please use `rasa.core.visualize.visualize` instead.
* `rasa.nlu.train`: Please use `rasa.nlu.train.train` instead.
* `rasa.nlu.test`: Please use `rasa.nlu.test.run_evaluation` instead.
* `rasa.nlu.cross_validate`: Please use `rasa.nlu.test.cross_validate` instead.
1 change: 0 additions & 1 deletion docs/docs/telemetry/telemetry.mdx
Expand Up @@ -113,7 +113,6 @@ Here is an example report that shows the data reported to Rasa after running
"project": "a0a7178e6e5f9e6484c5cfa3ea4497ffc0c96d0ad3f3ad8e9399a1edd88e3cf4",
"python": "3.7.5",
"rasa_open_source": "2.0.0",
"gpu": 0,
"cpu": 16
}
}
Expand Down
4 changes: 1 addition & 3 deletions rasa/__init__.py
@@ -1,12 +1,10 @@
import logging

from rasa import version
from rasa.api import run, train, test

# define the version before the other imports since these need it
__version__ = version.__version__

from rasa.run import run
from rasa.train import train
from rasa.test import test

logging.getLogger(__name__).addHandler(logging.NullHandler())
155 changes: 155 additions & 0 deletions rasa/api.py
@@ -0,0 +1,155 @@
import rasa.shared.constants
import typing

# WARNING: Be careful about adding any top level imports at this place!
# These functions are imported in `rasa.__init__` and any top level import
# added here will get executed as soon as someone runs `import rasa`.
# Some imports are very slow (e.g. `tensorflow`) and we want them to get
# imported when running `import rasa`. If you add more imports here,
# please check that in the chain you are importing, no slow packages
# are getting imported.

if typing.TYPE_CHECKING:
from typing import Any, Text, Dict, Union, List, Optional, NoReturn
from rasa.model_training import TrainingResult
import asyncio


def run(
model: "Text",
endpoints: "Text",
connector: "Text" = None,
credentials: "Text" = None,
**kwargs: "Dict[Text, Any]",
) -> "NoReturn":
"""Runs a Rasa model.
Args:
model: Path to model archive.
endpoints: Path to endpoints file.
connector: Connector which should be use (overwrites `credentials`
field).
credentials: Path to channel credentials file.
**kwargs: Additional arguments which are passed to
`rasa.core.run.serve_application`.
"""
import rasa.core.run
from rasa.core.utils import AvailableEndpoints
from rasa.shared.utils.cli import print_warning
import rasa.shared.utils.common
from rasa.shared.constants import DOCS_BASE_URL

_endpoints = AvailableEndpoints.read_endpoints(endpoints)

if not connector and not credentials:
connector = "rest"

print_warning(
f"No chat connector configured, falling back to the "
f"REST input channel. To connect your bot to another channel, "
f"read the docs here: {DOCS_BASE_URL}/messaging-and-voice-channels"
)

kwargs = rasa.shared.utils.common.minimal_kwargs(
kwargs, rasa.core.run.serve_application
)
rasa.core.run.serve_application(
model,
channel=connector,
credentials=credentials,
endpoints=_endpoints,
**kwargs,
)


def train(
domain: "Text",
config: "Text",
training_files: "Union[Text, List[Text]]",
output: "Text" = rasa.shared.constants.DEFAULT_MODELS_PATH,
dry_run: bool = False,
force_training: bool = False,
fixed_model_name: "Optional[Text]" = None,
persist_nlu_training_data: bool = False,
core_additional_arguments: "Optional[Dict]" = None,
nlu_additional_arguments: "Optional[Dict]" = None,
loop: "Optional[asyncio.AbstractEventLoop]" = None,
model_to_finetune: "Optional[Text]" = None,
finetuning_epoch_fraction: float = 1.0,
) -> "TrainingResult":
"""Runs Rasa Core and NLU training in `async` loop.
Args:
domain: Path to the domain file.
config: Path to the config for Core and NLU.
training_files: Paths to the training data for Core and NLU.
output: Output path.
dry_run: If `True` then no training will be done, and the information about
whether the training needs to be done will be printed.
force_training: If `True` retrain model even if data has not changed.
fixed_model_name: Name of model to be stored.
persist_nlu_training_data: `True` if the NLU training data should be persisted
with the model.
core_additional_arguments: Additional training parameters for core training.
nlu_additional_arguments: Additional training parameters forwarded to training
method of each NLU component.
loop: Optional EventLoop for running coroutines.
model_to_finetune: Optional path to a model which should be finetuned or
a directory in case the latest trained model should be used.
finetuning_epoch_fraction: The fraction currently specified training epochs
in the model configuration which should be used for finetuning.
Returns:
An instance of `TrainingResult`.
"""
from rasa.model_training import train_async
import rasa.utils.common

return rasa.utils.common.run_in_loop(
train_async(
domain=domain,
config=config,
training_files=training_files,
output=output,
dry_run=dry_run,
force_training=force_training,
fixed_model_name=fixed_model_name,
persist_nlu_training_data=persist_nlu_training_data,
core_additional_arguments=core_additional_arguments,
nlu_additional_arguments=nlu_additional_arguments,
model_to_finetune=model_to_finetune,
finetuning_epoch_fraction=finetuning_epoch_fraction,
),
loop,
)


def test(
model: "Text",
stories: "Text",
nlu_data: "Text",
output: "Text" = rasa.shared.constants.DEFAULT_RESULTS_PATH,
additional_arguments: "Optional[Dict]" = None,
) -> None:
"""Test a Rasa model against a set of test data.
Args:
model: model to test
stories: path to the dialogue test data
nlu_data: path to the NLU test data
output: path to folder where all output will be stored
additional_arguments: additional arguments for the test call
"""
from rasa.model_testing import test_core
import rasa.utils.common
from rasa.model_testing import test_nlu

if additional_arguments is None:
additional_arguments = {}

test_core(model, stories, output, additional_arguments)

rasa.utils.common.run_in_loop(
test_nlu(model, nlu_data, output, additional_arguments)
)
66 changes: 34 additions & 32 deletions rasa/cli/data.py
Expand Up @@ -11,11 +11,6 @@
from rasa.cli.arguments import data as arguments
from rasa.cli.arguments import default_arguments
import rasa.cli.utils
from rasa.core.training.converters.responses_prefix_converter import (
DomainResponsePrefixConverter,
StoryResponsePrefixConverter,
)
import rasa.nlu.convert
from rasa.shared.constants import (
DEFAULT_DATA_PATH,
DEFAULT_CONFIG_PATH,
Expand All @@ -24,6 +19,10 @@
)
import rasa.shared.data
from rasa.shared.core.constants import (
POLICY_NAME_FALLBACK,
POLICY_NAME_FORM,
POLICY_NAME_MAPPING,
POLICY_NAME_TWO_STAGE_FALLBACK,
USER_INTENT_OUT_OF_SCOPE,
ACTION_DEFAULT_FALLBACK_NAME,
)
Expand All @@ -38,18 +37,13 @@
import rasa.shared.nlu.training_data.util
import rasa.shared.utils.cli
import rasa.utils.common
from rasa.utils.converter import TrainingDataConverter
from rasa.validator import Validator
from rasa.shared.core.domain import Domain, InvalidDomain
import rasa.shared.utils.io
import rasa.core.config
from rasa.core.policies.form_policy import FormPolicy
from rasa.core.policies.fallback import FallbackPolicy
from rasa.core.policies.two_stage_fallback import TwoStageFallbackPolicy
from rasa.core.policies.mapping_policy import MappingPolicy

if TYPE_CHECKING:
from rasa.shared.core.training_data.structures import StoryStep
from rasa.validator import Validator
from rasa.utils.converter import TrainingDataConverter

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -242,6 +236,7 @@ def validate_files(args: argparse.Namespace, stories_only: bool = False) -> None
args: Commandline arguments
stories_only: If `True`, only the story structure is validated.
"""
from rasa.validator import Validator

config = rasa.cli.utils.get_validated_path(
args.config, "config", DEFAULT_CONFIG_PATH, none_is_valid=True
Expand Down Expand Up @@ -278,15 +273,15 @@ def validate_stories(args: argparse.Namespace) -> None:
validate_files(args, stories_only=True)


def _validate_domain(validator: Validator) -> bool:
def _validate_domain(validator: "Validator") -> bool:
return validator.verify_domain_validity()


def _validate_nlu(validator: Validator, args: argparse.Namespace) -> bool:
def _validate_nlu(validator: "Validator", args: argparse.Namespace) -> bool:
return validator.verify_nlu(not args.fail_on_warnings)


def _validate_story_structure(validator: Validator, args: argparse.Namespace) -> bool:
def _validate_story_structure(validator: "Validator", args: argparse.Namespace) -> bool:
# Check if a valid setting for `max_history` was given
if isinstance(args.max_history, int) and args.max_history < 1:
raise argparse.ArgumentTypeError(
Expand All @@ -299,6 +294,8 @@ def _validate_story_structure(validator: Validator, args: argparse.Namespace) ->


def _convert_nlu_data(args: argparse.Namespace) -> None:
import rasa.nlu.convert

from rasa.nlu.training_data.converters.nlu_markdown_to_yaml_converter import (
NLUMarkdownToYamlConverter,
)
Expand Down Expand Up @@ -356,8 +353,14 @@ def _convert_nlg_data(args: argparse.Namespace) -> None:

def _migrate_responses(args: argparse.Namespace) -> None:
"""Migrate retrieval intent responses to the new 2.0 format.
It does so modifying the stories and domain files.
"""
from rasa.core.training.converters.responses_prefix_converter import (
DomainResponsePrefixConverter,
StoryResponsePrefixConverter,
)

if args.format == "yaml":
rasa.utils.common.run_in_loop(
_convert_to_yaml(args.out, args.domain, DomainResponsePrefixConverter())
Expand All @@ -374,7 +377,7 @@ def _migrate_responses(args: argparse.Namespace) -> None:


async def _convert_to_yaml(
out_path: Text, data_path: Text, converter: TrainingDataConverter
out_path: Text, data_path: Text, converter: "TrainingDataConverter"
) -> None:

output = Path(out_path)
Expand Down Expand Up @@ -415,7 +418,7 @@ async def _convert_to_yaml(


async def _convert_file_to_yaml(
source_file: Path, target_dir: Path, converter: TrainingDataConverter
source_file: Path, target_dir: Path, converter: "TrainingDataConverter"
) -> bool:
"""Converts a single training data file to `YAML` format.
Expand Down Expand Up @@ -447,6 +450,8 @@ def _migrate_model_config(args: argparse.Namespace) -> None:
Args:
args: The commandline args with the required paths.
"""
import rasa.core.config

configuration_file = Path(args.config)
model_configuration = _get_configuration(configuration_file)

Expand Down Expand Up @@ -499,32 +504,32 @@ def _get_configuration(path: Path) -> Dict:
_assert_two_stage_fallback_policy_is_migratable(config)
_assert_only_one_fallback_policy_present(policy_names)

if FormPolicy.__name__ in policy_names:
if POLICY_NAME_FORM in policy_names:
_warn_about_manual_forms_migration()

return config


def _assert_config_needs_migration(policies: List[Text]) -> None:
migratable_policies = {
MappingPolicy.__name__,
FallbackPolicy.__name__,
TwoStageFallbackPolicy.__name__,
POLICY_NAME_MAPPING,
POLICY_NAME_FALLBACK,
POLICY_NAME_TWO_STAGE_FALLBACK,
}

if not migratable_policies.intersection((set(policies))):
rasa.shared.utils.cli.print_error_and_exit(
f"No policies were found which need migration. This command can migrate "
f"'{MappingPolicy.__name__}', '{FallbackPolicy.__name__}' and "
f"'{TwoStageFallbackPolicy.__name__}'."
f"'{POLICY_NAME_MAPPING}', '{POLICY_NAME_FALLBACK}' and "
f"'{POLICY_NAME_TWO_STAGE_FALLBACK}'."
)


def _warn_about_manual_forms_migration() -> None:
rasa.shared.utils.cli.print_warning(
f"Your model configuration contains the '{FormPolicy.__name__}'. "
f"Note that this command does not migrate the '{FormPolicy.__name__}' and "
f"you have to migrate the '{FormPolicy.__name__}' manually. "
f"Your model configuration contains the '{POLICY_NAME_FORM}'. "
f"Note that this command does not migrate the '{POLICY_NAME_FORM}' and "
f"you have to migrate the '{POLICY_NAME_FORM}' manually. "
f"Please see the migration guide for further details: "
f"{DOCS_URL_MIGRATION_GUIDE}"
)
Expand All @@ -533,7 +538,7 @@ def _warn_about_manual_forms_migration() -> None:
def _assert_nlu_pipeline_given(config: Dict, policy_names: List[Text]) -> None:
if not config.get("pipeline") and any(
policy in policy_names
for policy in [FallbackPolicy.__name__, TwoStageFallbackPolicy.__name__]
for policy in [POLICY_NAME_FALLBACK, POLICY_NAME_TWO_STAGE_FALLBACK]
):
rasa.shared.utils.cli.print_error_and_exit(
"The model configuration has to include an NLU pipeline. This is required "
Expand All @@ -546,7 +551,7 @@ def _assert_two_stage_fallback_policy_is_migratable(config: Dict) -> None:
(
policy_config
for policy_config in config.get("policies", [])
if policy_config.get("name") == TwoStageFallbackPolicy.__name__
if policy_config.get("name") == POLICY_NAME_TWO_STAGE_FALLBACK
),
None,
)
Expand Down Expand Up @@ -583,10 +588,7 @@ def _assert_two_stage_fallback_policy_is_migratable(config: Dict) -> None:


def _assert_only_one_fallback_policy_present(policies: List[Text]) -> None:
if (
FallbackPolicy.__name__ in policies
and TwoStageFallbackPolicy.__name__ in policies
):
if POLICY_NAME_FALLBACK in policies and POLICY_NAME_TWO_STAGE_FALLBACK in policies:
rasa.shared.utils.cli.print_error_and_exit(
"Your policy configuration contains two configured policies for handling "
"fallbacks. Please decide on one."
Expand Down

0 comments on commit 2dd3fe9

Please sign in to comment.