rasa-pro 3.9.18__py3-none-any.whl → 3.10.16__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 +0 -374
- rasa/__init__.py +1 -2
- rasa/__main__.py +5 -0
- rasa/anonymization/anonymization_rule_executor.py +2 -2
- rasa/api.py +27 -23
- 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 +11 -3
- rasa/cli/data.py +70 -8
- rasa/cli/e2e_test.py +104 -431
- rasa/cli/evaluate.py +1 -1
- rasa/cli/interactive.py +1 -0
- rasa/cli/llm_fine_tuning.py +398 -0
- rasa/cli/project_templates/calm/endpoints.yml +1 -1
- rasa/cli/project_templates/tutorial/endpoints.yml +1 -1
- rasa/cli/run.py +15 -14
- rasa/cli/scaffold.py +10 -8
- rasa/cli/studio/studio.py +35 -5
- rasa/cli/train.py +56 -8
- rasa/cli/utils.py +22 -5
- rasa/cli/x.py +1 -1
- 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/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/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 +113 -45
- rasa/core/policies/flows/flow_executor.py +122 -76
- rasa/core/policies/intentless_policy.py +83 -29
- rasa/core/processor.py +72 -54
- rasa/core/run.py +5 -4
- rasa/core/tracker_store.py +8 -4
- rasa/core/training/interactive.py +1 -1
- rasa/core/utils.py +56 -57
- rasa/dialogue_understanding/coexistence/llm_based_router.py +53 -13
- rasa/dialogue_understanding/commands/__init__.py +6 -0
- rasa/dialogue_understanding/commands/restart_command.py +58 -0
- rasa/dialogue_understanding/commands/session_start_command.py +59 -0
- rasa/dialogue_understanding/commands/utils.py +40 -0
- rasa/dialogue_understanding/generator/constants.py +10 -3
- rasa/dialogue_understanding/generator/flow_retrieval.py +21 -5
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +13 -3
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +134 -90
- rasa/dialogue_understanding/generator/nlu_command_adapter.py +47 -7
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +127 -41
- rasa/dialogue_understanding/patterns/restart.py +37 -0
- rasa/dialogue_understanding/patterns/session_start.py +37 -0
- rasa/dialogue_understanding/processor/command_processor.py +16 -3
- rasa/dialogue_understanding/processor/command_processor_component.py +6 -2
- rasa/e2e_test/aggregate_test_stats_calculator.py +134 -0
- rasa/e2e_test/assertions.py +1223 -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 +493 -71
- 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 +598 -0
- rasa/e2e_test/utils/validation.py +80 -0
- rasa/engine/graph.py +9 -3
- rasa/engine/recipes/default_components.py +0 -2
- rasa/engine/recipes/default_recipe.py +10 -2
- rasa/engine/storage/local_model_storage.py +40 -12
- rasa/engine/validation.py +78 -1
- rasa/env.py +9 -0
- rasa/graph_components/providers/story_graph_provider.py +59 -6
- 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 +56 -16
- rasa/nlu/persistor.py +157 -36
- rasa/server.py +45 -10
- rasa/shared/constants.py +76 -16
- rasa/shared/core/domain.py +27 -19
- 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 +33 -11
- 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/structures.py +20 -0
- rasa/shared/core/training_data/visualization.html +2 -2
- rasa/shared/exceptions.py +4 -0
- rasa/shared/importers/importer.py +64 -16
- rasa/shared/nlu/constants.py +2 -0
- rasa/shared/providers/_configs/__init__.py +0 -0
- rasa/shared/providers/_configs/azure_openai_client_config.py +183 -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 +176 -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 +259 -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 +251 -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 +293 -0
- rasa/shared/providers/mappings.py +75 -0
- rasa/shared/utils/cli.py +30 -0
- rasa/shared/utils/io.py +65 -2
- rasa/shared/utils/llm.py +246 -200
- rasa/shared/utils/yaml.py +121 -15
- rasa/studio/auth.py +6 -4
- rasa/studio/config.py +13 -4
- rasa/studio/constants.py +1 -0
- rasa/studio/data_handler.py +10 -3
- rasa/studio/download.py +19 -13
- rasa/studio/train.py +2 -3
- rasa/studio/upload.py +19 -11
- rasa/telemetry.py +113 -58
- rasa/tracing/instrumentation/attribute_extractors.py +32 -17
- rasa/utils/common.py +18 -19
- rasa/utils/endpoints.py +7 -4
- rasa/utils/json_utils.py +60 -0
- rasa/utils/licensing.py +9 -1
- rasa/utils/ml_utils.py +4 -2
- rasa/validator.py +213 -3
- rasa/version.py +1 -1
- rasa_pro-3.10.16.dist-info/METADATA +196 -0
- {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.16.dist-info}/RECORD +179 -113
- 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_pro-3.9.18.dist-info/METADATA +0 -563
- /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.16.dist-info}/NOTICE +0 -0
- {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.16.dist-info}/WHEEL +0 -0
- {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.16.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
from typing import List, Dict, Any, Optional, Set
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import structlog
|
|
5
|
+
|
|
6
|
+
from rasa.dialogue_understanding.commands import (
|
|
7
|
+
KnowledgeAnswerCommand,
|
|
8
|
+
StartFlowCommand,
|
|
9
|
+
SetSlotCommand,
|
|
10
|
+
ClarifyCommand,
|
|
11
|
+
HumanHandoffCommand,
|
|
12
|
+
CancelFlowCommand,
|
|
13
|
+
ChitChatAnswerCommand,
|
|
14
|
+
SkipQuestionCommand,
|
|
15
|
+
)
|
|
16
|
+
from rasa.e2e_test.e2e_test_result import TestResult
|
|
17
|
+
from rasa.shared.core.flows import FlowsList
|
|
18
|
+
from rasa.shared.core.flows.flow_path import FlowPath, FlowPathsList, PathNode
|
|
19
|
+
|
|
20
|
+
# Report column names
|
|
21
|
+
FLOW_NAME_COL_NAME = "Flow Name"
|
|
22
|
+
NUM_STEPS_COL_NAME = "Num Steps"
|
|
23
|
+
MISSING_STEPS_COL_NAME = "Missing Steps"
|
|
24
|
+
LINE_NUMBERS_COL_NAME = "Line Numbers"
|
|
25
|
+
COVERAGE_COL_NAME = "Coverage"
|
|
26
|
+
|
|
27
|
+
FLOWS_KEY = "flows"
|
|
28
|
+
NUMBER_OF_STEPS_KEY = "number_of_steps"
|
|
29
|
+
NUMBER_OF_UNTESTED_STEPS_KEY = "number_of_untested_steps"
|
|
30
|
+
UNTESTED_LINES_KEY = "untested_lines"
|
|
31
|
+
|
|
32
|
+
SUPPORTED_HISTOGRAM_COMMANDS = [
|
|
33
|
+
KnowledgeAnswerCommand.command(),
|
|
34
|
+
StartFlowCommand.command(),
|
|
35
|
+
SetSlotCommand.command(),
|
|
36
|
+
ClarifyCommand.command(),
|
|
37
|
+
HumanHandoffCommand.command(),
|
|
38
|
+
CancelFlowCommand.command(),
|
|
39
|
+
ChitChatAnswerCommand.command(),
|
|
40
|
+
SkipQuestionCommand.command(),
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
structlogger = structlog.get_logger()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def create_coverage_report(
|
|
47
|
+
flows: FlowsList,
|
|
48
|
+
test_results: List[TestResult],
|
|
49
|
+
) -> Optional[pd.DataFrame]:
|
|
50
|
+
"""Generates a coverage report.
|
|
51
|
+
|
|
52
|
+
This function extracts paths from predefined flows, loads tested paths based
|
|
53
|
+
on the provided e2e test results, and compares the unique nodes of all flow paths
|
|
54
|
+
of one flow to obtain untested nodes. It then generates
|
|
55
|
+
a report that highlights areas of the flows that are not adequately tested.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
flows: List of flows.
|
|
59
|
+
test_results: List of e2e test results.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
The coverage report as dataframe.
|
|
63
|
+
"""
|
|
64
|
+
if not test_results or flows.user_flows.is_empty():
|
|
65
|
+
return _empty_dataframe()
|
|
66
|
+
|
|
67
|
+
# Step 1: Get testable flow paths
|
|
68
|
+
flow_to_testable_paths = flows.extract_flow_paths()
|
|
69
|
+
|
|
70
|
+
# Step 2: Get tested flow paths from e2e tests run
|
|
71
|
+
tested_flow_paths = _extract_tested_flow_paths(test_results)
|
|
72
|
+
# No tested flow paths exists, cannot create coverage report
|
|
73
|
+
if not tested_flow_paths:
|
|
74
|
+
return _empty_dataframe()
|
|
75
|
+
|
|
76
|
+
# Step 3: Group flow paths by flow
|
|
77
|
+
flow_to_tested_paths = _group_flow_paths_by_flow(tested_flow_paths)
|
|
78
|
+
|
|
79
|
+
# Step 4: Get the unvisited nodes and number of unique nodes per flow
|
|
80
|
+
unvisited_nodes_per_flow = _get_unvisited_nodes_per_flow(
|
|
81
|
+
flow_to_testable_paths, flow_to_tested_paths
|
|
82
|
+
)
|
|
83
|
+
number_of_nodes_per_flow = {
|
|
84
|
+
flow: flow_paths.get_number_of_unique_nodes()
|
|
85
|
+
for flow, flow_paths in flow_to_testable_paths.items()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# Step 5: Produce the report
|
|
89
|
+
coverage_report_data = _create_coverage_report_data(
|
|
90
|
+
flows,
|
|
91
|
+
number_of_nodes_per_flow,
|
|
92
|
+
unvisited_nodes_per_flow,
|
|
93
|
+
)
|
|
94
|
+
return _create_data_frame(coverage_report_data)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _get_unvisited_nodes_per_flow(
|
|
98
|
+
flow_to_testable_paths: Dict[str, FlowPathsList],
|
|
99
|
+
flow_to_tested_paths: Dict[str, FlowPathsList],
|
|
100
|
+
) -> Dict[str, Set[PathNode]]:
|
|
101
|
+
"""Returns the unvisited path nodes per flow.
|
|
102
|
+
|
|
103
|
+
Compares the set of unique nodes of the testable paths to the unique nodes of the
|
|
104
|
+
tested paths.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
flow_to_testable_paths: Testable paths per flow.
|
|
108
|
+
flow_to_tested_paths: Tested paths per flow.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
The unvisited nodes per flow.
|
|
112
|
+
"""
|
|
113
|
+
unvisited_nodes_per_flow: Dict[str, Set[PathNode]] = {}
|
|
114
|
+
|
|
115
|
+
for flow, testable_paths in flow_to_testable_paths.items():
|
|
116
|
+
if flow in flow_to_tested_paths:
|
|
117
|
+
# get the difference of testable and tested nodes
|
|
118
|
+
testable_nodes = testable_paths.get_unique_nodes()
|
|
119
|
+
tested_nodes = flow_to_tested_paths[flow].get_unique_nodes()
|
|
120
|
+
unvisited_nodes = testable_nodes.difference(tested_nodes)
|
|
121
|
+
else:
|
|
122
|
+
# the flow was not tested at all
|
|
123
|
+
unvisited_nodes = testable_paths.get_unique_nodes()
|
|
124
|
+
unvisited_nodes_per_flow[flow] = unvisited_nodes
|
|
125
|
+
|
|
126
|
+
return unvisited_nodes_per_flow
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _group_flow_paths_by_flow(flow_paths: List[FlowPath]) -> Dict[str, FlowPathsList]:
|
|
130
|
+
"""Group the available flow paths by flow id.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
flow_paths: The list of all flow paths.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
A dictionary mapping a flow to its paths.
|
|
137
|
+
"""
|
|
138
|
+
flow_to_paths = {}
|
|
139
|
+
|
|
140
|
+
for flow_path in flow_paths:
|
|
141
|
+
if flow_path.flow not in flow_to_paths:
|
|
142
|
+
flow_to_paths[flow_path.flow] = FlowPathsList(flow_path.flow, [flow_path])
|
|
143
|
+
else:
|
|
144
|
+
flow_to_paths[flow_path.flow].paths.append(flow_path)
|
|
145
|
+
|
|
146
|
+
return flow_to_paths
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _create_data_frame(coverage_report_data: Dict[str, Any]) -> pd.DataFrame:
|
|
150
|
+
df = _construct_dataframe(coverage_report_data)
|
|
151
|
+
df = _calculate_coverage(df)
|
|
152
|
+
df = _append_total_row(df)
|
|
153
|
+
df = _reorder_columns(df)
|
|
154
|
+
return df
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _empty_dataframe() -> pd.DataFrame:
|
|
158
|
+
"""Generates an empty DataFrame.
|
|
159
|
+
|
|
160
|
+
The DataFrame includes all required columns for a coverage report.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
pd.DataFrame: An empty DataFrame with predefined columns.
|
|
164
|
+
"""
|
|
165
|
+
return pd.DataFrame(
|
|
166
|
+
columns=[
|
|
167
|
+
FLOW_NAME_COL_NAME,
|
|
168
|
+
COVERAGE_COL_NAME,
|
|
169
|
+
NUM_STEPS_COL_NAME,
|
|
170
|
+
MISSING_STEPS_COL_NAME,
|
|
171
|
+
LINE_NUMBERS_COL_NAME,
|
|
172
|
+
]
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _create_coverage_report_data(
|
|
177
|
+
flows: FlowsList,
|
|
178
|
+
number_of_nodes_per_flow: Dict[str, int],
|
|
179
|
+
unvisited_nodes_per_flow: Dict[str, Set[PathNode]],
|
|
180
|
+
) -> Dict[str, Any]:
|
|
181
|
+
"""Creates the data for the coverage report.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
flows: All available flow names
|
|
185
|
+
number_of_nodes_per_flow: Number of nodes per flow
|
|
186
|
+
unvisited_nodes_per_flow: Unvisited nodes per flow
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
A dictionary with processed data needed to construct the DataFrame.
|
|
190
|
+
"""
|
|
191
|
+
flow_ids = [flow.id for flow in flows.user_flows.underlying_flows]
|
|
192
|
+
|
|
193
|
+
flow_full_names = ["unknown"] * len(flow_ids)
|
|
194
|
+
number_of_steps = [0] * len(flow_ids)
|
|
195
|
+
number_of_untested_steps = [0] * len(flow_ids)
|
|
196
|
+
untested_lines: List[List[str]] = [[]] * len(flow_ids)
|
|
197
|
+
|
|
198
|
+
for flow in flow_ids:
|
|
199
|
+
nodes = unvisited_nodes_per_flow[flow]
|
|
200
|
+
lines: List[str] = [node.lines for node in nodes if node.lines]
|
|
201
|
+
|
|
202
|
+
index = flow_ids.index(flow)
|
|
203
|
+
if flow_object := flows.flow_by_id(flow):
|
|
204
|
+
flow_full_names[index] = flow_object.get_full_name()
|
|
205
|
+
number_of_steps[index] = number_of_nodes_per_flow[flow]
|
|
206
|
+
number_of_untested_steps[index] = len(nodes)
|
|
207
|
+
untested_lines[index] = lines
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
FLOWS_KEY: flow_full_names,
|
|
211
|
+
NUMBER_OF_STEPS_KEY: number_of_steps,
|
|
212
|
+
NUMBER_OF_UNTESTED_STEPS_KEY: number_of_untested_steps,
|
|
213
|
+
UNTESTED_LINES_KEY: _reformat_untested_lines(untested_lines),
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _reformat_untested_lines(untested_lines: List[List[str]]) -> List[str]:
|
|
218
|
+
"""Format lists of lists to list of str.
|
|
219
|
+
|
|
220
|
+
Formats nested lists of untested lines into a string format suitable
|
|
221
|
+
for display.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
untested_lines: A list of lists containing line information.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
A list of formatted strings representing untested lines.
|
|
228
|
+
"""
|
|
229
|
+
return ["[" + ", ".join(sublist) + "]" for sublist in untested_lines]
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _construct_dataframe(report_data: Dict[str, Any]) -> pd.DataFrame:
|
|
233
|
+
"""Constructs a DataFrame from the provided data.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
report_data: A dictionary containing report data.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
pd.DataFrame: A DataFrame constructed from the report data.
|
|
240
|
+
"""
|
|
241
|
+
return pd.DataFrame(
|
|
242
|
+
{
|
|
243
|
+
FLOW_NAME_COL_NAME: report_data[FLOWS_KEY],
|
|
244
|
+
NUM_STEPS_COL_NAME: report_data[NUMBER_OF_STEPS_KEY],
|
|
245
|
+
MISSING_STEPS_COL_NAME: report_data[NUMBER_OF_UNTESTED_STEPS_KEY],
|
|
246
|
+
LINE_NUMBERS_COL_NAME: report_data[UNTESTED_LINES_KEY],
|
|
247
|
+
}
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _calculate_coverage(df: pd.DataFrame) -> pd.DataFrame:
|
|
252
|
+
"""Calculates the coverage percentage and updates the DataFrame.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
df: The DataFrame to update with coverage data.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
The updated DataFrame with coverage percentages.
|
|
259
|
+
"""
|
|
260
|
+
df[COVERAGE_COL_NAME] = (
|
|
261
|
+
(df[NUM_STEPS_COL_NAME] - df[MISSING_STEPS_COL_NAME]) / df[NUM_STEPS_COL_NAME]
|
|
262
|
+
) * 100
|
|
263
|
+
|
|
264
|
+
# Set the float format for displaying coverage percentages
|
|
265
|
+
pd.options.display.float_format = "{:.2f}%".format
|
|
266
|
+
|
|
267
|
+
return df
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _append_total_row(df: pd.DataFrame) -> pd.DataFrame:
|
|
271
|
+
"""Appends a total summary row to the DataFrame.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
df: The DataFrame to which the total row will be appended.
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
The updated DataFrame with an appended total row.
|
|
278
|
+
"""
|
|
279
|
+
total_data = {
|
|
280
|
+
FLOW_NAME_COL_NAME: "Total",
|
|
281
|
+
NUM_STEPS_COL_NAME: df[NUM_STEPS_COL_NAME].sum(),
|
|
282
|
+
MISSING_STEPS_COL_NAME: df[MISSING_STEPS_COL_NAME].sum(),
|
|
283
|
+
LINE_NUMBERS_COL_NAME: "",
|
|
284
|
+
COVERAGE_COL_NAME: _calculate_total_coverage(df),
|
|
285
|
+
}
|
|
286
|
+
# Append the total row using `.loc`
|
|
287
|
+
df.loc[len(df)] = total_data
|
|
288
|
+
|
|
289
|
+
return df
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _calculate_total_coverage(df: pd.DataFrame) -> str:
|
|
293
|
+
"""Calculates the total coverage percentage for the DataFrame.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
df: The DataFrame for which total coverage is calculated.
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
The calculated total coverage percentage formatted as a string.
|
|
300
|
+
"""
|
|
301
|
+
total_steps = df[NUM_STEPS_COL_NAME].sum()
|
|
302
|
+
missing_steps = df[MISSING_STEPS_COL_NAME].sum()
|
|
303
|
+
return (
|
|
304
|
+
pd.NA
|
|
305
|
+
if total_steps == 0
|
|
306
|
+
else f"{((total_steps - missing_steps) / total_steps) * 100:.2f}%"
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _reorder_columns(df: pd.DataFrame) -> pd.DataFrame:
|
|
311
|
+
"""Reorders the columns of the DataFrame to prioritize specific columns.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
df: The DataFrame whose columns are to be reordered.
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
The DataFrame with reordered columns.
|
|
318
|
+
"""
|
|
319
|
+
columns = [FLOW_NAME_COL_NAME, COVERAGE_COL_NAME] + [
|
|
320
|
+
col for col in df.columns if col not in [FLOW_NAME_COL_NAME, COVERAGE_COL_NAME]
|
|
321
|
+
]
|
|
322
|
+
return df[columns]
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _extract_tested_flow_paths(test_results: List[TestResult]) -> List[FlowPath]:
|
|
326
|
+
"""Extract the flow paths of the test results.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
test_results: List of test results.
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
List[FlowPath]: A list of FlowPaths.
|
|
333
|
+
"""
|
|
334
|
+
flatten_paths = []
|
|
335
|
+
|
|
336
|
+
for test_result in test_results:
|
|
337
|
+
if test_result.tested_paths:
|
|
338
|
+
for path in test_result.tested_paths:
|
|
339
|
+
flatten_paths.append(path)
|
|
340
|
+
|
|
341
|
+
return flatten_paths
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def extract_tested_commands(test_results: List[TestResult]) -> Dict[str, int]:
|
|
345
|
+
"""Extract tested commands from the test results.
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
test_results: List of test results.
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
Dict[str, int]: A dictionary of commands and their counts.
|
|
352
|
+
"""
|
|
353
|
+
command_histogram_data = {}
|
|
354
|
+
for command in SUPPORTED_HISTOGRAM_COMMANDS:
|
|
355
|
+
command_histogram_data[command] = 0
|
|
356
|
+
|
|
357
|
+
for test_result in test_results:
|
|
358
|
+
if test_result.tested_commands:
|
|
359
|
+
for flow, commands_dict in test_result.tested_commands.items():
|
|
360
|
+
for command, count in commands_dict.items():
|
|
361
|
+
if command in command_histogram_data:
|
|
362
|
+
command_histogram_data[command] += count
|
|
363
|
+
|
|
364
|
+
return dict(command_histogram_data)
|
rasa/e2e_test/e2e_test_result.py
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
from typing import Any, Dict, List, Optional, Text
|
|
2
|
+
from typing import Any, Dict, List, Optional, TYPE_CHECKING, Text
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from rasa.e2e_test.assertions import AssertionFailure
|
|
6
|
+
from rasa.e2e_test.e2e_test_case import TestCase
|
|
7
|
+
from rasa.shared.core.flows.flow_path import FlowPath
|
|
5
8
|
|
|
6
9
|
NO_RESPONSE = "* No Bot Response *"
|
|
7
10
|
NO_SLOT = "* No Slot Set *"
|
|
@@ -11,24 +14,41 @@ NO_SLOT = "* No Slot Set *"
|
|
|
11
14
|
class TestResult:
|
|
12
15
|
"""Class for storing results of every test case run."""
|
|
13
16
|
|
|
14
|
-
test_case: TestCase
|
|
17
|
+
test_case: "TestCase"
|
|
15
18
|
pass_status: bool
|
|
16
19
|
difference: List[Text]
|
|
17
20
|
error_line: Optional[int] = None
|
|
21
|
+
assertion_failure: Optional["AssertionFailure"] = None
|
|
22
|
+
tested_paths: Optional[List["FlowPath"]] = None
|
|
23
|
+
tested_commands: Optional[Dict[str, Dict[str, int]]] = None
|
|
18
24
|
|
|
19
25
|
def as_dict(self) -> Dict[Text, Any]:
|
|
20
26
|
"""Returns the test result as a dictionary."""
|
|
21
|
-
|
|
27
|
+
serialized_test_result = {
|
|
22
28
|
"name": self.test_case.name,
|
|
23
29
|
"pass_status": self.pass_status,
|
|
24
30
|
"expected_steps": [s.as_dict() for s in self.test_case.steps],
|
|
25
|
-
"difference": self.difference,
|
|
26
31
|
}
|
|
27
32
|
|
|
33
|
+
if self.error_line is not None:
|
|
34
|
+
serialized_test_result["error_path"] = (
|
|
35
|
+
f"{self.test_case.file}:{self.error_line}"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if self.difference:
|
|
39
|
+
serialized_test_result["difference"] = self.difference
|
|
40
|
+
|
|
41
|
+
if self.assertion_failure is not None:
|
|
42
|
+
serialized_test_result["assertion_failure"] = (
|
|
43
|
+
self.assertion_failure.as_dict()
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
return serialized_test_result
|
|
47
|
+
|
|
28
48
|
|
|
29
49
|
@dataclass
|
|
30
50
|
class TestFailure:
|
|
31
51
|
"""Class for storing the test case failure."""
|
|
32
52
|
|
|
33
|
-
test_case: TestCase
|
|
53
|
+
test_case: "TestCase"
|
|
34
54
|
error_line: Optional[int]
|