Files
wehub-resource-sync dc6079821b
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
Automatic PR Merger / mergepal (push) Has been cancelled
CI Github Actions / Run Tests (push) Has been cancelled
Semgrep / Semgrep Workflow Security Scan (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:24:47 +08:00

527 lines
17 KiB
Python

import os
from pathlib import Path
import copy
from typing import Callable, List, Optional, Text, Dict, Any
from _pytest.monkeypatch import MonkeyPatch
import pytest
from rasa.engine.graph import ExecutionContext, GraphComponent, GraphSchema, SchemaNode
from rasa.engine.storage.resource import Resource
from rasa.engine.storage.storage import ModelStorage
from rasa.graph_components.validators.finetuning_validator import FinetuningValidator
from rasa.nlu.tokenizers.whitespace_tokenizer import WhitespaceTokenizer
from rasa.core.policies.rule_policy import RulePolicy
from rasa.shared.constants import (
DEFAULT_CONFIG_PATH,
DEFAULT_DATA_PATH,
DEFAULT_DOMAIN_PATH,
)
from rasa.shared.core.domain import KEY_RESPONSES, Domain
from rasa.shared.importers.rasa import RasaFileImporter
from rasa.shared.importers.importer import NluDataImporter, TrainingDataImporter
import rasa.shared.utils.io
from rasa.shared.exceptions import InvalidConfigException
from rasa.shared.nlu.constants import ACTION_NAME, INTENT, TEXT
from rasa.shared.nlu.training_data.message import Message
from rasa.shared.nlu.training_data.training_data import TrainingData
@pytest.fixture
def default_resource() -> Resource:
return Resource("FineTuningValidator")
ValidationMethodType = Callable[
[TrainingDataImporter, Dict[Text, Any]], TrainingDataImporter
]
@pytest.fixture
def get_finetuning_validator(
default_model_storage: ModelStorage,
default_execution_context: ExecutionContext,
default_resource: Resource,
) -> Callable[[bool, bool, Dict[Text, Any], GraphSchema], FinetuningValidator]:
def inner(
finetuning: bool,
load: bool,
config: Dict[Text, Any],
graph_schema: Optional[GraphSchema] = None,
) -> FinetuningValidator:
if load:
constructor = FinetuningValidator.load
else:
constructor = FinetuningValidator.create
if finetuning:
default_execution_context.is_finetuning = finetuning
if graph_schema is not None:
default_execution_context.graph_schema = graph_schema
return constructor(
config={**FinetuningValidator.get_default_config(), **config},
execution_context=default_execution_context,
model_storage=default_model_storage,
resource=default_resource,
)
return inner
@pytest.fixture
def get_validation_method(
get_finetuning_validator: Callable[[bool, bool], FinetuningValidator]
) -> Callable[[bool, bool, bool, bool, GraphSchema], ValidationMethodType]:
def inner(
finetuning: bool,
load: bool,
nlu: bool,
core: bool,
graph_schema: Optional[GraphSchema] = None,
) -> ValidationMethodType:
validator = get_finetuning_validator(
finetuning=finetuning,
load=load,
config={"validate_core": core, "validate_nlu": nlu},
graph_schema=graph_schema,
)
return validator.validate
return inner
class DummyNLUDataImporter(NluDataImporter):
def __init__(self, messages: List[Message]) -> None:
self.training_data = TrainingData(training_examples=messages)
def get_config(self) -> Dict:
return {}
def get_nlu_data(self, language: Optional[Text] = "en") -> TrainingData:
return self.training_data
class EmptyDataImporter(DummyNLUDataImporter):
def __init__(self) -> None:
super().__init__([])
def _project_files(
project: Text,
config_file: Text = DEFAULT_CONFIG_PATH,
domain: Text = DEFAULT_DOMAIN_PATH,
training_files: Text = DEFAULT_DATA_PATH,
) -> TrainingDataImporter:
paths = {
"config_file": config_file,
"domain_path": domain,
"training_data_paths": training_files,
}
paths = {
k: v if v is None or Path(v).is_absolute() else os.path.join(project, v)
for k, v in paths.items()
}
paths["training_data_paths"] = [paths["training_data_paths"]]
return RasaFileImporter(**paths)
@pytest.mark.parametrize("nlu, core", [(True, False), (False, True), (True, True)])
def test_validate_after_changing_response_text_in_domain(
get_validation_method: Callable[..., ValidationMethodType],
project: Text,
nlu: bool,
core: bool,
):
# training
importer = _project_files(project)
old_domain = importer.get_domain()
validate = get_validation_method(finetuning=False, load=False, core=core, nlu=nlu)
validate(importer=importer)
# Change NLG content but keep actions the same
domain_with_changed_nlg = old_domain.as_dict()
domain_with_changed_nlg[KEY_RESPONSES]["utter_greet"].append({"text": "hi"})
domain_with_changed_nlg = Domain.from_dict(domain_with_changed_nlg)
importer.get_domain = lambda: domain_with_changed_nlg
# finetuning
loaded_validate = get_validation_method(
finetuning=False, load=True, core=core, nlu=nlu
)
assert importer.get_domain() != old_domain
loaded_validate(importer=importer)
@pytest.mark.parametrize("nlu, core", [(True, False), (False, True), (True, True)])
def test_validate_after_adding_action_to_domain(
get_validation_method: Callable[..., ValidationMethodType],
project: Text,
nlu: bool,
core: bool,
):
# training
importer = _project_files(project)
old_domain = importer.get_domain()
validate = get_validation_method(finetuning=False, load=False, core=core, nlu=nlu)
validate(importer=importer)
# Add another action - via the response key
domain_with_new_action = old_domain.as_dict()
domain_with_new_action[KEY_RESPONSES]["utter_new"] = [{"text": "hi"}]
domain_with_new_action = Domain.from_dict(domain_with_new_action)
importer.get_domain = lambda: domain_with_new_action
# finetuning
loaded_validate = get_validation_method(
finetuning=True, load=True, core=core, nlu=nlu
)
assert importer.get_domain() != old_domain
if core:
with pytest.raises(InvalidConfigException):
loaded_validate(importer=importer)
else:
loaded_validate(importer=importer)
def _get_example_schema(num_epochs: int = 5, other_parameter: int = 10) -> GraphSchema:
example_configs = [
{
"epochs": num_epochs,
"other-parameter": other_parameter,
"some-parameter": "bla",
},
{"epochs": num_epochs, "yet-other-parameter": 344},
{"no-epochs-defined-here": None},
]
return GraphSchema(
nodes={
f"node-{idx}": SchemaNode(
needs={}, uses=GraphComponent, constructor_name="", fn="", config=config
)
for idx, config in enumerate(example_configs)
}
)
@pytest.mark.parametrize("nlu, core", [(True, False), (False, True), (True, True)])
def test_validate_after_changing_epochs_in_config(
get_validation_method: Callable[..., ValidationMethodType], nlu: bool, core: bool
):
# training
schema1 = _get_example_schema(num_epochs=5)
validate = get_validation_method(
finetuning=False, load=False, nlu=nlu, core=core, graph_schema=schema1
)
validate(importer=EmptyDataImporter())
# change schema - replace all epoch settings by a different value
schema2 = _get_example_schema(num_epochs=5)
for node in schema2.nodes.values():
node.constructor_name = "other"
# finetuning - does not complain
loaded_validate = get_validation_method(
finetuning=True, load=True, nlu=nlu, core=core, graph_schema=schema2
)
loaded_validate(importer=EmptyDataImporter())
@pytest.mark.parametrize("nlu, core", [(True, False), (False, True), (True, True)])
def test_validate_after_changing_constructor(
get_validation_method: Callable[..., ValidationMethodType], nlu: bool, core: bool
):
# training
schema1 = _get_example_schema(num_epochs=5)
validate = get_validation_method(
finetuning=False, load=False, nlu=nlu, core=core, graph_schema=schema1
)
validate(importer=EmptyDataImporter())
# change schema - replace all epoch settings by a different value
schema2 = _get_example_schema(num_epochs=10)
# finetuning - does not complain
loaded_validate = get_validation_method(
finetuning=True, load=True, nlu=nlu, core=core, graph_schema=schema2
)
loaded_validate(importer=EmptyDataImporter())
@pytest.mark.parametrize("nlu, core", [(True, False), (False, True), (True, True)])
def test_validate_after_removing_node_from_schema(
get_validation_method: Callable[..., ValidationMethodType], nlu: bool, core: bool
):
# training
schema1 = _get_example_schema(num_epochs=5)
validate = get_validation_method(
finetuning=False, load=False, nlu=nlu, core=core, graph_schema=schema1
)
validate(importer=EmptyDataImporter())
# change schema - remove a node
schema2 = copy.deepcopy(schema1)
schema2.nodes.pop(next(iter(schema2.nodes.keys())))
# finetuning raises - doesn't matter if it's nlu/core/both
loaded_validate = get_validation_method(
finetuning=True, load=True, nlu=nlu, core=core, graph_schema=schema2
)
with pytest.raises(InvalidConfigException):
loaded_validate(importer=EmptyDataImporter())
@pytest.mark.parametrize("nlu, core", [(True, False), (False, True), (True, True)])
def test_validate_after_adding_node_to_schema(
get_validation_method: Callable[..., ValidationMethodType], nlu: bool, core: bool
):
# training
schema1 = _get_example_schema()
schema2 = copy.deepcopy(schema1)
schema2.nodes.pop(next(iter(schema2.nodes.keys())))
validate = get_validation_method(
finetuning=False, load=False, nlu=nlu, core=core, graph_schema=schema2
)
validate(importer=EmptyDataImporter())
# change schema - continue with the schema with one more node than before
assert len(schema1.nodes) > len(schema2.nodes)
# finetuning raises - doesn't matter if it's nlu/core/both
loaded_validate = get_validation_method(
finetuning=True, load=True, nlu=nlu, core=core, graph_schema=schema1
)
with pytest.raises(InvalidConfigException):
loaded_validate(importer=EmptyDataImporter())
@pytest.mark.parametrize(
"nlu, core, what",
[
(nlu, core, what)
for what in ["uses", "needs", "fn", "config"]
for nlu, core in [(True, False), (False, True), (True, True)]
],
)
def test_validate_after_replacing_something_in_schema(
get_validation_method: Callable[..., ValidationMethodType],
nlu: bool,
core: bool,
what: Text,
):
# training
schema1 = _get_example_schema()
validate = get_validation_method(
finetuning=False, load=False, nlu=nlu, core=core, graph_schema=schema1
)
validate(importer=EmptyDataImporter())
# change schema
schema2 = copy.deepcopy(schema1)
schema_node = schema2.nodes["node-0"]
if what == "uses":
schema_node.uses = WhitespaceTokenizer
elif what == "fn":
schema_node.fn = "a-new-function"
elif what == "needs":
schema_node.needs = {"something-new": "node-1"}
elif what == "config":
schema_node.config["other-parameter"] = "some-new-value"
else:
assert False, "Please fix this test."
# finetuning raises - doesn't matter if it's nlu/core/both
loaded_validate = get_validation_method(
finetuning=True, load=True, nlu=nlu, core=core, graph_schema=schema2
)
with pytest.raises(InvalidConfigException):
loaded_validate(importer=EmptyDataImporter())
@pytest.mark.parametrize("nlu, core", [(True, False), (False, True), (True, True)])
def test_validate_after_adding_adding_default_parameter(
get_validation_method: Callable[..., ValidationMethodType], nlu: bool, core: bool
):
# create a schema and rely on rasa to fill in defaults later
schema1 = _get_example_schema()
schema1.nodes["nlu-node"] = SchemaNode(
needs={}, uses=WhitespaceTokenizer, constructor_name="", fn="", config={}
)
schema1.nodes["core-node"] = SchemaNode(
needs={}, uses=RulePolicy, constructor_name="", fn="", config={}
)
# training
validate = get_validation_method(
finetuning=False, load=False, nlu=nlu, core=core, graph_schema=schema1
)
validate(importer=EmptyDataImporter())
# same schema -- we just explicitly pass default values
schema2 = copy.deepcopy(schema1)
schema2.nodes["nlu-node"] = SchemaNode(
needs={},
uses=WhitespaceTokenizer,
constructor_name="",
fn="",
config=WhitespaceTokenizer.get_default_config(),
)
schema2.nodes["core-node"] = SchemaNode(
needs={},
uses=RulePolicy,
constructor_name="",
fn="",
config=RulePolicy.get_default_config(),
)
# finetuning *does not raise*
loaded_validate = get_validation_method(
finetuning=True, load=True, nlu=nlu, core=core, graph_schema=schema2
)
loaded_validate(importer=EmptyDataImporter())
@pytest.mark.parametrize(
"nlu, core,key",
[
(nlu, core, key)
for nlu, core in [(True, False), (False, True), (True, True)]
for key in [INTENT, ACTION_NAME]
],
)
def test_validate_after_removing_or_adding_intent_or_action_name(
get_validation_method: Callable[..., ValidationMethodType],
nlu: bool,
core: bool,
key: Text,
):
messages = [Message(data={key: "item-1"}), Message(data={key: "item-2"})]
message_with_new_item = Message(data={key: "item-3"})
# training
importer = DummyNLUDataImporter(messages)
validate = get_validation_method(finetuning=False, load=False, nlu=nlu, core=core)
validate(importer=importer)
# load validate method in finetuning mode
validate = get_validation_method(finetuning=True, load=True, nlu=nlu, core=core)
# ... apply with something suddenly missing
importer2 = DummyNLUDataImporter(messages[1:])
if nlu:
with pytest.raises(InvalidConfigException):
validate(importer=importer2)
else:
validate(importer=importer2)
# ... apply with additional item
importer3 = DummyNLUDataImporter(messages + [message_with_new_item])
if nlu:
with pytest.raises(InvalidConfigException):
validate(importer=importer3)
else:
validate(importer=importer3)
@pytest.mark.parametrize(
"nlu, core,key",
[
(nlu, core, key)
for nlu, core in [(True, False), (False, True), (True, True)]
for key in [INTENT, ACTION_NAME]
],
)
def test_validate_with_different_examples_for_intent_or_action_name(
get_validation_method: Callable[..., ValidationMethodType],
nlu: bool,
core: bool,
key: Text,
):
messages = [
Message(data={key: "item-1", TEXT: "a"}),
Message(data={key: "item-2", TEXT: "b"}),
]
# training
importer = DummyNLUDataImporter(messages)
validate = get_validation_method(finetuning=False, load=False, nlu=nlu, core=core)
validate(importer=importer)
# load validate method in finetuning mode
validate = get_validation_method(finetuning=True, load=True, nlu=nlu, core=core)
# ... apply with different messages
messages = [
Message(data={key: "item-1", TEXT: "c"}),
Message(data={key: "item-1", TEXT: "d"}),
Message(data={key: "item-2", TEXT: "e"}),
Message(data={key: "item-2", TEXT: "f"}),
]
importer2 = DummyNLUDataImporter(messages)
# does not complain:
validate(importer=importer2)
@pytest.mark.parametrize(
"nlu, core, min_compatible_version, old_version, can_tune",
[
(nlu, core, old_version, min_compatible_version, can_tune)
for nlu, core in [(True, False), (False, True), (True, True)]
for old_version, min_compatible_version, can_tune in [
("2.1.0", "2.1.0", True),
("2.0.0", "2.1.0", True),
("2.1.0", "2.0.0", False),
]
],
)
def test_validate_with_other_version(
monkeypatch: MonkeyPatch,
get_validation_method: Callable[..., ValidationMethodType],
nlu: bool,
core: bool,
min_compatible_version: Text,
old_version: Text,
can_tune: bool,
):
monkeypatch.setattr(rasa, "__version__", old_version)
monkeypatch.setattr(
rasa.graph_components.validators.finetuning_validator,
"MINIMUM_COMPATIBLE_VERSION",
min_compatible_version,
)
# training
importer = DummyNLUDataImporter([Message(data={INTENT: "dummy"})])
validate = get_validation_method(finetuning=False, load=False, nlu=nlu, core=core)
validate(importer=importer)
# finetuning
validate = get_validation_method(finetuning=True, load=True, nlu=nlu, core=core)
if not can_tune:
with pytest.raises(InvalidConfigException):
validate(importer=importer)
else:
validate(importer=importer)
@pytest.mark.parametrize("nlu, core", [(True, False), (False, True), (True, True)])
def test_validate_with_finetuning_fails_without_training(
get_validation_method: Callable[..., ValidationMethodType], nlu: bool, core: bool
):
validate = get_validation_method(finetuning=True, load=False, nlu=nlu, core=core)
with pytest.raises(InvalidConfigException):
validate(importer=EmptyDataImporter())
def test_loading_without_persisting(
get_finetuning_validator: Callable[
[bool, bool, Dict[Text, bool]], FinetuningValidator
]
):
with pytest.raises(ValueError):
get_finetuning_validator(finetuning=False, load=True, config={})