rasa-pro 3.12.10.dev1__py3-none-any.whl → 3.12.12.dev1__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/__init__.py +0 -6
- rasa/api.py +4 -0
- rasa/cli/arguments/default_arguments.py +13 -1
- rasa/cli/arguments/train.py +2 -0
- rasa/cli/train.py +1 -0
- rasa/constants.py +2 -0
- rasa/core/actions/action.py +1 -1
- rasa/core/channels/voice_ready/audiocodes.py +71 -31
- rasa/core/persistor.py +55 -20
- rasa/core/policies/intentless_policy.py +1 -3
- rasa/dialogue_understanding/coexistence/llm_based_router.py +1 -0
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +4 -15
- rasa/dialogue_understanding/generator/llm_command_generator.py +1 -3
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +4 -44
- rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +1 -14
- rasa/hooks.py +0 -55
- rasa/keys +1 -0
- rasa/model_manager/config.py +3 -1
- rasa/model_manager/model_api.py +1 -2
- rasa/model_manager/runner_service.py +8 -4
- rasa/model_manager/trainer_service.py +1 -0
- rasa/model_training.py +12 -3
- rasa/nlu/extractors/crf_entity_extractor.py +66 -16
- rasa/shared/constants.py +0 -5
- rasa/shared/providers/constants.py +0 -9
- rasa/shared/providers/llm/_base_litellm_client.py +4 -14
- rasa/shared/providers/llm/litellm_router_llm_client.py +7 -17
- rasa/shared/providers/llm/llm_client.py +15 -24
- rasa/shared/providers/llm/self_hosted_llm_client.py +2 -10
- rasa/tracing/instrumentation/attribute_extractors.py +2 -2
- rasa/version.py +1 -1
- {rasa_pro-3.12.10.dev1.dist-info → rasa_pro-3.12.12.dev1.dist-info}/METADATA +2 -3
- {rasa_pro-3.12.10.dev1.dist-info → rasa_pro-3.12.12.dev1.dist-info}/RECORD +36 -36
- rasa/monkey_patches.py +0 -91
- {rasa_pro-3.12.10.dev1.dist-info → rasa_pro-3.12.12.dev1.dist-info}/NOTICE +0 -0
- {rasa_pro-3.12.10.dev1.dist-info → rasa_pro-3.12.12.dev1.dist-info}/WHEEL +0 -0
- {rasa_pro-3.12.10.dev1.dist-info → rasa_pro-3.12.12.dev1.dist-info}/entry_points.txt +0 -0
rasa/__init__.py
CHANGED
|
@@ -5,11 +5,5 @@ from rasa import version
|
|
|
5
5
|
# define the version before the other imports since these need it
|
|
6
6
|
__version__ = version.__version__
|
|
7
7
|
|
|
8
|
-
from litellm.integrations.langfuse.langfuse import LangFuseLogger
|
|
9
|
-
|
|
10
|
-
from rasa.monkey_patches import litellm_langfuse_logger_init_fixed
|
|
11
|
-
|
|
12
|
-
# Monkey-patch the init method as early as possible before the class is used
|
|
13
|
-
LangFuseLogger.__init__ = litellm_langfuse_logger_init_fixed # type: ignore
|
|
14
8
|
|
|
15
9
|
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
rasa/api.py
CHANGED
|
@@ -81,6 +81,7 @@ def train(
|
|
|
81
81
|
remote_storage: Optional[StorageType] = None,
|
|
82
82
|
file_importer: Optional["TrainingDataImporter"] = None,
|
|
83
83
|
keep_local_model_copy: bool = False,
|
|
84
|
+
remote_root_only: bool = False,
|
|
84
85
|
) -> "TrainingResult":
|
|
85
86
|
"""Runs Rasa Core and NLU training in `async` loop.
|
|
86
87
|
|
|
@@ -108,6 +109,8 @@ def train(
|
|
|
108
109
|
If it is not provided, a new instance will be created.
|
|
109
110
|
keep_local_model_copy: If `True` the model will be stored locally even if
|
|
110
111
|
remote storage is configured.
|
|
112
|
+
remote_root_only: If `True`, the model will be stored in the root of the
|
|
113
|
+
remote model storage.
|
|
111
114
|
|
|
112
115
|
Returns:
|
|
113
116
|
An instance of `TrainingResult`.
|
|
@@ -131,6 +134,7 @@ def train(
|
|
|
131
134
|
remote_storage=remote_storage,
|
|
132
135
|
file_importer=file_importer,
|
|
133
136
|
keep_local_model_copy=keep_local_model_copy,
|
|
137
|
+
remote_root_only=remote_root_only,
|
|
134
138
|
)
|
|
135
139
|
)
|
|
136
140
|
|
|
@@ -172,7 +172,7 @@ def add_remote_storage_param(
|
|
|
172
172
|
) -> None:
|
|
173
173
|
parser.add_argument(
|
|
174
174
|
"--remote-storage",
|
|
175
|
-
help="Remote storage which should be used to store/load the model."
|
|
175
|
+
help="Remote storage which should be used to store/load the model. "
|
|
176
176
|
f"Supported storages are: {RemoteStorageType.list()}. "
|
|
177
177
|
"You can also provide your own implementation of the `Persistor` interface.",
|
|
178
178
|
required=required,
|
|
@@ -180,6 +180,18 @@ def add_remote_storage_param(
|
|
|
180
180
|
)
|
|
181
181
|
|
|
182
182
|
|
|
183
|
+
def add_remote_root_only_param(
|
|
184
|
+
parser: argparse.ArgumentParser, required: bool = False
|
|
185
|
+
) -> None:
|
|
186
|
+
parser.add_argument(
|
|
187
|
+
"--remote-root-only",
|
|
188
|
+
action="store_true",
|
|
189
|
+
help="If set, models will be stored only at the root directory "
|
|
190
|
+
"of the remote storage.",
|
|
191
|
+
required=required,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
|
|
183
195
|
def parse_remote_storage_arg(value: str) -> StorageType:
|
|
184
196
|
try:
|
|
185
197
|
return parse_remote_storage(value)
|
rasa/cli/arguments/train.py
CHANGED
|
@@ -7,6 +7,7 @@ from rasa.cli.arguments.default_arguments import (
|
|
|
7
7
|
add_endpoint_param,
|
|
8
8
|
add_nlu_data_param,
|
|
9
9
|
add_out_param,
|
|
10
|
+
add_remote_root_only_param,
|
|
10
11
|
add_remote_storage_param,
|
|
11
12
|
add_stories_param,
|
|
12
13
|
)
|
|
@@ -41,6 +42,7 @@ def set_train_arguments(parser: argparse.ArgumentParser) -> None:
|
|
|
41
42
|
parser, help_text="Configuration file for the connectors as a yml file."
|
|
42
43
|
)
|
|
43
44
|
add_remote_storage_param(parser)
|
|
45
|
+
add_remote_root_only_param(parser)
|
|
44
46
|
|
|
45
47
|
|
|
46
48
|
def set_train_core_arguments(parser: argparse.ArgumentParser) -> None:
|
rasa/cli/train.py
CHANGED
|
@@ -155,6 +155,7 @@ def run_training(args: argparse.Namespace, can_exit: bool = False) -> Optional[T
|
|
|
155
155
|
remote_storage=args.remote_storage,
|
|
156
156
|
file_importer=training_data_importer,
|
|
157
157
|
keep_local_model_copy=args.keep_local_model_copy,
|
|
158
|
+
remote_root_only=args.remote_root_only,
|
|
158
159
|
)
|
|
159
160
|
if training_result.code != 0 and can_exit:
|
|
160
161
|
display_research_study_prompt()
|
rasa/constants.py
CHANGED
rasa/core/actions/action.py
CHANGED
|
@@ -898,7 +898,7 @@ class RemoteAction(Action):
|
|
|
898
898
|
draft["buttons"].extend(buttons)
|
|
899
899
|
|
|
900
900
|
# Avoid overwriting `draft` values with empty values
|
|
901
|
-
response = {k: v for k, v in response.items() if v}
|
|
901
|
+
response = {k: v for k, v in response.items() if v is not None}
|
|
902
902
|
draft.update(response)
|
|
903
903
|
bot_messages.append(create_bot_utterance(draft))
|
|
904
904
|
|
|
@@ -6,7 +6,18 @@ import uuid
|
|
|
6
6
|
from collections import defaultdict
|
|
7
7
|
from dataclasses import asdict
|
|
8
8
|
from datetime import datetime, timedelta, timezone
|
|
9
|
-
from typing import
|
|
9
|
+
from typing import (
|
|
10
|
+
Any,
|
|
11
|
+
Awaitable,
|
|
12
|
+
Callable,
|
|
13
|
+
Dict,
|
|
14
|
+
List,
|
|
15
|
+
Optional,
|
|
16
|
+
Set,
|
|
17
|
+
Text,
|
|
18
|
+
Tuple,
|
|
19
|
+
Union,
|
|
20
|
+
)
|
|
10
21
|
|
|
11
22
|
import structlog
|
|
12
23
|
from jsonschema import ValidationError, validate
|
|
@@ -76,35 +87,45 @@ class Conversation:
|
|
|
76
87
|
|
|
77
88
|
@staticmethod
|
|
78
89
|
def get_metadata(activity: Dict[Text, Any]) -> Optional[Dict[Text, Any]]:
|
|
79
|
-
"""Get metadata from the activity.
|
|
80
|
-
|
|
90
|
+
"""Get metadata from the activity.
|
|
91
|
+
|
|
92
|
+
ONLY used for activities NOT for events (see _handle_event)."""
|
|
93
|
+
return activity.get("parameters")
|
|
81
94
|
|
|
82
95
|
@staticmethod
|
|
83
|
-
def _handle_event(event: Dict[Text, Any]) -> Text:
|
|
84
|
-
"""Handle
|
|
96
|
+
def _handle_event(event: Dict[Text, Any]) -> Tuple[Text, Dict[Text, Any]]:
|
|
97
|
+
"""Handle events and return a tuple of text and metadata.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
event: The event to handle.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Tuple of text and metadata.
|
|
104
|
+
text is either /session_start or /vaig_event_<event_name>
|
|
105
|
+
metadata is a dictionary with the event parameters.
|
|
106
|
+
"""
|
|
85
107
|
structlogger.debug("audiocodes.handle.event", event_payload=event)
|
|
86
108
|
if "name" not in event:
|
|
87
109
|
structlogger.warning(
|
|
88
110
|
"audiocodes.handle.event.no_name_key", event_payload=event
|
|
89
111
|
)
|
|
90
|
-
return ""
|
|
112
|
+
return "", {}
|
|
91
113
|
|
|
92
114
|
if event["name"] == EVENT_START:
|
|
93
115
|
text = f"{INTENT_MESSAGE_PREFIX}{USER_INTENT_SESSION_START}"
|
|
116
|
+
metadata = asdict(map_call_params(event.get("parameters", {})))
|
|
94
117
|
elif event["name"] == EVENT_DTMF:
|
|
95
118
|
text = f"{INTENT_MESSAGE_PREFIX}vaig_event_DTMF"
|
|
96
|
-
|
|
97
|
-
text += json.dumps(event_params)
|
|
119
|
+
metadata = {"value": event["value"]}
|
|
98
120
|
else:
|
|
99
121
|
# handle other events described by Audiocodes
|
|
100
122
|
# https://techdocs.audiocodes.com/voice-ai-connect/#VAIG_Combined/inactivity-detection.htm?TocPath=Bot%2520integration%257CReceiving%2520notifications%257C_____3
|
|
101
123
|
text = f"{INTENT_MESSAGE_PREFIX}vaig_event_{event['name']}"
|
|
102
|
-
|
|
124
|
+
metadata = {**event.get("parameters", {})}
|
|
103
125
|
if "value" in event:
|
|
104
|
-
|
|
105
|
-
text += json.dumps(event_params)
|
|
126
|
+
metadata["value"] = event["value"]
|
|
106
127
|
|
|
107
|
-
return text
|
|
128
|
+
return text, metadata
|
|
108
129
|
|
|
109
130
|
def is_active_conversation(self, now: datetime, delta: timedelta) -> bool:
|
|
110
131
|
"""Check if the conversation is active."""
|
|
@@ -139,21 +160,29 @@ class Conversation:
|
|
|
139
160
|
structlogger.warning(
|
|
140
161
|
"audiocodes.handle.activities.duplicate_activity",
|
|
141
162
|
activity_id=activity[ACTIVITY_ID_KEY],
|
|
163
|
+
event_info=(
|
|
164
|
+
"Audiocodes might send duplicate activities if the bot has not "
|
|
165
|
+
"responded to the previous one or responded too late. Please "
|
|
166
|
+
"consider enabling the `use_websocket` option to use"
|
|
167
|
+
" Audiocodes Asynchronous API."
|
|
168
|
+
),
|
|
142
169
|
)
|
|
143
170
|
continue
|
|
144
171
|
self.activity_ids.append(activity[ACTIVITY_ID_KEY])
|
|
145
172
|
if activity["type"] == ACTIVITY_MESSAGE:
|
|
146
173
|
text = activity["text"]
|
|
174
|
+
metadata = self.get_metadata(activity)
|
|
147
175
|
elif activity["type"] == ACTIVITY_EVENT:
|
|
148
|
-
text = self._handle_event(activity)
|
|
176
|
+
text, metadata = self._handle_event(activity)
|
|
149
177
|
else:
|
|
150
178
|
structlogger.warning(
|
|
151
179
|
"audiocodes.handle.activities.unknown_activity_type",
|
|
152
180
|
activity=activity,
|
|
153
181
|
)
|
|
182
|
+
continue
|
|
183
|
+
|
|
154
184
|
if not text:
|
|
155
185
|
continue
|
|
156
|
-
metadata = self.get_metadata(activity)
|
|
157
186
|
user_msg = UserMessage(
|
|
158
187
|
text=text,
|
|
159
188
|
input_channel=input_channel_name,
|
|
@@ -392,30 +421,41 @@ class AudiocodesInput(InputChannel):
|
|
|
392
421
|
"audiocodes.on_activities.no_conversation", request=request.json
|
|
393
422
|
)
|
|
394
423
|
return response.json({})
|
|
395
|
-
|
|
424
|
+
|
|
425
|
+
if self.use_websocket:
|
|
426
|
+
# send an empty response for this request
|
|
427
|
+
# activities are processed in the background
|
|
428
|
+
# chat response is sent via the websocket
|
|
396
429
|
ac_output: Union[WebsocketOutput, AudiocodesOutput] = WebsocketOutput(
|
|
397
430
|
conversation.ws, conversation_id
|
|
398
431
|
)
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
432
|
+
self._create_task(
|
|
433
|
+
conversation_id,
|
|
434
|
+
conversation.handle_activities(
|
|
435
|
+
request.json,
|
|
436
|
+
input_channel_name=self.name(),
|
|
437
|
+
output_channel=ac_output,
|
|
438
|
+
on_new_message=on_new_message,
|
|
439
|
+
),
|
|
440
|
+
)
|
|
441
|
+
return response.json({})
|
|
442
|
+
|
|
443
|
+
# without websockets, this becomes a blocking call
|
|
444
|
+
# and the response is sent back to the Audiocodes server
|
|
445
|
+
# after the activities are processed
|
|
446
|
+
ac_output = AudiocodesOutput()
|
|
447
|
+
await conversation.handle_activities(
|
|
448
|
+
request.json,
|
|
449
|
+
input_channel_name=self.name(),
|
|
450
|
+
output_channel=ac_output,
|
|
451
|
+
on_new_message=on_new_message,
|
|
452
|
+
)
|
|
453
|
+
return response.json(
|
|
454
|
+
{
|
|
404
455
|
"conversation": conversation_id,
|
|
405
456
|
"activities": ac_output.messages,
|
|
406
457
|
}
|
|
407
|
-
|
|
408
|
-
# start a background task to handle activities
|
|
409
|
-
self._create_task(
|
|
410
|
-
conversation_id,
|
|
411
|
-
conversation.handle_activities(
|
|
412
|
-
request.json,
|
|
413
|
-
input_channel_name=self.name(),
|
|
414
|
-
output_channel=ac_output,
|
|
415
|
-
on_new_message=on_new_message,
|
|
416
|
-
),
|
|
417
458
|
)
|
|
418
|
-
return response.json(response_json)
|
|
419
459
|
|
|
420
460
|
@ac_webhook.route(
|
|
421
461
|
"/conversation/<conversation_id>/disconnect", methods=["POST"]
|
rasa/core/persistor.py
CHANGED
|
@@ -121,10 +121,12 @@ def get_persistor(storage: StorageType) -> Optional[Persistor]:
|
|
|
121
121
|
class Persistor(abc.ABC):
|
|
122
122
|
"""Store models in cloud and fetch them when needed."""
|
|
123
123
|
|
|
124
|
-
def persist(self, trained_model: str) -> None:
|
|
124
|
+
def persist(self, trained_model: str, remote_root_only: bool = False) -> None:
|
|
125
125
|
"""Uploads a trained model persisted in the `target_dir` to cloud storage."""
|
|
126
126
|
absolute_file_key = self._create_file_key(trained_model)
|
|
127
|
-
file_key =
|
|
127
|
+
file_key = (
|
|
128
|
+
Path(absolute_file_key).name if remote_root_only else absolute_file_key
|
|
129
|
+
)
|
|
128
130
|
self._persist_tar(file_key, trained_model)
|
|
129
131
|
|
|
130
132
|
def retrieve(self, model_name: Text, target_path: Text) -> Text:
|
|
@@ -143,30 +145,32 @@ class Persistor(abc.ABC):
|
|
|
143
145
|
# ensure backward compatibility
|
|
144
146
|
tar_name = self._tar_name(model_name)
|
|
145
147
|
tar_name = self._create_file_key(tar_name)
|
|
146
|
-
|
|
147
|
-
self._retrieve_tar(target_filename)
|
|
148
|
-
self._copy(os.path.basename(tar_name), target_path)
|
|
148
|
+
self._retrieve_tar(tar_name, target_path)
|
|
149
149
|
|
|
150
150
|
if os.path.isdir(target_path):
|
|
151
151
|
return os.path.join(target_path, model_name)
|
|
152
152
|
|
|
153
153
|
return target_path
|
|
154
154
|
|
|
155
|
-
def size_of_persisted_model(
|
|
155
|
+
def size_of_persisted_model(
|
|
156
|
+
self, model_name: Text, target_path: Optional[str] = None
|
|
157
|
+
) -> int:
|
|
156
158
|
"""Returns the size of the model that has been persisted to cloud storage.
|
|
157
159
|
|
|
158
160
|
Args:
|
|
159
161
|
model_name: The name of the model to retrieve.
|
|
162
|
+
target_path: The path to which the model should be saved.
|
|
160
163
|
"""
|
|
161
164
|
tar_name = model_name
|
|
162
165
|
if not model_name.endswith(MODEL_ARCHIVE_EXTENSION):
|
|
163
166
|
# ensure backward compatibility
|
|
164
167
|
tar_name = self._tar_name(model_name)
|
|
165
168
|
tar_name = self._create_file_key(tar_name)
|
|
166
|
-
|
|
167
|
-
return self._retrieve_tar_size(target_filename)
|
|
169
|
+
return self._retrieve_tar_size(tar_name, target_path)
|
|
168
170
|
|
|
169
|
-
def _retrieve_tar_size(
|
|
171
|
+
def _retrieve_tar_size(
|
|
172
|
+
self, filename: Text, target_path: Optional[str] = None
|
|
173
|
+
) -> int:
|
|
170
174
|
"""Returns the size of the model that has been persisted to cloud storage."""
|
|
171
175
|
structlogger.warning(
|
|
172
176
|
"persistor.retrieve_tar_size.not_implemented",
|
|
@@ -179,11 +183,11 @@ class Persistor(abc.ABC):
|
|
|
179
183
|
"size directly from the cloud storage."
|
|
180
184
|
),
|
|
181
185
|
)
|
|
182
|
-
self._retrieve_tar(filename)
|
|
186
|
+
self._retrieve_tar(filename, target_path)
|
|
183
187
|
return os.path.getsize(os.path.basename(filename))
|
|
184
188
|
|
|
185
189
|
@abc.abstractmethod
|
|
186
|
-
def _retrieve_tar(self, filename:
|
|
190
|
+
def _retrieve_tar(self, filename: str, target_path: Optional[str] = None) -> None:
|
|
187
191
|
"""Downloads a model previously persisted to cloud storage."""
|
|
188
192
|
raise NotImplementedError
|
|
189
193
|
|
|
@@ -302,7 +306,9 @@ class AWSPersistor(Persistor):
|
|
|
302
306
|
with open(tar_path, "rb") as f:
|
|
303
307
|
self.s3.Object(self.bucket_name, file_key).put(Body=f)
|
|
304
308
|
|
|
305
|
-
def _retrieve_tar_size(
|
|
309
|
+
def _retrieve_tar_size(
|
|
310
|
+
self, model_path: Text, target_path: Optional[str] = None
|
|
311
|
+
) -> int:
|
|
306
312
|
"""Returns the size of the model that has been persisted to s3."""
|
|
307
313
|
try:
|
|
308
314
|
obj = self.s3.Object(self.bucket_name, model_path)
|
|
@@ -310,7 +316,9 @@ class AWSPersistor(Persistor):
|
|
|
310
316
|
except Exception:
|
|
311
317
|
raise ModelNotFound()
|
|
312
318
|
|
|
313
|
-
def _retrieve_tar(
|
|
319
|
+
def _retrieve_tar(
|
|
320
|
+
self, target_filename: str, target_path: Optional[str] = None
|
|
321
|
+
) -> None:
|
|
314
322
|
"""Downloads a model that has previously been persisted to s3."""
|
|
315
323
|
from botocore import exceptions
|
|
316
324
|
|
|
@@ -320,8 +328,14 @@ class AWSPersistor(Persistor):
|
|
|
320
328
|
f"in the bucket."
|
|
321
329
|
)
|
|
322
330
|
|
|
331
|
+
tar_name = (
|
|
332
|
+
os.path.join(target_path, os.path.basename(target_filename))
|
|
333
|
+
if target_path
|
|
334
|
+
else os.path.basename(target_filename)
|
|
335
|
+
)
|
|
336
|
+
|
|
323
337
|
try:
|
|
324
|
-
with open(
|
|
338
|
+
with open(tar_name, "wb") as f:
|
|
325
339
|
self.bucket.download_fileobj(target_filename, f)
|
|
326
340
|
|
|
327
341
|
structlogger.debug(
|
|
@@ -425,7 +439,9 @@ class GCSPersistor(Persistor):
|
|
|
425
439
|
blob = self.bucket.blob(file_key)
|
|
426
440
|
blob.upload_from_filename(tar_path)
|
|
427
441
|
|
|
428
|
-
def _retrieve_tar_size(
|
|
442
|
+
def _retrieve_tar_size(
|
|
443
|
+
self, target_filename: Text, target_path: Optional[str] = None
|
|
444
|
+
) -> int:
|
|
429
445
|
"""Returns the size of the model that has been persisted to GCS."""
|
|
430
446
|
try:
|
|
431
447
|
blob = self.bucket.blob(target_filename)
|
|
@@ -433,13 +449,22 @@ class GCSPersistor(Persistor):
|
|
|
433
449
|
except Exception:
|
|
434
450
|
raise ModelNotFound()
|
|
435
451
|
|
|
436
|
-
def _retrieve_tar(
|
|
452
|
+
def _retrieve_tar(
|
|
453
|
+
self, target_filename: str, target_path: Optional[str] = None
|
|
454
|
+
) -> None:
|
|
437
455
|
"""Downloads a model that has previously been persisted to GCS."""
|
|
438
456
|
from google.api_core import exceptions
|
|
439
457
|
|
|
440
458
|
blob = self.bucket.blob(target_filename)
|
|
459
|
+
|
|
460
|
+
destination = (
|
|
461
|
+
os.path.join(target_path, os.path.basename(target_filename))
|
|
462
|
+
if target_path
|
|
463
|
+
else target_filename
|
|
464
|
+
)
|
|
465
|
+
|
|
441
466
|
try:
|
|
442
|
-
blob.download_to_filename(
|
|
467
|
+
blob.download_to_filename(destination)
|
|
443
468
|
|
|
444
469
|
structlogger.debug(
|
|
445
470
|
"gcs_persistor.retrieve_tar.object_found", object_key=target_filename
|
|
@@ -500,7 +525,9 @@ class AzurePersistor(Persistor):
|
|
|
500
525
|
with open(tar_path, "rb") as data:
|
|
501
526
|
self._container_client().upload_blob(name=file_key, data=data)
|
|
502
527
|
|
|
503
|
-
def _retrieve_tar_size(
|
|
528
|
+
def _retrieve_tar_size(
|
|
529
|
+
self, target_filename: Text, target_path: Optional[str] = None
|
|
530
|
+
) -> int:
|
|
504
531
|
"""Returns the size of the model that has been persisted to Azure."""
|
|
505
532
|
try:
|
|
506
533
|
blob_client = self._container_client().get_blob_client(target_filename)
|
|
@@ -509,12 +536,20 @@ class AzurePersistor(Persistor):
|
|
|
509
536
|
except Exception:
|
|
510
537
|
raise ModelNotFound()
|
|
511
538
|
|
|
512
|
-
def _retrieve_tar(
|
|
539
|
+
def _retrieve_tar(
|
|
540
|
+
self, target_filename: Text, target_path: Optional[str] = None
|
|
541
|
+
) -> None:
|
|
513
542
|
"""Downloads a model that has previously been persisted to Azure."""
|
|
514
543
|
from azure.core.exceptions import AzureError
|
|
515
544
|
|
|
545
|
+
destination = (
|
|
546
|
+
os.path.join(target_path, os.path.basename(target_filename))
|
|
547
|
+
if target_path
|
|
548
|
+
else target_filename
|
|
549
|
+
)
|
|
550
|
+
|
|
516
551
|
try:
|
|
517
|
-
with open(
|
|
552
|
+
with open(destination, "wb") as model_file:
|
|
518
553
|
blob_client = self._container_client().get_blob_client(target_filename)
|
|
519
554
|
download_stream = blob_client.download_blob()
|
|
520
555
|
model_file.write(download_stream.readall())
|
|
@@ -715,9 +715,7 @@ class IntentlessPolicy(LLMHealthCheckMixin, EmbeddingsHealthCheckMixin, Policy):
|
|
|
715
715
|
final_response_examples.append(resp)
|
|
716
716
|
|
|
717
717
|
llm_response = await self.generate_answer(
|
|
718
|
-
final_response_examples,
|
|
719
|
-
conversation_samples,
|
|
720
|
-
history,
|
|
718
|
+
final_response_examples, conversation_samples, history
|
|
721
719
|
)
|
|
722
720
|
if not llm_response:
|
|
723
721
|
structlogger.debug("intentless_policy.prediction.skip_llm_fail")
|
|
@@ -166,6 +166,7 @@ class LLMBasedRouter(LLMHealthCheckMixin, GraphComponent):
|
|
|
166
166
|
**kwargs: Any,
|
|
167
167
|
) -> "LLMBasedRouter":
|
|
168
168
|
"""Loads trained component (see parent class for full docstring)."""
|
|
169
|
+
|
|
169
170
|
# Perform health check on the resolved LLM client config
|
|
170
171
|
llm_config = resolve_model_client_config(config.get(LLM_CONFIG_KEY, {}))
|
|
171
172
|
cls.perform_llm_health_check(
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from asyncio import Lock
|
|
3
2
|
from functools import lru_cache
|
|
4
3
|
from typing import Any, Dict, List, Optional, Set, Text, Tuple, Union
|
|
5
|
-
from uuid import UUID, uuid4
|
|
6
4
|
|
|
7
5
|
import structlog
|
|
8
6
|
from jinja2 import Environment, Template, select_autoescape
|
|
@@ -91,9 +89,6 @@ class LLMBasedCommandGenerator(
|
|
|
91
89
|
else:
|
|
92
90
|
self.flow_retrieval = None
|
|
93
91
|
|
|
94
|
-
self.sender_id_to_session_id_mapping: Dict[str, UUID] = {}
|
|
95
|
-
self._lock = Lock()
|
|
96
|
-
|
|
97
92
|
### Abstract methods
|
|
98
93
|
@staticmethod
|
|
99
94
|
@abstractmethod
|
|
@@ -230,7 +225,8 @@ class LLMBasedCommandGenerator(
|
|
|
230
225
|
|
|
231
226
|
@lru_cache
|
|
232
227
|
def compile_template(self, template: str) -> Template:
|
|
233
|
-
"""
|
|
228
|
+
"""
|
|
229
|
+
Compile the prompt template and register custom filters.
|
|
234
230
|
Compiling the template is an expensive operation,
|
|
235
231
|
so we cache the result.
|
|
236
232
|
"""
|
|
@@ -332,9 +328,7 @@ class LLMBasedCommandGenerator(
|
|
|
332
328
|
|
|
333
329
|
@measure_llm_latency
|
|
334
330
|
async def invoke_llm(
|
|
335
|
-
self,
|
|
336
|
-
prompt: Union[List[dict], List[str], str],
|
|
337
|
-
metadata: Optional[Dict[str, Any]] = None,
|
|
331
|
+
self, prompt: Union[List[dict], List[str], str]
|
|
338
332
|
) -> Optional[LLMResponse]:
|
|
339
333
|
"""Use LLM to generate a response.
|
|
340
334
|
|
|
@@ -347,7 +341,6 @@ class LLMBasedCommandGenerator(
|
|
|
347
341
|
- a list of messages. Each message is a string and will be formatted
|
|
348
342
|
as a user message.
|
|
349
343
|
- a single message as a string which will be formatted as user message.
|
|
350
|
-
metadata: Optional metadata to be passed to the LLM call.
|
|
351
344
|
|
|
352
345
|
Returns:
|
|
353
346
|
An LLMResponse object.
|
|
@@ -359,7 +352,7 @@ class LLMBasedCommandGenerator(
|
|
|
359
352
|
self.config.get(LLM_CONFIG_KEY), self.get_default_llm_config()
|
|
360
353
|
)
|
|
361
354
|
try:
|
|
362
|
-
return await llm.acompletion(prompt
|
|
355
|
+
return await llm.acompletion(prompt)
|
|
363
356
|
except Exception as e:
|
|
364
357
|
# unfortunately, langchain does not wrap LLM exceptions which means
|
|
365
358
|
# we have to catch all exceptions here
|
|
@@ -662,7 +655,3 @@ class LLMBasedCommandGenerator(
|
|
|
662
655
|
def get_default_llm_config() -> Dict[str, Any]:
|
|
663
656
|
"""Get the default LLM config for the command generator."""
|
|
664
657
|
return DEFAULT_LLM_CONFIG
|
|
665
|
-
|
|
666
|
-
async def _get_or_create_session_id(self, sender_id: str) -> UUID:
|
|
667
|
-
async with self._lock:
|
|
668
|
-
return self.sender_id_to_session_id_mapping.setdefault(sender_id, uuid4())
|
|
@@ -55,9 +55,7 @@ class LLMCommandGenerator(SingleStepLLMCommandGenerator):
|
|
|
55
55
|
)
|
|
56
56
|
|
|
57
57
|
async def invoke_llm(
|
|
58
|
-
self,
|
|
59
|
-
prompt: Union[List[dict], List[str], str],
|
|
60
|
-
metadata: Optional[Dict[str, Any]] = None,
|
|
58
|
+
self, prompt: Union[List[dict], List[str], str]
|
|
61
59
|
) -> Optional[LLMResponse]:
|
|
62
60
|
try:
|
|
63
61
|
return await super().invoke_llm(prompt)
|
|
@@ -42,9 +42,6 @@ from rasa.engine.storage.resource import Resource
|
|
|
42
42
|
from rasa.engine.storage.storage import ModelStorage
|
|
43
43
|
from rasa.shared.constants import (
|
|
44
44
|
EMBEDDINGS_CONFIG_KEY,
|
|
45
|
-
LANGFUSE_CUSTOM_METADATA_DICT,
|
|
46
|
-
LANGFUSE_METADATA_SESSION_ID,
|
|
47
|
-
LANGFUSE_TAGS,
|
|
48
45
|
RASA_PATTERN_CANNOT_HANDLE_NOT_SUPPORTED,
|
|
49
46
|
ROUTE_TO_CALM_SLOT,
|
|
50
47
|
)
|
|
@@ -110,7 +107,7 @@ structlogger = structlog.get_logger()
|
|
|
110
107
|
)
|
|
111
108
|
@deprecated(
|
|
112
109
|
reason=(
|
|
113
|
-
"The MultiStepLLMCommandGenerator is
|
|
110
|
+
"The MultiStepLLMCommandGenerator is deprecated and will be removed in "
|
|
114
111
|
"Rasa `4.0.0`."
|
|
115
112
|
)
|
|
116
113
|
)
|
|
@@ -495,20 +492,7 @@ class MultiStepLLMCommandGenerator(LLMBasedCommandGenerator):
|
|
|
495
492
|
prompt=prompt,
|
|
496
493
|
)
|
|
497
494
|
|
|
498
|
-
|
|
499
|
-
session_id = str(await self._get_or_create_session_id(tracker.sender_id))
|
|
500
|
-
else:
|
|
501
|
-
session_id = "unknown"
|
|
502
|
-
metadata = {
|
|
503
|
-
LANGFUSE_METADATA_SESSION_ID: session_id,
|
|
504
|
-
LANGFUSE_CUSTOM_METADATA_DICT: {
|
|
505
|
-
"component": self.__class__.__name__,
|
|
506
|
-
"function": "_predict_commands_for_active_flow",
|
|
507
|
-
},
|
|
508
|
-
LANGFUSE_TAGS: [self.__class__.__name__],
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
response = await self.invoke_llm(prompt, metadata)
|
|
495
|
+
response = await self.invoke_llm(prompt)
|
|
512
496
|
llm_response = LLMResponse.ensure_llm_response(response)
|
|
513
497
|
actions = None
|
|
514
498
|
if llm_response and llm_response.choices:
|
|
@@ -562,20 +546,8 @@ class MultiStepLLMCommandGenerator(LLMBasedCommandGenerator):
|
|
|
562
546
|
".prompt_rendered",
|
|
563
547
|
prompt=prompt,
|
|
564
548
|
)
|
|
565
|
-
if tracker:
|
|
566
|
-
session_id = str(await self._get_or_create_session_id(tracker.sender_id))
|
|
567
|
-
else:
|
|
568
|
-
session_id = "unknown"
|
|
569
|
-
metadata = {
|
|
570
|
-
LANGFUSE_METADATA_SESSION_ID: session_id,
|
|
571
|
-
LANGFUSE_CUSTOM_METADATA_DICT: {
|
|
572
|
-
"component": self.__class__.__name__,
|
|
573
|
-
"function": "_predict_commands_for_handling_flows",
|
|
574
|
-
},
|
|
575
|
-
LANGFUSE_TAGS: [self.__class__.__name__],
|
|
576
|
-
}
|
|
577
549
|
|
|
578
|
-
response = await self.invoke_llm(prompt
|
|
550
|
+
response = await self.invoke_llm(prompt)
|
|
579
551
|
llm_response = LLMResponse.ensure_llm_response(response)
|
|
580
552
|
actions = None
|
|
581
553
|
if llm_response and llm_response.choices:
|
|
@@ -664,20 +636,8 @@ class MultiStepLLMCommandGenerator(LLMBasedCommandGenerator):
|
|
|
664
636
|
flow=newly_started_flow.id,
|
|
665
637
|
prompt=prompt,
|
|
666
638
|
)
|
|
667
|
-
if tracker:
|
|
668
|
-
session_id = str(await self._get_or_create_session_id(tracker.sender_id))
|
|
669
|
-
else:
|
|
670
|
-
session_id = "unknown"
|
|
671
|
-
metadata = {
|
|
672
|
-
LANGFUSE_METADATA_SESSION_ID: session_id,
|
|
673
|
-
LANGFUSE_CUSTOM_METADATA_DICT: {
|
|
674
|
-
"component": self.__class__.__name__,
|
|
675
|
-
"function": "_predict_commands_for_newly_started_flow",
|
|
676
|
-
},
|
|
677
|
-
LANGFUSE_TAGS: [self.__class__.__name__],
|
|
678
|
-
}
|
|
679
639
|
|
|
680
|
-
response = await self.invoke_llm(prompt
|
|
640
|
+
response = await self.invoke_llm(prompt)
|
|
681
641
|
llm_response = LLMResponse.ensure_llm_response(response)
|
|
682
642
|
actions = None
|
|
683
643
|
if llm_response and llm_response.choices:
|
|
@@ -47,9 +47,6 @@ from rasa.shared.constants import (
|
|
|
47
47
|
AWS_BEDROCK_PROVIDER,
|
|
48
48
|
AZURE_OPENAI_PROVIDER,
|
|
49
49
|
EMBEDDINGS_CONFIG_KEY,
|
|
50
|
-
LANGFUSE_CUSTOM_METADATA_DICT,
|
|
51
|
-
LANGFUSE_METADATA_SESSION_ID,
|
|
52
|
-
LANGFUSE_TAGS,
|
|
53
50
|
MAX_TOKENS_CONFIG_KEY,
|
|
54
51
|
PROMPT_TEMPLATE_CONFIG_KEY,
|
|
55
52
|
ROUTE_TO_CALM_SLOT,
|
|
@@ -369,17 +366,7 @@ class CompactLLMCommandGenerator(LLMBasedCommandGenerator):
|
|
|
369
366
|
prompt=flow_prompt,
|
|
370
367
|
)
|
|
371
368
|
|
|
372
|
-
|
|
373
|
-
session_id = str(await self._get_or_create_session_id(tracker.sender_id))
|
|
374
|
-
else:
|
|
375
|
-
session_id = "unknown"
|
|
376
|
-
metadata = {
|
|
377
|
-
LANGFUSE_METADATA_SESSION_ID: session_id,
|
|
378
|
-
LANGFUSE_CUSTOM_METADATA_DICT: {"component": self.__class__.__name__},
|
|
379
|
-
LANGFUSE_TAGS: [self.__class__.__name__],
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
response = await self.invoke_llm(flow_prompt, metadata)
|
|
369
|
+
response = await self.invoke_llm(flow_prompt)
|
|
383
370
|
llm_response = LLMResponse.ensure_llm_response(response)
|
|
384
371
|
# The check for 'None' maintains compatibility with older versions
|
|
385
372
|
# of LLMCommandGenerator. In previous implementations, 'invoke_llm'
|