dc6079821b
CI Github Actions / Run Tests (push) Waiting to run
Automatic PR Merger / mergepal (push) Waiting to run
Semgrep / Semgrep Workflow Security Scan (push) Waiting to run
Docs Tests / Check for file changes (push) Has been cancelled
Docs Tests / Test Documentation (push) Has been cancelled
Docs Tests / Documentation Linting Checks (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-policies) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (ubuntu-24.04, 3.10) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (ubuntu-24.04, 3.8) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (ubuntu-24.04, 3.9) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (windows-2022, 3.10) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (windows-2022, 3.8) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (windows-2022, 3.9) (push) Has been cancelled
Continuous Integration / Check for file changes (push) Has been cancelled
Continuous Integration / Wait for docs tests (push) Has been cancelled
Continuous Integration / Code Quality (push) Has been cancelled
Continuous Integration / Check for changelog (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Upload coverage reports to codeclimate (push) Has been cancelled
Continuous Integration / Run Non-Sequential Integration Tests (push) Has been cancelled
Continuous Integration / Run Broker Integration Tests (push) Has been cancelled
Continuous Integration / Run Sequential Integration Tests (push) Has been cancelled
Continuous Integration / Build Docker base images and setup environment (push) Has been cancelled
Continuous Integration / Build Docker (default) (push) Has been cancelled
Continuous Integration / Build Docker (full) (push) Has been cancelled
Continuous Integration / Build Docker (mitie-en) (push) Has been cancelled
Continuous Integration / Build Docker (spacy-de) (push) Has been cancelled
Continuous Integration / Build Docker (spacy-en) (push) Has been cancelled
Continuous Integration / Build Docker (spacy-it) (push) Has been cancelled
Continuous Integration / Deploy to PyPI (push) Has been cancelled
Continuous Integration / Notify Slack & Publish Release Notes (push) Has been cancelled
Publish Documentation / Evaluate release tag (push) Has been cancelled
Publish Documentation / Prebuild Docs (push) Has been cancelled
Publish Documentation / Preview Docs (push) Has been cancelled
Publish Documentation / Check for file changes (push) Has been cancelled
Publish Documentation / Publish Docs (push) Has been cancelled
1223 lines
35 KiB
Python
1223 lines
35 KiB
Python
from typing import Any, Callable, Dict, Text, Tuple, Type, Optional, List
|
|
from collections import namedtuple
|
|
import itertools
|
|
from unittest.mock import Mock
|
|
|
|
import pytest
|
|
from rasa.core.policies.policy import PolicyPrediction
|
|
|
|
from rasa.engine import validation
|
|
from rasa.engine.exceptions import GraphSchemaValidationException
|
|
from rasa.engine.graph import (
|
|
GraphComponent,
|
|
ExecutionContext,
|
|
GraphSchema,
|
|
SchemaNode,
|
|
GraphModelConfiguration,
|
|
)
|
|
from rasa.engine.constants import PLACEHOLDER_IMPORTER
|
|
from rasa.engine.storage.resource import Resource
|
|
from rasa.engine.storage.storage import ModelStorage
|
|
from rasa.shared.core.domain import Domain
|
|
from rasa.shared.data import TrainingType
|
|
from rasa.shared.importers.importer import TrainingDataImporter
|
|
from rasa.shared.nlu.training_data.message import Message
|
|
from rasa.shared.nlu.training_data.training_data import TrainingData
|
|
|
|
|
|
class TestComponentWithoutRun(GraphComponent):
|
|
@classmethod
|
|
def create(
|
|
cls,
|
|
config: Dict[Text, Any],
|
|
model_storage: ModelStorage,
|
|
resource: Resource,
|
|
execution_context: ExecutionContext,
|
|
) -> GraphComponent:
|
|
return cls()
|
|
|
|
|
|
class TestComponentWithRun(TestComponentWithoutRun):
|
|
def run(self) -> TrainingData:
|
|
pass
|
|
|
|
|
|
class TestComponentWithRunAndParam(TestComponentWithoutRun):
|
|
def run(self, training_data: TrainingData) -> TrainingData:
|
|
pass
|
|
|
|
|
|
class TestNLUTarget(TestComponentWithoutRun):
|
|
def run(self) -> List[Message]:
|
|
pass
|
|
|
|
|
|
class TestCoreTarget(TestComponentWithoutRun):
|
|
def run(self) -> PolicyPrediction:
|
|
pass
|
|
|
|
|
|
class TestComponentWithClsTypeHints(GraphComponent):
|
|
@classmethod
|
|
def create(
|
|
cls: "TestComponentWithClsTypeHints",
|
|
config: Dict[Text, Any],
|
|
model_storage: ModelStorage,
|
|
resource: Resource,
|
|
execution_context: ExecutionContext,
|
|
) -> GraphComponent:
|
|
return cls()
|
|
|
|
|
|
DEFAULT_PREDICT_SCHEMA = GraphSchema(
|
|
{
|
|
"nlu_target": SchemaNode(
|
|
needs={},
|
|
uses=TestNLUTarget,
|
|
eager=True,
|
|
constructor_name="load",
|
|
fn="run",
|
|
config={},
|
|
)
|
|
}
|
|
)
|
|
|
|
|
|
def create_test_schema(
|
|
uses: Type, # The unspecified type is on purpose to enable testing of invalid cases
|
|
constructor_name: Text = "create",
|
|
run_fn: Text = "run",
|
|
needs: Optional[Dict[Text, Text]] = None,
|
|
eager: bool = True,
|
|
parent: Optional[Type[GraphComponent]] = None,
|
|
language: Optional[Text] = None,
|
|
is_train_graph: bool = True,
|
|
) -> GraphModelConfiguration:
|
|
|
|
parent_node = {}
|
|
if parent:
|
|
parent_node = {
|
|
"parent": SchemaNode(
|
|
needs={}, uses=parent, constructor_name="create", fn="run", config={}
|
|
)
|
|
}
|
|
|
|
train_schema = GraphSchema({})
|
|
predict_schema = DEFAULT_PREDICT_SCHEMA
|
|
# noinspection PyTypeChecker
|
|
schema = GraphSchema(
|
|
{
|
|
"my_node": SchemaNode(
|
|
needs=needs or {},
|
|
uses=uses,
|
|
eager=eager,
|
|
constructor_name=constructor_name,
|
|
fn=run_fn,
|
|
config={},
|
|
),
|
|
**DEFAULT_PREDICT_SCHEMA.nodes,
|
|
**parent_node,
|
|
}
|
|
)
|
|
|
|
if is_train_graph:
|
|
train_schema = schema
|
|
else:
|
|
predict_schema = schema
|
|
|
|
return GraphModelConfiguration(
|
|
train_schema=train_schema,
|
|
predict_schema=predict_schema,
|
|
training_type=TrainingType.BOTH,
|
|
assistant_id="test_assistant",
|
|
core_target=None,
|
|
nlu_target="nlu_target",
|
|
language=language,
|
|
)
|
|
|
|
|
|
def test_graph_component_is_no_graph_component():
|
|
class MyComponent:
|
|
def other(self) -> TrainingData:
|
|
pass
|
|
|
|
graph_config = create_test_schema(uses=MyComponent)
|
|
|
|
with pytest.raises(GraphSchemaValidationException, match="implement .+ interface"):
|
|
validation.validate(graph_config)
|
|
|
|
|
|
def test_graph_component_fn_does_not_exist():
|
|
graph_config = create_test_schema(uses=TestComponentWithRun, run_fn="some_fn")
|
|
|
|
with pytest.raises(
|
|
GraphSchemaValidationException, match="required method 'some_fn'"
|
|
):
|
|
validation.validate(graph_config)
|
|
|
|
|
|
def test_graph_output_is_not_fingerprintable_int():
|
|
class MyComponent(TestComponentWithoutRun):
|
|
def run(self) -> int:
|
|
pass
|
|
|
|
graph_config = create_test_schema(uses=MyComponent)
|
|
|
|
with pytest.raises(GraphSchemaValidationException, match="fingerprintable"):
|
|
validation.validate(graph_config)
|
|
|
|
|
|
def test_predict_graph_output_is_not_fingerprintable():
|
|
class MyComponent(TestComponentWithoutRun):
|
|
def run(self) -> int:
|
|
pass
|
|
|
|
graph_config = create_test_schema(uses=MyComponent, is_train_graph=False)
|
|
|
|
validation.validate(graph_config)
|
|
|
|
|
|
def test_graph_output_is_not_fingerprintable_any():
|
|
class MyComponent(TestComponentWithoutRun):
|
|
def run(self) -> Any:
|
|
pass
|
|
|
|
graph_config = create_test_schema(uses=MyComponent)
|
|
|
|
with pytest.raises(GraphSchemaValidationException, match="fingerprintable"):
|
|
validation.validate(graph_config)
|
|
|
|
|
|
def test_graph_output_is_not_fingerprintable_None():
|
|
class MyComponent(TestComponentWithoutRun):
|
|
def run(self) -> None:
|
|
pass
|
|
|
|
graph_config = create_test_schema(uses=MyComponent)
|
|
|
|
with pytest.raises(GraphSchemaValidationException, match="fingerprintable"):
|
|
validation.validate(graph_config)
|
|
|
|
|
|
def test_graph_with_forward_referenced_output_type():
|
|
class MyComponent(TestComponentWithoutRun):
|
|
# The non imported type annotation is on purpose so we can provoke a error in
|
|
# the test
|
|
def run(self) -> "UserUttered": # noqa: F821
|
|
pass
|
|
|
|
graph_config = create_test_schema(uses=MyComponent)
|
|
|
|
with pytest.raises(GraphSchemaValidationException, match="forward reference"):
|
|
validation.validate(graph_config)
|
|
|
|
|
|
def test_graph_output_missing_type_annotation():
|
|
class MyComponent(TestComponentWithoutRun):
|
|
def run(self):
|
|
pass
|
|
|
|
graph_config = create_test_schema(uses=MyComponent)
|
|
|
|
with pytest.raises(
|
|
GraphSchemaValidationException, match="does not have a type annotation"
|
|
):
|
|
validation.validate(graph_config)
|
|
|
|
|
|
def test_graph_with_fingerprintable_output():
|
|
class MyComponent(TestComponentWithoutRun):
|
|
def run(self) -> TrainingData:
|
|
pass
|
|
|
|
graph_config = create_test_schema(uses=MyComponent)
|
|
|
|
validation.validate(graph_config)
|
|
|
|
|
|
class MyTrainingData(TrainingData):
|
|
pass
|
|
|
|
|
|
def test_graph_with_fingerprintable_output_subclass():
|
|
class MyComponent(TestComponentWithoutRun):
|
|
def run(self) -> MyTrainingData:
|
|
pass
|
|
|
|
graph_config = create_test_schema(uses=MyComponent)
|
|
|
|
validation.validate(graph_config)
|
|
|
|
|
|
def test_graph_constructor_missing():
|
|
class MyComponent(TestComponentWithoutRun):
|
|
def run(self) -> TrainingData:
|
|
pass
|
|
|
|
graph_config = create_test_schema(uses=MyComponent, constructor_name="invalid")
|
|
|
|
with pytest.raises(
|
|
GraphSchemaValidationException, match="required method 'invalid'"
|
|
):
|
|
validation.validate(graph_config)
|
|
|
|
|
|
def test_graph_constructor_config_wrong_type():
|
|
class MyComponent(TestComponentWithRun):
|
|
@classmethod
|
|
def create(
|
|
cls,
|
|
config: Dict[int, int],
|
|
model_storage: ModelStorage,
|
|
resource: Resource,
|
|
execution_context: ExecutionContext,
|
|
) -> GraphComponent:
|
|
pass
|
|
|
|
graph_config = create_test_schema(uses=MyComponent)
|
|
|
|
with pytest.raises(GraphSchemaValidationException, match="incompatible type"):
|
|
validation.validate(graph_config)
|
|
|
|
|
|
def test_graph_constructor_resource_wrong_type():
|
|
class MyComponent(TestComponentWithRun):
|
|
@classmethod
|
|
def create(
|
|
cls,
|
|
config: Dict[Text, Any],
|
|
model_storage: ModelStorage,
|
|
resource: Dict,
|
|
execution_context: ExecutionContext,
|
|
) -> GraphComponent:
|
|
pass
|
|
|
|
graph_config = create_test_schema(uses=MyComponent)
|
|
|
|
with pytest.raises(GraphSchemaValidationException, match="incompatible type"):
|
|
validation.validate(graph_config)
|
|
|
|
|
|
def test_graph_constructor_model_storage_wrong_type():
|
|
class MyComponent(TestComponentWithRun):
|
|
@classmethod
|
|
def create(
|
|
cls,
|
|
config: Dict[Text, Any],
|
|
model_storage: Any,
|
|
resource: Resource,
|
|
execution_context: ExecutionContext,
|
|
) -> GraphComponent:
|
|
pass
|
|
|
|
graph_config = create_test_schema(uses=MyComponent)
|
|
|
|
with pytest.raises(GraphSchemaValidationException, match="incompatible type"):
|
|
validation.validate(graph_config)
|
|
|
|
|
|
def test_graph_constructor_execution_context_wrong_type():
|
|
class MyComponent(TestComponentWithRun):
|
|
@classmethod
|
|
def create(
|
|
cls,
|
|
config: Dict[Text, Any],
|
|
model_storage: ModelStorage,
|
|
resource: Resource,
|
|
execution_context: Any,
|
|
) -> GraphComponent:
|
|
pass
|
|
|
|
graph_config = create_test_schema(uses=MyComponent)
|
|
|
|
with pytest.raises(GraphSchemaValidationException, match="incompatible type"):
|
|
validation.validate(graph_config)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"current_language, supported_languages",
|
|
[("de", ["en"]), ("en", ["zh", "fi"]), ("us", [])],
|
|
)
|
|
def test_graph_constructor_execution_not_supported_language(
|
|
current_language: Text, supported_languages: Optional[List[Text]]
|
|
):
|
|
class MyComponent(TestComponentWithRun):
|
|
@staticmethod
|
|
def supported_languages() -> Optional[List[Text]]:
|
|
return supported_languages
|
|
|
|
graph_config = create_test_schema(uses=MyComponent, language=current_language)
|
|
|
|
with pytest.raises(
|
|
GraphSchemaValidationException, match="does not support .* language"
|
|
):
|
|
validation.validate(graph_config)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"current_language, supported_languages",
|
|
[(None, None), ("en", ["zh", "en"]), ("zh", None), (None, ["en"])],
|
|
)
|
|
def test_graph_constructor_execution_supported_language(
|
|
current_language: Optional[Text], supported_languages: Optional[List[Text]]
|
|
):
|
|
class MyComponent(TestComponentWithRun):
|
|
@staticmethod
|
|
def supported_languages() -> Optional[List[Text]]:
|
|
return supported_languages
|
|
|
|
graph_config = create_test_schema(uses=MyComponent, language=current_language)
|
|
|
|
validation.validate(graph_config)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"current_language, not_supported_languages", [("de", ["de", "en"]), ("en", ["en"])]
|
|
)
|
|
def test_graph_constructor_execution_exclusive_list_not_supported_language(
|
|
current_language: Text, not_supported_languages: Optional[List[Text]]
|
|
):
|
|
class MyComponent(TestComponentWithRun):
|
|
@staticmethod
|
|
def not_supported_languages() -> Optional[List[Text]]:
|
|
return not_supported_languages
|
|
|
|
graph_config = create_test_schema(
|
|
uses=MyComponent, language=current_language, is_train_graph=False
|
|
)
|
|
|
|
with pytest.raises(
|
|
GraphSchemaValidationException, match="does not support .* language"
|
|
):
|
|
validation.validate(graph_config)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"current_language, not_supported_languages",
|
|
[(None, None), ("en", ["zh"]), ("zh", None), (None, ["de"])],
|
|
)
|
|
def test_graph_constructor_execution_exclusive_list_supported_language(
|
|
current_language: Optional[Text], not_supported_languages: Optional[List[Text]]
|
|
):
|
|
class MyComponent(TestComponentWithRun):
|
|
@staticmethod
|
|
def not_supported_languages() -> Optional[List[Text]]:
|
|
return not_supported_languages
|
|
|
|
graph_config = create_test_schema(
|
|
uses=MyComponent, language=current_language, is_train_graph=False
|
|
)
|
|
|
|
validation.validate(graph_config)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"required_packages", [["pytorch"], ["tensorflow", "kubernetes"]]
|
|
)
|
|
def test_graph_missing_package_requirements(required_packages: List[Text]):
|
|
class MyComponent(TestComponentWithRun):
|
|
@staticmethod
|
|
def required_packages() -> List[Text]:
|
|
"""Any extra python dependencies required for this component to run."""
|
|
return required_packages
|
|
|
|
graph_config = create_test_schema(uses=MyComponent)
|
|
|
|
with pytest.raises(GraphSchemaValidationException, match="not installed"):
|
|
validation.validate(graph_config)
|
|
|
|
|
|
@pytest.mark.parametrize("required_packages", [["tensorflow"], ["tensorflow", "numpy"]])
|
|
def test_graph_satisfied_package_requirements(required_packages: List[Text]):
|
|
class MyComponent(TestComponentWithRun):
|
|
@staticmethod
|
|
def required_packages() -> List[Text]:
|
|
"""Any extra python dependencies required for this component to run."""
|
|
return required_packages
|
|
|
|
graph_config = create_test_schema(uses=MyComponent)
|
|
|
|
validation.validate(graph_config)
|
|
|
|
|
|
def test_run_param_not_satisfied():
|
|
class MyComponent(TestComponentWithoutRun):
|
|
def run(self, some_param: TrainingData) -> TrainingData:
|
|
pass
|
|
|
|
graph_config = create_test_schema(uses=MyComponent)
|
|
|
|
with pytest.raises(GraphSchemaValidationException, match="needs the param"):
|
|
validation.validate(graph_config)
|
|
|
|
|
|
def test_run_param_satifisfied_due_to_default():
|
|
class MyComponent(TestComponentWithoutRun):
|
|
def run(self, some_param: TrainingData = TrainingData()) -> TrainingData:
|
|
pass
|
|
|
|
graph_config = create_test_schema(uses=MyComponent)
|
|
|
|
validation.validate(graph_config)
|
|
|
|
|
|
def test_too_many_supplied_params():
|
|
graph_config = create_test_schema(
|
|
uses=TestComponentWithRun, needs={"some_param": "parent"}
|
|
)
|
|
|
|
with pytest.raises(
|
|
GraphSchemaValidationException, match="does not accept a parameter"
|
|
):
|
|
validation.validate(graph_config)
|
|
|
|
|
|
def test_too_many_supplied_params_but_kwargs():
|
|
class MyComponent(TestComponentWithoutRun):
|
|
def run(self, **kwargs: Any) -> TrainingData:
|
|
pass
|
|
|
|
graph_config = create_test_schema(
|
|
uses=MyComponent, needs={"some_param": "parent"}, parent=TestComponentWithRun
|
|
)
|
|
|
|
validation.validate(graph_config)
|
|
|
|
|
|
def test_run_fn_with_variable_length_positional_param():
|
|
class MyComponent(TestComponentWithoutRun):
|
|
def run(self, *args: Any, some_param: TrainingData) -> TrainingData:
|
|
pass
|
|
|
|
graph_config = create_test_schema(
|
|
uses=MyComponent, needs={"some_param": "parent"}, parent=TestComponentWithRun
|
|
)
|
|
|
|
validation.validate(graph_config)
|
|
|
|
|
|
def test_matching_params_due_to_constructor():
|
|
class MyComponent(TestComponentWithRun):
|
|
@classmethod
|
|
def load(
|
|
cls,
|
|
config: Dict[Text, Any],
|
|
model_storage: ModelStorage,
|
|
resource: Resource,
|
|
execution_context: ExecutionContext,
|
|
some_param: TrainingData,
|
|
) -> GraphComponent:
|
|
pass
|
|
|
|
graph_config = create_test_schema(
|
|
uses=MyComponent,
|
|
needs={"some_param": "parent"},
|
|
eager=False,
|
|
constructor_name="load",
|
|
parent=TestComponentWithRun,
|
|
)
|
|
|
|
validation.validate(graph_config)
|
|
|
|
|
|
def test_matching_params_due_to_constructor_but_eager():
|
|
class MyComponent(TestComponentWithRun):
|
|
@classmethod
|
|
def load(
|
|
cls,
|
|
config: Dict[Text, Any],
|
|
model_storage: ModelStorage,
|
|
resource: Resource,
|
|
execution_context: ExecutionContext,
|
|
some_param: TrainingData,
|
|
) -> GraphComponent:
|
|
pass
|
|
|
|
graph_config = create_test_schema(
|
|
uses=MyComponent,
|
|
needs={"some_param": "parent"},
|
|
eager=True,
|
|
constructor_name="load",
|
|
)
|
|
|
|
with pytest.raises(
|
|
GraphSchemaValidationException, match="which is used during training"
|
|
):
|
|
validation.validate(graph_config)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"eager, error_message", [(True, "during training"), (False, "needs the param")]
|
|
)
|
|
def test_unsatisfied_constructor(eager: bool, error_message: Text):
|
|
class MyComponent(TestComponentWithRun):
|
|
@classmethod
|
|
def load(
|
|
cls,
|
|
config: Dict[Text, Any],
|
|
model_storage: ModelStorage,
|
|
resource: Resource,
|
|
execution_context: ExecutionContext,
|
|
some_param: TrainingData,
|
|
) -> GraphComponent:
|
|
pass
|
|
|
|
graph_config = create_test_schema(
|
|
uses=MyComponent, eager=eager, constructor_name="load"
|
|
)
|
|
|
|
with pytest.raises(GraphSchemaValidationException, match=error_message):
|
|
validation.validate(graph_config)
|
|
|
|
|
|
def test_parent_is_missing():
|
|
graph_config = create_test_schema(
|
|
uses=TestComponentWithRunAndParam,
|
|
needs={"training_data": "not existing parent"},
|
|
)
|
|
|
|
with pytest.raises(
|
|
GraphSchemaValidationException, match="The component is missing from"
|
|
):
|
|
validation.validate(graph_config)
|
|
|
|
|
|
def test_parent_supplying_wrong_type():
|
|
class MyUnreliableParent(TestComponentWithoutRun):
|
|
def run(self) -> Domain:
|
|
pass
|
|
|
|
graph_config = create_test_schema(
|
|
uses=TestComponentWithRunAndParam,
|
|
parent=MyUnreliableParent,
|
|
needs={"training_data": "parent"},
|
|
)
|
|
|
|
with pytest.raises(
|
|
GraphSchemaValidationException, match="expects an input of type"
|
|
):
|
|
validation.validate(graph_config)
|
|
|
|
|
|
def test_parent_supplying_wrong_type_to_constructor():
|
|
class MyUnreliableParent(TestComponentWithoutRun):
|
|
def run(self) -> Domain:
|
|
pass
|
|
|
|
class MyComponent(TestComponentWithRun):
|
|
@classmethod
|
|
def load(
|
|
cls,
|
|
config: Dict[Text, Any],
|
|
model_storage: ModelStorage,
|
|
resource: Resource,
|
|
execution_context: ExecutionContext,
|
|
some_param: TrainingData,
|
|
) -> GraphComponent:
|
|
pass
|
|
|
|
graph_config = create_test_schema(
|
|
uses=MyComponent,
|
|
eager=False,
|
|
constructor_name="load",
|
|
parent=MyUnreliableParent,
|
|
needs={"some_param": "parent"},
|
|
)
|
|
|
|
with pytest.raises(
|
|
GraphSchemaValidationException, match="expects an input of type"
|
|
):
|
|
validation.validate(graph_config)
|
|
|
|
|
|
def test_parent_supplying_subtype():
|
|
class Parent(TestComponentWithoutRun):
|
|
def run(self) -> MyTrainingData:
|
|
pass
|
|
|
|
class MyComponent(TestComponentWithoutRun):
|
|
def run(self, training_data: TrainingData) -> TrainingData:
|
|
pass
|
|
|
|
graph_config = create_test_schema(
|
|
uses=MyComponent, parent=Parent, needs={"training_data": "parent"}
|
|
)
|
|
|
|
validation.validate(graph_config)
|
|
|
|
|
|
def test_child_accepting_any_type_from_parent():
|
|
class Parent(TestComponentWithoutRun):
|
|
def run(self) -> MyTrainingData:
|
|
pass
|
|
|
|
class MyComponent(TestComponentWithoutRun):
|
|
def run(self, training_data: Any) -> TrainingData:
|
|
pass
|
|
|
|
graph_config = create_test_schema(
|
|
uses=MyComponent, parent=Parent, needs={"training_data": "parent"}
|
|
)
|
|
|
|
validation.validate(graph_config)
|
|
|
|
|
|
@pytest.mark.parametrize("is_train_graph", [True, False])
|
|
def test_cycle(is_train_graph: bool):
|
|
class MyTestComponent(TestComponentWithoutRun):
|
|
def run(self, training_data: TrainingData) -> TrainingData:
|
|
pass
|
|
|
|
train_schema = GraphSchema({})
|
|
predict_schema = DEFAULT_PREDICT_SCHEMA
|
|
|
|
schema = GraphSchema(
|
|
{
|
|
"A": SchemaNode(
|
|
needs={"training_data": "B"},
|
|
uses=MyTestComponent,
|
|
eager=True,
|
|
constructor_name="create",
|
|
fn="run",
|
|
is_target=True,
|
|
config={},
|
|
),
|
|
"B": SchemaNode(
|
|
needs={"training_data": "C"},
|
|
uses=MyTestComponent,
|
|
eager=True,
|
|
constructor_name="create",
|
|
fn="run",
|
|
config={},
|
|
),
|
|
"C": SchemaNode(
|
|
needs={"training_data": "A"},
|
|
uses=MyTestComponent,
|
|
eager=True,
|
|
constructor_name="create",
|
|
fn="run",
|
|
config={},
|
|
),
|
|
}
|
|
)
|
|
|
|
if is_train_graph:
|
|
train_schema = schema
|
|
else:
|
|
predict_schema = schema
|
|
|
|
with pytest.raises(GraphSchemaValidationException, match="Cycles"):
|
|
validation.validate(
|
|
GraphModelConfiguration(
|
|
train_schema=train_schema,
|
|
predict_schema=predict_schema,
|
|
training_type=TrainingType.BOTH,
|
|
assistant_id="test_assistant",
|
|
language=None,
|
|
core_target=None,
|
|
nlu_target="nlu_target",
|
|
)
|
|
)
|
|
|
|
|
|
def test_validation_with_placeholders():
|
|
class MyTestComponent(TestComponentWithoutRun):
|
|
def run(self, training_data: TrainingDataImporter) -> TrainingDataImporter:
|
|
pass
|
|
|
|
graph_config = GraphSchema(
|
|
{
|
|
"A": SchemaNode(
|
|
needs={"training_data": "B"},
|
|
uses=MyTestComponent,
|
|
eager=True,
|
|
constructor_name="create",
|
|
fn="run",
|
|
is_target=True,
|
|
config={},
|
|
),
|
|
"B": SchemaNode(
|
|
needs={"training_data": PLACEHOLDER_IMPORTER},
|
|
uses=MyTestComponent,
|
|
eager=True,
|
|
constructor_name="create",
|
|
fn="run",
|
|
config={},
|
|
),
|
|
}
|
|
)
|
|
|
|
# Does not raise
|
|
validation.validate(
|
|
GraphModelConfiguration(
|
|
train_schema=graph_config,
|
|
predict_schema=DEFAULT_PREDICT_SCHEMA,
|
|
training_type=TrainingType.BOTH,
|
|
assistant_id="test_assistant",
|
|
language=None,
|
|
core_target=None,
|
|
nlu_target="nlu_target",
|
|
)
|
|
)
|
|
|
|
|
|
def test_validation_with_missing_nlu_target():
|
|
graph_config = GraphSchema(
|
|
{
|
|
"A": SchemaNode(
|
|
needs={},
|
|
uses=TestNLUTarget,
|
|
eager=True,
|
|
constructor_name="create",
|
|
fn="run",
|
|
config={},
|
|
)
|
|
}
|
|
)
|
|
|
|
with pytest.raises(
|
|
GraphSchemaValidationException, match="no target for the 'nlu_target'"
|
|
):
|
|
validation.validate(
|
|
GraphModelConfiguration(
|
|
train_schema=GraphSchema({}),
|
|
predict_schema=graph_config,
|
|
training_type=TrainingType.BOTH,
|
|
assistant_id="test_assistant",
|
|
language=None,
|
|
core_target=None,
|
|
nlu_target=None,
|
|
)
|
|
)
|
|
|
|
|
|
def test_validation_with_nlu_target_used_by_other_node():
|
|
class NLUTargetConsumer(TestComponentWithoutRun):
|
|
def run(self, nlu_target_output: List[Message]) -> List[Message]:
|
|
pass
|
|
|
|
graph_config = GraphSchema(
|
|
{
|
|
"A": SchemaNode(
|
|
needs={},
|
|
uses=TestNLUTarget,
|
|
eager=True,
|
|
constructor_name="create",
|
|
fn="run",
|
|
config={},
|
|
),
|
|
"B": SchemaNode(
|
|
needs={"nlu_target_output": "A"},
|
|
uses=NLUTargetConsumer,
|
|
eager=True,
|
|
constructor_name="create",
|
|
fn="run",
|
|
config={},
|
|
),
|
|
}
|
|
)
|
|
|
|
with pytest.raises(
|
|
GraphSchemaValidationException, match="uses the NLU target 'A' as input"
|
|
):
|
|
validation.validate(
|
|
GraphModelConfiguration(
|
|
train_schema=GraphSchema({}),
|
|
predict_schema=graph_config,
|
|
training_type=TrainingType.BOTH,
|
|
assistant_id="test_assistant",
|
|
language=None,
|
|
core_target=None,
|
|
nlu_target="A",
|
|
)
|
|
)
|
|
|
|
|
|
def test_validation_with_nlu_target_wrong_type():
|
|
graph_config = GraphSchema(
|
|
{
|
|
"A": SchemaNode(
|
|
needs={},
|
|
uses=TestCoreTarget,
|
|
eager=True,
|
|
constructor_name="create",
|
|
fn="run",
|
|
config={},
|
|
)
|
|
}
|
|
)
|
|
|
|
with pytest.raises(GraphSchemaValidationException, match="invalid return type"):
|
|
validation.validate(
|
|
GraphModelConfiguration(
|
|
train_schema=GraphSchema({}),
|
|
predict_schema=graph_config,
|
|
training_type=TrainingType.BOTH,
|
|
assistant_id="test_assistant",
|
|
language=None,
|
|
core_target=None,
|
|
nlu_target="A",
|
|
)
|
|
)
|
|
|
|
|
|
def test_validation_with_missing_core_target():
|
|
graph_config = GraphSchema(
|
|
{
|
|
"A": SchemaNode(
|
|
needs={},
|
|
uses=TestNLUTarget,
|
|
eager=True,
|
|
constructor_name="create",
|
|
fn="run",
|
|
config={},
|
|
)
|
|
}
|
|
)
|
|
|
|
with pytest.raises(GraphSchemaValidationException, match="invalid Core target"):
|
|
validation.validate(
|
|
GraphModelConfiguration(
|
|
train_schema=GraphSchema({}),
|
|
predict_schema=graph_config,
|
|
training_type=TrainingType.BOTH,
|
|
assistant_id="test_assistant",
|
|
language=None,
|
|
core_target="B",
|
|
nlu_target="A",
|
|
)
|
|
)
|
|
|
|
|
|
def test_validation_with_core_target_wrong_type():
|
|
graph_config = GraphSchema(
|
|
{
|
|
"A": SchemaNode(
|
|
needs={},
|
|
uses=TestNLUTarget,
|
|
eager=True,
|
|
constructor_name="create",
|
|
fn="run",
|
|
config={},
|
|
)
|
|
}
|
|
)
|
|
|
|
with pytest.raises(
|
|
GraphSchemaValidationException, match="Core model's .* invalid return type"
|
|
):
|
|
validation.validate(
|
|
GraphModelConfiguration(
|
|
train_schema=GraphSchema({}),
|
|
predict_schema=graph_config,
|
|
training_type=TrainingType.BOTH,
|
|
assistant_id="test_assistant",
|
|
language=None,
|
|
core_target="A",
|
|
nlu_target="A",
|
|
)
|
|
)
|
|
|
|
|
|
def test_validation_with_core_target_used_by_other_node():
|
|
class CoreTargetConsumer(TestComponentWithoutRun):
|
|
def run(self, core_target_output: PolicyPrediction) -> PolicyPrediction:
|
|
pass
|
|
|
|
graph_config = GraphSchema(
|
|
{
|
|
"A": SchemaNode(
|
|
needs={},
|
|
uses=TestNLUTarget,
|
|
eager=True,
|
|
constructor_name="create",
|
|
fn="run",
|
|
config={},
|
|
),
|
|
"B": SchemaNode(
|
|
needs={},
|
|
uses=TestCoreTarget,
|
|
eager=True,
|
|
constructor_name="create",
|
|
fn="run",
|
|
config={},
|
|
),
|
|
"C": SchemaNode(
|
|
needs={"core_target_output": "B"},
|
|
uses=CoreTargetConsumer,
|
|
eager=True,
|
|
constructor_name="create",
|
|
fn="run",
|
|
config={},
|
|
),
|
|
}
|
|
)
|
|
|
|
with pytest.raises(
|
|
GraphSchemaValidationException, match="uses the Core target 'B' as input"
|
|
):
|
|
validation.validate(
|
|
GraphModelConfiguration(
|
|
train_schema=GraphSchema({}),
|
|
predict_schema=graph_config,
|
|
training_type=TrainingType.BOTH,
|
|
assistant_id="test_assistant",
|
|
language=None,
|
|
core_target="B",
|
|
nlu_target="A",
|
|
)
|
|
)
|
|
|
|
|
|
def _create_run_function(num_args) -> Callable[..., TrainingData]:
|
|
# Note: setting __annotations__ is not sufficient for the validation and
|
|
# creating a function via types.FunctionType is cumbersome, so we just
|
|
# explicitly create the function we need:
|
|
if num_args == 0:
|
|
|
|
def run() -> TrainingData:
|
|
return TrainingData()
|
|
|
|
elif num_args == 1:
|
|
|
|
def run(param0: TrainingData) -> TrainingData:
|
|
return TrainingData()
|
|
|
|
elif num_args == 2:
|
|
|
|
def run(param0: TrainingData, param1: TrainingData) -> TrainingData:
|
|
return TrainingData()
|
|
|
|
elif num_args == 3:
|
|
|
|
def run(
|
|
param0: TrainingData, param1: TrainingData, param2: TrainingData
|
|
) -> TrainingData:
|
|
return TrainingData()
|
|
|
|
else:
|
|
assert False, f"This test doesn't work with num_args={num_args} ."
|
|
return run
|
|
|
|
|
|
def _create_component_type_and_subtype_with_run_function(
|
|
component_type_name: Text, needs: List[int]
|
|
) -> Tuple[Type[GraphComponent], Type[GraphComponent]]:
|
|
main_type = type(
|
|
component_type_name,
|
|
(TestComponentWithoutRun,),
|
|
{
|
|
"run": _create_run_function(num_args=len(needs)),
|
|
"create": lambda *args, **kwargs: None,
|
|
"__init__": lambda: None,
|
|
},
|
|
)
|
|
sub_type = type(f"subclass_of_{component_type_name}", (main_type,), {})
|
|
return main_type, sub_type
|
|
|
|
|
|
def _create_graph_schema_from_requirements(
|
|
node_needs_requires: List[Tuple[int, List[int], List[int]]],
|
|
targets: List[int],
|
|
use_subclass: bool,
|
|
) -> GraphSchema:
|
|
# create some component types
|
|
component_types = {
|
|
node: _create_component_type_and_subtype_with_run_function(
|
|
component_type_name=f"class_{node}", needs=needs
|
|
)
|
|
for node, needs, _ in node_needs_requires
|
|
}
|
|
|
|
# add required components
|
|
for node, _, required_components in node_needs_requires:
|
|
for component_type in component_types[node]:
|
|
component_type.required_components = Mock(
|
|
return_value=[
|
|
component_types[required][0] for required in required_components
|
|
]
|
|
)
|
|
|
|
# create graph schema
|
|
graph_schema = GraphSchema(
|
|
{
|
|
f"node-{node}": SchemaNode(
|
|
needs={
|
|
f"param{param}": f"node-{needed_node}"
|
|
for param, needed_node in enumerate(needs)
|
|
},
|
|
uses=component_types[node][use_subclass], # use subclass if required
|
|
fn="run",
|
|
constructor_name="create",
|
|
config={},
|
|
is_target=node in targets,
|
|
)
|
|
for node, needs, _ in node_needs_requires
|
|
}
|
|
)
|
|
graph_schema.nodes.update(DEFAULT_PREDICT_SCHEMA.nodes)
|
|
return graph_schema
|
|
|
|
|
|
RequiredComponentsTestCase = namedtuple(
|
|
"RequiredComponentsTestCase",
|
|
{
|
|
"node_needs_requires_tuples": List[Tuple[int, List[int], List[int]]],
|
|
"targets": List[int],
|
|
"num_unmet_requirements": int,
|
|
},
|
|
)
|
|
REQUIRED_COMPONENT_TEST_CASES: List[RequiredComponentsTestCase] = [
|
|
RequiredComponentsTestCase(
|
|
node_needs_requires_tuples=[(1, [2], [2]), (2, [], [])],
|
|
targets=[1],
|
|
num_unmet_requirements=0,
|
|
),
|
|
RequiredComponentsTestCase(
|
|
node_needs_requires_tuples=[
|
|
(1, [2, 3, 4], [2, 3, 4]),
|
|
(2, [], []),
|
|
(3, [], []),
|
|
(4, [], []),
|
|
],
|
|
targets=[1],
|
|
num_unmet_requirements=0,
|
|
),
|
|
RequiredComponentsTestCase(
|
|
node_needs_requires_tuples=[
|
|
(1, [3], [4]),
|
|
(2, [3], [4]),
|
|
(3, [4, 5], []),
|
|
(4, [], []),
|
|
(5, [6], []),
|
|
(6, [], []),
|
|
],
|
|
targets=[1, 3],
|
|
num_unmet_requirements=0,
|
|
),
|
|
RequiredComponentsTestCase(
|
|
node_needs_requires_tuples=[(1, [], [2]), (2, [], [])],
|
|
targets=[1],
|
|
num_unmet_requirements=1,
|
|
), # 2 is not reachable from 1
|
|
RequiredComponentsTestCase(
|
|
node_needs_requires_tuples=[
|
|
(1, [3], [4]),
|
|
(2, [3], [4]),
|
|
(3, [4], [5]),
|
|
(4, [], []),
|
|
(5, [], []),
|
|
],
|
|
targets=[1],
|
|
num_unmet_requirements=1, # 5 is not reachable from 3
|
|
),
|
|
RequiredComponentsTestCase(
|
|
node_needs_requires_tuples=[
|
|
(1, [2], [3]),
|
|
(2, [], [4]),
|
|
(3, [], []),
|
|
(4, [], []),
|
|
],
|
|
targets=[1],
|
|
num_unmet_requirements=2,
|
|
), # 3 and 4 are not reachable from 1 and 2
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"test_case, is_train_graph, test_subclass",
|
|
itertools.product(REQUIRED_COMPONENT_TEST_CASES, [True, False], [True, False]),
|
|
)
|
|
def test_validate_validates_required_components(
|
|
test_case: List[RequiredComponentsTestCase],
|
|
is_train_graph: bool,
|
|
test_subclass: bool,
|
|
):
|
|
train_schema = GraphSchema({})
|
|
predict_schema = DEFAULT_PREDICT_SCHEMA
|
|
graph_schema = _create_graph_schema_from_requirements(
|
|
node_needs_requires=test_case.node_needs_requires_tuples,
|
|
targets=test_case.targets,
|
|
use_subclass=test_subclass,
|
|
)
|
|
|
|
if is_train_graph:
|
|
train_schema = graph_schema
|
|
else:
|
|
predict_schema = graph_schema
|
|
graph_config = GraphModelConfiguration(
|
|
train_schema,
|
|
predict_schema,
|
|
TrainingType.BOTH,
|
|
"test_assistant",
|
|
None,
|
|
None,
|
|
"nlu_target",
|
|
)
|
|
|
|
num_unmet = test_case.num_unmet_requirements
|
|
if num_unmet == 0:
|
|
validation.validate(graph_config)
|
|
else:
|
|
message = f"{num_unmet} components are missing"
|
|
with pytest.raises(GraphSchemaValidationException, match=message):
|
|
validation.validate(graph_config)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"test_case, test_subclass",
|
|
itertools.product(REQUIRED_COMPONENT_TEST_CASES, [True, False]),
|
|
)
|
|
def test_validate_required_components(
|
|
test_case: List[RequiredComponentsTestCase], test_subclass: bool
|
|
):
|
|
graph_schema = _create_graph_schema_from_requirements(
|
|
node_needs_requires=test_case.node_needs_requires_tuples,
|
|
targets=test_case.targets,
|
|
use_subclass=test_subclass,
|
|
)
|
|
num_unmet = test_case.num_unmet_requirements
|
|
if num_unmet == 0:
|
|
validation._validate_required_components(schema=graph_schema)
|
|
else:
|
|
message = f"{num_unmet} components are missing"
|
|
with pytest.raises(GraphSchemaValidationException, match=message):
|
|
validation._validate_required_components(schema=graph_schema)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"test_case, test_subclass",
|
|
itertools.product(
|
|
[
|
|
test_case
|
|
for test_case in REQUIRED_COMPONENT_TEST_CASES
|
|
if len(test_case.targets) == 1
|
|
],
|
|
[True, False],
|
|
),
|
|
)
|
|
def test_recursively_validate_required_components(
|
|
test_case: List[RequiredComponentsTestCase], test_subclass: bool
|
|
):
|
|
graph_schema = _create_graph_schema_from_requirements(
|
|
node_needs_requires=test_case.node_needs_requires_tuples,
|
|
targets=test_case.targets,
|
|
use_subclass=test_subclass,
|
|
)
|
|
num_unmet = test_case.num_unmet_requirements
|
|
|
|
unmet_requirements, _ = validation._recursively_check_required_components(
|
|
node_name=f"node-{test_case.targets[0]}", schema=graph_schema
|
|
)
|
|
assert len(unmet_requirements) == num_unmet
|
|
|
|
|
|
def test_graph_with_cls_type_hint():
|
|
class MyComponent(TestComponentWithClsTypeHints):
|
|
def run(self) -> MyTrainingData:
|
|
pass
|
|
|
|
graph_config = create_test_schema(uses=MyComponent)
|
|
|
|
validation.validate(graph_config)
|