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.

Files changed (183) hide show
  1. README.md +0 -374
  2. rasa/__init__.py +1 -2
  3. rasa/__main__.py +5 -0
  4. rasa/anonymization/anonymization_rule_executor.py +2 -2
  5. rasa/api.py +27 -23
  6. rasa/cli/arguments/data.py +27 -2
  7. rasa/cli/arguments/default_arguments.py +25 -3
  8. rasa/cli/arguments/run.py +9 -9
  9. rasa/cli/arguments/train.py +11 -3
  10. rasa/cli/data.py +70 -8
  11. rasa/cli/e2e_test.py +104 -431
  12. rasa/cli/evaluate.py +1 -1
  13. rasa/cli/interactive.py +1 -0
  14. rasa/cli/llm_fine_tuning.py +398 -0
  15. rasa/cli/project_templates/calm/endpoints.yml +1 -1
  16. rasa/cli/project_templates/tutorial/endpoints.yml +1 -1
  17. rasa/cli/run.py +15 -14
  18. rasa/cli/scaffold.py +10 -8
  19. rasa/cli/studio/studio.py +35 -5
  20. rasa/cli/train.py +56 -8
  21. rasa/cli/utils.py +22 -5
  22. rasa/cli/x.py +1 -1
  23. rasa/constants.py +7 -1
  24. rasa/core/actions/action.py +98 -49
  25. rasa/core/actions/action_run_slot_rejections.py +4 -1
  26. rasa/core/actions/custom_action_executor.py +9 -6
  27. rasa/core/actions/direct_custom_actions_executor.py +80 -0
  28. rasa/core/actions/e2e_stub_custom_action_executor.py +68 -0
  29. rasa/core/actions/grpc_custom_action_executor.py +2 -2
  30. rasa/core/actions/http_custom_action_executor.py +6 -5
  31. rasa/core/agent.py +21 -17
  32. rasa/core/channels/__init__.py +2 -0
  33. rasa/core/channels/audiocodes.py +1 -16
  34. rasa/core/channels/voice_aware/__init__.py +0 -0
  35. rasa/core/channels/voice_aware/jambonz.py +103 -0
  36. rasa/core/channels/voice_aware/jambonz_protocol.py +344 -0
  37. rasa/core/channels/voice_aware/utils.py +20 -0
  38. rasa/core/channels/voice_native/__init__.py +0 -0
  39. rasa/core/constants.py +6 -1
  40. rasa/core/information_retrieval/faiss.py +7 -4
  41. rasa/core/information_retrieval/information_retrieval.py +8 -0
  42. rasa/core/information_retrieval/milvus.py +9 -2
  43. rasa/core/information_retrieval/qdrant.py +1 -1
  44. rasa/core/nlg/contextual_response_rephraser.py +32 -10
  45. rasa/core/nlg/summarize.py +4 -3
  46. rasa/core/policies/enterprise_search_policy.py +113 -45
  47. rasa/core/policies/flows/flow_executor.py +122 -76
  48. rasa/core/policies/intentless_policy.py +83 -29
  49. rasa/core/processor.py +72 -54
  50. rasa/core/run.py +5 -4
  51. rasa/core/tracker_store.py +8 -4
  52. rasa/core/training/interactive.py +1 -1
  53. rasa/core/utils.py +56 -57
  54. rasa/dialogue_understanding/coexistence/llm_based_router.py +53 -13
  55. rasa/dialogue_understanding/commands/__init__.py +6 -0
  56. rasa/dialogue_understanding/commands/restart_command.py +58 -0
  57. rasa/dialogue_understanding/commands/session_start_command.py +59 -0
  58. rasa/dialogue_understanding/commands/utils.py +40 -0
  59. rasa/dialogue_understanding/generator/constants.py +10 -3
  60. rasa/dialogue_understanding/generator/flow_retrieval.py +21 -5
  61. rasa/dialogue_understanding/generator/llm_based_command_generator.py +13 -3
  62. rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +134 -90
  63. rasa/dialogue_understanding/generator/nlu_command_adapter.py +47 -7
  64. rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +127 -41
  65. rasa/dialogue_understanding/patterns/restart.py +37 -0
  66. rasa/dialogue_understanding/patterns/session_start.py +37 -0
  67. rasa/dialogue_understanding/processor/command_processor.py +16 -3
  68. rasa/dialogue_understanding/processor/command_processor_component.py +6 -2
  69. rasa/e2e_test/aggregate_test_stats_calculator.py +134 -0
  70. rasa/e2e_test/assertions.py +1223 -0
  71. rasa/e2e_test/assertions_schema.yml +106 -0
  72. rasa/e2e_test/constants.py +20 -0
  73. rasa/e2e_test/e2e_config.py +220 -0
  74. rasa/e2e_test/e2e_config_schema.yml +26 -0
  75. rasa/e2e_test/e2e_test_case.py +131 -8
  76. rasa/e2e_test/e2e_test_converter.py +363 -0
  77. rasa/e2e_test/e2e_test_converter_prompt.jinja2 +70 -0
  78. rasa/e2e_test/e2e_test_coverage_report.py +364 -0
  79. rasa/e2e_test/e2e_test_result.py +26 -6
  80. rasa/e2e_test/e2e_test_runner.py +493 -71
  81. rasa/e2e_test/e2e_test_schema.yml +96 -0
  82. rasa/e2e_test/pykwalify_extensions.py +39 -0
  83. rasa/e2e_test/stub_custom_action.py +70 -0
  84. rasa/e2e_test/utils/__init__.py +0 -0
  85. rasa/e2e_test/utils/e2e_yaml_utils.py +55 -0
  86. rasa/e2e_test/utils/io.py +598 -0
  87. rasa/e2e_test/utils/validation.py +80 -0
  88. rasa/engine/graph.py +9 -3
  89. rasa/engine/recipes/default_components.py +0 -2
  90. rasa/engine/recipes/default_recipe.py +10 -2
  91. rasa/engine/storage/local_model_storage.py +40 -12
  92. rasa/engine/validation.py +78 -1
  93. rasa/env.py +9 -0
  94. rasa/graph_components/providers/story_graph_provider.py +59 -6
  95. rasa/llm_fine_tuning/__init__.py +0 -0
  96. rasa/llm_fine_tuning/annotation_module.py +241 -0
  97. rasa/llm_fine_tuning/conversations.py +144 -0
  98. rasa/llm_fine_tuning/llm_data_preparation_module.py +178 -0
  99. rasa/llm_fine_tuning/notebooks/unsloth_finetuning.ipynb +407 -0
  100. rasa/llm_fine_tuning/paraphrasing/__init__.py +0 -0
  101. rasa/llm_fine_tuning/paraphrasing/conversation_rephraser.py +281 -0
  102. rasa/llm_fine_tuning/paraphrasing/default_rephrase_prompt_template.jina2 +44 -0
  103. rasa/llm_fine_tuning/paraphrasing/rephrase_validator.py +121 -0
  104. rasa/llm_fine_tuning/paraphrasing/rephrased_user_message.py +10 -0
  105. rasa/llm_fine_tuning/paraphrasing_module.py +128 -0
  106. rasa/llm_fine_tuning/storage.py +174 -0
  107. rasa/llm_fine_tuning/train_test_split_module.py +441 -0
  108. rasa/model_training.py +56 -16
  109. rasa/nlu/persistor.py +157 -36
  110. rasa/server.py +45 -10
  111. rasa/shared/constants.py +76 -16
  112. rasa/shared/core/domain.py +27 -19
  113. rasa/shared/core/events.py +28 -2
  114. rasa/shared/core/flows/flow.py +208 -13
  115. rasa/shared/core/flows/flow_path.py +84 -0
  116. rasa/shared/core/flows/flows_list.py +33 -11
  117. rasa/shared/core/flows/flows_yaml_schema.json +269 -193
  118. rasa/shared/core/flows/validation.py +112 -25
  119. rasa/shared/core/flows/yaml_flows_io.py +149 -10
  120. rasa/shared/core/trackers.py +6 -0
  121. rasa/shared/core/training_data/structures.py +20 -0
  122. rasa/shared/core/training_data/visualization.html +2 -2
  123. rasa/shared/exceptions.py +4 -0
  124. rasa/shared/importers/importer.py +64 -16
  125. rasa/shared/nlu/constants.py +2 -0
  126. rasa/shared/providers/_configs/__init__.py +0 -0
  127. rasa/shared/providers/_configs/azure_openai_client_config.py +183 -0
  128. rasa/shared/providers/_configs/client_config.py +57 -0
  129. rasa/shared/providers/_configs/default_litellm_client_config.py +130 -0
  130. rasa/shared/providers/_configs/huggingface_local_embedding_client_config.py +234 -0
  131. rasa/shared/providers/_configs/openai_client_config.py +175 -0
  132. rasa/shared/providers/_configs/self_hosted_llm_client_config.py +176 -0
  133. rasa/shared/providers/_configs/utils.py +101 -0
  134. rasa/shared/providers/_ssl_verification_utils.py +124 -0
  135. rasa/shared/providers/embedding/__init__.py +0 -0
  136. rasa/shared/providers/embedding/_base_litellm_embedding_client.py +259 -0
  137. rasa/shared/providers/embedding/_langchain_embedding_client_adapter.py +74 -0
  138. rasa/shared/providers/embedding/azure_openai_embedding_client.py +277 -0
  139. rasa/shared/providers/embedding/default_litellm_embedding_client.py +102 -0
  140. rasa/shared/providers/embedding/embedding_client.py +90 -0
  141. rasa/shared/providers/embedding/embedding_response.py +41 -0
  142. rasa/shared/providers/embedding/huggingface_local_embedding_client.py +191 -0
  143. rasa/shared/providers/embedding/openai_embedding_client.py +172 -0
  144. rasa/shared/providers/llm/__init__.py +0 -0
  145. rasa/shared/providers/llm/_base_litellm_client.py +251 -0
  146. rasa/shared/providers/llm/azure_openai_llm_client.py +338 -0
  147. rasa/shared/providers/llm/default_litellm_llm_client.py +84 -0
  148. rasa/shared/providers/llm/llm_client.py +76 -0
  149. rasa/shared/providers/llm/llm_response.py +50 -0
  150. rasa/shared/providers/llm/openai_llm_client.py +155 -0
  151. rasa/shared/providers/llm/self_hosted_llm_client.py +293 -0
  152. rasa/shared/providers/mappings.py +75 -0
  153. rasa/shared/utils/cli.py +30 -0
  154. rasa/shared/utils/io.py +65 -2
  155. rasa/shared/utils/llm.py +246 -200
  156. rasa/shared/utils/yaml.py +121 -15
  157. rasa/studio/auth.py +6 -4
  158. rasa/studio/config.py +13 -4
  159. rasa/studio/constants.py +1 -0
  160. rasa/studio/data_handler.py +10 -3
  161. rasa/studio/download.py +19 -13
  162. rasa/studio/train.py +2 -3
  163. rasa/studio/upload.py +19 -11
  164. rasa/telemetry.py +113 -58
  165. rasa/tracing/instrumentation/attribute_extractors.py +32 -17
  166. rasa/utils/common.py +18 -19
  167. rasa/utils/endpoints.py +7 -4
  168. rasa/utils/json_utils.py +60 -0
  169. rasa/utils/licensing.py +9 -1
  170. rasa/utils/ml_utils.py +4 -2
  171. rasa/validator.py +213 -3
  172. rasa/version.py +1 -1
  173. rasa_pro-3.10.16.dist-info/METADATA +196 -0
  174. {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.16.dist-info}/RECORD +179 -113
  175. rasa/nlu/classifiers/llm_intent_classifier.py +0 -519
  176. rasa/shared/providers/openai/clients.py +0 -43
  177. rasa/shared/providers/openai/session_handler.py +0 -110
  178. rasa_pro-3.9.18.dist-info/METADATA +0 -563
  179. /rasa/{shared/providers/openai → cli/project_templates/tutorial/actions}/__init__.py +0 -0
  180. /rasa/cli/project_templates/tutorial/{actions.py → actions/actions.py} +0 -0
  181. {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.16.dist-info}/NOTICE +0 -0
  182. {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.16.dist-info}/WHEEL +0 -0
  183. {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)
@@ -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
- from rasa.e2e_test.e2e_test_case import TestCase
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
- return {
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]