rasa-pro 3.11.15__py3-none-any.whl → 3.11.17__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.

@@ -1,7 +1,9 @@
1
+ import glob
1
2
  import importlib.resources
2
3
  import json
4
+ import os.path
3
5
  import re
4
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, Text
6
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Text, Tuple
5
7
  import dotenv
6
8
  import structlog
7
9
  from jinja2 import Template
@@ -148,6 +150,8 @@ DEFAULT_ENTERPRISE_SEARCH_PROMPT_WITH_CITATION_TEMPLATE = importlib.resources.re
148
150
  "rasa.core.policies", "enterprise_search_prompt_with_citation_template.jinja2"
149
151
  )
150
152
 
153
+ _ENTERPRISE_SEARCH_CITATION_PATTERN = re.compile(r"\[([^\]]+)\]")
154
+
151
155
 
152
156
  class VectorStoreConnectionError(RasaException):
153
157
  """Exception raised for errors in connecting to the vector store."""
@@ -323,9 +327,11 @@ class EnterpriseSearchPolicy(LLMHealthCheckMixin, EmbeddingsHealthCheckMixin, Po
323
327
 
324
328
  if store_type == DEFAULT_VECTOR_STORE_TYPE:
325
329
  logger.info("enterprise_search_policy.train.faiss")
330
+ docs_folder = self.vector_store_config.get(SOURCE_PROPERTY)
331
+ self._validate_documents_folder(docs_folder)
326
332
  with self._model_storage.write_to(self._resource) as path:
327
333
  self.vector_store = FAISS_Store(
328
- docs_folder=self.vector_store_config.get(SOURCE_PROPERTY),
334
+ docs_folder=docs_folder,
329
335
  embeddings=embeddings,
330
336
  index_path=path,
331
337
  create_index=True,
@@ -685,6 +691,33 @@ class EnterpriseSearchPolicy(LLMHealthCheckMixin, EmbeddingsHealthCheckMixin, Po
685
691
  result[domain.index_for_action(action_name)] = score # type: ignore[assignment]
686
692
  return result
687
693
 
694
+ @classmethod
695
+ def _validate_documents_folder(cls, docs_folder: str) -> None:
696
+ if not os.path.exists(docs_folder) or not os.path.isdir(docs_folder):
697
+ error_message = (
698
+ f"Document source directory does not exist or is not a "
699
+ f"directory: '{docs_folder}'. "
700
+ "Please specify a valid path to the documents source directory in the "
701
+ "vector_store configuration."
702
+ )
703
+ logger.error(
704
+ "enterprise_search_policy.train.faiss.invalid_source_directory",
705
+ message=error_message,
706
+ )
707
+ print_error_and_exit(error_message)
708
+
709
+ docs = glob.glob(os.path.join(docs_folder, "*.txt"), recursive=True)
710
+ if not docs or len(docs) < 1:
711
+ error_message = (
712
+ f"Document source directory is empty: '{docs_folder}'. "
713
+ "Please add documents to this directory or specify a different one."
714
+ )
715
+ logger.error(
716
+ "enterprise_search_policy.train.faiss.source_directory_empty",
717
+ message=error_message,
718
+ )
719
+ print_error_and_exit(error_message)
720
+
688
721
  @classmethod
689
722
  def load(
690
723
  cls,
@@ -695,7 +728,6 @@ class EnterpriseSearchPolicy(LLMHealthCheckMixin, EmbeddingsHealthCheckMixin, Po
695
728
  **kwargs: Any,
696
729
  ) -> "EnterpriseSearchPolicy":
697
730
  """Loads a trained policy (see parent class for full docstring)."""
698
-
699
731
  # Perform health checks for both LLM and embeddings client configs
700
732
  cls._perform_health_checks(config, "enterprise_search_policy.load")
701
733
 
@@ -759,7 +791,7 @@ class EnterpriseSearchPolicy(LLMHealthCheckMixin, EmbeddingsHealthCheckMixin, Po
759
791
  return None
760
792
 
761
793
  source = merged_config.get(VECTOR_STORE_PROPERTY, {}).get(SOURCE_PROPERTY)
762
- if not source:
794
+ if not source or not os.path.exists(source) or not os.path.isdir(source):
763
795
  return None
764
796
 
765
797
  docs = FAISS_Store.load_documents(source)
@@ -794,10 +826,18 @@ class EnterpriseSearchPolicy(LLMHealthCheckMixin, EmbeddingsHealthCheckMixin, Po
794
826
 
795
827
  @staticmethod
796
828
  def post_process_citations(llm_answer: str) -> str:
797
- """Post-process the LLM answer.
798
-
799
- Re-writes the bracketed numbers to start from 1 and
800
- re-arranges the sources to follow the enumeration order.
829
+ """Post-processes the LLM answer to correctly number and sort citations and
830
+ sources.
831
+
832
+ - Handles both single `[1]` and grouped `[1, 3]` citations.
833
+ - Rewrites the numbers in square brackets in the answer text to start from 1
834
+ and be sorted within each group.
835
+ - Reorders the sources according to the order of their first appearance
836
+ in the text.
837
+ - Removes citations from the text that point to sources missing from
838
+ the source list.
839
+ - Keeps sources that are not cited in the text, placing them at the end
840
+ of the list.
801
841
 
802
842
  Args:
803
843
  llm_answer: The LLM answer.
@@ -811,77 +851,160 @@ class EnterpriseSearchPolicy(LLMHealthCheckMixin, EmbeddingsHealthCheckMixin, Po
811
851
 
812
852
  # Split llm_answer into answer and citations
813
853
  try:
814
- answer, citations = llm_answer.rsplit("Sources:", 1)
854
+ answer_part, sources_part = llm_answer.rsplit("Sources:", 1)
815
855
  except ValueError:
816
- # if there is no "Sources:" in the llm_answer
817
- return llm_answer
818
-
819
- # Find all source references in the answer
820
- pattern = r"\[\s*(\d+(?:\s*,\s*\d+)*)\s*\]"
821
- matches = re.findall(pattern, answer)
822
- old_source_indices = [
823
- int(num.strip()) for match in matches for num in match.split(",")
824
- ]
856
+ # if there is no "Sources:" separator, return the original llm_answer
857
+ return llm_answer.strip()
858
+
859
+ # Parse the sources block to extract valid sources and other lines
860
+ valid_sources, other_source_lines = EnterpriseSearchPolicy._parse_sources_block(
861
+ sources_part
862
+ )
863
+
864
+ # Find all unique, valid citations in the answer text in their order
865
+ # of appearance
866
+ cited_order = EnterpriseSearchPolicy._get_cited_order(
867
+ answer_part, valid_sources
868
+ )
869
+
870
+ # Create a mapping from the old source numbers to the new, sequential numbers.
871
+ # For example, if the citation order in the text was [3, 1, 2], this map
872
+ # becomes {3: 1, 1: 2, 2: 3}. This allows for a quick lookup when rewriting
873
+ # the citations
874
+ renumbering_map = {
875
+ old_num: new_num + 1 for new_num, old_num in enumerate(cited_order)
876
+ }
877
+
878
+ # Rewrite the citations in the answer text based on the renumbering map
879
+ processed_answer = EnterpriseSearchPolicy._rewrite_answer_citations(
880
+ answer_part, renumbering_map
881
+ )
882
+
883
+ # Build the new list of sources
884
+ new_sources_list = EnterpriseSearchPolicy._build_final_sources_list(
885
+ cited_order,
886
+ renumbering_map,
887
+ valid_sources,
888
+ other_source_lines,
889
+ )
825
890
 
826
- # Map old source references to the correct enumeration
827
- renumber_mapping = {num: idx + 1 for idx, num in enumerate(old_source_indices)}
828
-
829
- # remove whitespace from original source citations in answer
830
- for match in matches:
831
- answer = answer.replace(f"[{match}]", f"[{match.replace(' ', '')}]")
832
-
833
- new_answer = []
834
- for word in answer.split():
835
- matches = re.findall(pattern, word)
836
- if matches:
837
- for match in matches:
838
- if "," in match:
839
- old_indices = [
840
- int(num.strip()) for num in match.split(",") if num
841
- ]
842
- new_indices = [
843
- renumber_mapping[old_index]
844
- for old_index in old_indices
845
- if old_index in renumber_mapping
846
- ]
847
- if not new_indices:
848
- continue
849
-
850
- word = word.replace(
851
- match, f"{', '.join(map(str, new_indices))}"
852
- )
853
- else:
854
- old_index = int(match.strip("[].,:;?!"))
855
- new_index = renumber_mapping.get(old_index)
856
- if not new_index:
857
- continue
858
-
859
- word = word.replace(str(old_index), str(new_index))
860
- new_answer.append(word)
861
-
862
- # join the words
863
- joined_answer = " ".join(new_answer)
864
- joined_answer += "\nSources:\n"
865
-
866
- new_sources: List[str] = []
867
-
868
- for line in citations.split("\n"):
869
- pattern = r"(?<=\[)\d+"
870
- match = re.search(pattern, line)
891
+ if len(new_sources_list) > 0:
892
+ processed_answer += "\nSources:\n" + "\n".join(new_sources_list)
893
+
894
+ return processed_answer
895
+
896
+ @staticmethod
897
+ def _parse_sources_block(sources_part: str) -> Tuple[Dict[int, str], List[str]]:
898
+ """Parses the sources block from the LLM response.
899
+ Returns a tuple containing:
900
+ - A dictionary of valid sources matching the "[1] ..." format,
901
+ where the key is the source number
902
+ - A list of other source lines that do not match the specified format
903
+ """
904
+ valid_sources: Dict[int, str] = {}
905
+ other_source_lines: List[str] = []
906
+ source_line_pattern = re.compile(r"^\s*\[(\d+)\](.*)")
907
+
908
+ source_lines = sources_part.strip().split("\n")
909
+
910
+ for line in source_lines:
911
+ line = line.strip()
912
+ if not line:
913
+ continue
914
+
915
+ match = source_line_pattern.match(line)
871
916
  if match:
872
- old_index = int(match.group(0))
873
- new_index = renumber_mapping[old_index]
874
- # replace only the first occurrence of the old index
875
- line = line.replace(f"[{old_index}]", f"[{new_index}]", 1)
917
+ num = int(match.group(1))
918
+ valid_sources[num] = line
919
+ else:
920
+ other_source_lines.append(line)
921
+
922
+ return valid_sources, other_source_lines
923
+
924
+ @staticmethod
925
+ def _get_cited_order(
926
+ answer_part: str, available_sources: Dict[int, str]
927
+ ) -> List[int]:
928
+ """Find all unique, valid citations in the answer text in their order
929
+ # of appearance
930
+ """
931
+ cited_order: List[int] = []
932
+ seen_indices = set()
933
+
934
+ for match in _ENTERPRISE_SEARCH_CITATION_PATTERN.finditer(answer_part):
935
+ content = match.group(1)
936
+ indices_str = [s.strip() for s in content.split(",")]
937
+ for index_str in indices_str:
938
+ if index_str.isdigit():
939
+ index = int(index_str)
940
+ if index in available_sources and index not in seen_indices:
941
+ cited_order.append(index)
942
+ seen_indices.add(index)
943
+
944
+ return cited_order
945
+
946
+ @staticmethod
947
+ def _rewrite_answer_citations(
948
+ answer_part: str, renumber_map: Dict[int, int]
949
+ ) -> str:
950
+ """Rewrites the citations in the answer text based on the renumbering map."""
951
+
952
+ def replacer(match: re.Match) -> str:
953
+ content = match.group(1)
954
+ old_indices_str = [s.strip() for s in content.split(",")]
955
+ new_indices = [
956
+ renumber_map[int(s)]
957
+ for s in old_indices_str
958
+ if s.isdigit() and int(s) in renumber_map
959
+ ]
960
+ if not new_indices:
961
+ return ""
962
+
963
+ return f"[{', '.join(map(str, sorted(list(set(new_indices)))))}]"
964
+
965
+ processed_answer = _ENTERPRISE_SEARCH_CITATION_PATTERN.sub(
966
+ replacer, answer_part
967
+ )
968
+
969
+ # Clean up formatting after replacements
970
+ processed_answer = re.sub(r"\s+([,.?])", r"\1", processed_answer)
971
+ processed_answer = processed_answer.replace("[]", " ")
972
+ processed_answer = re.sub(r"\s+", " ", processed_answer)
973
+ processed_answer = processed_answer.strip()
974
+
975
+ return processed_answer
976
+
977
+ @staticmethod
978
+ def _build_final_sources_list(
979
+ cited_order: List[int],
980
+ renumbering_map: Dict[int, int],
981
+ valid_sources: Dict[int, str],
982
+ other_source_lines: List[str],
983
+ ) -> List[str]:
984
+ """Builds the final list of sources based on the cited order and
985
+ renumbering map.
986
+ """
987
+ new_sources_list: List[str] = []
988
+
989
+ # First, add the sorted, used sources
990
+ for old_num in cited_order:
991
+ new_num = renumbering_map[old_num]
992
+ source_line = valid_sources[old_num]
993
+ new_sources_list.append(
994
+ source_line.replace(f"[{old_num}]", f"[{new_num}]", 1)
995
+ )
876
996
 
877
- # insert the line into the new_index position
878
- new_sources.insert(new_index - 1, line)
879
- elif line.strip():
880
- new_sources.append(line)
997
+ # Then, add the unused but validly numbered sources
998
+ used_source_nums = set(cited_order)
999
+ # Sort by number to ensure a consistent order for uncited sources
1000
+ for num, line in sorted(valid_sources.items()):
1001
+ if num not in used_source_nums:
1002
+ new_sources_list.append(line)
881
1003
 
882
- joined_sources = "\n".join(new_sources)
1004
+ # Finally, add any other source lines
1005
+ new_sources_list.extend(other_source_lines)
883
1006
 
884
- return joined_answer + joined_sources
1007
+ return new_sources_list
885
1008
 
886
1009
  @classmethod
887
1010
  def _perform_health_checks(
rasa/core/processor.py CHANGED
@@ -17,6 +17,9 @@ from rasa.dialogue_understanding.commands import (
17
17
  NoopCommand,
18
18
  SetSlotCommand,
19
19
  CannotHandleCommand,
20
+ SessionStartCommand,
21
+ SessionEndCommand,
22
+ RestartCommand,
20
23
  )
21
24
  from rasa.engine import loader
22
25
  from rasa.engine.constants import (
@@ -855,19 +858,61 @@ class MessageProcessor:
855
858
  tracker.has_coexistence_routing_slot
856
859
  and tracker.get_slot(ROUTE_TO_CALM_SLOT) is None
857
860
  ):
858
- # if we are currently not routing to either CALM or dm1
859
- # we make a sticky routing to CALM if there are any commands
860
- # from the trigger intent parsing
861
- # or a sticky routing to dm1 if there are no commands
861
+ # If we are currently not routing to either CALM or DM1:
862
+ # - Sticky route to CALM if there are any commands
863
+ # from the trigger intent parsing
864
+ # - Sticky route to DM1 if there are no commands present
865
+ route_to_calm_slot_value = self._determine_route_to_calm_slot_value(
866
+ nlu_adapted_commands
867
+ )
862
868
  commands += [
863
869
  SetSlotCommand(
864
- ROUTE_TO_CALM_SLOT, len(nlu_adapted_commands) > 0
870
+ ROUTE_TO_CALM_SLOT, route_to_calm_slot_value
865
871
  ).as_dict()
866
872
  ]
867
873
 
868
874
  parse_data[COMMANDS] = commands
869
875
  return parse_data
870
876
 
877
+ def _determine_route_to_calm_slot_value(
878
+ self, nlu_adapted_commands: List[Dict[str, Any]]
879
+ ) -> Optional[bool]:
880
+ """Determines what value should be assigned to `ROUTE_TO_CALM_SLOT`.
881
+
882
+ Returns:
883
+ - True: If any command other than:
884
+ - SessionStartCommand
885
+ - SessionEndCommand
886
+ - RestartCommand
887
+ is present.
888
+ - None: If only ignored system commands are present.
889
+ - False If no commands at all.
890
+ """
891
+ system_commands_to_ignore = [
892
+ SessionStartCommand.command(),
893
+ SessionEndCommand.command(),
894
+ RestartCommand.command(),
895
+ ]
896
+
897
+ # Exclude the system commands, as it doesn't originate from the user's
898
+ # input intent and shouldn't influence the decision for setting
899
+ # ROUTE_TO_CALM_SLOT.
900
+ intent_triggered_commands = [
901
+ command
902
+ for command in nlu_adapted_commands
903
+ if command.get("command") not in system_commands_to_ignore
904
+ ]
905
+
906
+ if len(intent_triggered_commands) > 0:
907
+ # There are commands other than system commands present - route to CALM
908
+ return True
909
+ elif len(nlu_adapted_commands) > 0:
910
+ # Only system command is present — defer routing decision
911
+ return None
912
+ else:
913
+ # No commands at all — route to DM1
914
+ return False
915
+
871
916
  def _update_full_retrieval_intent(self, parse_data: Dict[Text, Any]) -> None:
872
917
  """Update the parse data with the full retrieval intent.
873
918
 
@@ -88,8 +88,9 @@ class CancelFlowCommand(Command):
88
88
  original_stack = original_tracker.stack
89
89
 
90
90
  applied_events: List[Event] = []
91
-
92
- user_frame = top_user_flow_frame(original_stack)
91
+ user_frame = top_user_flow_frame(
92
+ original_stack, ignore_call_and_link_frames=False
93
+ )
93
94
  current_flow = user_frame.flow(all_flows) if user_frame else None
94
95
 
95
96
  if not current_flow:
@@ -0,0 +1,45 @@
1
+ from typing import Dict, Type
2
+
3
+ from rasa.dialogue_understanding.commands import (
4
+ CancelFlowCommand,
5
+ CannotHandleCommand,
6
+ ChitChatAnswerCommand,
7
+ Command,
8
+ HumanHandoffCommand,
9
+ KnowledgeAnswerCommand,
10
+ SessionStartCommand,
11
+ SkipQuestionCommand,
12
+ RestartCommand,
13
+ )
14
+ from rasa.dialogue_understanding.commands.user_silence_command import UserSilenceCommand
15
+ from rasa.dialogue_understanding.patterns.cancel import CancelPatternFlowStackFrame
16
+ from rasa.dialogue_understanding.patterns.cannot_handle import (
17
+ CannotHandlePatternFlowStackFrame,
18
+ )
19
+ from rasa.dialogue_understanding.patterns.chitchat import ChitchatPatternFlowStackFrame
20
+ from rasa.dialogue_understanding.patterns.human_handoff import (
21
+ HumanHandoffPatternFlowStackFrame,
22
+ )
23
+ from rasa.dialogue_understanding.patterns.restart import RestartPatternFlowStackFrame
24
+ from rasa.dialogue_understanding.patterns.search import SearchPatternFlowStackFrame
25
+ from rasa.dialogue_understanding.patterns.session_start import (
26
+ SessionStartPatternFlowStackFrame,
27
+ )
28
+ from rasa.dialogue_understanding.patterns.skip_question import (
29
+ SkipQuestionPatternFlowStackFrame,
30
+ )
31
+ from rasa.dialogue_understanding.patterns.user_silence import (
32
+ UserSilencePatternFlowStackFrame,
33
+ )
34
+
35
+ triggerable_pattern_to_command_class: Dict[str, Type[Command]] = {
36
+ SessionStartPatternFlowStackFrame.flow_id: SessionStartCommand,
37
+ UserSilencePatternFlowStackFrame.flow_id: UserSilenceCommand,
38
+ CancelPatternFlowStackFrame.flow_id: CancelFlowCommand,
39
+ ChitchatPatternFlowStackFrame.flow_id: ChitChatAnswerCommand,
40
+ HumanHandoffPatternFlowStackFrame.flow_id: HumanHandoffCommand,
41
+ SearchPatternFlowStackFrame.flow_id: KnowledgeAnswerCommand,
42
+ SkipQuestionPatternFlowStackFrame.flow_id: SkipQuestionCommand,
43
+ CannotHandlePatternFlowStackFrame.flow_id: CannotHandleCommand,
44
+ RestartPatternFlowStackFrame.flow_id: RestartCommand,
45
+ }
@@ -6,6 +6,9 @@ from typing import Any, Dict, List
6
6
 
7
7
  import structlog
8
8
  from rasa.dialogue_understanding.commands import Command
9
+ from rasa.dialogue_understanding.commands.utils import (
10
+ find_default_flows_collecting_slot,
11
+ )
9
12
  from rasa.dialogue_understanding.patterns.collect_information import (
10
13
  CollectInformationPatternFlowStackFrame,
11
14
  )
@@ -135,6 +138,11 @@ class SetSlotCommand(Command):
135
138
  ):
136
139
  # Get the other predicted flows from the most recent message on the tracker.
137
140
  predicted_flows = get_flows_predicted_to_start_from_tracker(tracker)
141
+ if not predicted_flows:
142
+ # If no predicted flows, check for default flows collecting the slot.
143
+ predicted_flows = find_default_flows_collecting_slot(
144
+ self.name, all_flows
145
+ )
138
146
  use_slot_fill = any(
139
147
  step.collect == self.name and not step.ask_before_filling
140
148
  for flow in all_flows.underlying_flows
@@ -1,45 +1,26 @@
1
- from typing import Dict, Type
1
+ from typing import List
2
+ from rasa.shared.core.flows import FlowsList
2
3
 
3
- from rasa.dialogue_understanding.commands import (
4
- CancelFlowCommand,
5
- CannotHandleCommand,
6
- ChitChatAnswerCommand,
7
- Command,
8
- HumanHandoffCommand,
9
- KnowledgeAnswerCommand,
10
- SessionStartCommand,
11
- SkipQuestionCommand,
12
- RestartCommand,
13
- )
14
- from rasa.dialogue_understanding.commands.user_silence_command import UserSilenceCommand
15
- from rasa.dialogue_understanding.patterns.cancel import CancelPatternFlowStackFrame
16
- from rasa.dialogue_understanding.patterns.cannot_handle import (
17
- CannotHandlePatternFlowStackFrame,
18
- )
19
- from rasa.dialogue_understanding.patterns.chitchat import ChitchatPatternFlowStackFrame
20
- from rasa.dialogue_understanding.patterns.human_handoff import (
21
- HumanHandoffPatternFlowStackFrame,
22
- )
23
- from rasa.dialogue_understanding.patterns.restart import RestartPatternFlowStackFrame
24
- from rasa.dialogue_understanding.patterns.search import SearchPatternFlowStackFrame
25
- from rasa.dialogue_understanding.patterns.session_start import (
26
- SessionStartPatternFlowStackFrame,
27
- )
28
- from rasa.dialogue_understanding.patterns.skip_question import (
29
- SkipQuestionPatternFlowStackFrame,
30
- )
31
- from rasa.dialogue_understanding.patterns.user_silence import (
32
- UserSilencePatternFlowStackFrame,
33
- )
34
4
 
35
- triggerable_pattern_to_command_class: Dict[str, Type[Command]] = {
36
- SessionStartPatternFlowStackFrame.flow_id: SessionStartCommand,
37
- UserSilencePatternFlowStackFrame.flow_id: UserSilenceCommand,
38
- CancelPatternFlowStackFrame.flow_id: CancelFlowCommand,
39
- ChitchatPatternFlowStackFrame.flow_id: ChitChatAnswerCommand,
40
- HumanHandoffPatternFlowStackFrame.flow_id: HumanHandoffCommand,
41
- SearchPatternFlowStackFrame.flow_id: KnowledgeAnswerCommand,
42
- SkipQuestionPatternFlowStackFrame.flow_id: SkipQuestionCommand,
43
- CannotHandlePatternFlowStackFrame.flow_id: CannotHandleCommand,
44
- RestartPatternFlowStackFrame.flow_id: RestartCommand,
45
- }
5
+ def find_default_flows_collecting_slot(
6
+ slot_name: str, all_flows: FlowsList
7
+ ) -> List[str]:
8
+ """Find default flows that have collect steps matching the specified slot name.
9
+
10
+ Args:
11
+ slot_name: The name of the slot to search for.
12
+ all_flows: All flows in the assistant.
13
+
14
+ Returns:
15
+ List of flow IDs for default flows that collect the specified slot
16
+ without asking before filling.
17
+ """
18
+ return [
19
+ flow.id
20
+ for flow in all_flows.underlying_flows
21
+ if flow.is_rasa_default_flow
22
+ and any(
23
+ step.collect == slot_name and not step.ask_before_filling
24
+ for step in flow.get_collect_steps()
25
+ )
26
+ ]
@@ -8,7 +8,7 @@ from rasa.dialogue_understanding.commands import (
8
8
  SetSlotCommand,
9
9
  )
10
10
  from rasa.dialogue_understanding.commands.set_slot_command import SetSlotExtractor
11
- from rasa.dialogue_understanding.commands.utils import (
11
+ from rasa.dialogue_understanding.commands.pattern_to_command_mapping import (
12
12
  triggerable_pattern_to_command_class,
13
13
  )
14
14
  from rasa.dialogue_understanding.generator import CommandGenerator
@@ -52,12 +52,6 @@ from rasa.shared.nlu.constants import COMMANDS
52
52
 
53
53
  structlogger = structlog.get_logger()
54
54
 
55
- CANNOT_HANDLE_REASON = (
56
- "A command generator attempted to set a slot "
57
- "with a value extracted by an extractor "
58
- "that is incompatible with the slot mapping type."
59
- )
60
-
61
55
 
62
56
  def contains_command(commands: List[Command], typ: Type[Command]) -> bool:
63
57
  """Check if a list of commands contains a command of a given type.
@@ -432,9 +426,9 @@ def clean_up_commands(
432
426
  # when coexistence is enabled, by default there will be a SetSlotCommand
433
427
  # for the ROUTE_TO_CALM_SLOT slot.
434
428
  if tracker.has_coexistence_routing_slot and len(clean_commands) > 2:
435
- clean_commands = filter_cannot_handle_command_for_skipped_slots(clean_commands)
429
+ clean_commands = filter_cannot_handle_command(clean_commands)
436
430
  elif not tracker.has_coexistence_routing_slot and len(clean_commands) > 1:
437
- clean_commands = filter_cannot_handle_command_for_skipped_slots(clean_commands)
431
+ clean_commands = filter_cannot_handle_command(clean_commands)
438
432
 
439
433
  clean_commands = ensure_max_number_of_command_type(
440
434
  clean_commands, RepeatBotMessagesCommand, 1
@@ -534,10 +528,18 @@ def clean_up_slot_command(
534
528
  "command_processor.clean_up_slot_command.skip_command_slot_not_in_domain",
535
529
  command=command,
536
530
  )
531
+ resulting_commands.append(
532
+ CannotHandleCommand(
533
+ reason="The slot predicted by the LLM is not defined in the domain."
534
+ )
535
+ )
537
536
  return resulting_commands
538
537
 
539
538
  if not should_slot_be_set(slot, command):
540
- cannot_handle = CannotHandleCommand(reason=CANNOT_HANDLE_REASON)
539
+ cannot_handle = CannotHandleCommand(
540
+ reason="A command generator attempted to set a slot with a value extracted "
541
+ "by an extractor that is incompatible with the slot mapping type."
542
+ )
541
543
  if cannot_handle not in resulting_commands:
542
544
  resulting_commands.append(cannot_handle)
543
545
 
@@ -551,9 +553,9 @@ def clean_up_slot_command(
551
553
  resulting_commands.append(command)
552
554
  return resulting_commands
553
555
 
554
- if (slot := tracker.slots.get(command.name)) is not None and slot.value == str(
555
- command.value
556
- ):
556
+ if (slot := tracker.slots.get(command.name)) is not None and str(
557
+ slot.value
558
+ ) == str(command.value):
557
559
  # the slot is already set, we don't need to set it again
558
560
  structlogger.debug(
559
561
  "command_processor.clean_up_slot_command.skip_command_slot_already_set",
@@ -713,12 +715,12 @@ def should_slot_be_set(slot: Slot, command: SetSlotCommand) -> bool:
713
715
  return True
714
716
 
715
717
 
716
- def filter_cannot_handle_command_for_skipped_slots(
718
+ def filter_cannot_handle_command(
717
719
  clean_commands: List[Command],
718
720
  ) -> List[Command]:
719
- """Filter out a 'cannot handle' command for skipped slots.
721
+ """Filter out a 'cannot handle' command.
720
722
 
721
- This is used to filter out a 'cannot handle' command for skipped slots
723
+ This is used to filter out a 'cannot handle' command
722
724
  in case other commands are present.
723
725
 
724
726
  Returns:
@@ -727,9 +729,5 @@ def filter_cannot_handle_command_for_skipped_slots(
727
729
  return [
728
730
  command
729
731
  for command in clean_commands
730
- if not (
731
- isinstance(command, CannotHandleCommand)
732
- and command.reason
733
- and CANNOT_HANDLE_REASON == command.reason
734
- )
732
+ if not isinstance(command, CannotHandleCommand)
735
733
  ]
@@ -50,7 +50,8 @@ class FlowStackFrameType(str, Enum):
50
50
  typ: The string to create the `FlowStackFrameType` from.
51
51
 
52
52
  Returns:
53
- The created `FlowStackFrameType`."""
53
+ The created `FlowStackFrameType`.
54
+ """
54
55
  if typ is None:
55
56
  return FlowStackFrameType.REGULAR
56
57
  elif typ == FlowStackFrameType.INTERRUPT.value:
@@ -104,7 +105,8 @@ class BaseFlowStackFrame(DialogueStackFrame):
104
105
  all_flows: All flows in the assistant.
105
106
 
106
107
  Returns:
107
- The current flow."""
108
+ The current flow.
109
+ """
108
110
  flow = all_flows.flow_by_id(self.flow_id)
109
111
  if not flow:
110
112
  # we shouldn't ever end up with a frame that belongs to a non
@@ -119,9 +121,20 @@ class BaseFlowStackFrame(DialogueStackFrame):
119
121
  all_flows: All flows in the assistant.
120
122
 
121
123
  Returns:
122
- The current flow step."""
124
+ The current flow step.
125
+ """
123
126
  flow = self.flow(all_flows)
124
- step = flow.step_by_id(self.step_id)
127
+
128
+ step_id = self.step_id
129
+ # in 3.11.4 we added the flow_id as a prefix to the step_id
130
+ # this causes issues when loading old dialogues as the prefix is missing
131
+ # (see https://rasahq.atlassian.net/jira/software/c/projects/ENG/boards/43?selectedIssue=ENG-1939)
132
+ # so we try to find the step by adding the flow prefix to old step_ids as well
133
+ # TODO: remove this in 4.0.0
134
+ alternative_step_id = f"{self.flow_id}_{self.step_id}"
135
+
136
+ step = flow.step_by_id(step_id) or flow.step_by_id(alternative_step_id)
137
+
125
138
  if not step:
126
139
  # we shouldn't ever end up with a frame that belongs to a non
127
140
  # existing step, but if we do, we should raise an error
@@ -57,7 +57,9 @@ def top_flow_frame(
57
57
  return None
58
58
 
59
59
 
60
- def top_user_flow_frame(dialogue_stack: DialogueStack) -> Optional[UserFlowStackFrame]:
60
+ def top_user_flow_frame(
61
+ dialogue_stack: DialogueStack, ignore_call_and_link_frames: bool = True
62
+ ) -> Optional[UserFlowStackFrame]:
61
63
  """Returns the topmost user flow frame from the tracker.
62
64
 
63
65
  User flows are flows that are created by developers of an assistant and
@@ -69,16 +71,19 @@ def top_user_flow_frame(dialogue_stack: DialogueStack) -> Optional[UserFlowStack
69
71
 
70
72
  Args:
71
73
  dialogue_stack: The dialogue stack to use.
74
+ ignore_call_and_link_frames: Whether to ignore user frames of type `call`
75
+ and `link`. By default, these frames are ignored.
72
76
 
73
77
  Returns:
74
78
  The topmost user flow frame from the tracker.
75
79
  """
76
80
  for frame in reversed(dialogue_stack.frames):
77
- if (
78
- isinstance(frame, UserFlowStackFrame)
79
- and frame.frame_type != FlowStackFrameType.CALL
80
- and frame.frame_type != FlowStackFrameType.LINK
81
- ):
81
+ if isinstance(frame, UserFlowStackFrame):
82
+ if ignore_call_and_link_frames and (
83
+ frame.frame_type == FlowStackFrameType.CALL
84
+ or frame.frame_type == FlowStackFrameType.LINK
85
+ ):
86
+ continue
82
87
  return frame
83
88
  return None
84
89
 
@@ -201,7 +206,9 @@ def get_collect_steps_excluding_ask_before_filling_for_active_flow(
201
206
  All collect steps that are part of the current active flow,
202
207
  excluding the collect steps that have to be asked before filling.
203
208
  """
204
- active_frame = top_user_flow_frame(dialogue_stack)
209
+ active_frame = top_user_flow_frame(
210
+ dialogue_stack, ignore_call_and_link_frames=False
211
+ )
205
212
  if active_frame is None:
206
213
  return set()
207
214
  active_flow = active_frame.flow(all_flows)
@@ -21,7 +21,7 @@ from rasa.shared.core.flows.flow_path import FlowPath, FlowPathsList, PathNode
21
21
  FLOW_NAME_COL_NAME = "Flow Name"
22
22
  NUM_STEPS_COL_NAME = "Num Steps"
23
23
  MISSING_STEPS_COL_NAME = "Missing Steps"
24
- LINE_NUMBERS_COL_NAME = "Line Numbers"
24
+ LINE_NUMBERS_COL_NAME = "Line Numbers for Missing Steps"
25
25
  COVERAGE_COL_NAME = "Coverage"
26
26
 
27
27
  FLOWS_KEY = "flows"
@@ -0,0 +1,2 @@
1
+ KEY_CALLED_FLOW = "called_flow"
2
+ KEY_LINKED_FLOW = "linked_flow"
@@ -4,14 +4,18 @@ import copy
4
4
  from dataclasses import dataclass, field
5
5
  from functools import cached_property
6
6
  from pathlib import Path
7
- from typing import Text, Optional, Dict, Any, List, Set, Union
7
+ from typing import Any, Dict, List, Optional, Set, Text, Tuple, Union
8
8
 
9
9
  import structlog
10
10
  from pypred import Predicate
11
11
 
12
12
  import rasa.shared.utils.io
13
13
  from rasa.shared.constants import RASA_DEFAULT_FLOW_PATTERN_PREFIX
14
- from rasa.shared.core.flows.flow_path import PathNode, FlowPath, FlowPathsList
14
+ from rasa.shared.core.flows.constants import (
15
+ KEY_CALLED_FLOW,
16
+ KEY_LINKED_FLOW,
17
+ )
18
+ from rasa.shared.core.flows.flow_path import FlowPath, FlowPathsList, PathNode
15
19
  from rasa.shared.core.flows.flow_step import FlowStep
16
20
  from rasa.shared.core.flows.flow_step_links import (
17
21
  FlowStepLink,
@@ -26,6 +30,7 @@ from rasa.shared.core.flows.steps import (
26
30
  CallFlowStep,
27
31
  CollectInformationFlowStep,
28
32
  EndFlowStep,
33
+ LinkFlowStep,
29
34
  StartFlowStep,
30
35
  )
31
36
  from rasa.shared.core.flows.steps.constants import (
@@ -424,6 +429,9 @@ class Flow:
424
429
  current_path: FlowPath,
425
430
  all_paths: FlowPathsList,
426
431
  visited_step_ids: Set[str],
432
+ call_stack: Optional[
433
+ List[Tuple[Optional[FlowStep], Optional[Flow], str]]
434
+ ] = None,
427
435
  ) -> None:
428
436
  """Processes the flow steps recursively.
429
437
 
@@ -432,19 +440,25 @@ class Flow:
432
440
  current_path: The current path being constructed.
433
441
  all_paths: The list where completed paths are added.
434
442
  visited_step_ids: A set of steps that have been visited to avoid cycles.
443
+ call_stack: Tuple list of (flow, path, flow_type) to track path when \
444
+ calling flows through call and link steps.
435
445
 
436
446
  Returns:
437
447
  None: This function modifies all_paths in place by appending new paths
438
448
  as they are found.
439
449
  """
450
+ if call_stack is None:
451
+ call_stack = []
452
+
440
453
  # Check if the step is relevant for testable_paths extraction.
441
- # We only create new path nodes for ActionFlowStep, CallFlowStep and
442
- # CollectInformationFlowStep because these are externally visible
443
- # changes in the assistant's behaviour (trackable in the e2e tests).
454
+ # We only create new path nodes for CollectInformationFlowStep,
455
+ # ActionFlowStep, CallFlowStep and LinkFlowStep,
456
+ # because these are externally visible changes
457
+ # in the assistant's behaviour (trackable in the e2e tests).
444
458
  # For other flow steps, we only follow their links.
445
- # We decided to ignore calls to other flows in our coverage analysis.
446
459
  should_add_node = isinstance(
447
- current_step, (CollectInformationFlowStep, ActionFlowStep, CallFlowStep)
460
+ current_step,
461
+ (CollectInformationFlowStep, ActionFlowStep, CallFlowStep, LinkFlowStep),
448
462
  )
449
463
  if should_add_node:
450
464
  # Add current step to the current path that is being constructed.
@@ -456,10 +470,45 @@ class Flow:
456
470
  )
457
471
  )
458
472
 
473
+ # Check if the current step has already been visited or
474
+ # if the end of the path has been reached.
475
+ # If so, and we’re not within a called flow, we terminate the current path.
476
+ # This also applies for when we're inside a linked flow and reach its end.
477
+ # If we're inside a called flow and reach its end,
478
+ # continue with the next steps in its parent flow.
459
479
  if current_step.id in visited_step_ids or self.is_end_of_path(current_step):
460
- # Found a cycle, or reached an end step, do not proceed further.
461
- all_paths.paths.append(copy.deepcopy(current_path))
462
- # Remove the last node from the path if it was added.
480
+ # Shallow copy is sufficient, since we only pop from the list and
481
+ # don't mutate the objects inside the tuples.
482
+ # The state of FlowStep and Flow does not change during the traversal.
483
+ call_stack_copy = call_stack.copy()
484
+ # parent_flow_type could be any of: None, i.e. main flow,
485
+ # KEY_CALLED_FLOW(=called_flow) or KEY_LINKED_FLOW(=linked_flow)
486
+ parent_step, parent_flow, parent_flow_type = (
487
+ call_stack_copy.pop() if call_stack_copy else (None, None, None)
488
+ )
489
+
490
+ # Check if within a called flow.
491
+ # If within linked flow, stop the traversal as this takes precedence.
492
+ if parent_step and parent_flow_type == KEY_CALLED_FLOW:
493
+ # As we have reached the END step of a called flow, we need to
494
+ # continue with the next links of the parent step.
495
+ if parent_flow is not None:
496
+ for link in parent_step.next.links:
497
+ parent_flow._handle_link(
498
+ current_path,
499
+ all_paths,
500
+ visited_step_ids,
501
+ link,
502
+ call_stack_copy,
503
+ )
504
+
505
+ else:
506
+ # Found a cycle, or reached an end step, do not proceed further.
507
+ all_paths.paths.append(copy.deepcopy(current_path))
508
+
509
+ # Backtrack: remove the last node after reaching a terminal step.
510
+ # Ensures the path is correctly backtracked, after a path ends or
511
+ # a cycle is detected.
463
512
  if should_add_node:
464
513
  current_path.nodes.pop()
465
514
  return
@@ -467,6 +516,62 @@ class Flow:
467
516
  # Mark current step as visited in this path.
468
517
  visited_step_ids.add(current_step.id)
469
518
 
519
+ # If the current step is a call step, we need to resolve the call
520
+ # and continue with the steps of the called flow.
521
+ if isinstance(current_step, CallFlowStep):
522
+ # Get the steps of the called flow and continue with them.
523
+ called_flow = current_step.called_flow_reference
524
+ if called_flow and (
525
+ start_step_in_called_flow := called_flow.first_step_in_flow()
526
+ ):
527
+ call_stack.append((current_step, self, KEY_CALLED_FLOW))
528
+ called_flow._go_over_steps(
529
+ start_step_in_called_flow,
530
+ current_path,
531
+ all_paths,
532
+ visited_step_ids,
533
+ call_stack,
534
+ )
535
+
536
+ # After processing the steps of the called (child) flow,
537
+ # remove them from the visited steps
538
+ # to allow the calling (parent) flow to revisit them later.
539
+ visited_step_ids.remove(current_step.id)
540
+ call_stack.pop()
541
+
542
+ # Backtrack: remove the last node
543
+ # after returning from a called (child) flow.
544
+ # Ensures the parent flow can continue exploring other branches.
545
+ if should_add_node:
546
+ current_path.nodes.pop()
547
+ return
548
+
549
+ # If the current step is a LinkFlowStep, step into the linked flow,
550
+ # process its links, and do not return from that flow anymore.
551
+ if isinstance(current_step, LinkFlowStep):
552
+ # Get the steps of the linked flow and continue with them.
553
+ linked_flow = current_step.linked_flow_reference
554
+ if linked_flow and (
555
+ start_step_in_linked_flow := linked_flow.first_step_in_flow()
556
+ ):
557
+ call_stack.append((current_step, self, KEY_LINKED_FLOW))
558
+ linked_flow._go_over_steps(
559
+ start_step_in_linked_flow,
560
+ current_path,
561
+ all_paths,
562
+ visited_step_ids,
563
+ call_stack,
564
+ )
565
+ visited_step_ids.remove(current_step.id)
566
+ call_stack.pop()
567
+
568
+ # Backtrack: remove the last node
569
+ # after returning from a linked (child) flow.
570
+ # Ensures the parent can continue after the linked flow is processed.
571
+ if should_add_node:
572
+ current_path.nodes.pop()
573
+ return
574
+
470
575
  # Iterate over all links of the current step.
471
576
  for link in current_step.next.links:
472
577
  self._handle_link(
@@ -474,12 +579,15 @@ class Flow:
474
579
  all_paths,
475
580
  visited_step_ids,
476
581
  link,
582
+ call_stack,
477
583
  )
478
584
 
479
585
  # Backtrack the current step and remove it from the path.
480
586
  visited_step_ids.remove(current_step.id)
481
587
 
482
- # Remove the last node from the path if it was added.
588
+ # Backtrack: remove the last node
589
+ # after processing all links of the current step.
590
+ # Ensures the next recursion can start once all links are explored.
483
591
  if should_add_node:
484
592
  current_path.nodes.pop()
485
593
 
@@ -489,6 +597,9 @@ class Flow:
489
597
  all_paths: FlowPathsList,
490
598
  visited_step_ids: Set[str],
491
599
  link: FlowStepLink,
600
+ call_stack: Optional[
601
+ List[Tuple[Optional[FlowStep], Optional[Flow], str]]
602
+ ] = None,
492
603
  ) -> None:
493
604
  """Handles the next step in a flow.
494
605
 
@@ -497,6 +608,8 @@ class Flow:
497
608
  all_paths: The list where completed paths are added.
498
609
  visited_step_ids: A set of steps that have been visited to avoid cycles.
499
610
  link: The link to be followed.
611
+ call_stack: Tuple list of (flow, path, flow_type) to track path when \
612
+ calling flows through call and link steps..
500
613
 
501
614
  Returns:
502
615
  None: This function modifies all_paths in place by appending new paths
@@ -511,6 +624,7 @@ class Flow:
511
624
  current_path,
512
625
  all_paths,
513
626
  visited_step_ids,
627
+ call_stack,
514
628
  )
515
629
  return
516
630
  # IfFlowStepLink and ElseFlowStepLink are conditional links.
@@ -524,6 +638,7 @@ class Flow:
524
638
  current_path,
525
639
  all_paths,
526
640
  visited_step_ids,
641
+ call_stack,
527
642
  )
528
643
  return
529
644
  else:
@@ -534,6 +649,7 @@ class Flow:
534
649
  current_path,
535
650
  all_paths,
536
651
  visited_step_ids,
652
+ call_stack,
537
653
  )
538
654
  return
539
655
 
@@ -36,6 +36,7 @@ class FlowsList:
36
36
  def __post_init__(self) -> None:
37
37
  """Initializes the FlowsList object."""
38
38
  self._resolve_called_flows()
39
+ self._resolve_linked_flows()
39
40
 
40
41
  def __iter__(self) -> Generator[Flow, None, None]:
41
42
  """Iterates over the flows."""
@@ -103,7 +104,10 @@ class FlowsList:
103
104
  )
104
105
 
105
106
  def _resolve_called_flows(self) -> None:
106
- """Resolves the called flows."""
107
+ """Resolves the called flows.
108
+
109
+ `Resolving` here means connecting the step to the actual `Flow` object.
110
+ """
107
111
  from rasa.shared.core.flows.steps import CallFlowStep
108
112
 
109
113
  for flow in self.underlying_flows:
@@ -112,6 +116,19 @@ class FlowsList:
112
116
  # only resolve the reference, if it isn't already resolved
113
117
  step.called_flow_reference = self.flow_by_id(step.call)
114
118
 
119
+ def _resolve_linked_flows(self) -> None:
120
+ """Resolves the linked flows.
121
+
122
+ `Resolving` here means connecting the step to the actual `Flow` object.
123
+ """
124
+ from rasa.shared.core.flows.steps import LinkFlowStep
125
+
126
+ for flow in self.underlying_flows:
127
+ for step in flow.steps:
128
+ if isinstance(step, LinkFlowStep) and not step.linked_flow_reference:
129
+ # only resolve the reference, if it isn't already resolved
130
+ step.linked_flow_reference = self.flow_by_id(step.link)
131
+
115
132
  def as_json_list(self) -> List[Dict[Text, Any]]:
116
133
  """Serialize the FlowsList object to list format and not to the original dict.
117
134
 
@@ -1,9 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
- from typing import Text, Dict, Any
4
+ from typing import TYPE_CHECKING, Any, Dict, Text
5
5
 
6
- from rasa.shared.core.flows.flow_step import FlowStep
6
+ from rasa.shared.core.flows.flow_step import FlowStep, Optional
7
+
8
+ if TYPE_CHECKING:
9
+ from rasa.shared.core.flows.flow import Flow
7
10
 
8
11
 
9
12
  @dataclass
@@ -12,6 +15,8 @@ class LinkFlowStep(FlowStep):
12
15
 
13
16
  link: Text
14
17
  """The id of the flow that should be started subsequently."""
18
+ linked_flow_reference: Optional["Flow"] = None
19
+ """The flow that is linked to by this step."""
15
20
 
16
21
  def does_allow_for_next_step(self) -> bool:
17
22
  """Returns whether this step allows for following steps.
rasa/version.py CHANGED
@@ -1,3 +1,3 @@
1
1
  # this file will automatically be changed,
2
2
  # do not add anything but the version number here!
3
- __version__ = "3.11.15"
3
+ __version__ = "3.11.17"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: rasa-pro
3
- Version: 3.11.15
3
+ Version: 3.11.17
4
4
  Summary: State-of-the-art open-core Conversational AI framework for Enterprises that natively leverages generative AI for effortless assistant development.
5
5
  Keywords: nlp,machine-learning,machine-learning-library,bot,bots,botkit,rasa conversational-agents,conversational-ai,chatbot,chatbot-framework,bot-framework
6
6
  Author: Rasa Technologies GmbH
@@ -321,7 +321,7 @@ rasa/core/nlg/summarize.py,sha256=0Was54hj7hCOwXPne_QXLyMkRyBn2ngQBE-Vl7TZ8GA,31
321
321
  rasa/core/persistor.py,sha256=Rooda9cO9XZylLWFz2n3M2NAmiRbIK84pS50Fz47f7A,21295
322
322
  rasa/core/policies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
323
323
  rasa/core/policies/ensemble.py,sha256=AjNOEy2Iubbe-LdKaoFUXG8ch6yPrg3bTvcTcAPmeOs,12959
324
- rasa/core/policies/enterprise_search_policy.py,sha256=8k5UYslBIbkzIVfBYFfDuDlJ_meWiKguhtK4305p5os,34267
324
+ rasa/core/policies/enterprise_search_policy.py,sha256=WT5D9tw3-6c70ky7mdr2LQ5G4M-LQFVhCydGCmobeWs,39075
325
325
  rasa/core/policies/enterprise_search_prompt_template.jinja2,sha256=dCS_seyBGxMQoMsOjjvPp0dd31OSzZCJSZeev1FJK5Q,1187
326
326
  rasa/core/policies/enterprise_search_prompt_with_citation_template.jinja2,sha256=va9rpP97dN3PKoJZOVfyuISt3cPBlb10Pqyz25RwO_Q,3294
327
327
  rasa/core/policies/flow_policy.py,sha256=wGb1l_59cGM9ZaexSIK5uXFi618739oNfLOxx2FC0_Y,7490
@@ -336,7 +336,7 @@ rasa/core/policies/policy.py,sha256=HeVtIaV0dA1QcAG3vjdn-4g7-oUEJPL4u01ETJt78YA,
336
336
  rasa/core/policies/rule_policy.py,sha256=YNDPZUZkpKFCvZwKe1kSfP6LQnDL9CQ6JU69JRwdmWw,50729
337
337
  rasa/core/policies/ted_policy.py,sha256=_DHiDH5Upx1yFNzMXBA3SGdHBRfsitTLlr7howUHPoo,87750
338
338
  rasa/core/policies/unexpected_intent_policy.py,sha256=5pGe9EMS-NLHIDDhqY6KCH_Kv7_TGMzSbe_GsjuKH1w,39649
339
- rasa/core/processor.py,sha256=vB9YpzGzn74lyqPn1Y-lIAxmaJd0xfPGMHxwyqeMHkg,55571
339
+ rasa/core/processor.py,sha256=Dbr40giQRL8k039CPC9DMKMxqu8Ue6cqNskLMjMwenY,57221
340
340
  rasa/core/run.py,sha256=NxZ2iZm_nYhaxEN7jvWSVxMAMqZTHj0m2Y7dqCxb3Pw,11653
341
341
  rasa/core/secrets_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
342
342
  rasa/core/secrets_manager/constants.py,sha256=dTDHenvG1JBVi34QIR6FpdO5RDOXQwAjAxLlgJ2ZNEI,1193
@@ -363,7 +363,7 @@ rasa/dialogue_understanding/coexistence/llm_based_router.py,sha256=7qLFOYm6MSRNJ
363
363
  rasa/dialogue_understanding/coexistence/router_template.jinja2,sha256=CHWFreN0sv1EbPh-hf5AlCt3zxy2_llX1Pdn9Q11Y18,357
364
364
  rasa/dialogue_understanding/commands/__init__.py,sha256=hzlP15s6Qpr5acSWbS2svWvCNfY7GY2HFqtpVUhHjx4,2259
365
365
  rasa/dialogue_understanding/commands/can_not_handle_command.py,sha256=2sNnIo1jZ2UEadkBdEWmDasm2tBES59lzvFcf0iY51U,2235
366
- rasa/dialogue_understanding/commands/cancel_flow_command.py,sha256=z1TmONIPQkYvPm2ZIULfBIXpgn5v6-VIsqInQnW3NH0,4275
366
+ rasa/dialogue_understanding/commands/cancel_flow_command.py,sha256=FkAOPTOU8Zn1Ph5c0I6U8mzTvxtWBHfildm6h3-zaGQ,4331
367
367
  rasa/dialogue_understanding/commands/change_flow_command.py,sha256=0s3g-3InNZs2eAiI7kmBKp3MzBJN69YzdT-IFJjaCaE,1338
368
368
  rasa/dialogue_understanding/commands/chit_chat_answer_command.py,sha256=jl8em1Xw867VAj5EogeuOjWK93uoT71IhjHU076c2mg,1773
369
369
  rasa/dialogue_understanding/commands/clarify_command.py,sha256=OzkqVRZrQo20cveqUoQGZE96DdRLLPgfPxJhroubIQw,2822
@@ -375,15 +375,16 @@ rasa/dialogue_understanding/commands/handle_code_change_command.py,sha256=pKaj8E
375
375
  rasa/dialogue_understanding/commands/human_handoff_command.py,sha256=viSzL3Zmudm2WEjWmOS2s0zfOTXNsWoVU2pS-JXDFlU,1928
376
376
  rasa/dialogue_understanding/commands/knowledge_answer_command.py,sha256=JPiWQC5qWJmryE7DgApDlf9AdGVMazuU9TXx44gc78w,1773
377
377
  rasa/dialogue_understanding/commands/noop_command.py,sha256=302wi-pPXnraqclTDXug6_IYucEZCwDtP032OhPv1JY,1476
378
+ rasa/dialogue_understanding/commands/pattern_to_command_mapping.py,sha256=OiyLFGEsrfFSIJcvBY6lTIIXqDY9OxaikVGtcl4Kokk,1911
378
379
  rasa/dialogue_understanding/commands/repeat_bot_messages_command.py,sha256=9hbDrTruhe2NogR8Xtt0SVcJUAlN2sQTzmynCaB_-ns,1858
379
380
  rasa/dialogue_understanding/commands/restart_command.py,sha256=lUGO8iAyHH6esuWi7sKZQzZIfzYipx4Vo58-B9U20gg,1678
380
381
  rasa/dialogue_understanding/commands/session_end_command.py,sha256=xMVOZJv7J3WAShGzJywQPcd5w263w6K_eYSROwpHUsk,1808
381
382
  rasa/dialogue_understanding/commands/session_start_command.py,sha256=k5cGcE4Usxwfua1sE-KY-qWdwHlHZKgVx5XPDt_-RFY,1742
382
- rasa/dialogue_understanding/commands/set_slot_command.py,sha256=jsiG3K0SYqsx3bah9mxBw0DR5EliaiZ8iMsDzv2wTxw,5187
383
+ rasa/dialogue_understanding/commands/set_slot_command.py,sha256=7HLw7iSXxygLG5v1ppV-iCkEDfg4ODvSIqV30rmW3jo,5537
383
384
  rasa/dialogue_understanding/commands/skip_question_command.py,sha256=bSrUFOHUz1ozdaHma-KAaAArhP59RB3W8CEEBQaPIkA,2251
384
385
  rasa/dialogue_understanding/commands/start_flow_command.py,sha256=a0Yk8xpBpFgC3Hkh4J8kAudz4s4ZLQWuoDq_a63lQXM,3309
385
386
  rasa/dialogue_understanding/commands/user_silence_command.py,sha256=QtqsMU5mrbUp5dla2yGSpxXfIfi_h6Eu72mTDZQ_aTU,1724
386
- rasa/dialogue_understanding/commands/utils.py,sha256=OiyLFGEsrfFSIJcvBY6lTIIXqDY9OxaikVGtcl4Kokk,1911
387
+ rasa/dialogue_understanding/commands/utils.py,sha256=GA3J3K2fLhMJNtbul8Es9UdkIAmm8zT2MP9GBM9gITI,766
387
388
  rasa/dialogue_understanding/generator/__init__.py,sha256=Ykeb2wQ1DuiUWAWO0hLIPSTK1_Ktiq9DZXF6D3ugN78,764
388
389
  rasa/dialogue_understanding/generator/command_generator.py,sha256=woJ4-59Iarugyxy0fT0zM0QnF8el8m2bBSpy-9Gh8U0,11945
389
390
  rasa/dialogue_understanding/generator/constants.py,sha256=KhvcY7rP8x3oWOSqFqE6JYoHzfYIqDFT-o4KbrDUPTY,811
@@ -395,7 +396,7 @@ rasa/dialogue_understanding/generator/multi_step/__init__.py,sha256=47DEQpj8HBSa
395
396
  rasa/dialogue_understanding/generator/multi_step/fill_slots_prompt.jinja2,sha256=Y0m673tAML3cFPaLM-urMXDsBYUUcXIw9YUpkAhGUuA,2933
396
397
  rasa/dialogue_understanding/generator/multi_step/handle_flows_prompt.jinja2,sha256=8l93_QBKBYnqLICVdiTu5ejZDE8F36BU8-qwba0px44,1927
397
398
  rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py,sha256=jo3hZO2SYDgUdwYGKAqiJJC2uYTwyBrOQo9o6u1mq0c,32670
398
- rasa/dialogue_understanding/generator/nlu_command_adapter.py,sha256=wEqoSVUtr00lhbE9L9WqKfZkMKOvm7UGe_E5aqXgn6I,9210
399
+ rasa/dialogue_understanding/generator/nlu_command_adapter.py,sha256=AcmzRZZM0x3_mauIverkbKRykg7xHSh_dggVqlAVD6c,9231
399
400
  rasa/dialogue_understanding/generator/single_step/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
400
401
  rasa/dialogue_understanding/generator/single_step/command_prompt_template.jinja2,sha256=nMayu-heJYH1QmcL1cFmXb8SeiJzfdDR_9Oy5IRUXsM,3937
401
402
  rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py,sha256=UqbbuFCPaNHVL6Q-8XB_bMWYbQ07awlRPjH7nIoMatk,18170
@@ -419,17 +420,17 @@ rasa/dialogue_understanding/patterns/session_start.py,sha256=yglhIEkkquRf0YppZ4C
419
420
  rasa/dialogue_understanding/patterns/skip_question.py,sha256=rvZuVUxulikwUhP01MAIgkcHZ4Si7mzxNedH6QBPdX4,1214
420
421
  rasa/dialogue_understanding/patterns/user_silence.py,sha256=uwpCJRkRmeSvFDZQBZnEL4rumweF6mQ8ht_WqrTPVKU,1140
421
422
  rasa/dialogue_understanding/processor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
422
- rasa/dialogue_understanding/processor/command_processor.py,sha256=mPyvsQPx0BIRNZj6S469LW_NxblPt5HoTFX78BXaAEY,26136
423
+ rasa/dialogue_understanding/processor/command_processor.py,sha256=Qor-Dfnnku-9xCgglUYuPrPn85Saj37xK5lUNAZ9t0I,26088
423
424
  rasa/dialogue_understanding/processor/command_processor_component.py,sha256=rkErI_Uo7s3LsEojUSGSRbWGyGaX7GtGOYSJn0V-TI4,1650
424
425
  rasa/dialogue_understanding/stack/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
425
426
  rasa/dialogue_understanding/stack/dialogue_stack.py,sha256=j8MnLCyv6cAZVpKRaUVM-Z5HqgWP-scrnaiQXzLNBwY,5243
426
427
  rasa/dialogue_understanding/stack/frames/__init__.py,sha256=CXLs8I_eeJ-d2tQmS19V26OM6CHy3VN5whH5uHBodj4,656
427
428
  rasa/dialogue_understanding/stack/frames/chit_chat_frame.py,sha256=bfMCyeTK69SB3KP9TUCfdBGNDmNbblfDNWM_ll3ppPc,736
428
429
  rasa/dialogue_understanding/stack/frames/dialogue_stack_frame.py,sha256=rmG09a66PerYiuoWiG-Jlb4Oi6bct1OIHAJGTMN_F2M,4126
429
- rasa/dialogue_understanding/stack/frames/flow_stack_frame.py,sha256=W4mEmihIN5Bih2C9KDpKf-rxCHjLCzK9TcT6JslhW7g,5047
430
+ rasa/dialogue_understanding/stack/frames/flow_stack_frame.py,sha256=HRlR5YCkoOlI1DWCkgl_ak3YjFQEbmgwvzC16elkK54,5578
430
431
  rasa/dialogue_understanding/stack/frames/pattern_frame.py,sha256=EVrYWv5dCP7XTvNV-HqtOOrseP-IkF0jD2_JacAvIYw,235
431
432
  rasa/dialogue_understanding/stack/frames/search_frame.py,sha256=rJ9og28k_udUIjP-2Z5xeb_2T5HvCzwDCnxVG9K7lws,728
432
- rasa/dialogue_understanding/stack/utils.py,sha256=UpcxXwbneAMrvc94Fi1__r17UU2BAZQQ_RYI25Mek_c,7710
433
+ rasa/dialogue_understanding/stack/utils.py,sha256=PWA4b1EgRGVZx5uWWa1ZVM45WhJ5tr0C9Md5TA7pxpU,8019
433
434
  rasa/e2e_test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
434
435
  rasa/e2e_test/aggregate_test_stats_calculator.py,sha256=XMI7t5xEP7Mo-F8cCCZx2w5ckUKa5sDvyitl6bk6hc8,4924
435
436
  rasa/e2e_test/assertions.py,sha256=ofn2CbnW2wR3OZ3-PPmD_07YqlGQ_tWL1TSv95vxKy0,45428
@@ -440,7 +441,7 @@ rasa/e2e_test/e2e_config_schema.yml,sha256=V5wmuMOCJDrXUdNHdG7B_5JhFFt8PmXhHh0cB
440
441
  rasa/e2e_test/e2e_test_case.py,sha256=MkKCMD-6oPqytBlPyaD8an9mIsP2wZZz_0lTyUCjIIo,20714
441
442
  rasa/e2e_test/e2e_test_converter.py,sha256=VxIx7uk36HzLIyEumJiR6G6-CyyqkV_lYoX-XQ9eKno,12588
442
443
  rasa/e2e_test/e2e_test_converter_prompt.jinja2,sha256=EMy-aCd7jLARHmwAuZUGT5ABnNHjR872_pexRIMGA7c,2791
443
- rasa/e2e_test/e2e_test_coverage_report.py,sha256=Cv5bMtoOC240231YMNHz10ibSqm_UD1-eskQVdjPUsw,11326
444
+ rasa/e2e_test/e2e_test_coverage_report.py,sha256=rEoEiyMOuxFxNSzpXQO6NiCACKeGQXuh1hGh4WfMaJE,11344
444
445
  rasa/e2e_test/e2e_test_result.py,sha256=9LlH6vIQeK_dxDwMQ5RzlNHoxCNXpWC9S527-ch8kUA,1649
445
446
  rasa/e2e_test/e2e_test_runner.py,sha256=IbQKWXQUmBJDnb3v1TLOjH6FXD0ADCL-IVneUsZcN8I,44146
446
447
  rasa/e2e_test/e2e_test_schema.yml,sha256=0deWjuKRHNo6e_LSCnUoiw9NLIYf6dj1-zFPl_AqLYA,5632
@@ -595,12 +596,13 @@ rasa/shared/core/conversation.py,sha256=tw1fD2XB3gOdQjDI8hHo5TAAmE2JYNogQGWe3rE9
595
596
  rasa/shared/core/domain.py,sha256=bGjonMV54wbwGLPjKHI2NoWwxr2wyUZwhEvjBWhP-W0,81710
596
597
  rasa/shared/core/events.py,sha256=zdGSP1bNV1RyKC9Z54S7EbQ8TfGne_n9XKj64aoghdI,85803
597
598
  rasa/shared/core/flows/__init__.py,sha256=HszhIvEARpmyxABFc1MKYvj8oy04WiZW1xmCdToakbs,181
598
- rasa/shared/core/flows/flow.py,sha256=6RR-CdOR6xS31zCiOe3Fcz-ajCQ-H0M7FxfMa63Ka7A,20820
599
+ rasa/shared/core/flows/constants.py,sha256=oLzPFeBIBTfV7Jh55rXXOKiep9jEEfzES_THjUrIxDM,64
600
+ rasa/shared/core/flows/flow.py,sha256=sXD8EdRlwCK6l7t-LXX_qn18XqpNunG8U-zwDia1Fy8,26083
599
601
  rasa/shared/core/flows/flow_path.py,sha256=xstwahZBU5cfMY46mREA4NoOGlKLBRAqeP_mJ3UZqOI,2283
600
602
  rasa/shared/core/flows/flow_step.py,sha256=6hoVOMXryTKHgT7-p7jzTqH2r9QREmy_6d6bX2OyxI0,4550
601
603
  rasa/shared/core/flows/flow_step_links.py,sha256=zMZV_9rWVjEa8kHIFSIbXCWA1qaUvp8r4uSCK_NsKKs,10548
602
604
  rasa/shared/core/flows/flow_step_sequence.py,sha256=tXUEhubX3g312G2FH8Hiez7_v9XG9EaspKlANRa7Sa8,2362
603
- rasa/shared/core/flows/flows_list.py,sha256=-vG6YU2gNU90FmluW3cBgDAsnY9dzVJTXIHzBK7IqEY,8728
605
+ rasa/shared/core/flows/flows_list.py,sha256=LLkiPEdO0QqlXQ7aVVZkYNzA5hgX5BJGdIS542-5jsk,9415
604
606
  rasa/shared/core/flows/flows_yaml_schema.json,sha256=9LjPQ_xtU8n4zvcxmeolGRT2S0_1tIngdZsncAmVK78,11189
605
607
  rasa/shared/core/flows/nlu_trigger.py,sha256=gfnRUi7QJaUjMb43RTyQWbLoIUSx2I78LeM8CjDIIDE,3907
606
608
  rasa/shared/core/flows/steps/__init__.py,sha256=M43kHDuB8okcIE3WLivnvuCBzIsA5Qi2ATXUGTVW2Rw,655
@@ -611,7 +613,7 @@ rasa/shared/core/flows/steps/constants.py,sha256=DCxrEUGbJciBknHm-_t4tmcnH19IZKP
611
613
  rasa/shared/core/flows/steps/continuation.py,sha256=5Rzayr80FsgS4bAajuRObVvVcLqPEh9nxGbT2te85xY,1498
612
614
  rasa/shared/core/flows/steps/end.py,sha256=0XrPlQMVBnQKVeZs0or8P9IrVqG7i6RoSNDsVrvAeDk,749
613
615
  rasa/shared/core/flows/steps/internal.py,sha256=vqBapuLBYQin3Me-YZKn_Tyg5VKozRIJ-tFmjVvmFBs,1449
614
- rasa/shared/core/flows/steps/link.py,sha256=dlx56o-VnFRAn9Xmsu67Di3Fc9MjkDVXzIttHHRaTzA,1507
616
+ rasa/shared/core/flows/steps/link.py,sha256=nOY1a92OEDjf4rqoZlbGAW2lXMeGOyNZwgLIK6e7if4,1702
615
617
  rasa/shared/core/flows/steps/no_operation.py,sha256=I0xWFWJqhUAbvU0HSFXtw4v2gmIiTEveGv4AImvLDfo,1399
616
618
  rasa/shared/core/flows/steps/set_slots.py,sha256=NgBXLPl5adIa0-M-Tgt2Teg3gXYggcDqpaf96BPpvKE,1492
617
619
  rasa/shared/core/flows/steps/start.py,sha256=AJpKIm0S3GZYLEs3ybXW0Zrq03Pu9lvirNahiUy2I6k,1010
@@ -780,9 +782,9 @@ rasa/utils/train_utils.py,sha256=f1NWpp5y6al0dzoQyyio4hc4Nf73DRoRSHDzEK6-C4E,212
780
782
  rasa/utils/url_tools.py,sha256=JQcHL2aLqLHu82k7_d9imUoETCm2bmlHaDpOJ-dKqBc,1218
781
783
  rasa/utils/yaml.py,sha256=KjbZq5C94ZP7Jdsw8bYYF7HASI6K4-C_kdHfrnPLpSI,2000
782
784
  rasa/validator.py,sha256=O1wjCeV7ITJ0luvb3GCWy8x1fGgzWVbClEMlPnLBowQ,67265
783
- rasa/version.py,sha256=EsBzcOqFP6_X23ASpuMHkDkUXvmi7_xZPzmDvVKOSlM,118
784
- rasa_pro-3.11.15.dist-info/METADATA,sha256=zrSODCD6rUjW_RSBrLNkg4SkOuCTw_K5dlVIbZk7f0A,10725
785
- rasa_pro-3.11.15.dist-info/NOTICE,sha256=7HlBoMHJY9CL2GlYSfTQ-PZsVmLmVkYmMiPlTjhuCqA,218
786
- rasa_pro-3.11.15.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
787
- rasa_pro-3.11.15.dist-info/entry_points.txt,sha256=ckJ2SfEyTPgBqj_I6vm_tqY9dZF_LAPJZA335Xp0Q9U,43
788
- rasa_pro-3.11.15.dist-info/RECORD,,
785
+ rasa/version.py,sha256=-sDWwO6NaEndrFCW_IM97xNLKz_ChNxABiOrABXSgj0,118
786
+ rasa_pro-3.11.17.dist-info/METADATA,sha256=H8kcNcNjldaFNlletjHpLVDi2MD4AVEru07HLJJeAto,10725
787
+ rasa_pro-3.11.17.dist-info/NOTICE,sha256=7HlBoMHJY9CL2GlYSfTQ-PZsVmLmVkYmMiPlTjhuCqA,218
788
+ rasa_pro-3.11.17.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
789
+ rasa_pro-3.11.17.dist-info/entry_points.txt,sha256=ckJ2SfEyTPgBqj_I6vm_tqY9dZF_LAPJZA335Xp0Q9U,43
790
+ rasa_pro-3.11.17.dist-info/RECORD,,