rasa-pro 3.11.16__py3-none-any.whl → 3.11.18__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 (60) hide show
  1. rasa/core/channels/inspector/dist/assets/{arc-f09fea11.js → arc-6f1cf469.js} +1 -1
  2. rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-95518007.js → blockDiagram-38ab4fdb-e4687a7a.js} +1 -1
  3. rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-c91a4a08.js → c4Diagram-3d4e48cf-350db3c4.js} +1 -1
  4. rasa/core/channels/inspector/dist/assets/channel-e01523b7.js +1 -0
  5. rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-27f7869b.js → classDiagram-70f12bd4-87791d92.js} +1 -1
  6. rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-1ab94cdb.js → classDiagram-v2-f2320105-325a055a.js} +1 -1
  7. rasa/core/channels/inspector/dist/assets/clone-3bfaf7a0.js +1 -0
  8. rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-a7900089.js → createText-2e5e7dd3-be863ae0.js} +1 -1
  9. rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-3d5b2697.js → edges-e0da2a9e-ced68061.js} +1 -1
  10. rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-443cc11b.js → erDiagram-9861fffd-60684f40.js} +1 -1
  11. rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-8a6f8c52.js → flowDb-956e92f1-664e800c.js} +1 -1
  12. rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-06a0b4f3.js → flowDiagram-66a62f08-6ebc9040.js} +1 -1
  13. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-ce3f0138.js +1 -0
  14. rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-7a01e0b5.js → flowchart-elk-definition-4a651766-3a139e6d.js} +1 -1
  15. rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-5f1289f2.js → ganttDiagram-c361ad54-b4446b93.js} +1 -1
  16. rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-44409666.js → gitGraphDiagram-72cf32ee-0ca05d79.js} +1 -1
  17. rasa/core/channels/inspector/dist/assets/{graph-3c393c89.js → graph-d55c7150.js} +1 -1
  18. rasa/core/channels/inspector/dist/assets/{index-3862675e-4d0c4142.js → index-3862675e-21ec23d0.js} +1 -1
  19. rasa/core/channels/inspector/dist/assets/{index-b208b2c3.js → index-397b9bf3.js} +148 -148
  20. rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-ae0fa7ff.js → infoDiagram-f8f76790-1dbec276.js} +1 -1
  21. rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-5c3b08cc.js → journeyDiagram-49397b02-c59ccb68.js} +1 -1
  22. rasa/core/channels/inspector/dist/assets/{layout-b24c95cb.js → layout-084d6acb.js} +1 -1
  23. rasa/core/channels/inspector/dist/assets/{line-999a77c5.js → line-7e3445bc.js} +1 -1
  24. rasa/core/channels/inspector/dist/assets/{linear-81a792fd.js → linear-37acf20b.js} +1 -1
  25. rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-c574f712.js → mindmap-definition-fc14e90a-053067ef.js} +1 -1
  26. rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-1919891d.js → pieDiagram-8a3498a8-00feec2e.js} +1 -1
  27. rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-26e43d09.js → quadrantDiagram-120e2f19-7b0f9725.js} +1 -1
  28. rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-f4b22985.js → requirementDiagram-deff3bca-c23f6699.js} +1 -1
  29. rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-b957b472.js → sankeyDiagram-04a897e0-7d7264d7.js} +1 -1
  30. rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-1d8ca073.js → sequenceDiagram-704730f1-78c3fce6.js} +1 -1
  31. rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-c67b1b71.js → stateDiagram-587899a1-4a241b87.js} +1 -1
  32. rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-ee820f55.js → stateDiagram-v2-d93cdb3a-5c57975b.js} +1 -1
  33. rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-b162bdf3.js → styles-6aaf32cf-af1e4802.js} +1 -1
  34. rasa/core/channels/inspector/dist/assets/{styles-9a916d00-67a7b254.js → styles-9a916d00-1a8391b1.js} +1 -1
  35. rasa/core/channels/inspector/dist/assets/{styles-c10674c1-81a8ac73.js → styles-c10674c1-bbd035f8.js} +1 -1
  36. rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-ede42905.js → svgDrawCommon-08f97a94-e8aa007c.js} +1 -1
  37. rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-b0f41635.js → timeline-definition-85554ec2-adc8097c.js} +1 -1
  38. rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-d715dfb0.js → xychartDiagram-e933f94c-3dd24cea.js} +1 -1
  39. rasa/core/channels/inspector/dist/index.html +1 -1
  40. rasa/core/channels/inspector/package.json +4 -3
  41. rasa/core/channels/inspector/yarn.lock +12 -12
  42. rasa/core/policies/enterprise_search_policy.py +196 -73
  43. rasa/dialogue_understanding/commands/cancel_flow_command.py +3 -2
  44. rasa/dialogue_understanding/commands/correct_slots_command.py +0 -10
  45. rasa/dialogue_understanding/processor/command_processor.py +23 -20
  46. rasa/dialogue_understanding/stack/utils.py +14 -7
  47. rasa/e2e_test/e2e_test_coverage_report.py +1 -1
  48. rasa/shared/core/flows/constants.py +2 -0
  49. rasa/shared/core/flows/flow.py +127 -11
  50. rasa/shared/core/flows/flows_list.py +18 -1
  51. rasa/shared/core/flows/steps/link.py +7 -2
  52. rasa/version.py +1 -1
  53. {rasa_pro-3.11.16.dist-info → rasa_pro-3.11.18.dist-info}/METADATA +4 -4
  54. {rasa_pro-3.11.16.dist-info → rasa_pro-3.11.18.dist-info}/RECORD +57 -56
  55. rasa/core/channels/inspector/dist/assets/channel-cc7720dc.js +0 -1
  56. rasa/core/channels/inspector/dist/assets/clone-3688e1f7.js +0 -1
  57. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-5055ec2d.js +0 -1
  58. {rasa_pro-3.11.16.dist-info → rasa_pro-3.11.18.dist-info}/NOTICE +0 -0
  59. {rasa_pro-3.11.16.dist-info → rasa_pro-3.11.18.dist-info}/WHEEL +0 -0
  60. {rasa_pro-3.11.16.dist-info → rasa_pro-3.11.18.dist-info}/entry_points.txt +0 -0
