rasa-pro 3.13.0rc2__py3-none-any.whl → 3.13.0rc4__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.
- rasa/core/channels/voice_stream/audiocodes.py +1 -1
- rasa/core/policies/enterprise_search_policy.py +159 -66
- rasa/core/policies/enterprise_search_prompt_with_relevancy_check_and_citation_template.jinja2 +4 -1
- rasa/core/policies/flows/flow_executor.py +9 -3
- rasa/core/tracker_stores/auth_retry_tracker_store.py +64 -3
- rasa/core/tracker_stores/dynamo_tracker_store.py +10 -0
- rasa/core/tracker_stores/mongo_tracker_store.py +17 -0
- rasa/core/tracker_stores/redis_tracker_store.py +38 -5
- rasa/core/tracker_stores/sql_tracker_store.py +27 -0
- rasa/core/tracker_stores/tracker_store.py +36 -2
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +6 -3
- rasa/dialogue_understanding/processor/command_processor.py +9 -7
- rasa/llm_fine_tuning/annotation_module.py +43 -11
- rasa/privacy/privacy_filter.py +57 -4
- rasa/privacy/privacy_manager.py +24 -21
- rasa/shared/core/constants.py +1 -0
- rasa/studio/pull/domains.py +14 -3
- rasa/version.py +1 -1
- {rasa_pro-3.13.0rc2.dist-info → rasa_pro-3.13.0rc4.dist-info}/METADATA +1 -1
- {rasa_pro-3.13.0rc2.dist-info → rasa_pro-3.13.0rc4.dist-info}/RECORD +23 -23
- {rasa_pro-3.13.0rc2.dist-info → rasa_pro-3.13.0rc4.dist-info}/NOTICE +0 -0
- {rasa_pro-3.13.0rc2.dist-info → rasa_pro-3.13.0rc4.dist-info}/WHEEL +0 -0
- {rasa_pro-3.13.0rc2.dist-info → rasa_pro-3.13.0rc4.dist-info}/entry_points.txt +0 -0
|
@@ -104,10 +104,10 @@ class AudiocodesVoiceInputChannel(VoiceInputChannel):
|
|
|
104
104
|
|
|
105
105
|
def __init__(
|
|
106
106
|
self,
|
|
107
|
-
token: Optional[Text],
|
|
108
107
|
server_url: str,
|
|
109
108
|
asr_config: Dict,
|
|
110
109
|
tts_config: Dict,
|
|
110
|
+
token: Optional[Text] = None,
|
|
111
111
|
):
|
|
112
112
|
mark_as_beta_feature("Audiocodes (audiocodes_stream) Channel")
|
|
113
113
|
super().__init__(
|
|
@@ -4,7 +4,7 @@ import importlib.resources
|
|
|
4
4
|
import json
|
|
5
5
|
import os.path
|
|
6
6
|
import re
|
|
7
|
-
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Text
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Text, Tuple
|
|
8
8
|
|
|
9
9
|
import dotenv
|
|
10
10
|
import structlog
|
|
@@ -139,6 +139,8 @@ DEFAULT_ENTERPRISE_SEARCH_PROMPT_WITH_RELEVANCY_CHECK_AND_CITATION_TEMPLATE = (
|
|
|
139
139
|
|
|
140
140
|
_ENTERPRISE_SEARCH_ANSWER_NOT_RELEVANT_PATTERN = re.compile(r"\[NO_RAG_ANSWER\]")
|
|
141
141
|
|
|
142
|
+
_ENTERPRISE_SEARCH_CITATION_PATTERN = re.compile(r"\[([^\]]+)\]")
|
|
143
|
+
|
|
142
144
|
|
|
143
145
|
class VectorStoreConnectionError(RasaException):
|
|
144
146
|
"""Exception raised for errors in connecting to the vector store."""
|
|
@@ -944,10 +946,18 @@ class EnterpriseSearchPolicy(LLMHealthCheckMixin, EmbeddingsHealthCheckMixin, Po
|
|
|
944
946
|
|
|
945
947
|
@staticmethod
|
|
946
948
|
def post_process_citations(llm_answer: str) -> str:
|
|
947
|
-
"""Post-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
949
|
+
"""Post-processes the LLM answer to correctly number and sort citations and
|
|
950
|
+
sources.
|
|
951
|
+
|
|
952
|
+
- Handles both single `[1]` and grouped `[1, 3]` citations.
|
|
953
|
+
- Rewrites the numbers in square brackets in the answer text to start from 1
|
|
954
|
+
and be sorted within each group.
|
|
955
|
+
- Reorders the sources according to the order of their first appearance
|
|
956
|
+
in the text.
|
|
957
|
+
- Removes citations from the text that point to sources missing from
|
|
958
|
+
the source list.
|
|
959
|
+
- Keeps sources that are not cited in the text, placing them at the end
|
|
960
|
+
of the list.
|
|
951
961
|
|
|
952
962
|
Args:
|
|
953
963
|
llm_answer: The LLM answer.
|
|
@@ -961,77 +971,160 @@ class EnterpriseSearchPolicy(LLMHealthCheckMixin, EmbeddingsHealthCheckMixin, Po
|
|
|
961
971
|
|
|
962
972
|
# Split llm_answer into answer and citations
|
|
963
973
|
try:
|
|
964
|
-
|
|
974
|
+
answer_part, sources_part = llm_answer.rsplit("Sources:", 1)
|
|
965
975
|
except ValueError:
|
|
966
|
-
# if there is no "Sources:"
|
|
967
|
-
return llm_answer
|
|
968
|
-
|
|
969
|
-
# Find all source references in the answer
|
|
970
|
-
pattern = r"\[\s*(\d+(?:\s*,\s*\d+)*)\s*\]"
|
|
971
|
-
matches = re.findall(pattern, answer)
|
|
972
|
-
old_source_indices = [
|
|
973
|
-
int(num.strip()) for match in matches for num in match.split(",")
|
|
974
|
-
]
|
|
976
|
+
# if there is no "Sources:" separator, return the original llm_answer
|
|
977
|
+
return llm_answer.strip()
|
|
975
978
|
|
|
976
|
-
#
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
for old_index in old_indices
|
|
995
|
-
if old_index in renumber_mapping
|
|
996
|
-
]
|
|
997
|
-
if not new_indices:
|
|
998
|
-
continue
|
|
999
|
-
|
|
1000
|
-
word = word.replace(
|
|
1001
|
-
match, f"{', '.join(map(str, new_indices))}"
|
|
1002
|
-
)
|
|
1003
|
-
else:
|
|
1004
|
-
old_index = int(match.strip("[].,:;?!"))
|
|
1005
|
-
new_index = renumber_mapping.get(old_index)
|
|
1006
|
-
if not new_index:
|
|
1007
|
-
continue
|
|
979
|
+
# Parse the sources block to extract valid sources and other lines
|
|
980
|
+
valid_sources, other_source_lines = EnterpriseSearchPolicy._parse_sources_block(
|
|
981
|
+
sources_part
|
|
982
|
+
)
|
|
983
|
+
|
|
984
|
+
# Find all unique, valid citations in the answer text in their order
|
|
985
|
+
# of appearance
|
|
986
|
+
cited_order = EnterpriseSearchPolicy._get_cited_order(
|
|
987
|
+
answer_part, valid_sources
|
|
988
|
+
)
|
|
989
|
+
|
|
990
|
+
# Create a mapping from the old source numbers to the new, sequential numbers.
|
|
991
|
+
# For example, if the citation order in the text was [3, 1, 2], this map
|
|
992
|
+
# becomes {3: 1, 1: 2, 2: 3}. This allows for a quick lookup when rewriting
|
|
993
|
+
# the citations
|
|
994
|
+
renumbering_map = {
|
|
995
|
+
old_num: new_num + 1 for new_num, old_num in enumerate(cited_order)
|
|
996
|
+
}
|
|
1008
997
|
|
|
1009
|
-
|
|
1010
|
-
|
|
998
|
+
# Rewrite the citations in the answer text based on the renumbering map
|
|
999
|
+
processed_answer = EnterpriseSearchPolicy._rewrite_answer_citations(
|
|
1000
|
+
answer_part, renumbering_map
|
|
1001
|
+
)
|
|
1002
|
+
|
|
1003
|
+
# Build the new list of sources
|
|
1004
|
+
new_sources_list = EnterpriseSearchPolicy._build_final_sources_list(
|
|
1005
|
+
cited_order,
|
|
1006
|
+
renumbering_map,
|
|
1007
|
+
valid_sources,
|
|
1008
|
+
other_source_lines,
|
|
1009
|
+
)
|
|
1010
|
+
|
|
1011
|
+
if len(new_sources_list) > 0:
|
|
1012
|
+
processed_answer += "\nSources:\n" + "\n".join(new_sources_list)
|
|
1013
|
+
|
|
1014
|
+
return processed_answer
|
|
1015
|
+
|
|
1016
|
+
@staticmethod
|
|
1017
|
+
def _parse_sources_block(sources_part: str) -> Tuple[Dict[int, str], List[str]]:
|
|
1018
|
+
"""Parses the sources block from the LLM response.
|
|
1019
|
+
Returns a tuple containing:
|
|
1020
|
+
- A dictionary of valid sources matching the "[1] ..." format,
|
|
1021
|
+
where the key is the source number
|
|
1022
|
+
- A list of other source lines that do not match the specified format
|
|
1023
|
+
"""
|
|
1024
|
+
valid_sources: Dict[int, str] = {}
|
|
1025
|
+
other_source_lines: List[str] = []
|
|
1026
|
+
source_line_pattern = re.compile(r"^\s*\[(\d+)\](.*)")
|
|
1011
1027
|
|
|
1012
|
-
|
|
1013
|
-
joined_answer = " ".join(new_answer)
|
|
1014
|
-
joined_answer += "\nSources:\n"
|
|
1028
|
+
source_lines = sources_part.strip().split("\n")
|
|
1015
1029
|
|
|
1016
|
-
|
|
1030
|
+
for line in source_lines:
|
|
1031
|
+
line = line.strip()
|
|
1032
|
+
if not line:
|
|
1033
|
+
continue
|
|
1017
1034
|
|
|
1018
|
-
|
|
1019
|
-
pattern = r"(?<=\[)\d+"
|
|
1020
|
-
match = re.search(pattern, line)
|
|
1035
|
+
match = source_line_pattern.match(line)
|
|
1021
1036
|
if match:
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1037
|
+
num = int(match.group(1))
|
|
1038
|
+
valid_sources[num] = line
|
|
1039
|
+
else:
|
|
1040
|
+
other_source_lines.append(line)
|
|
1041
|
+
|
|
1042
|
+
return valid_sources, other_source_lines
|
|
1043
|
+
|
|
1044
|
+
@staticmethod
|
|
1045
|
+
def _get_cited_order(
|
|
1046
|
+
answer_part: str, available_sources: Dict[int, str]
|
|
1047
|
+
) -> List[int]:
|
|
1048
|
+
"""Find all unique, valid citations in the answer text in their order
|
|
1049
|
+
# of appearance
|
|
1050
|
+
"""
|
|
1051
|
+
cited_order: List[int] = []
|
|
1052
|
+
seen_indices = set()
|
|
1053
|
+
|
|
1054
|
+
for match in _ENTERPRISE_SEARCH_CITATION_PATTERN.finditer(answer_part):
|
|
1055
|
+
content = match.group(1)
|
|
1056
|
+
indices_str = [s.strip() for s in content.split(",")]
|
|
1057
|
+
for index_str in indices_str:
|
|
1058
|
+
if index_str.isdigit():
|
|
1059
|
+
index = int(index_str)
|
|
1060
|
+
if index in available_sources and index not in seen_indices:
|
|
1061
|
+
cited_order.append(index)
|
|
1062
|
+
seen_indices.add(index)
|
|
1063
|
+
|
|
1064
|
+
return cited_order
|
|
1065
|
+
|
|
1066
|
+
@staticmethod
|
|
1067
|
+
def _rewrite_answer_citations(
|
|
1068
|
+
answer_part: str, renumber_map: Dict[int, int]
|
|
1069
|
+
) -> str:
|
|
1070
|
+
"""Rewrites the citations in the answer text based on the renumbering map."""
|
|
1071
|
+
|
|
1072
|
+
def replacer(match: re.Match) -> str:
|
|
1073
|
+
content = match.group(1)
|
|
1074
|
+
old_indices_str = [s.strip() for s in content.split(",")]
|
|
1075
|
+
new_indices = [
|
|
1076
|
+
renumber_map[int(s)]
|
|
1077
|
+
for s in old_indices_str
|
|
1078
|
+
if s.isdigit() and int(s) in renumber_map
|
|
1079
|
+
]
|
|
1080
|
+
if not new_indices:
|
|
1081
|
+
return ""
|
|
1082
|
+
|
|
1083
|
+
return f"[{', '.join(map(str, sorted(list(set(new_indices)))))}]"
|
|
1084
|
+
|
|
1085
|
+
processed_answer = _ENTERPRISE_SEARCH_CITATION_PATTERN.sub(
|
|
1086
|
+
replacer, answer_part
|
|
1087
|
+
)
|
|
1088
|
+
|
|
1089
|
+
# Clean up formatting after replacements
|
|
1090
|
+
processed_answer = re.sub(r"\s+([,.?])", r"\1", processed_answer)
|
|
1091
|
+
processed_answer = processed_answer.replace("[]", " ")
|
|
1092
|
+
processed_answer = re.sub(r"\s+", " ", processed_answer)
|
|
1093
|
+
processed_answer = processed_answer.strip()
|
|
1094
|
+
|
|
1095
|
+
return processed_answer
|
|
1096
|
+
|
|
1097
|
+
@staticmethod
|
|
1098
|
+
def _build_final_sources_list(
|
|
1099
|
+
cited_order: List[int],
|
|
1100
|
+
renumbering_map: Dict[int, int],
|
|
1101
|
+
valid_sources: Dict[int, str],
|
|
1102
|
+
other_source_lines: List[str],
|
|
1103
|
+
) -> List[str]:
|
|
1104
|
+
"""Builds the final list of sources based on the cited order and
|
|
1105
|
+
renumbering map.
|
|
1106
|
+
"""
|
|
1107
|
+
new_sources_list: List[str] = []
|
|
1108
|
+
|
|
1109
|
+
# First, add the sorted, used sources
|
|
1110
|
+
for old_num in cited_order:
|
|
1111
|
+
new_num = renumbering_map[old_num]
|
|
1112
|
+
source_line = valid_sources[old_num]
|
|
1113
|
+
new_sources_list.append(
|
|
1114
|
+
source_line.replace(f"[{old_num}]", f"[{new_num}]", 1)
|
|
1115
|
+
)
|
|
1026
1116
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1117
|
+
# Then, add the unused but validly numbered sources
|
|
1118
|
+
used_source_nums = set(cited_order)
|
|
1119
|
+
# Sort by number to ensure a consistent order for uncited sources
|
|
1120
|
+
for num, line in sorted(valid_sources.items()):
|
|
1121
|
+
if num not in used_source_nums:
|
|
1122
|
+
new_sources_list.append(line)
|
|
1031
1123
|
|
|
1032
|
-
|
|
1124
|
+
# Finally, add any other source lines
|
|
1125
|
+
new_sources_list.extend(other_source_lines)
|
|
1033
1126
|
|
|
1034
|
-
return
|
|
1127
|
+
return new_sources_list
|
|
1035
1128
|
|
|
1036
1129
|
@classmethod
|
|
1037
1130
|
def _perform_health_checks(
|
rasa/core/policies/enterprise_search_prompt_with_relevancy_check_and_citation_template.jinja2
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
Based on the provided documents and the recent conversation context, answer the following question.
|
|
1
|
+
{% if check_relevancy %}Based on the provided documents and the recent conversation context, answer the following question.
|
|
2
2
|
Before responding, ensure the answer is directly supported by the documents or context.
|
|
3
3
|
Do not make assumptions or infer beyond the given information.
|
|
4
4
|
Only answer if you are more than 80% confident that the response is fully supported.
|
|
5
5
|
If the answer cannot be determined, respond with: [NO_RAG_ANSWER]
|
|
6
|
+
{% else %}Given the following information, please provide an answer based on the provided documents and the context of the recent conversation.
|
|
7
|
+
If the answer is not known or cannot be determined from the provided documents or context, please state that you do not know to the user.
|
|
8
|
+
{% endif %}
|
|
6
9
|
|
|
7
10
|
### Relevant Documents
|
|
8
11
|
Use the following documents to answer the question:
|
|
@@ -41,6 +41,7 @@ from rasa.dialogue_understanding.patterns.internal_error import (
|
|
|
41
41
|
InternalErrorPatternFlowStackFrame,
|
|
42
42
|
)
|
|
43
43
|
from rasa.dialogue_understanding.patterns.search import SearchPatternFlowStackFrame
|
|
44
|
+
from rasa.dialogue_understanding.patterns.user_silence import FLOW_PATTERN_USER_SILENCE
|
|
44
45
|
from rasa.dialogue_understanding.stack.dialogue_stack import DialogueStack
|
|
45
46
|
from rasa.dialogue_understanding.stack.frames import (
|
|
46
47
|
BaseFlowStackFrame,
|
|
@@ -590,9 +591,9 @@ def run_step(
|
|
|
590
591
|
initial_events.append(FlowStarted(flow.id, metadata=stack.current_context()))
|
|
591
592
|
|
|
592
593
|
# FLow does not start with collect step or we are not in collect information pattern
|
|
593
|
-
if _first_step_is_not_collect(
|
|
594
|
-
|
|
595
|
-
)
|
|
594
|
+
if _first_step_is_not_collect(step, previous_step_id) and not (
|
|
595
|
+
_in_collect_information_pattern(flow) or _in_pattern_user_silence(flow)
|
|
596
|
+
):
|
|
596
597
|
_append_global_silence_timeout_event(initial_events, tracker)
|
|
597
598
|
|
|
598
599
|
if isinstance(step, CollectInformationFlowStep):
|
|
@@ -650,6 +651,11 @@ def _in_collect_information_pattern(flow: Flow) -> bool:
|
|
|
650
651
|
return flow.id == FLOW_PATTERN_COLLECT_INFORMATION
|
|
651
652
|
|
|
652
653
|
|
|
654
|
+
def _in_pattern_user_silence(flow: Flow) -> bool:
|
|
655
|
+
"""Check if the current flow is a user silence pattern."""
|
|
656
|
+
return flow.id == FLOW_PATTERN_USER_SILENCE
|
|
657
|
+
|
|
658
|
+
|
|
653
659
|
def _run_end_step(
|
|
654
660
|
flow: Flow,
|
|
655
661
|
flows: FlowsList,
|
|
@@ -92,6 +92,29 @@ class AuthRetryTrackerStore(TrackerStore):
|
|
|
92
92
|
)
|
|
93
93
|
return None
|
|
94
94
|
|
|
95
|
+
async def retrieve_full_tracker(
|
|
96
|
+
self, sender_id: Text
|
|
97
|
+
) -> Optional["DialogueStateTracker"]:
|
|
98
|
+
"""Retries retrieving the full tracker if it fails."""
|
|
99
|
+
# add + 1 to retries because the retries are additional to the first attempt
|
|
100
|
+
for _ in range(self.retries + 1):
|
|
101
|
+
try:
|
|
102
|
+
return await self._tracker_store.retrieve_full_tracker(sender_id)
|
|
103
|
+
except Exception as e:
|
|
104
|
+
logger.warning(
|
|
105
|
+
f"Failed to retrieve full tracker for {sender_id}. Retrying...",
|
|
106
|
+
exc_info=e,
|
|
107
|
+
)
|
|
108
|
+
self._tracker_store = self.recreate_tracker_store(
|
|
109
|
+
self.domain, self.event_broker
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
logger.error(
|
|
113
|
+
f"Failed to retrieve full tracker for {sender_id} "
|
|
114
|
+
f"after {self.retries} retries."
|
|
115
|
+
)
|
|
116
|
+
return None
|
|
117
|
+
|
|
95
118
|
async def save(self, tracker: "DialogueStateTracker") -> None:
|
|
96
119
|
"""Retries saving the tracker if it fails."""
|
|
97
120
|
# add + 1 to retries because the retries are additional to the first attempt
|
|
@@ -120,6 +143,44 @@ class AuthRetryTrackerStore(TrackerStore):
|
|
|
120
143
|
endpoint_config = EndpointResolver.update_config(self.endpoint_config)
|
|
121
144
|
return create_tracker_store(endpoint_config, domain, event_broker)
|
|
122
145
|
|
|
123
|
-
async def delete(self, sender_id:
|
|
124
|
-
"""
|
|
125
|
-
|
|
146
|
+
async def delete(self, sender_id: str) -> None:
|
|
147
|
+
"""Retries deleting the tracker for the given sender_id."""
|
|
148
|
+
# add + 1 to retries because the retries are additional to the first attempt
|
|
149
|
+
for _ in range(self.retries + 1):
|
|
150
|
+
try:
|
|
151
|
+
await self._tracker_store.delete(sender_id)
|
|
152
|
+
break
|
|
153
|
+
except Exception as e:
|
|
154
|
+
logger.warning(
|
|
155
|
+
f"Failed to delete tracker for {sender_id}. Retrying...",
|
|
156
|
+
exc_info=e,
|
|
157
|
+
)
|
|
158
|
+
self._tracker_store = self.recreate_tracker_store(
|
|
159
|
+
self.domain, self.event_broker
|
|
160
|
+
)
|
|
161
|
+
else:
|
|
162
|
+
logger.error(
|
|
163
|
+
f"Failed to delete tracker for {sender_id} "
|
|
164
|
+
f"after {self.retries} retries."
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
async def update(self, tracker: DialogueStateTracker) -> None:
|
|
168
|
+
"""Retries replacing the tracker if it fails."""
|
|
169
|
+
# add + 1 to retries because the retries are additional to the first attempt
|
|
170
|
+
for _ in range(self.retries + 1):
|
|
171
|
+
try:
|
|
172
|
+
await self._tracker_store.update(tracker)
|
|
173
|
+
break
|
|
174
|
+
except Exception as e:
|
|
175
|
+
logger.warning(
|
|
176
|
+
f"Failed to replace tracker for {tracker.sender_id}. Retrying...",
|
|
177
|
+
exc_info=e,
|
|
178
|
+
)
|
|
179
|
+
self._tracker_store = self.recreate_tracker_store(
|
|
180
|
+
self.domain, self.event_broker
|
|
181
|
+
)
|
|
182
|
+
else:
|
|
183
|
+
logger.error(
|
|
184
|
+
f"Failed to replace tracker for {tracker.sender_id} "
|
|
185
|
+
f"after {self.retries} retries."
|
|
186
|
+
)
|
|
@@ -216,3 +216,13 @@ class DynamoTrackerStore(TrackerStore, SerializedTrackerAsDict):
|
|
|
216
216
|
sender_ids.extend([i["sender_id"] for i in response["Items"]])
|
|
217
217
|
|
|
218
218
|
return sender_ids
|
|
219
|
+
|
|
220
|
+
async def update(self, tracker: DialogueStateTracker) -> None:
|
|
221
|
+
"""Overwrites the tracker for the given sender_id."""
|
|
222
|
+
serialized = self.serialise_tracker(tracker)
|
|
223
|
+
self.db.put_item(Item=serialized)
|
|
224
|
+
|
|
225
|
+
structlogger.info(
|
|
226
|
+
"dynamo_tracker_store.replace.replaced_tracker",
|
|
227
|
+
sender_id=tracker.sender_id,
|
|
228
|
+
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import itertools
|
|
4
|
+
from datetime import datetime
|
|
4
5
|
from typing import Any, Dict, Iterable, Iterator, List, Optional, Text
|
|
5
6
|
|
|
6
7
|
import structlog
|
|
@@ -204,3 +205,19 @@ class MongoTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
204
205
|
async def keys(self) -> Iterable[Text]:
|
|
205
206
|
"""Returns sender_ids of the Mongo Tracker Store."""
|
|
206
207
|
return [c["sender_id"] for c in self.conversations.find()]
|
|
208
|
+
|
|
209
|
+
async def update(self, tracker: DialogueStateTracker) -> None:
|
|
210
|
+
"""Overwrites the tracker for the given sender_id."""
|
|
211
|
+
self.conversations.replace_one(
|
|
212
|
+
{"sender_id": tracker.sender_id},
|
|
213
|
+
tracker.current_state(EventVerbosity.ALL),
|
|
214
|
+
upsert=True,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
first_event_timestamp = str(datetime.fromtimestamp(tracker.events[0].timestamp))
|
|
218
|
+
|
|
219
|
+
structlogger.info(
|
|
220
|
+
"redis_tracker_store.update.updated_tracker",
|
|
221
|
+
sender_id=tracker.sender_id,
|
|
222
|
+
first_event_timestamp=first_event_timestamp,
|
|
223
|
+
)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from datetime import datetime
|
|
3
4
|
from typing import Any, Dict, Iterable, Optional, Text
|
|
4
5
|
|
|
5
6
|
import structlog
|
|
@@ -88,17 +89,21 @@ class RedisTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
88
89
|
if not timeout and self.record_exp:
|
|
89
90
|
timeout = self.record_exp
|
|
90
91
|
|
|
91
|
-
|
|
92
|
+
# if the sender_id starts with the key prefix, we remove it
|
|
93
|
+
# this is used to avoid storing the prefix twice
|
|
94
|
+
sender_id = tracker.sender_id
|
|
95
|
+
if sender_id.startswith(self.key_prefix):
|
|
96
|
+
sender_id = sender_id[len(self.key_prefix) :]
|
|
97
|
+
|
|
98
|
+
stored = self.red.get(self.key_prefix + sender_id)
|
|
92
99
|
|
|
93
100
|
if stored is not None:
|
|
94
|
-
prior_tracker = self.deserialise_tracker(
|
|
101
|
+
prior_tracker = self.deserialise_tracker(sender_id, stored)
|
|
95
102
|
|
|
96
103
|
tracker = self._merge_trackers(prior_tracker, tracker)
|
|
97
104
|
|
|
98
105
|
serialised_tracker = self.serialise_tracker(tracker)
|
|
99
|
-
self.red.set(
|
|
100
|
-
self.key_prefix + tracker.sender_id, serialised_tracker, ex=timeout
|
|
101
|
-
)
|
|
106
|
+
self.red.set(self.key_prefix + sender_id, serialised_tracker, ex=timeout)
|
|
102
107
|
|
|
103
108
|
async def delete(self, sender_id: Text) -> None:
|
|
104
109
|
"""Delete tracker for the given sender_id.
|
|
@@ -113,6 +118,9 @@ class RedisTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
113
118
|
)
|
|
114
119
|
return None
|
|
115
120
|
|
|
121
|
+
if sender_id.startswith(self.key_prefix):
|
|
122
|
+
sender_id = sender_id[len(self.key_prefix) :]
|
|
123
|
+
|
|
116
124
|
self.red.delete(self.key_prefix + sender_id)
|
|
117
125
|
structlogger.info(
|
|
118
126
|
"redis_tracker_store.delete.deleted_tracker",
|
|
@@ -156,6 +164,9 @@ class RedisTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
156
164
|
sender_id: Conversation ID to fetch the tracker for.
|
|
157
165
|
fetch_all_sessions: Whether to fetch all sessions or only the last one.
|
|
158
166
|
"""
|
|
167
|
+
if sender_id.startswith(self.key_prefix):
|
|
168
|
+
sender_id = sender_id[len(self.key_prefix) :]
|
|
169
|
+
|
|
159
170
|
stored = self.red.get(self.key_prefix + sender_id)
|
|
160
171
|
if stored is None:
|
|
161
172
|
structlogger.debug(
|
|
@@ -217,3 +228,25 @@ class RedisTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
217
228
|
merged.update(new_event)
|
|
218
229
|
|
|
219
230
|
return merged
|
|
231
|
+
|
|
232
|
+
async def update(self, tracker: DialogueStateTracker) -> None:
|
|
233
|
+
"""Overwrites the tracker for the given sender_id."""
|
|
234
|
+
serialised_tracker = self.serialise_tracker(tracker)
|
|
235
|
+
|
|
236
|
+
# if the sender_id starts with the key prefix, we remove it
|
|
237
|
+
# this is used to avoid storing the prefix twice
|
|
238
|
+
sender_id = tracker.sender_id
|
|
239
|
+
if sender_id.startswith(self.key_prefix):
|
|
240
|
+
sender_id = sender_id[len(self.key_prefix) :]
|
|
241
|
+
|
|
242
|
+
self.red.set(
|
|
243
|
+
self.key_prefix + sender_id, serialised_tracker, ex=self.record_exp
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
first_event_timestamp = str(datetime.fromtimestamp(tracker.events[0].timestamp))
|
|
247
|
+
|
|
248
|
+
structlogger.info(
|
|
249
|
+
"redis_tracker_store.update.updated_tracker",
|
|
250
|
+
sender_id=tracker.sender_id,
|
|
251
|
+
first_event_timestamp=first_event_timestamp,
|
|
252
|
+
)
|
|
@@ -4,6 +4,7 @@ import contextlib
|
|
|
4
4
|
import itertools
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
|
+
from datetime import datetime
|
|
7
8
|
from time import sleep
|
|
8
9
|
from typing import (
|
|
9
10
|
TYPE_CHECKING,
|
|
@@ -553,3 +554,29 @@ class SQLTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
553
554
|
return itertools.islice(
|
|
554
555
|
tracker.events, number_of_events_since_last_session, len(tracker.events)
|
|
555
556
|
)
|
|
557
|
+
|
|
558
|
+
async def update(self, tracker_to_keep: DialogueStateTracker) -> None:
|
|
559
|
+
"""Overwrite the tracker in the SQL tracker store."""
|
|
560
|
+
with self.session_scope() as session:
|
|
561
|
+
# Delete events whose timestamp are older
|
|
562
|
+
# than the first event of the tracker to keep.
|
|
563
|
+
statement = sa.delete(self.SQLEvent).where(
|
|
564
|
+
self.SQLEvent.sender_id == tracker_to_keep.sender_id,
|
|
565
|
+
self.SQLEvent.timestamp < tracker_to_keep.events[0].timestamp
|
|
566
|
+
if tracker_to_keep.events
|
|
567
|
+
else 0,
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
result = session.execute(statement)
|
|
571
|
+
session.commit()
|
|
572
|
+
|
|
573
|
+
first_event_timestamp = str(
|
|
574
|
+
datetime.fromtimestamp(tracker_to_keep.events[0].timestamp)
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
structlogger.info(
|
|
578
|
+
"sql_tracker_store.update.updated_tracker",
|
|
579
|
+
sender_id=tracker_to_keep.sender_id,
|
|
580
|
+
first_event_timestamp=first_event_timestamp,
|
|
581
|
+
event_info=f"{result.rowcount} rows removed from tracker.",
|
|
582
|
+
)
|
|
@@ -251,6 +251,14 @@ class TrackerStore:
|
|
|
251
251
|
"""
|
|
252
252
|
raise NotImplementedError()
|
|
253
253
|
|
|
254
|
+
async def update(self, tracker: DialogueStateTracker) -> None:
|
|
255
|
+
"""Replace an existing tracker with a new one.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
tracker: The tracker to update.
|
|
259
|
+
"""
|
|
260
|
+
raise NotImplementedError()
|
|
261
|
+
|
|
254
262
|
async def retrieve_full_tracker(
|
|
255
263
|
self, conversation_id: Text
|
|
256
264
|
) -> Optional[DialogueStateTracker]:
|
|
@@ -478,6 +486,14 @@ class InMemoryTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
478
486
|
|
|
479
487
|
return multiple_tracker_sessions[-1]
|
|
480
488
|
|
|
489
|
+
async def update(self, tracker: DialogueStateTracker) -> None:
|
|
490
|
+
"""Replace an existing tracker with a new one.
|
|
491
|
+
|
|
492
|
+
Args:
|
|
493
|
+
tracker: The tracker to update.
|
|
494
|
+
"""
|
|
495
|
+
await self.save(tracker)
|
|
496
|
+
|
|
481
497
|
|
|
482
498
|
def validate_port(port: Any) -> Optional[int]:
|
|
483
499
|
"""Ensure that port can be converted to integer.
|
|
@@ -583,7 +599,19 @@ class FailSafeTrackerStore(TrackerStore):
|
|
|
583
599
|
|
|
584
600
|
async def delete(self, sender_id: Text) -> None:
|
|
585
601
|
"""Delete tracker for the given sender_id."""
|
|
586
|
-
|
|
602
|
+
try:
|
|
603
|
+
await self._tracker_store.delete(sender_id)
|
|
604
|
+
except Exception as e:
|
|
605
|
+
self.on_tracker_store_error(e)
|
|
606
|
+
await self.fallback_tracker_store.delete(sender_id)
|
|
607
|
+
|
|
608
|
+
async def update(self, tracker: DialogueStateTracker) -> None:
|
|
609
|
+
"""Replace an existing tracker with a new one."""
|
|
610
|
+
try:
|
|
611
|
+
await self._tracker_store.update(tracker)
|
|
612
|
+
except Exception as e:
|
|
613
|
+
self.on_tracker_store_error(e)
|
|
614
|
+
await self.fallback_tracker_store.update(tracker)
|
|
587
615
|
|
|
588
616
|
async def retrieve_full_tracker(
|
|
589
617
|
self, sender_id: Text
|
|
@@ -793,7 +821,13 @@ class AwaitableTrackerStore(TrackerStore):
|
|
|
793
821
|
|
|
794
822
|
async def delete(self, sender_id: Text) -> None:
|
|
795
823
|
"""Delete tracker for the given sender_id."""
|
|
796
|
-
|
|
824
|
+
result = self._tracker_store.delete(sender_id)
|
|
825
|
+
return await result if isawaitable(result) else result
|
|
826
|
+
|
|
827
|
+
async def update(self, tracker: DialogueStateTracker) -> None:
|
|
828
|
+
"""Replace an existing tracker with a new one."""
|
|
829
|
+
result = self._tracker_store.update(tracker)
|
|
830
|
+
return await result if isawaitable(result) else result
|
|
797
831
|
|
|
798
832
|
async def retrieve_full_tracker(
|
|
799
833
|
self, conversation_id: Text
|
|
@@ -139,7 +139,8 @@ flows:
|
|
|
139
139
|
steps:
|
|
140
140
|
- noop: true
|
|
141
141
|
next:
|
|
142
|
-
#
|
|
142
|
+
# Fallback for ChitChat command when IntentlessPolicy isn't set, but
|
|
143
|
+
# pattern_chitchat invokes action_trigger_chitchat
|
|
143
144
|
- if: context.reason is "cannot_handle_chitchat"
|
|
144
145
|
then:
|
|
145
146
|
- action: utter_cannot_handle
|
|
@@ -164,7 +165,9 @@ flows:
|
|
|
164
165
|
description: Conversation repair flow for off-topic interactions that won't disrupt the main conversation
|
|
165
166
|
name: pattern chitchat
|
|
166
167
|
steps:
|
|
167
|
-
- action:
|
|
168
|
+
- action: utter_cannot_handle
|
|
169
|
+
# To enable free-form response use:
|
|
170
|
+
# - action: utter_free_chitchat_response
|
|
168
171
|
|
|
169
172
|
pattern_clarification:
|
|
170
173
|
description: Conversation repair flow for handling ambiguous requests that could match multiple flows
|
|
@@ -246,7 +249,7 @@ flows:
|
|
|
246
249
|
next: END
|
|
247
250
|
|
|
248
251
|
pattern_repeat_bot_messages:
|
|
249
|
-
description:
|
|
252
|
+
description: Conversation repair flow for repeating previous messages
|
|
250
253
|
name: pattern repeat bot messages
|
|
251
254
|
steps:
|
|
252
255
|
- action: action_repeat_bot_messages
|
|
@@ -64,12 +64,6 @@ from rasa.shared.nlu.constants import COMMANDS
|
|
|
64
64
|
|
|
65
65
|
structlogger = structlog.get_logger()
|
|
66
66
|
|
|
67
|
-
CANNOT_HANDLE_REASON = (
|
|
68
|
-
"A command generator attempted to set a slot "
|
|
69
|
-
"with a value extracted by an extractor "
|
|
70
|
-
"that is incompatible with the slot mapping type."
|
|
71
|
-
)
|
|
72
|
-
|
|
73
67
|
|
|
74
68
|
def contains_command(commands: List[Command], typ: Type[Command]) -> bool:
|
|
75
69
|
"""Check if a list of commands contains a command of a given type.
|
|
@@ -587,6 +581,11 @@ def clean_up_slot_command(
|
|
|
587
581
|
"command_processor.clean_up_slot_command.skip_command_slot_not_in_domain",
|
|
588
582
|
command=command,
|
|
589
583
|
)
|
|
584
|
+
resulting_commands.append(
|
|
585
|
+
CannotHandleCommand(
|
|
586
|
+
reason="The slot predicted by the LLM is not defined in the domain."
|
|
587
|
+
)
|
|
588
|
+
)
|
|
590
589
|
return resulting_commands
|
|
591
590
|
|
|
592
591
|
if not should_slot_be_set(slot, command, resulting_commands):
|
|
@@ -605,7 +604,10 @@ def clean_up_slot_command(
|
|
|
605
604
|
for command in resulting_commands
|
|
606
605
|
)
|
|
607
606
|
|
|
608
|
-
cannot_handle = CannotHandleCommand(
|
|
607
|
+
cannot_handle = CannotHandleCommand(
|
|
608
|
+
reason="A command generator attempted to set a slot with a value extracted "
|
|
609
|
+
"by an extractor that is incompatible with the slot mapping type."
|
|
610
|
+
)
|
|
609
611
|
if not slot_command_exists_already and cannot_handle not in resulting_commands:
|
|
610
612
|
resulting_commands.append(cannot_handle)
|
|
611
613
|
|
|
@@ -9,8 +9,8 @@ from rasa.e2e_test.e2e_test_case import ActualStepOutput, TestCase, TestStep, Te
|
|
|
9
9
|
from rasa.e2e_test.e2e_test_runner import TEST_TURNS_TYPE, E2ETestRunner
|
|
10
10
|
from rasa.llm_fine_tuning.conversations import Conversation, ConversationStep
|
|
11
11
|
from rasa.llm_fine_tuning.storage import StorageContext
|
|
12
|
-
from rasa.shared.core.constants import USER
|
|
13
|
-
from rasa.shared.core.events import UserUttered
|
|
12
|
+
from rasa.shared.core.constants import BOT, USER
|
|
13
|
+
from rasa.shared.core.events import BotUttered, UserUttered
|
|
14
14
|
from rasa.shared.core.trackers import DialogueStateTracker
|
|
15
15
|
from rasa.shared.exceptions import FinetuningDataPreparationException
|
|
16
16
|
from rasa.shared.nlu.constants import LLM_COMMANDS, LLM_PROMPT
|
|
@@ -83,16 +83,18 @@ def generate_conversation(
|
|
|
83
83
|
Conversation.
|
|
84
84
|
"""
|
|
85
85
|
steps = []
|
|
86
|
-
tracker_event_indices = [
|
|
87
|
-
i for i, event in enumerate(tracker.events) if isinstance(event, UserUttered)
|
|
88
|
-
]
|
|
89
|
-
|
|
90
|
-
if len(test_case.steps) != len(tracker_event_indices):
|
|
91
|
-
raise FinetuningDataPreparationException(
|
|
92
|
-
"Number of test case steps and tracker events do not match."
|
|
93
|
-
)
|
|
94
86
|
|
|
95
87
|
if assertions_used:
|
|
88
|
+
tracker_event_indices = [
|
|
89
|
+
i
|
|
90
|
+
for i, event in enumerate(tracker.events)
|
|
91
|
+
if isinstance(event, UserUttered)
|
|
92
|
+
]
|
|
93
|
+
if len(test_case.steps) != len(tracker_event_indices):
|
|
94
|
+
raise FinetuningDataPreparationException(
|
|
95
|
+
"Number of test case steps and tracker events do not match."
|
|
96
|
+
)
|
|
97
|
+
|
|
96
98
|
# we only have user steps, extract the bot response from the bot uttered
|
|
97
99
|
# events of the test turn
|
|
98
100
|
for i, (original_step, tracker_event_index) in enumerate(
|
|
@@ -110,8 +112,30 @@ def generate_conversation(
|
|
|
110
112
|
)
|
|
111
113
|
steps.extend(_create_bot_test_steps(test_turns[i]))
|
|
112
114
|
else:
|
|
115
|
+
tracker_event_indices = [
|
|
116
|
+
i
|
|
117
|
+
for i, event in enumerate(tracker.events)
|
|
118
|
+
if isinstance(event, UserUttered) or isinstance(event, BotUttered)
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
# Generally, we expect one or more bot response(s) for each user utterance
|
|
122
|
+
# in the test case, so that we can evaluate the actual bot response.
|
|
123
|
+
# If the test case ends with one or more user utterance(s) instead,
|
|
124
|
+
# we should thus trim those from the test case steps.
|
|
125
|
+
# This only applies to test cases that have at least one bot utterance;
|
|
126
|
+
# otherwise, all test case steps would be removed.
|
|
127
|
+
has_bot_utterance = any(step.actor == BOT for step in test_case.steps)
|
|
128
|
+
i = len(test_case.steps)
|
|
129
|
+
if has_bot_utterance:
|
|
130
|
+
while i > 0 and test_case.steps[i - 1].actor == USER:
|
|
131
|
+
i -= 1
|
|
132
|
+
test_case_steps = test_case.steps[:i]
|
|
133
|
+
|
|
134
|
+
# If the number of test case steps and tracker events differ,
|
|
135
|
+
# using zip ensures we only process pairs that exist in both lists.
|
|
136
|
+
# Prevents index errors and ensures we don't process unmatched steps or events.
|
|
113
137
|
for i, (original_step, tracker_event_index) in enumerate(
|
|
114
|
-
zip(
|
|
138
|
+
zip(test_case_steps, tracker_event_indices)
|
|
115
139
|
):
|
|
116
140
|
if original_step.actor == USER:
|
|
117
141
|
previous_turn = _get_previous_actual_step_output(test_turns, i)
|
|
@@ -127,6 +151,14 @@ def generate_conversation(
|
|
|
127
151
|
else:
|
|
128
152
|
steps.append(original_step)
|
|
129
153
|
|
|
154
|
+
# the tracker should only include events up to the last bot utterance
|
|
155
|
+
# so that the resulting transcript ends with the last bot utterance too
|
|
156
|
+
# only applies to test cases that have at least one bot utterance
|
|
157
|
+
if has_bot_utterance and test_case.steps and test_case.steps[-1].actor == USER:
|
|
158
|
+
event_to_go_to = tracker_event_indices[len(test_case_steps)] - 1
|
|
159
|
+
timestamp = tracker.events[event_to_go_to].timestamp
|
|
160
|
+
tracker = tracker.travel_back_in_time(timestamp)
|
|
161
|
+
|
|
130
162
|
# Some messages in an e2e test case could be mapped to commands via
|
|
131
163
|
# 'NLUCommandAdapter', e.g. the message will not be annotated with a prompt and
|
|
132
164
|
# commands pair. Only convert steps that have a prompt and commands present into a
|
rasa/privacy/privacy_filter.py
CHANGED
|
@@ -2,6 +2,7 @@ import copy
|
|
|
2
2
|
import datetime
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
|
+
from pathlib import Path
|
|
5
6
|
from typing import Any, Dict, List, Optional
|
|
6
7
|
|
|
7
8
|
import structlog
|
|
@@ -60,8 +61,17 @@ class PrivacyFilter:
|
|
|
60
61
|
def _load_gliner_model() -> Optional[Any]:
|
|
61
62
|
"""Load the GLiNER model for PII detection."""
|
|
62
63
|
local_model_path = os.getenv(GLINER_MODEL_PATH_ENV_VAR_NAME)
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
cache_dir_env_value = os.getenv(HUGGINGFACE_CACHE_DIR_ENV_VAR_NAME)
|
|
65
|
+
cache_dir = Path(cache_dir_env_value).resolve() if cache_dir_env_value else None
|
|
66
|
+
model_path = (
|
|
67
|
+
Path(local_model_path).resolve() if local_model_path else DEFAULT_PII_MODEL
|
|
68
|
+
)
|
|
69
|
+
local_files_only = isinstance(model_path, Path) and model_path.exists()
|
|
70
|
+
|
|
71
|
+
structlogger.debug(
|
|
72
|
+
"rasa.privacy.privacy_filter.loading_gliner_model",
|
|
73
|
+
local_files_only=local_files_only,
|
|
74
|
+
)
|
|
65
75
|
|
|
66
76
|
try:
|
|
67
77
|
from gliner import GLiNER
|
|
@@ -69,6 +79,7 @@ class PrivacyFilter:
|
|
|
69
79
|
return GLiNER.from_pretrained(
|
|
70
80
|
model_path,
|
|
71
81
|
cache_dir=cache_dir,
|
|
82
|
+
local_files_only=local_files_only,
|
|
72
83
|
)
|
|
73
84
|
except ImportError:
|
|
74
85
|
structlogger.warning(
|
|
@@ -193,7 +204,9 @@ class PrivacyFilter:
|
|
|
193
204
|
|
|
194
205
|
for key, slot in anonymized_slots.items():
|
|
195
206
|
original_slot_value = key.split(":", 1)[1]
|
|
196
|
-
anonymized_text =
|
|
207
|
+
anonymized_text = self._smart_replace(
|
|
208
|
+
user_event.text, original_slot_value, slot.value
|
|
209
|
+
)
|
|
197
210
|
user_event.text = anonymized_text
|
|
198
211
|
|
|
199
212
|
anonymized_parse_data[TEXT_KEY] = anonymized_text
|
|
@@ -232,7 +245,9 @@ class PrivacyFilter:
|
|
|
232
245
|
|
|
233
246
|
for key, slot in anonymized_slots.items():
|
|
234
247
|
original_slot_value = key.split(":", 1)[1]
|
|
235
|
-
anonymized_text =
|
|
248
|
+
anonymized_text = self._smart_replace(
|
|
249
|
+
bot_event.text, original_slot_value, slot.value
|
|
250
|
+
)
|
|
236
251
|
bot_event.text = anonymized_text
|
|
237
252
|
|
|
238
253
|
bot_event.text = self._anonymize_edge_cases(bot_event.text, anonymized_slots)
|
|
@@ -338,3 +353,41 @@ class PrivacyFilter:
|
|
|
338
353
|
text = text.replace(entity_value, self._mask(entity[ENTITY_LABEL_KEY]))
|
|
339
354
|
|
|
340
355
|
return text
|
|
356
|
+
|
|
357
|
+
@staticmethod
|
|
358
|
+
def _smart_replace(text: str, original_value: str, replacement: str) -> str:
|
|
359
|
+
"""Replace original_value with replacement in text.
|
|
360
|
+
|
|
361
|
+
This method performs a string replacement in the text,
|
|
362
|
+
with special handling for floats.
|
|
363
|
+
If original_value is a float string like "24.0",
|
|
364
|
+
also tries replacing the integer version "24".
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
text (str): The text to perform replacements on
|
|
368
|
+
original_value (str): The value to replace
|
|
369
|
+
replacement (str): The replacement value
|
|
370
|
+
|
|
371
|
+
Returns:
|
|
372
|
+
str: The text with replacements applied
|
|
373
|
+
"""
|
|
374
|
+
# First try the original replacement
|
|
375
|
+
result = text.replace(original_value, replacement)
|
|
376
|
+
if text != result:
|
|
377
|
+
return result
|
|
378
|
+
|
|
379
|
+
# If replacement didn't happen and it's a float,
|
|
380
|
+
# try replacing the integer version
|
|
381
|
+
if "." in original_value:
|
|
382
|
+
try:
|
|
383
|
+
float_val = float(original_value)
|
|
384
|
+
if float_val.is_integer():
|
|
385
|
+
int_version = str(int(float_val))
|
|
386
|
+
result = result.replace(int_version, replacement)
|
|
387
|
+
except ValueError:
|
|
388
|
+
structlogger.warning(
|
|
389
|
+
"rasa.privacy.privacy_filter.smart_replace_float_error",
|
|
390
|
+
event_info="Unable to anonymize float value.",
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
return result
|
rasa/privacy/privacy_manager.py
CHANGED
|
@@ -12,7 +12,7 @@ import structlog
|
|
|
12
12
|
from apscheduler.schedulers.background import BackgroundScheduler
|
|
13
13
|
|
|
14
14
|
import rasa.shared.core.trackers
|
|
15
|
-
from rasa.core.tracker_stores.tracker_store import
|
|
15
|
+
from rasa.core.tracker_stores.tracker_store import TrackerStore
|
|
16
16
|
from rasa.privacy.constants import (
|
|
17
17
|
TEXT_KEY,
|
|
18
18
|
USER_CHAT_INACTIVITY_IN_MINUTES_ENV_VAR_NAME,
|
|
@@ -25,7 +25,7 @@ from rasa.privacy.privacy_config import (
|
|
|
25
25
|
)
|
|
26
26
|
from rasa.privacy.privacy_filter import PrivacyFilter
|
|
27
27
|
from rasa.shared.core.events import Event, SlotSet, UserUttered, split_events
|
|
28
|
-
from rasa.shared.core.trackers import DialogueStateTracker
|
|
28
|
+
from rasa.shared.core.trackers import DialogueStateTracker, EventVerbosity
|
|
29
29
|
|
|
30
30
|
if TYPE_CHECKING:
|
|
31
31
|
from asyncio import AbstractEventLoop
|
|
@@ -97,7 +97,7 @@ class BackgroundPrivacyManager:
|
|
|
97
97
|
else TrackerStore.create(None)
|
|
98
98
|
)
|
|
99
99
|
|
|
100
|
-
self.tracker_store =
|
|
100
|
+
self.tracker_store = tracker_store
|
|
101
101
|
|
|
102
102
|
self.event_brokers: List["EventBroker"] = []
|
|
103
103
|
self.event_loop = event_loop
|
|
@@ -264,15 +264,16 @@ class BackgroundPrivacyManager:
|
|
|
264
264
|
)
|
|
265
265
|
return None
|
|
266
266
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
267
|
+
latest_user_message = tracker.get_last_event_for(
|
|
268
|
+
UserUttered, event_verbosity=EventVerbosity.ALL
|
|
269
|
+
)
|
|
270
|
+
if latest_user_message is None or not latest_user_message.text:
|
|
270
271
|
structlogger.debug(
|
|
271
272
|
"rasa.privacy_manager.no_user_message.skipping_processing",
|
|
272
273
|
)
|
|
273
274
|
return None
|
|
274
275
|
|
|
275
|
-
return
|
|
276
|
+
return latest_user_message
|
|
276
277
|
|
|
277
278
|
@staticmethod
|
|
278
279
|
def _has_session_been_anonymized(events: List[Event]) -> bool:
|
|
@@ -352,7 +353,7 @@ class BackgroundPrivacyManager:
|
|
|
352
353
|
if not full_tracker:
|
|
353
354
|
structlogger.debug(
|
|
354
355
|
"rasa.privacy_manager.no_tracker_found_for_sender_id",
|
|
355
|
-
|
|
356
|
+
sender_id=key,
|
|
356
357
|
)
|
|
357
358
|
continue
|
|
358
359
|
|
|
@@ -360,9 +361,13 @@ class BackgroundPrivacyManager:
|
|
|
360
361
|
full_tracker
|
|
361
362
|
)
|
|
362
363
|
|
|
363
|
-
await self.tracker_store.delete(sender_id=key)
|
|
364
|
-
|
|
365
364
|
if not events_to_be_retained:
|
|
365
|
+
await self.tracker_store.delete(sender_id=key)
|
|
366
|
+
structlogger.info(
|
|
367
|
+
"rasa.privacy_manager.tracker_session_deleted",
|
|
368
|
+
sender_id=full_tracker.sender_id,
|
|
369
|
+
triggered_by="deletion_cron_job",
|
|
370
|
+
)
|
|
366
371
|
continue
|
|
367
372
|
|
|
368
373
|
tracker = DialogueStateTracker.from_events(
|
|
@@ -370,12 +375,13 @@ class BackgroundPrivacyManager:
|
|
|
370
375
|
evts=events_to_be_retained,
|
|
371
376
|
slots=full_tracker.slots.values(),
|
|
372
377
|
)
|
|
373
|
-
await self.tracker_store.
|
|
378
|
+
await self.tracker_store.update(tracker)
|
|
374
379
|
|
|
375
380
|
structlogger.info(
|
|
376
|
-
"rasa.privacy_manager.
|
|
377
|
-
|
|
378
|
-
event_info="
|
|
381
|
+
"rasa.privacy_manager.overwritten_tracker",
|
|
382
|
+
sender_id=key,
|
|
383
|
+
event_info="Deleted eligible events and saved "
|
|
384
|
+
"tracker with events not scheduled "
|
|
379
385
|
"for deletion yet.",
|
|
380
386
|
)
|
|
381
387
|
|
|
@@ -523,14 +529,11 @@ class BackgroundPrivacyManager:
|
|
|
523
529
|
):
|
|
524
530
|
structlogger.info(
|
|
525
531
|
"rasa.privacy_manager.anonymizing_tracker_session",
|
|
526
|
-
|
|
532
|
+
sender_id=session.sender_id,
|
|
527
533
|
last_event_timestamp=last_event_timestamp,
|
|
528
534
|
triggered_by="anonymization_cron_job",
|
|
529
535
|
)
|
|
530
|
-
|
|
531
|
-
session.sender_id, session.events
|
|
532
|
-
)
|
|
533
|
-
events = self.process_events(tracker, process_all=True)
|
|
536
|
+
events = self.process_events(session, process_all=True)
|
|
534
537
|
processed_events.extend(events)
|
|
535
538
|
else:
|
|
536
539
|
# If the session is not valid for anonymization,
|
|
@@ -539,7 +542,7 @@ class BackgroundPrivacyManager:
|
|
|
539
542
|
uneligible_events.extend(events)
|
|
540
543
|
structlogger.debug(
|
|
541
544
|
"rasa.privacy_manager.session_not_valid_for_anonymization",
|
|
542
|
-
|
|
545
|
+
sender_id=session.sender_id,
|
|
543
546
|
session_id=session.sender_id,
|
|
544
547
|
last_event_timestamp=last_event_timestamp,
|
|
545
548
|
)
|
|
@@ -575,7 +578,7 @@ class BackgroundPrivacyManager:
|
|
|
575
578
|
|
|
576
579
|
structlogger.info(
|
|
577
580
|
"rasa.privacy_manager.tracker_session_scheduled_for_deletion",
|
|
578
|
-
|
|
581
|
+
sender_id=full_tracker.sender_id,
|
|
579
582
|
last_event_timestamp=last_event_timestamp,
|
|
580
583
|
triggered_by="deletion_cron_job",
|
|
581
584
|
)
|
rasa/shared/core/constants.py
CHANGED
|
@@ -182,6 +182,7 @@ class SetSlotExtractor(Enum):
|
|
|
182
182
|
# the keys for `State` (USER, PREVIOUS_ACTION, SLOTS, ACTIVE_LOOP)
|
|
183
183
|
# represent the origin of a `SubState`
|
|
184
184
|
USER = "user"
|
|
185
|
+
BOT = "bot"
|
|
185
186
|
SLOTS = "slots"
|
|
186
187
|
|
|
187
188
|
USE_TEXT_FOR_FEATURIZATION = "use_text_for_featurization"
|
rasa/studio/pull/domains.py
CHANGED
|
@@ -15,8 +15,7 @@ def merge_domain(
|
|
|
15
15
|
data_local: TrainingDataImporter,
|
|
16
16
|
domain_path: Path,
|
|
17
17
|
) -> None:
|
|
18
|
-
"""
|
|
19
|
-
Merges the domain from Rasa Studio with the local domain.
|
|
18
|
+
"""Merges the domain from Rasa Studio with the local domain.
|
|
20
19
|
|
|
21
20
|
Args:
|
|
22
21
|
data_from_studio: The training data importer for the Rasa Studio domain.
|
|
@@ -29,10 +28,22 @@ def merge_domain(
|
|
|
29
28
|
else:
|
|
30
29
|
all_local_domain_files = data_local.get_domain_files([str(domain_path)])
|
|
31
30
|
|
|
31
|
+
studio_domain_file_path = domain_path / STUDIO_DOMAIN_FILENAME
|
|
32
|
+
|
|
32
33
|
# leftover_domain represents the items in the studio
|
|
33
34
|
# domain that are not in the local domain
|
|
34
35
|
leftover_domain = data_from_studio.get_user_domain()
|
|
35
36
|
for file_path in all_local_domain_files:
|
|
37
|
+
if file_path == str(studio_domain_file_path):
|
|
38
|
+
# we need to exclude the studio domain file from the merge,
|
|
39
|
+
# since we want to dump ALL the remaining items to this path
|
|
40
|
+
# after the merge. if we include it here, we will remove the existing
|
|
41
|
+
# items from the leftover domain and after this loop we will
|
|
42
|
+
# overwrite the studio domain file with the remaining items in
|
|
43
|
+
# the leftover domain - this means we loose the items that were
|
|
44
|
+
# in the studio domain file before we started the merge.
|
|
45
|
+
continue
|
|
46
|
+
|
|
36
47
|
# For each local domain file, we do a partial merge
|
|
37
48
|
local_domain = Domain.from_file(str(file_path))
|
|
38
49
|
updated_local_domain = local_domain.partial_merge(leftover_domain)
|
|
@@ -46,4 +57,4 @@ def merge_domain(
|
|
|
46
57
|
|
|
47
58
|
# If there are still items in leftover_domain, persist them
|
|
48
59
|
if not leftover_domain.is_empty():
|
|
49
|
-
leftover_domain.persist(
|
|
60
|
+
leftover_domain.persist(studio_domain_file_path)
|
rasa/version.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: rasa-pro
|
|
3
|
-
Version: 3.13.
|
|
3
|
+
Version: 3.13.0rc4
|
|
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
|
|
@@ -270,7 +270,7 @@ rasa/core/channels/voice_stream/asr/asr_event.py,sha256=skPwrkRrcsptmeWXu9q68i4B
|
|
|
270
270
|
rasa/core/channels/voice_stream/asr/azure.py,sha256=dUFxtNVVwGM2D1VyqQ5FWeSpKwUQekMXUxWZv6tPJ7w,6114
|
|
271
271
|
rasa/core/channels/voice_stream/asr/deepgram.py,sha256=9cIqRuv9gWzOfEKxeDbhijGoT8EPUV7Oo493WXaHlBo,5682
|
|
272
272
|
rasa/core/channels/voice_stream/audio_bytes.py,sha256=3V0QQplPD-kVfebaaeVcKgV7pwIJyjnTenujVD3y3sY,340
|
|
273
|
-
rasa/core/channels/voice_stream/audiocodes.py,sha256=
|
|
273
|
+
rasa/core/channels/voice_stream/audiocodes.py,sha256=TbLC3iuupyA8d8xdxBHVCtljKidFT-ZzLH9LvMPsxEI,12349
|
|
274
274
|
rasa/core/channels/voice_stream/browser_audio.py,sha256=KDUexINUh1gElQ_2ccpAWltlH-pdakm-x1L6bvKffUY,3931
|
|
275
275
|
rasa/core/channels/voice_stream/call_state.py,sha256=fbwVbT0ddE7AjTYjx-Mq5jBMEGXanbug5wlBwstaews,899
|
|
276
276
|
rasa/core/channels/voice_stream/genesys.py,sha256=-PL1y0b2dJClM-S000loaDUWbm8qwcFSYwYaNaPG_9c,17708
|
|
@@ -320,15 +320,15 @@ rasa/core/nlg/translate.py,sha256=PBMTbIgdkhx8rhzqv6h0u5r9jqdfiVIh7u0qb363sJA,18
|
|
|
320
320
|
rasa/core/persistor.py,sha256=7LCZHAwCM-xrUI38aaJ5dkxJvLdJXWI1TEUKsBo4_EE,21295
|
|
321
321
|
rasa/core/policies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
322
322
|
rasa/core/policies/ensemble.py,sha256=XoHxU0jcb_io_LBOpjJffylzqtGEB7CH9ivhRyO8pDc,12960
|
|
323
|
-
rasa/core/policies/enterprise_search_policy.py,sha256=
|
|
323
|
+
rasa/core/policies/enterprise_search_policy.py,sha256=QmphxTTLWKaKrVCwBYWuVhw4TraW1aCbugNrg2Qrbcg,46869
|
|
324
324
|
rasa/core/policies/enterprise_search_policy_config.py,sha256=rTIGBrfGfe_lvsYQW1cU20tza07p_-oxFfjXhw7-phc,8644
|
|
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
|
-
rasa/core/policies/enterprise_search_prompt_with_relevancy_check_and_citation_template.jinja2,sha256=
|
|
327
|
+
rasa/core/policies/enterprise_search_prompt_with_relevancy_check_and_citation_template.jinja2,sha256=b_8ZzK1ar0SvLZRFS8d9tHWvYS7Xb8xPJiBKg-UcyAM,3743
|
|
328
328
|
rasa/core/policies/flow_policy.py,sha256=Rvx5MIGDHi9sVxGazf-dXs6F-hFHSi3UoVjjSP8ATik,7470
|
|
329
329
|
rasa/core/policies/flows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
330
330
|
rasa/core/policies/flows/flow_exceptions.py,sha256=_FQuN-cerQDM1pivce9bz4zylh5UYkljvYS1gjDukHI,1527
|
|
331
|
-
rasa/core/policies/flows/flow_executor.py,sha256=
|
|
331
|
+
rasa/core/policies/flows/flow_executor.py,sha256=3T3uGzJsPflZrtOg2Gg5WFgOvmX3uRuacaESiOtvDXA,28601
|
|
332
332
|
rasa/core/policies/flows/flow_step_result.py,sha256=agjPrD6lahGSe2ViO5peBeoMdI9ngVGRSgtytgxmJmg,1360
|
|
333
333
|
rasa/core/policies/intentless_policy.py,sha256=1A7FSkI4PQdN3t1p3GQhSImmO-m6UVCUzzEsjxz4nKc,38040
|
|
334
334
|
rasa/core/policies/intentless_prompt_template.jinja2,sha256=KhIL3cruMmkxhrs5oVbqgSvK6ZiN_6TQ_jXrgtEB-ZY,677
|
|
@@ -347,12 +347,12 @@ rasa/core/secrets_manager/secret_manager.py,sha256=oYihfIMIMUKA8U_ZRA3blcErFlCoz
|
|
|
347
347
|
rasa/core/secrets_manager/vault.py,sha256=02wlsrN3QoHSZkqygHaM_CWDtkpkJ25iD-ksEu3ejBw,20481
|
|
348
348
|
rasa/core/test.py,sha256=VGlGvmrXRqHaPMaetvvN1fa65oZMryN5ot9L3otNH5E,49282
|
|
349
349
|
rasa/core/tracker_stores/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
350
|
-
rasa/core/tracker_stores/auth_retry_tracker_store.py,sha256=
|
|
351
|
-
rasa/core/tracker_stores/dynamo_tracker_store.py,sha256
|
|
352
|
-
rasa/core/tracker_stores/mongo_tracker_store.py,sha256=
|
|
353
|
-
rasa/core/tracker_stores/redis_tracker_store.py,sha256=
|
|
354
|
-
rasa/core/tracker_stores/sql_tracker_store.py,sha256=
|
|
355
|
-
rasa/core/tracker_stores/tracker_store.py,sha256=
|
|
350
|
+
rasa/core/tracker_stores/auth_retry_tracker_store.py,sha256=eOwsLuQZhWu5Q4RZHJYffL3FhpKOglzJ0MNGffv2E60,7270
|
|
351
|
+
rasa/core/tracker_stores/dynamo_tracker_store.py,sha256=-C0M03H4dlIyX7yOYtL6VJOtnDkNjnCMTdbRIYedN6g,8271
|
|
352
|
+
rasa/core/tracker_stores/mongo_tracker_store.py,sha256=v2ktPXxp7PWfoHPqOJKLkMzML_5E_Of0Ihna2Vcqfbg,7644
|
|
353
|
+
rasa/core/tracker_stores/redis_tracker_store.py,sha256=q38nCZoXW4G7xdZ6nOcOpanIB5XrzEM4rMdFnUHR3pE,8864
|
|
354
|
+
rasa/core/tracker_stores/sql_tracker_store.py,sha256=oJgAqDysP3KXJkPRCZ1s8HhlxOn-y9Apmo6OVO3lQmc,20960
|
|
355
|
+
rasa/core/tracker_stores/tracker_store.py,sha256=bJMsxzuhw90vGY2uGbbCogikoy4HucPy6SvYfcyFQJw,29472
|
|
356
356
|
rasa/core/train.py,sha256=ESBhZ9vZySziZIG8fvshHO4AYApD0hywRFCmA9h1wFI,3521
|
|
357
357
|
rasa/core/training/__init__.py,sha256=23DaZynwsxIaGYvZBSwQRxanaMWa-ihfON7f-NetK_g,3213
|
|
358
358
|
rasa/core/training/converters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -431,7 +431,7 @@ rasa/dialogue_understanding/patterns/collect_information.py,sha256=8YWvhFTt8CJML
|
|
|
431
431
|
rasa/dialogue_understanding/patterns/completed.py,sha256=7qkyUj2d__2R3mpwWVmQpfwCCbJruBrjRZbmbDr3Zbo,1278
|
|
432
432
|
rasa/dialogue_understanding/patterns/continue_interrupted.py,sha256=OSTbe5l0B0ECNIYWpYB0pdzIeaqM3m3UZskNNjL5vrw,1682
|
|
433
433
|
rasa/dialogue_understanding/patterns/correction.py,sha256=7fQ02-JU1CGZiTjTi9YqmD1F4o-9Tv5WCAXnFgZlvtY,11380
|
|
434
|
-
rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml,sha256=
|
|
434
|
+
rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml,sha256=NwNXqrodwOTgDPYtu6L5n6epBLzXjnnRCzH_oBPLMnc,11030
|
|
435
435
|
rasa/dialogue_understanding/patterns/domain_for_patterns.py,sha256=Zv_lCJn4nbkxeNYOPGsR0V8tmYAUsM_Ho_9to8hku-o,6493
|
|
436
436
|
rasa/dialogue_understanding/patterns/human_handoff.py,sha256=1hkSdL6kui42rZc7zERZ9R7nLyvRHi_tHgNU7FyrhAQ,1132
|
|
437
437
|
rasa/dialogue_understanding/patterns/internal_error.py,sha256=APCKVv16M6mSQ4upu4UwG0yIaaKTyr7uB2yV8ZtpMzo,1609
|
|
@@ -443,7 +443,7 @@ rasa/dialogue_understanding/patterns/skip_question.py,sha256=fJ1MC0WEEtS-BpnGJEf
|
|
|
443
443
|
rasa/dialogue_understanding/patterns/user_silence.py,sha256=xP-QMnd-MsybH5z4g01hBv4OLOHcw6m3rc26LQfe2zo,1140
|
|
444
444
|
rasa/dialogue_understanding/patterns/validate_slot.py,sha256=hqd5AEGT3M3HLNhMwuI9W9kZNCvgU6GyI-2xc2b4kz8,2085
|
|
445
445
|
rasa/dialogue_understanding/processor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
446
|
-
rasa/dialogue_understanding/processor/command_processor.py,sha256=
|
|
446
|
+
rasa/dialogue_understanding/processor/command_processor.py,sha256=d9LRwNzV4Jqe_6DOvGITgElwqDZfz4JvvuUpPJ4ceZI,30296
|
|
447
447
|
rasa/dialogue_understanding/processor/command_processor_component.py,sha256=rkErI_Uo7s3LsEojUSGSRbWGyGaX7GtGOYSJn0V-TI4,1650
|
|
448
448
|
rasa/dialogue_understanding/stack/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
449
449
|
rasa/dialogue_understanding/stack/dialogue_stack.py,sha256=cYV6aQeh0EuOJHODDqK3biqXozYTX8baPgLwHhPxFqs,5244
|
|
@@ -542,7 +542,7 @@ rasa/hooks.py,sha256=HRWGfHIe_YG9nrV_Ly0ontfPSbcsyVVwA-8e8frr57U,2986
|
|
|
542
542
|
rasa/jupyter.py,sha256=TCYVD4QPQIMmfA6ZwDUBOBTAECwCwbU2XOkosodLO9k,1782
|
|
543
543
|
rasa/keys,sha256=2Stg1fstgJ203cOoW1B2gGMY29fhEnjIfTVxKv_fqPo,101
|
|
544
544
|
rasa/llm_fine_tuning/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
545
|
-
rasa/llm_fine_tuning/annotation_module.py,sha256=
|
|
545
|
+
rasa/llm_fine_tuning/annotation_module.py,sha256=7vKwesRLvtKQAt9etHIT51HN8D21dSR3smNY7aIbGx4,11267
|
|
546
546
|
rasa/llm_fine_tuning/conversations.py,sha256=qzoTFQiwADmzL9mocqML4a-nAgEu6hlOSE3K87LvhM0,4272
|
|
547
547
|
rasa/llm_fine_tuning/llm_data_preparation_module.py,sha256=Vh6HHDvH1ueaNgBWnzIA7ymcTwHpqVvKxIPAnMKZtyY,7153
|
|
548
548
|
rasa/llm_fine_tuning/paraphrasing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -636,14 +636,14 @@ rasa/privacy/constants.py,sha256=PkmfLcQXKmRUnEJ-NzsG4bxbx1x0n5canE0Ab5DX2MM,212
|
|
|
636
636
|
rasa/privacy/event_broker_utils.py,sha256=K0ej9vRED5sJiG8YJq3_pynMRJEenCyKSjt6xOq2x84,2704
|
|
637
637
|
rasa/privacy/privacy_config.py,sha256=mctRStSliSUbbjHAl5ISTczJ2TgU7T5yjSfZvLmUtlI,9339
|
|
638
638
|
rasa/privacy/privacy_config_schema.json,sha256=kQVJCrlKljJMkOCL3WDzquVS8V1-KE6_XMEyVSuUkJw,1835
|
|
639
|
-
rasa/privacy/privacy_filter.py,sha256=
|
|
640
|
-
rasa/privacy/privacy_manager.py,sha256=
|
|
639
|
+
rasa/privacy/privacy_filter.py,sha256=s1LxnOeIoGh16vzTzxoM5vSDelk1P55Ay3bm0XRuINs,14988
|
|
640
|
+
rasa/privacy/privacy_manager.py,sha256=IWbWIpJeX7EgHXPjYXPmq-5RaoJEnCFa5xKE7M2roTE,22593
|
|
641
641
|
rasa/server.py,sha256=prH4FVO5kd7VKgSJnP_dt7PhXlW0vf8KWf62ccK8mlw,60505
|
|
642
642
|
rasa/shared/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
643
643
|
rasa/shared/constants.py,sha256=pYMlsFE9kao5BUOXd-8cHWLV9jMfUop7Mfm57mBPcxc,12856
|
|
644
644
|
rasa/shared/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
645
645
|
rasa/shared/core/command_payload_reader.py,sha256=aWmEe6NyGdGZ8qaCPxGZu1frLROv04SFbwPpZNrtj7Q,3741
|
|
646
|
-
rasa/shared/core/constants.py,sha256=
|
|
646
|
+
rasa/shared/core/constants.py,sha256=_YGXcxuIsgug7P-__KQJbrUjkK-gT_RfWjBESy9QZTo,6410
|
|
647
647
|
rasa/shared/core/conversation.py,sha256=0nUhcbQkPDnO3_Rig7oiinrWmPy5fsVQs_U6Fx1hG5c,1384
|
|
648
648
|
rasa/shared/core/domain.py,sha256=PTSwkm_m9E5hzuxUrIjoLVEtYTHk1gjul3ic6Q0tUg8,86791
|
|
649
649
|
rasa/shared/core/events.py,sha256=FAWCA0JXim89u4AcWLKCt4F9A0pkYDE5NhCIyiKwWTM,89973
|
|
@@ -793,7 +793,7 @@ rasa/studio/link.py,sha256=IB--VKX72ERL7OOLQ6SgfVuRSg8zWkq3RxmwxFzoA2I,6590
|
|
|
793
793
|
rasa/studio/prompts.py,sha256=KRQ6WmwyDyEiNFleZ1ftVOE6m6zBI6pWCHYfePJQjaM,6803
|
|
794
794
|
rasa/studio/pull/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
795
795
|
rasa/studio/pull/data.py,sha256=e5VJuOJ2W7Ni74PuDB5cODhjygUcjjxI-i4xA1MzdOc,7565
|
|
796
|
-
rasa/studio/pull/domains.py,sha256=
|
|
796
|
+
rasa/studio/pull/domains.py,sha256=qPClJkILTpZuLEic56f1vAgnoABv3hYWm6tduK1BXXM,2443
|
|
797
797
|
rasa/studio/pull/pull.py,sha256=Qr-Ms4pXNS04hvdciZCfbeC1hag6v2puwhHwhcFpA8Q,7750
|
|
798
798
|
rasa/studio/push.py,sha256=_EopU6RQnbQub33x0TVXOTWCYUfOQMDc6KdDNmltLMs,4279
|
|
799
799
|
rasa/studio/results_logger.py,sha256=lwKROoQjzzJVnFoceLQ-z-5Hg35TfHo-8R4MDrMLYHY,5126
|
|
@@ -846,9 +846,9 @@ rasa/utils/train_utils.py,sha256=ClJx-6x3-h3Vt6mskacgkcCUJTMXjFPe3zAcy_DfmaU,212
|
|
|
846
846
|
rasa/utils/url_tools.py,sha256=dZ1HGkVdWTJB7zYEdwoDIrEuyX9HE5WsxKKFVsXBLE0,1218
|
|
847
847
|
rasa/utils/yaml.py,sha256=KjbZq5C94ZP7Jdsw8bYYF7HASI6K4-C_kdHfrnPLpSI,2000
|
|
848
848
|
rasa/validator.py,sha256=JXi8bz3SsTB2c1tbDRY3r3TkcfSbhxacoxs-rVx6a9s,82937
|
|
849
|
-
rasa/version.py,sha256=
|
|
850
|
-
rasa_pro-3.13.
|
|
851
|
-
rasa_pro-3.13.
|
|
852
|
-
rasa_pro-3.13.
|
|
853
|
-
rasa_pro-3.13.
|
|
854
|
-
rasa_pro-3.13.
|
|
849
|
+
rasa/version.py,sha256=CR4TIGitcXn4TjFFR4dvuB7MwIBGoxfQBLSyXmoOigk,120
|
|
850
|
+
rasa_pro-3.13.0rc4.dist-info/METADATA,sha256=kyiTLbOAtGtqCKm9hjUNlkTdUSTMCuZIUNB76NXTDu8,10556
|
|
851
|
+
rasa_pro-3.13.0rc4.dist-info/NOTICE,sha256=7HlBoMHJY9CL2GlYSfTQ-PZsVmLmVkYmMiPlTjhuCqA,218
|
|
852
|
+
rasa_pro-3.13.0rc4.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
|
853
|
+
rasa_pro-3.13.0rc4.dist-info/entry_points.txt,sha256=ckJ2SfEyTPgBqj_I6vm_tqY9dZF_LAPJZA335Xp0Q9U,43
|
|
854
|
+
rasa_pro-3.13.0rc4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|