rasa-pro 3.9.18__py3-none-any.whl → 3.10.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of rasa-pro might be problematic. Click here for more details.
- README.md +26 -57
- rasa/__init__.py +1 -2
- rasa/__main__.py +5 -0
- rasa/anonymization/anonymization_rule_executor.py +2 -2
- rasa/api.py +26 -22
- rasa/cli/arguments/data.py +27 -2
- rasa/cli/arguments/default_arguments.py +25 -3
- rasa/cli/arguments/run.py +9 -9
- rasa/cli/arguments/train.py +2 -0
- rasa/cli/data.py +70 -8
- rasa/cli/e2e_test.py +108 -433
- rasa/cli/interactive.py +1 -0
- rasa/cli/llm_fine_tuning.py +395 -0
- rasa/cli/project_templates/calm/endpoints.yml +1 -1
- rasa/cli/project_templates/tutorial/endpoints.yml +1 -1
- rasa/cli/run.py +14 -13
- rasa/cli/scaffold.py +10 -8
- rasa/cli/train.py +8 -7
- rasa/cli/utils.py +15 -0
- rasa/constants.py +7 -1
- rasa/core/actions/action.py +98 -49
- rasa/core/actions/action_run_slot_rejections.py +4 -1
- rasa/core/actions/custom_action_executor.py +9 -6
- rasa/core/actions/direct_custom_actions_executor.py +80 -0
- rasa/core/actions/e2e_stub_custom_action_executor.py +68 -0
- rasa/core/actions/grpc_custom_action_executor.py +2 -2
- rasa/core/actions/http_custom_action_executor.py +6 -5
- rasa/core/agent.py +21 -17
- rasa/core/channels/__init__.py +2 -0
- rasa/core/channels/audiocodes.py +1 -16
- rasa/core/channels/inspector/dist/index.html +0 -2
- rasa/core/channels/inspector/index.html +0 -2
- rasa/core/channels/voice_aware/__init__.py +0 -0
- rasa/core/channels/voice_aware/jambonz.py +103 -0
- rasa/core/channels/voice_aware/jambonz_protocol.py +344 -0
- rasa/core/channels/voice_aware/utils.py +20 -0
- rasa/core/channels/voice_native/__init__.py +0 -0
- rasa/core/constants.py +6 -1
- rasa/core/featurizers/single_state_featurizer.py +1 -22
- rasa/core/featurizers/tracker_featurizers.py +18 -115
- rasa/core/information_retrieval/faiss.py +7 -4
- rasa/core/information_retrieval/information_retrieval.py +8 -0
- rasa/core/information_retrieval/milvus.py +9 -2
- rasa/core/information_retrieval/qdrant.py +1 -1
- rasa/core/nlg/contextual_response_rephraser.py +32 -10
- rasa/core/nlg/summarize.py +4 -3
- rasa/core/policies/enterprise_search_policy.py +100 -44
- rasa/core/policies/flows/flow_executor.py +130 -94
- rasa/core/policies/intentless_policy.py +52 -28
- rasa/core/policies/ted_policy.py +33 -58
- rasa/core/policies/unexpected_intent_policy.py +7 -15
- rasa/core/processor.py +20 -53
- rasa/core/run.py +5 -4
- rasa/core/tracker_store.py +8 -4
- rasa/core/utils.py +45 -56
- rasa/dialogue_understanding/coexistence/llm_based_router.py +45 -12
- rasa/dialogue_understanding/commands/__init__.py +4 -0
- rasa/dialogue_understanding/commands/change_flow_command.py +0 -6
- rasa/dialogue_understanding/commands/session_start_command.py +59 -0
- rasa/dialogue_understanding/commands/set_slot_command.py +1 -5
- rasa/dialogue_understanding/commands/utils.py +38 -0
- rasa/dialogue_understanding/generator/constants.py +10 -3
- rasa/dialogue_understanding/generator/flow_retrieval.py +14 -5
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +12 -2
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +106 -87
- rasa/dialogue_understanding/generator/nlu_command_adapter.py +28 -6
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +90 -37
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +15 -15
- rasa/dialogue_understanding/patterns/session_start.py +37 -0
- rasa/dialogue_understanding/processor/command_processor.py +13 -14
- rasa/e2e_test/aggregate_test_stats_calculator.py +124 -0
- rasa/e2e_test/assertions.py +1181 -0
- rasa/e2e_test/assertions_schema.yml +106 -0
- rasa/e2e_test/constants.py +20 -0
- rasa/e2e_test/e2e_config.py +220 -0
- rasa/e2e_test/e2e_config_schema.yml +26 -0
- rasa/e2e_test/e2e_test_case.py +131 -8
- rasa/e2e_test/e2e_test_converter.py +363 -0
- rasa/e2e_test/e2e_test_converter_prompt.jinja2 +70 -0
- rasa/e2e_test/e2e_test_coverage_report.py +364 -0
- rasa/e2e_test/e2e_test_result.py +26 -6
- rasa/e2e_test/e2e_test_runner.py +491 -72
- rasa/e2e_test/e2e_test_schema.yml +96 -0
- rasa/e2e_test/pykwalify_extensions.py +39 -0
- rasa/e2e_test/stub_custom_action.py +70 -0
- rasa/e2e_test/utils/__init__.py +0 -0
- rasa/e2e_test/utils/e2e_yaml_utils.py +55 -0
- rasa/e2e_test/utils/io.py +596 -0
- rasa/e2e_test/utils/validation.py +80 -0
- rasa/engine/recipes/default_components.py +0 -2
- rasa/engine/storage/local_model_storage.py +0 -1
- rasa/env.py +9 -0
- rasa/llm_fine_tuning/__init__.py +0 -0
- rasa/llm_fine_tuning/annotation_module.py +241 -0
- rasa/llm_fine_tuning/conversations.py +144 -0
- rasa/llm_fine_tuning/llm_data_preparation_module.py +178 -0
- rasa/llm_fine_tuning/notebooks/unsloth_finetuning.ipynb +407 -0
- rasa/llm_fine_tuning/paraphrasing/__init__.py +0 -0
- rasa/llm_fine_tuning/paraphrasing/conversation_rephraser.py +281 -0
- rasa/llm_fine_tuning/paraphrasing/default_rephrase_prompt_template.jina2 +44 -0
- rasa/llm_fine_tuning/paraphrasing/rephrase_validator.py +121 -0
- rasa/llm_fine_tuning/paraphrasing/rephrased_user_message.py +10 -0
- rasa/llm_fine_tuning/paraphrasing_module.py +128 -0
- rasa/llm_fine_tuning/storage.py +174 -0
- rasa/llm_fine_tuning/train_test_split_module.py +441 -0
- rasa/model_training.py +48 -16
- rasa/nlu/classifiers/diet_classifier.py +25 -38
- rasa/nlu/classifiers/logistic_regression_classifier.py +9 -44
- rasa/nlu/classifiers/sklearn_intent_classifier.py +16 -37
- rasa/nlu/extractors/crf_entity_extractor.py +50 -93
- rasa/nlu/featurizers/sparse_featurizer/count_vectors_featurizer.py +45 -78
- rasa/nlu/featurizers/sparse_featurizer/lexical_syntactic_featurizer.py +17 -52
- rasa/nlu/featurizers/sparse_featurizer/regex_featurizer.py +3 -5
- rasa/nlu/persistor.py +129 -32
- rasa/server.py +45 -10
- rasa/shared/constants.py +63 -15
- rasa/shared/core/domain.py +15 -12
- rasa/shared/core/events.py +28 -2
- rasa/shared/core/flows/flow.py +208 -13
- rasa/shared/core/flows/flow_path.py +84 -0
- rasa/shared/core/flows/flows_list.py +28 -10
- rasa/shared/core/flows/flows_yaml_schema.json +269 -193
- rasa/shared/core/flows/validation.py +112 -25
- rasa/shared/core/flows/yaml_flows_io.py +149 -10
- rasa/shared/core/trackers.py +6 -0
- rasa/shared/core/training_data/visualization.html +2 -2
- rasa/shared/exceptions.py +4 -0
- rasa/shared/importers/importer.py +60 -11
- rasa/shared/importers/remote_importer.py +196 -0
- rasa/shared/nlu/constants.py +2 -0
- rasa/shared/nlu/training_data/features.py +2 -120
- rasa/shared/providers/_configs/__init__.py +0 -0
- rasa/shared/providers/_configs/azure_openai_client_config.py +181 -0
- rasa/shared/providers/_configs/client_config.py +57 -0
- rasa/shared/providers/_configs/default_litellm_client_config.py +130 -0
- rasa/shared/providers/_configs/huggingface_local_embedding_client_config.py +234 -0
- rasa/shared/providers/_configs/openai_client_config.py +175 -0
- rasa/shared/providers/_configs/self_hosted_llm_client_config.py +171 -0
- rasa/shared/providers/_configs/utils.py +101 -0
- rasa/shared/providers/_ssl_verification_utils.py +124 -0
- rasa/shared/providers/embedding/__init__.py +0 -0
- rasa/shared/providers/embedding/_base_litellm_embedding_client.py +254 -0
- rasa/shared/providers/embedding/_langchain_embedding_client_adapter.py +74 -0
- rasa/shared/providers/embedding/azure_openai_embedding_client.py +277 -0
- rasa/shared/providers/embedding/default_litellm_embedding_client.py +102 -0
- rasa/shared/providers/embedding/embedding_client.py +90 -0
- rasa/shared/providers/embedding/embedding_response.py +41 -0
- rasa/shared/providers/embedding/huggingface_local_embedding_client.py +191 -0
- rasa/shared/providers/embedding/openai_embedding_client.py +172 -0
- rasa/shared/providers/llm/__init__.py +0 -0
- rasa/shared/providers/llm/_base_litellm_client.py +227 -0
- rasa/shared/providers/llm/azure_openai_llm_client.py +338 -0
- rasa/shared/providers/llm/default_litellm_llm_client.py +84 -0
- rasa/shared/providers/llm/llm_client.py +76 -0
- rasa/shared/providers/llm/llm_response.py +50 -0
- rasa/shared/providers/llm/openai_llm_client.py +155 -0
- rasa/shared/providers/llm/self_hosted_llm_client.py +169 -0
- rasa/shared/providers/mappings.py +75 -0
- rasa/shared/utils/cli.py +30 -0
- rasa/shared/utils/io.py +65 -3
- rasa/shared/utils/llm.py +223 -200
- rasa/shared/utils/yaml.py +122 -7
- rasa/studio/download.py +19 -13
- rasa/studio/train.py +2 -3
- rasa/studio/upload.py +2 -3
- rasa/telemetry.py +113 -58
- rasa/tracing/config.py +2 -3
- rasa/tracing/instrumentation/attribute_extractors.py +29 -17
- rasa/tracing/instrumentation/instrumentation.py +4 -47
- rasa/utils/common.py +18 -19
- rasa/utils/endpoints.py +7 -4
- rasa/utils/io.py +66 -0
- rasa/utils/json_utils.py +60 -0
- rasa/utils/licensing.py +9 -1
- rasa/utils/ml_utils.py +4 -2
- rasa/utils/tensorflow/model_data.py +193 -2
- rasa/validator.py +195 -1
- rasa/version.py +1 -1
- {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.3.dist-info}/METADATA +47 -72
- {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.3.dist-info}/RECORD +185 -121
- rasa/nlu/classifiers/llm_intent_classifier.py +0 -519
- rasa/shared/providers/openai/clients.py +0 -43
- rasa/shared/providers/openai/session_handler.py +0 -110
- rasa/utils/tensorflow/feature_array.py +0 -366
- /rasa/{shared/providers/openai → cli/project_templates/tutorial/actions}/__init__.py +0 -0
- /rasa/cli/project_templates/tutorial/{actions.py → actions/actions.py} +0 -0
- {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.3.dist-info}/NOTICE +0 -0
- {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.3.dist-info}/WHEEL +0 -0
- {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.3.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import asyncio
|
|
3
|
+
import sys
|
|
4
|
+
from typing import List, Any, Dict
|
|
5
|
+
|
|
6
|
+
import structlog
|
|
7
|
+
|
|
8
|
+
import rasa.cli.utils
|
|
9
|
+
import rasa.shared.utils.cli
|
|
10
|
+
import rasa.shared.utils.io
|
|
11
|
+
import rasa.shared.utils.yaml
|
|
12
|
+
from rasa.cli import SubParsersAction
|
|
13
|
+
from rasa.cli.arguments.default_arguments import (
|
|
14
|
+
add_endpoint_param,
|
|
15
|
+
add_model_param,
|
|
16
|
+
add_remote_storage_param,
|
|
17
|
+
)
|
|
18
|
+
from rasa.cli.e2e_test import (
|
|
19
|
+
read_test_cases,
|
|
20
|
+
validate_model_path,
|
|
21
|
+
RASA_PRO_BETA_FINE_TUNING_RECIPE_ENV_VAR_NAME,
|
|
22
|
+
)
|
|
23
|
+
from rasa.core.exceptions import AgentNotReady
|
|
24
|
+
from rasa.core.utils import AvailableEndpoints
|
|
25
|
+
from rasa.e2e_test.e2e_test_runner import E2ETestRunner
|
|
26
|
+
from rasa.llm_fine_tuning.annotation_module import annotate_e2e_tests
|
|
27
|
+
from rasa.llm_fine_tuning.llm_data_preparation_module import convert_to_fine_tuning_data
|
|
28
|
+
from rasa.llm_fine_tuning.paraphrasing.conversation_rephraser import (
|
|
29
|
+
ConversationRephraser,
|
|
30
|
+
)
|
|
31
|
+
from rasa.llm_fine_tuning.paraphrasing_module import create_paraphrased_conversations
|
|
32
|
+
from rasa.llm_fine_tuning.storage import (
|
|
33
|
+
StorageContext,
|
|
34
|
+
StorageType,
|
|
35
|
+
FileStorageStrategy,
|
|
36
|
+
)
|
|
37
|
+
from rasa.llm_fine_tuning.train_test_split_module import (
|
|
38
|
+
split_llm_fine_tuning_data,
|
|
39
|
+
INSTRUCTION_DATA_FORMAT,
|
|
40
|
+
CONVERSATIONAL_DATA_FORMAT,
|
|
41
|
+
)
|
|
42
|
+
from rasa.shared.constants import (
|
|
43
|
+
DEFAULT_ENDPOINTS_PATH,
|
|
44
|
+
DEFAULT_MODELS_PATH,
|
|
45
|
+
LLM_CONFIG_KEY,
|
|
46
|
+
)
|
|
47
|
+
from rasa.shared.utils.yaml import read_config_file
|
|
48
|
+
from rasa.utils.beta import ensure_beta_feature_is_enabled
|
|
49
|
+
|
|
50
|
+
DEFAULT_INPUT_E2E_TEST_PATH = "e2e_tests"
|
|
51
|
+
DEFAULT_OUTPUT_FOLDER = "output"
|
|
52
|
+
RESULT_SUMMARY_FILE = "result_summary.yaml"
|
|
53
|
+
PARAMETERS_FILE = "params.yaml"
|
|
54
|
+
|
|
55
|
+
structlogger = structlog.get_logger()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def add_subparser(
|
|
59
|
+
subparsers: SubParsersAction, parents: List[argparse.ArgumentParser]
|
|
60
|
+
) -> None:
|
|
61
|
+
"""Add the llm fine-tuning subparser to `rasa test`.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
subparsers: subparser we are going to attach to
|
|
65
|
+
parents: Parent parsers, needed to ensure tree structure in argparse
|
|
66
|
+
"""
|
|
67
|
+
llm_parser = subparsers.add_parser(
|
|
68
|
+
"llm",
|
|
69
|
+
parents=parents,
|
|
70
|
+
conflict_handler="resolve",
|
|
71
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
72
|
+
help="Commands related to LLMs.",
|
|
73
|
+
)
|
|
74
|
+
llm_subparsers = llm_parser.add_subparsers()
|
|
75
|
+
|
|
76
|
+
llm_finetune_parser = llm_subparsers.add_parser(
|
|
77
|
+
"finetune",
|
|
78
|
+
parents=parents,
|
|
79
|
+
conflict_handler="resolve",
|
|
80
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
81
|
+
description="Commands related to LLM fine-tuning.",
|
|
82
|
+
)
|
|
83
|
+
llm_finetune_subparser = llm_finetune_parser.add_subparsers()
|
|
84
|
+
|
|
85
|
+
create_llm_finetune_data_preparation_subparser(llm_finetune_subparser, parents)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def create_llm_finetune_data_preparation_subparser(
|
|
89
|
+
fine_tune_llm_parser: SubParsersAction,
|
|
90
|
+
parents: List[argparse.ArgumentParser],
|
|
91
|
+
) -> argparse.ArgumentParser:
|
|
92
|
+
"""Create fine-tuning LLM data preparation subparser."""
|
|
93
|
+
data_preparation_subparser = fine_tune_llm_parser.add_parser(
|
|
94
|
+
"prepare-data",
|
|
95
|
+
parents=parents,
|
|
96
|
+
conflict_handler="resolve",
|
|
97
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
98
|
+
description="Prepares data for LLM fine-tuning.",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
data_preparation_subparser.set_defaults(func=prepare_llm_fine_tuning_data)
|
|
102
|
+
|
|
103
|
+
add_data_preparation_arguments(data_preparation_subparser)
|
|
104
|
+
add_model_param(data_preparation_subparser, add_positional_arg=False)
|
|
105
|
+
add_endpoint_param(
|
|
106
|
+
data_preparation_subparser,
|
|
107
|
+
help_text="Configuration file for the model server and the connectors as a "
|
|
108
|
+
"yml file.",
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
return data_preparation_subparser
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def add_data_preparation_arguments(parser: argparse.ArgumentParser) -> None:
|
|
115
|
+
"""Arguments for preparing LLM fine-tuning data."""
|
|
116
|
+
parser.add_argument(
|
|
117
|
+
"-o",
|
|
118
|
+
"--out",
|
|
119
|
+
type=str,
|
|
120
|
+
default=DEFAULT_OUTPUT_FOLDER,
|
|
121
|
+
help="The output folder to store the data to.",
|
|
122
|
+
)
|
|
123
|
+
parser.add_argument(
|
|
124
|
+
"path-to-e2e-test-cases",
|
|
125
|
+
nargs="?",
|
|
126
|
+
type=str,
|
|
127
|
+
help="Input file or folder containing end-to-end test cases.",
|
|
128
|
+
default=DEFAULT_INPUT_E2E_TEST_PATH,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
add_remote_storage_param(parser)
|
|
132
|
+
|
|
133
|
+
rephrasing_arguments = parser.add_argument_group("Rephrasing Module")
|
|
134
|
+
rephrasing_arguments.add_argument(
|
|
135
|
+
"--num-rephrases",
|
|
136
|
+
choices=range(0, 50),
|
|
137
|
+
type=int,
|
|
138
|
+
default=10,
|
|
139
|
+
help="Number of rephrases to be generated per user utterance.",
|
|
140
|
+
)
|
|
141
|
+
rephrasing_arguments.add_argument(
|
|
142
|
+
"--rephrase-config",
|
|
143
|
+
type=str,
|
|
144
|
+
default=None,
|
|
145
|
+
help="Path to config file that contains the configuration of the "
|
|
146
|
+
"rephrasing module.",
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
train_test_split_arguments = parser.add_argument_group("Train/Test Split Module")
|
|
150
|
+
train_test_split_arguments.add_argument(
|
|
151
|
+
"--train-frac",
|
|
152
|
+
type=restricted_float,
|
|
153
|
+
default=0.8,
|
|
154
|
+
help="The amount of data that should go into the training dataset. The value "
|
|
155
|
+
"should be >0.0 and <=1.0.",
|
|
156
|
+
)
|
|
157
|
+
train_test_split_arguments.add_argument(
|
|
158
|
+
"--output-format",
|
|
159
|
+
choices=[INSTRUCTION_DATA_FORMAT, CONVERSATIONAL_DATA_FORMAT],
|
|
160
|
+
type=str,
|
|
161
|
+
nargs="?",
|
|
162
|
+
default=INSTRUCTION_DATA_FORMAT,
|
|
163
|
+
help="Format of the output file.",
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def prepare_llm_fine_tuning_data(args: argparse.Namespace) -> None:
|
|
168
|
+
"""Prepare LLM fine-tuning data.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
args: Commandline arguments.
|
|
172
|
+
"""
|
|
173
|
+
ensure_beta_feature_is_enabled(
|
|
174
|
+
"LLM fine-tuning recipe",
|
|
175
|
+
env_flag=RASA_PRO_BETA_FINE_TUNING_RECIPE_ENV_VAR_NAME,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
rephrase_config = (
|
|
179
|
+
read_config_file(args.rephrase_config) if args.rephrase_config else {}
|
|
180
|
+
)
|
|
181
|
+
ConversationRephraser.validate_config(rephrase_config)
|
|
182
|
+
|
|
183
|
+
# make sure the output directory exists
|
|
184
|
+
output_dir = args.out
|
|
185
|
+
rasa.shared.utils.io.create_directory(output_dir)
|
|
186
|
+
|
|
187
|
+
# read e2e test cases
|
|
188
|
+
path_to_test_cases = getattr(
|
|
189
|
+
args, "path-to-e2e-test-cases", DEFAULT_INPUT_E2E_TEST_PATH
|
|
190
|
+
)
|
|
191
|
+
test_suite = read_test_cases(path_to_test_cases)
|
|
192
|
+
# set up the e2e test runner
|
|
193
|
+
e2e_test_runner = set_up_e2e_test_runner(args)
|
|
194
|
+
|
|
195
|
+
if e2e_test_runner.agent.processor is None:
|
|
196
|
+
rasa.shared.utils.cli.print_error(
|
|
197
|
+
"No processor: Not able to retrieve flows and config from trained model."
|
|
198
|
+
)
|
|
199
|
+
sys.exit(0)
|
|
200
|
+
|
|
201
|
+
flows = asyncio.run(e2e_test_runner.agent.processor.get_flows())
|
|
202
|
+
llm_command_generator_config = _get_llm_command_generator_config(e2e_test_runner)
|
|
203
|
+
|
|
204
|
+
# set up storage context
|
|
205
|
+
storage_context = create_storage_context(StorageType.FILE, output_dir)
|
|
206
|
+
|
|
207
|
+
statistics = {}
|
|
208
|
+
|
|
209
|
+
# 1. annotate e2e tests
|
|
210
|
+
log_start_of_module("Annotation")
|
|
211
|
+
conversations = annotate_e2e_tests(e2e_test_runner, test_suite, storage_context)
|
|
212
|
+
statistics["num_input_e2e_tests"] = len(test_suite.test_cases)
|
|
213
|
+
statistics["num_annotated_conversations"] = len(conversations)
|
|
214
|
+
statistics["num_user_messages_across_conversations"] = sum(
|
|
215
|
+
[len(conversation.get_user_messages()) for conversation in conversations]
|
|
216
|
+
)
|
|
217
|
+
statistics["num_user_messages_to_rephrase_across_conversations"] = sum(
|
|
218
|
+
[
|
|
219
|
+
len(conversation.get_user_messages_to_rephrase())
|
|
220
|
+
for conversation in conversations
|
|
221
|
+
]
|
|
222
|
+
)
|
|
223
|
+
log_end_of_module("Annotation", statistics)
|
|
224
|
+
|
|
225
|
+
# 2. paraphrase conversations
|
|
226
|
+
log_start_of_module("Rephrasing")
|
|
227
|
+
conversations, rephrase_config = asyncio.run(
|
|
228
|
+
create_paraphrased_conversations(
|
|
229
|
+
conversations,
|
|
230
|
+
rephrase_config,
|
|
231
|
+
args.num_rephrases,
|
|
232
|
+
flows,
|
|
233
|
+
llm_command_generator_config,
|
|
234
|
+
storage_context,
|
|
235
|
+
)
|
|
236
|
+
)
|
|
237
|
+
statistics["num_passing_rephrased_user_messages"] = sum(
|
|
238
|
+
[conversation.get_number_of_rephrases(True) for conversation in conversations]
|
|
239
|
+
)
|
|
240
|
+
statistics["num_failing_rephrased_user_messages"] = sum(
|
|
241
|
+
[conversation.get_number_of_rephrases(False) for conversation in conversations]
|
|
242
|
+
)
|
|
243
|
+
log_end_of_module("Rephrasing", statistics)
|
|
244
|
+
|
|
245
|
+
# 3. create fine-tuning dataset
|
|
246
|
+
log_start_of_module("LLM Data Preparation")
|
|
247
|
+
llm_fine_tuning_data = convert_to_fine_tuning_data(conversations, storage_context)
|
|
248
|
+
statistics["num_ft_data_points"] = len(llm_fine_tuning_data)
|
|
249
|
+
log_end_of_module("LLM Data Preparation", statistics)
|
|
250
|
+
|
|
251
|
+
# 4. create train/test split
|
|
252
|
+
log_start_of_module("Train/Test Split")
|
|
253
|
+
train_data, val_data = split_llm_fine_tuning_data(
|
|
254
|
+
llm_fine_tuning_data,
|
|
255
|
+
args.train_frac,
|
|
256
|
+
args.output_format,
|
|
257
|
+
storage_context,
|
|
258
|
+
test_suite,
|
|
259
|
+
)
|
|
260
|
+
statistics["num_train_data_points"] = len(train_data)
|
|
261
|
+
statistics["num_val_data_points"] = len(val_data)
|
|
262
|
+
log_end_of_module("Train/Test Split", statistics)
|
|
263
|
+
|
|
264
|
+
# write down params and statistics to a file
|
|
265
|
+
write_params(args, rephrase_config, output_dir)
|
|
266
|
+
write_statistics(statistics, output_dir)
|
|
267
|
+
|
|
268
|
+
rasa.shared.utils.cli.print_success(
|
|
269
|
+
f"Data and intermediate results are written " f"to '{output_dir}'."
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _get_llm_command_generator_config(e2e_test_runner: E2ETestRunner) -> Dict[str, Any]:
|
|
274
|
+
from rasa.dialogue_understanding.generator.constants import DEFAULT_LLM_CONFIG
|
|
275
|
+
|
|
276
|
+
train_schema = e2e_test_runner.agent.processor.model_metadata.train_schema # type: ignore
|
|
277
|
+
|
|
278
|
+
for node in train_schema.nodes:
|
|
279
|
+
if "SingleStepLLMCommandGenerator" in node:
|
|
280
|
+
return {
|
|
281
|
+
**DEFAULT_LLM_CONFIG,
|
|
282
|
+
**train_schema.nodes[node].config.get(LLM_CONFIG_KEY),
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
rasa.shared.utils.cli.print_error(
|
|
286
|
+
"The provided model was not trained with the 'SingleStepLLMCommandGenerator'."
|
|
287
|
+
"Without the 'SingleStepLLMCommandGenerator' no data for fine-tuning can be "
|
|
288
|
+
"created. Please add the 'SingleStepLLMCommandGenerator' to your config and"
|
|
289
|
+
"train your model."
|
|
290
|
+
)
|
|
291
|
+
sys.exit(0)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def log_start_of_module(module_name: str) -> None:
|
|
295
|
+
log_info = f"Starting {module_name} Module"
|
|
296
|
+
rasa.shared.utils.cli.print_info(
|
|
297
|
+
f"{rasa.shared.utils.cli.pad(log_info, char='-')}\n"
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def log_end_of_module(module_name: str, statistics: Dict[str, int]) -> None:
|
|
302
|
+
log_info = f"Finished {module_name} Module"
|
|
303
|
+
rasa.shared.utils.cli.print_info(
|
|
304
|
+
f"{rasa.shared.utils.cli.pad(log_info, char='-')}\n"
|
|
305
|
+
)
|
|
306
|
+
rasa.shared.utils.cli.print_color(
|
|
307
|
+
"Current Statistics:", color=rasa.shared.utils.io.bcolors.BOLD
|
|
308
|
+
)
|
|
309
|
+
for key, value in statistics.items():
|
|
310
|
+
rasa.shared.utils.cli.print_color(
|
|
311
|
+
f" {key}: {value}", color=rasa.shared.utils.io.bcolors.BOLD
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def restricted_float(x: Any) -> float:
|
|
316
|
+
try:
|
|
317
|
+
x = float(x)
|
|
318
|
+
except ValueError:
|
|
319
|
+
raise argparse.ArgumentTypeError("%r not a floating-point literal" % (x,))
|
|
320
|
+
|
|
321
|
+
if x <= 0.0 or x > 1.0:
|
|
322
|
+
raise argparse.ArgumentTypeError("%r not in range [0.0, 1.0]" % (x,))
|
|
323
|
+
return x
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def write_params(
|
|
327
|
+
args: argparse.Namespace, rephrase_config: Dict[str, Any], output_path: str
|
|
328
|
+
) -> None:
|
|
329
|
+
yaml_data = {
|
|
330
|
+
"parameters": {
|
|
331
|
+
"num_rephrases": args.num_rephrases,
|
|
332
|
+
"rephrase_config": rephrase_config,
|
|
333
|
+
"model": args.model,
|
|
334
|
+
"endpoints": args.endpoints,
|
|
335
|
+
"remote-storage": args.remote_storage,
|
|
336
|
+
"train_frac": args.train_frac,
|
|
337
|
+
"output_format": args.output_format,
|
|
338
|
+
"out": output_path,
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
rasa.shared.utils.yaml.write_yaml(yaml_data, f"{output_path}/{PARAMETERS_FILE}")
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def write_statistics(statistics: Dict[str, Any], output_path: str) -> None:
|
|
346
|
+
rasa.shared.utils.yaml.write_yaml(
|
|
347
|
+
statistics, f"{output_path}/{RESULT_SUMMARY_FILE}"
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def get_valid_endpoints(endpoints_file: str) -> AvailableEndpoints:
|
|
352
|
+
validated_endpoints_file = rasa.cli.utils.get_validated_path(
|
|
353
|
+
endpoints_file, "endpoints", DEFAULT_ENDPOINTS_PATH, True
|
|
354
|
+
)
|
|
355
|
+
endpoints = AvailableEndpoints.read_endpoints(validated_endpoints_file)
|
|
356
|
+
|
|
357
|
+
# Ignore all endpoints apart from action server, model, nlu and nlg
|
|
358
|
+
# to ensure InMemoryTrackerStore is being used instead of production
|
|
359
|
+
# tracker store
|
|
360
|
+
endpoints.tracker_store = None
|
|
361
|
+
endpoints.lock_store = None
|
|
362
|
+
endpoints.event_broker = None
|
|
363
|
+
|
|
364
|
+
return endpoints
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def set_up_e2e_test_runner(args: argparse.Namespace) -> E2ETestRunner:
|
|
368
|
+
endpoints = get_valid_endpoints(args.endpoints)
|
|
369
|
+
|
|
370
|
+
if endpoints.model is None:
|
|
371
|
+
args.model = validate_model_path(args.model, "model", DEFAULT_MODELS_PATH)
|
|
372
|
+
|
|
373
|
+
try:
|
|
374
|
+
return E2ETestRunner(
|
|
375
|
+
remote_storage=args.remote_storage,
|
|
376
|
+
model_path=args.model,
|
|
377
|
+
model_server=endpoints.model,
|
|
378
|
+
endpoints=endpoints,
|
|
379
|
+
)
|
|
380
|
+
except AgentNotReady as error:
|
|
381
|
+
structlogger.error(
|
|
382
|
+
"cli.finetune_llm.prepare_data.set_up_e2e_test_runner", error=error.message
|
|
383
|
+
)
|
|
384
|
+
sys.exit(1)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def create_storage_context(
|
|
388
|
+
storage_type: StorageType, output_dir: str
|
|
389
|
+
) -> StorageContext:
|
|
390
|
+
if storage_type == StorageType.FILE:
|
|
391
|
+
strategy = FileStorageStrategy(output_dir)
|
|
392
|
+
else:
|
|
393
|
+
raise ValueError("Unsupported storage type")
|
|
394
|
+
|
|
395
|
+
return StorageContext(strategy)
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
# https://rasa.com/docs/rasa-pro/concepts/custom-actions
|
|
12
12
|
|
|
13
13
|
action_endpoint:
|
|
14
|
-
|
|
14
|
+
actions_module: "actions"
|
|
15
15
|
|
|
16
16
|
# Tracker store which is used to store the conversations.
|
|
17
17
|
# By default the conversations are stored in memory.
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
# https://rasa.com/docs/rasa-pro/concepts/custom-actions
|
|
12
12
|
|
|
13
13
|
action_endpoint:
|
|
14
|
-
|
|
14
|
+
actions_module: "actions"
|
|
15
15
|
|
|
16
16
|
# Tracker store which is used to store the conversations.
|
|
17
17
|
# By default the conversations are stored in memory.
|
rasa/cli/run.py
CHANGED
|
@@ -3,16 +3,19 @@ import logging
|
|
|
3
3
|
import os
|
|
4
4
|
from typing import List, Text
|
|
5
5
|
|
|
6
|
+
from rasa.api import run as rasa_run
|
|
6
7
|
from rasa.cli import SubParsersAction
|
|
7
8
|
from rasa.cli.arguments import run as arguments
|
|
9
|
+
from rasa.cli.utils import get_validated_path
|
|
10
|
+
from rasa.exceptions import ModelNotFound
|
|
8
11
|
from rasa.shared.constants import (
|
|
9
|
-
DOCS_BASE_URL,
|
|
10
|
-
DEFAULT_ENDPOINTS_PATH,
|
|
11
|
-
DEFAULT_CREDENTIALS_PATH,
|
|
12
12
|
DEFAULT_ACTIONS_PATH,
|
|
13
|
+
DEFAULT_CREDENTIALS_PATH,
|
|
14
|
+
DEFAULT_ENDPOINTS_PATH,
|
|
13
15
|
DEFAULT_MODELS_PATH,
|
|
16
|
+
DOCS_BASE_URL,
|
|
14
17
|
)
|
|
15
|
-
from rasa.
|
|
18
|
+
from rasa.shared.utils.cli import print_error
|
|
16
19
|
|
|
17
20
|
logger = logging.getLogger(__name__)
|
|
18
21
|
|
|
@@ -77,19 +80,17 @@ def run(args: argparse.Namespace) -> None:
|
|
|
77
80
|
Args:
|
|
78
81
|
args: The CLI arguments.
|
|
79
82
|
"""
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
args.endpoints = rasa.cli.utils.get_validated_path(
|
|
83
|
+
args.endpoints = get_validated_path(
|
|
83
84
|
args.endpoints, "endpoints", DEFAULT_ENDPOINTS_PATH, True
|
|
84
85
|
)
|
|
85
|
-
args.credentials =
|
|
86
|
+
args.credentials = get_validated_path(
|
|
86
87
|
args.credentials, "credentials", DEFAULT_CREDENTIALS_PATH, True
|
|
87
88
|
)
|
|
88
89
|
|
|
89
90
|
if args.enable_api:
|
|
90
91
|
if not args.remote_storage:
|
|
91
92
|
args.model = _validate_model_path(args.model, "model", DEFAULT_MODELS_PATH)
|
|
92
|
-
|
|
93
|
+
rasa_run(**vars(args))
|
|
93
94
|
return
|
|
94
95
|
|
|
95
96
|
# if the API is not enable you cannot start without a model
|
|
@@ -101,14 +102,14 @@ def run(args: argparse.Namespace) -> None:
|
|
|
101
102
|
|
|
102
103
|
# start server if remote storage is configured
|
|
103
104
|
if args.remote_storage is not None:
|
|
104
|
-
|
|
105
|
+
rasa_run(**vars(args))
|
|
105
106
|
return
|
|
106
107
|
|
|
107
108
|
# start server if model server is configured
|
|
108
109
|
endpoints = AvailableEndpoints.read_endpoints(args.endpoints)
|
|
109
110
|
model_server = endpoints.model if endpoints and endpoints.model else None
|
|
110
111
|
if model_server is not None:
|
|
111
|
-
|
|
112
|
+
rasa_run(**vars(args))
|
|
112
113
|
return
|
|
113
114
|
|
|
114
115
|
# start server if local model found
|
|
@@ -120,10 +121,10 @@ def run(args: argparse.Namespace) -> None:
|
|
|
120
121
|
local_model_set = False
|
|
121
122
|
|
|
122
123
|
if local_model_set:
|
|
123
|
-
|
|
124
|
+
rasa_run(**vars(args))
|
|
124
125
|
return
|
|
125
126
|
|
|
126
|
-
|
|
127
|
+
print_error(
|
|
127
128
|
f"No model found. You have three options to provide a model:\n"
|
|
128
129
|
f"1. Configure a model server in the endpoint configuration and provide "
|
|
129
130
|
f"the configuration via '--endpoints'.\n"
|
rasa/cli/scaffold.py
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
import argparse
|
|
2
|
-
from collections import defaultdict
|
|
3
|
-
from enum import Enum
|
|
4
2
|
import os
|
|
5
3
|
import sys
|
|
4
|
+
from collections import defaultdict
|
|
5
|
+
from enum import Enum
|
|
6
6
|
from typing import List, Text
|
|
7
7
|
|
|
8
8
|
from rasa import telemetry
|
|
9
|
+
from rasa.api import train
|
|
9
10
|
from rasa.cli import SubParsersAction
|
|
10
11
|
from rasa.cli.inspect import inspect
|
|
11
|
-
from rasa.shared.utils.cli import print_success, print_error_and_exit
|
|
12
12
|
from rasa.shared.constants import (
|
|
13
|
-
DOCS_BASE_URL,
|
|
14
13
|
DEFAULT_CONFIG_PATH,
|
|
15
|
-
DEFAULT_DOMAIN_PATH,
|
|
16
14
|
DEFAULT_DATA_PATH,
|
|
15
|
+
DEFAULT_DOMAIN_PATH,
|
|
17
16
|
DEFAULT_MODELS_PATH,
|
|
17
|
+
DOCS_BASE_URL,
|
|
18
18
|
)
|
|
19
|
+
from rasa.shared.utils.cli import print_error_and_exit, print_success
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class ProjectTemplateName(Enum):
|
|
@@ -72,7 +73,6 @@ def add_subparser(
|
|
|
72
73
|
def print_train_or_instructions(args: argparse.Namespace) -> None:
|
|
73
74
|
"""Train a model if the user wants to."""
|
|
74
75
|
import questionary
|
|
75
|
-
import rasa
|
|
76
76
|
|
|
77
77
|
print_success("Finished creating project structure.")
|
|
78
78
|
|
|
@@ -84,7 +84,7 @@ def print_train_or_instructions(args: argparse.Namespace) -> None:
|
|
|
84
84
|
|
|
85
85
|
if should_train:
|
|
86
86
|
print_success("Training an initial model...")
|
|
87
|
-
training_result =
|
|
87
|
+
training_result = train(
|
|
88
88
|
template_domain_path[args.template],
|
|
89
89
|
DEFAULT_CONFIG_PATH,
|
|
90
90
|
DEFAULT_DATA_PATH,
|
|
@@ -102,9 +102,10 @@ def print_train_or_instructions(args: argparse.Namespace) -> None:
|
|
|
102
102
|
|
|
103
103
|
|
|
104
104
|
def print_run_or_instructions(args: argparse.Namespace) -> None:
|
|
105
|
-
from rasa.core import constants
|
|
106
105
|
import questionary
|
|
107
106
|
|
|
107
|
+
from rasa.core import constants
|
|
108
|
+
|
|
108
109
|
should_run = (
|
|
109
110
|
questionary.confirm("Do you want to speak to the trained assistant? 🤖")
|
|
110
111
|
.skip_if(args.no_prompt, default=False)
|
|
@@ -164,6 +165,7 @@ def create_initial_project(
|
|
|
164
165
|
|
|
165
166
|
def scaffold_path(template: ProjectTemplateName) -> Text:
|
|
166
167
|
import importlib_resources
|
|
168
|
+
|
|
167
169
|
import rasa.cli.project_templates
|
|
168
170
|
|
|
169
171
|
template_module = rasa.cli.project_templates.__name__
|
rasa/cli/train.py
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import argparse
|
|
2
|
-
import structlog
|
|
3
|
-
import sys
|
|
4
2
|
import asyncio
|
|
3
|
+
import sys
|
|
5
4
|
from pathlib import Path
|
|
6
5
|
from typing import Dict, List, Optional, Text, Union
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
import rasa.cli.arguments.train as train_arguments
|
|
7
|
+
import structlog
|
|
10
8
|
|
|
9
|
+
import rasa.cli.arguments.train as train_arguments
|
|
11
10
|
import rasa.cli.utils
|
|
12
11
|
import rasa.core.utils
|
|
13
|
-
from rasa.shared.importers.importer import TrainingDataImporter
|
|
14
12
|
import rasa.utils.common
|
|
13
|
+
from rasa.cli import SubParsersAction
|
|
15
14
|
from rasa.core.nlg.generator import NaturalLanguageGenerator
|
|
16
15
|
from rasa.core.train import do_compare_training
|
|
17
16
|
from rasa.shared.constants import (
|
|
17
|
+
CONFIG_MANDATORY_KEYS,
|
|
18
18
|
CONFIG_MANDATORY_KEYS_CORE,
|
|
19
19
|
CONFIG_MANDATORY_KEYS_NLU,
|
|
20
|
-
CONFIG_MANDATORY_KEYS,
|
|
21
20
|
DEFAULT_DATA_PATH,
|
|
22
21
|
DEFAULT_DOMAIN_PATHS,
|
|
23
22
|
)
|
|
23
|
+
from rasa.shared.importers.importer import TrainingDataImporter
|
|
24
24
|
|
|
25
25
|
structlogger = structlog.getLogger(__name__)
|
|
26
26
|
|
|
@@ -94,7 +94,7 @@ def run_training(args: argparse.Namespace, can_exit: bool = False) -> Optional[T
|
|
|
94
94
|
Returns:
|
|
95
95
|
Path to a trained model or `None` if training was not successful.
|
|
96
96
|
"""
|
|
97
|
-
from rasa import train as train_all
|
|
97
|
+
from rasa.api import train as train_all
|
|
98
98
|
|
|
99
99
|
domain = rasa.cli.utils.get_validated_path(
|
|
100
100
|
args.domain, "domain", DEFAULT_DOMAIN_PATHS, none_is_valid=True
|
|
@@ -137,6 +137,7 @@ def run_training(args: argparse.Namespace, can_exit: bool = False) -> Optional[T
|
|
|
137
137
|
nlu_additional_arguments=extract_nlu_additional_arguments(args),
|
|
138
138
|
model_to_finetune=_model_for_finetuning(args),
|
|
139
139
|
finetuning_epoch_fraction=args.epoch_fraction,
|
|
140
|
+
remote_storage=args.remote_storage,
|
|
140
141
|
)
|
|
141
142
|
if training_result.code != 0 and can_exit:
|
|
142
143
|
sys.exit(training_result.code)
|
rasa/cli/utils.py
CHANGED
|
@@ -462,3 +462,18 @@ def warn_if_rasa_plus_package_installed() -> None:
|
|
|
462
462
|
def check_if_studio_command() -> bool:
|
|
463
463
|
"""Checks if the command is a Rasa Studio command."""
|
|
464
464
|
return len(sys.argv) >= 2 and sys.argv[1] == "studio"
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def get_e2e_results_file_name(
|
|
468
|
+
results_output_path: Path,
|
|
469
|
+
result_type: str,
|
|
470
|
+
) -> str:
|
|
471
|
+
"""Returns the name of the e2e results file."""
|
|
472
|
+
if results_output_path.is_dir():
|
|
473
|
+
file_name = str(results_output_path) + f"/e2e_results_{result_type}.yml"
|
|
474
|
+
else:
|
|
475
|
+
parent = results_output_path.parent
|
|
476
|
+
stem = results_output_path.stem
|
|
477
|
+
file_name = str(parent) + f"/{stem}_{result_type}.yml"
|
|
478
|
+
|
|
479
|
+
return file_name
|
rasa/constants.py
CHANGED
|
@@ -18,7 +18,7 @@ CONFIG_TELEMETRY_ID = "rasa_user_id"
|
|
|
18
18
|
CONFIG_TELEMETRY_ENABLED = "enabled"
|
|
19
19
|
CONFIG_TELEMETRY_DATE = "date"
|
|
20
20
|
|
|
21
|
-
MINIMUM_COMPATIBLE_VERSION = "3.
|
|
21
|
+
MINIMUM_COMPATIBLE_VERSION = "3.10.0rc1"
|
|
22
22
|
|
|
23
23
|
GLOBAL_USER_CONFIG_PATH = os.path.expanduser("~/.config/rasa/global.yml")
|
|
24
24
|
|
|
@@ -27,6 +27,7 @@ ENV_LOG_LEVEL_LIBRARIES = "LOG_LEVEL_LIBRARIES"
|
|
|
27
27
|
ENV_LOG_LEVEL_MATPLOTLIB = "LOG_LEVEL_MATPLOTLIB"
|
|
28
28
|
ENV_LOG_LEVEL_RABBITMQ = "LOG_LEVEL_RABBITMQ"
|
|
29
29
|
ENV_LOG_LEVEL_KAFKA = "LOG_LEVEL_KAFKA"
|
|
30
|
+
ENV_LOG_LEVEL_MLFLOW = "LOG_LEVEL_MLFLOW"
|
|
30
31
|
|
|
31
32
|
DEFAULT_SANIC_WORKERS = 1
|
|
32
33
|
ENV_SANIC_WORKERS = "SANIC_WORKERS"
|
|
@@ -35,3 +36,8 @@ ENV_SANIC_BACKLOG = "SANIC_BACKLOG"
|
|
|
35
36
|
ENV_GPU_CONFIG = "TF_GPU_MEMORY_ALLOC"
|
|
36
37
|
ENV_CPU_INTER_OP_CONFIG = "TF_INTER_OP_PARALLELISM_THREADS"
|
|
37
38
|
ENV_CPU_INTRA_OP_CONFIG = "TF_INTRA_OP_PARALLELISM_THREADS"
|
|
39
|
+
|
|
40
|
+
MODEL_ARCHIVE_EXTENSION = "tar.gz"
|
|
41
|
+
|
|
42
|
+
HTTP_STATUS_FORBIDDEN = 403
|
|
43
|
+
HTTP_STATUS_NOT_FOUND = 404
|