@@ -7,10 +7,10 @@
7
7
  resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf"
8
8
  integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==
9
9
 
10
- "@adobe/css-tools@^4.3.1":
11
- version "4.3.1"
12
- resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.1.tgz#abfccb8ca78075a2b6187345c26243c1a0842f28"
13
- integrity sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==
10
+ "@adobe/css-tools@^4.3.1", "@adobe/css-tools@^4.3.2":
11
+ version "4.4.3"
12
+ resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.4.3.tgz#beebbefb0264fdeb32d3052acae0e0d94315a9a2"
13
+ integrity sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==
14
14
 
15
15
  "@ampproject/remapping@^2.2.0":
16
16
  version "2.2.1"
@@ -2295,10 +2295,10 @@ available-typed-arrays@^1.0.5:
2295
2295
  resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
2296
2296
  integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==
2297
2297
 
2298
- axios@1.7.4:
2299
- version "1.7.4"
2300
- resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.4.tgz#4c8ded1b43683c8dd362973c393f3ede24052aa2"
2301
- integrity sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==
2298
+ axios@1.8.2:
2299
+ version "1.8.2"
2300
+ resolved "https://registry.yarnpkg.com/axios/-/axios-1.8.2.tgz#fabe06e241dfe83071d4edfbcaa7b1c3a40f7979"
2301
+ integrity sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==
2302
2302
  dependencies:
2303
2303
  follow-redirects "^1.15.6"
2304
2304
  form-data "^4.0.0"
@@ -6054,10 +6054,10 @@ v8-to-istanbul@^9.0.1:
6054
6054
  "@types/istanbul-lib-coverage" "^2.0.1"
6055
6055
  convert-source-map "^2.0.0"
6056
6056
 
6057
- vite@4.5.2:
6058
- version "4.5.2"
6059
- resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.2.tgz#d6ea8610e099851dad8c7371599969e0f8b97e82"
6060
- integrity sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==
6057
+ vite@4.5.12:
6058
+ version "4.5.12"
6059
+ resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.12.tgz#48f48dbcf789722765e91bc32a99cb66c628eadc"
6060
+ integrity sha512-qrMwavANtSz91nDy3zEiUHMtL09x0mniQsSMvDkNxuCBM1W5vriJ22hEmwTth6DhLSWsZnHBT0yHFAQXt6efGA==
6061
6061
  dependencies:
6062
6062
  esbuild "^0.18.10"
6063
6063
  postcss "^8.4.27"
@@ -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(
@@ -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:
@@ -225,16 +225,6 @@ class CorrectSlotsCommand(Command):
225
225
  proposed_slots, all_flows, tracker
226
226
  )
227
227
 
228
- if not earliest_collect and not is_reset_only:
229
- # if we could not find any step in the flow, where the slots were
230
- # previously set, and we also don't want to reset the slots, do
231
- # not correct the slots.
232
- structlogger.debug(
233
- "command_executor.skip_correction",
234
- is_reset_only=is_reset_only,
235
- )
236
- return None
237
-
238
228
  return CorrectionPatternFlowStackFrame(
239
229
  is_reset_only=is_reset_only,
240
230
  corrected_slots=proposed_slots,
@@ -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.
@@ -362,6 +356,11 @@ def clean_up_commands(
362
356
 
363
357
  slots_so_far, active_flow = filled_slots_for_active_flow(tracker, all_flows)
364
358
 
359
+ # update the slots so far with the slots that were set in the tracker
360
+ slots_so_far.update(
361
+ {event.key for event in tracker.events if isinstance(event, SlotSet)}
362
+ )
363
+
365
364
  clean_commands: List[Command] = []
366
365
 
367
366
  for command in commands:
@@ -432,9 +431,9 @@ def clean_up_commands(
432
431
  # when coexistence is enabled, by default there will be a SetSlotCommand
433
432
  # for the ROUTE_TO_CALM_SLOT slot.
434
433
  if tracker.has_coexistence_routing_slot and len(clean_commands) > 2:
435
- clean_commands = filter_cannot_handle_command_for_skipped_slots(clean_commands)
434
+ clean_commands = filter_cannot_handle_command(clean_commands)
436
435
  elif not tracker.has_coexistence_routing_slot and len(clean_commands) > 1:
437
- clean_commands = filter_cannot_handle_command_for_skipped_slots(clean_commands)
436
+ clean_commands = filter_cannot_handle_command(clean_commands)
438
437
 
439
438
  clean_commands = ensure_max_number_of_command_type(
440
439
  clean_commands, RepeatBotMessagesCommand, 1
@@ -534,10 +533,18 @@ def clean_up_slot_command(
534
533
  "command_processor.clean_up_slot_command.skip_command_slot_not_in_domain",
535
534
  command=command,
536
535
  )
536
+ resulting_commands.append(
537
+ CannotHandleCommand(
538
+ reason="The slot predicted by the LLM is not defined in the domain."
539
+ )
540
+ )
537
541
  return resulting_commands
538
542
 
539
543
  if not should_slot_be_set(slot, command):
540
- cannot_handle = CannotHandleCommand(reason=CANNOT_HANDLE_REASON)
544
+ cannot_handle = CannotHandleCommand(
545
+ reason="A command generator attempted to set a slot with a value extracted "
546
+ "by an extractor that is incompatible with the slot mapping type."
547
+ )
541
548
  if cannot_handle not in resulting_commands:
542
549
  resulting_commands.append(cannot_handle)
543
550
 
@@ -551,9 +558,9 @@ def clean_up_slot_command(
551
558
  resulting_commands.append(command)
552
559
  return resulting_commands
553
560
 
554
- if (slot := tracker.slots.get(command.name)) is not None and slot.value == str(
555
- command.value
556
- ):
561
+ if (slot := tracker.slots.get(command.name)) is not None and str(
562
+ slot.value
563
+ ) == str(command.value):
557
564
  # the slot is already set, we don't need to set it again
558
565
  structlogger.debug(
559
566
  "command_processor.clean_up_slot_command.skip_command_slot_already_set",
@@ -713,12 +720,12 @@ def should_slot_be_set(slot: Slot, command: SetSlotCommand) -> bool:
713
720
  return True
714
721
 
715
722
 
716
- def filter_cannot_handle_command_for_skipped_slots(
723
+ def filter_cannot_handle_command(
717
724
  clean_commands: List[Command],
718
725
  ) -> List[Command]:
719
- """Filter out a 'cannot handle' command for skipped slots.
726
+ """Filter out a 'cannot handle' command.
720
727
 
721
- This is used to filter out a 'cannot handle' command for skipped slots
728
+ This is used to filter out a 'cannot handle' command
722
729
  in case other commands are present.
723
730
 
724
731
  Returns:
@@ -727,9 +734,5 @@ def filter_cannot_handle_command_for_skipped_slots(
727
734
  return [
728
735
  command
729
736
  for command in clean_commands
730
- if not (
731
- isinstance(command, CannotHandleCommand)
732
- and command.reason
733
- and CANNOT_HANDLE_REASON == command.reason
734
- )
737
+ if not isinstance(command, CannotHandleCommand)
735
738
  ]
@@ -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"