rasa-pro 3.13.0rc1__py3-none-any.whl → 3.13.0rc2__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 (41) hide show
  1. rasa/cli/studio/link.py +0 -16
  2. rasa/cli/studio/train.py +1 -4
  3. rasa/cli/studio/upload.py +1 -1
  4. rasa/core/agent.py +6 -0
  5. rasa/core/channels/__init__.py +1 -0
  6. rasa/core/channels/voice_ready/jambonz.py +5 -6
  7. rasa/core/channels/voice_ready/twilio_voice.py +13 -12
  8. rasa/core/channels/voice_ready/utils.py +22 -0
  9. rasa/core/channels/voice_stream/audiocodes.py +4 -10
  10. rasa/core/channels/voice_stream/genesys.py +35 -16
  11. rasa/core/channels/voice_stream/jambonz.py +69 -3
  12. rasa/core/channels/voice_stream/twilio_media_streams.py +5 -7
  13. rasa/core/channels/voice_stream/voice_channel.py +39 -10
  14. rasa/core/policies/enterprise_search_policy.py +38 -2
  15. rasa/core/processor.py +6 -0
  16. rasa/dialogue_understanding/coexistence/llm_based_router.py +11 -0
  17. rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +3 -2
  18. rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +9 -0
  19. rasa/dialogue_understanding/processor/command_processor.py +3 -3
  20. rasa/e2e_test/constants.py +1 -1
  21. rasa/llm_fine_tuning/paraphrasing/conversation_rephraser.py +1 -1
  22. rasa/model_manager/runner_service.py +20 -4
  23. rasa/model_manager/trainer_service.py +6 -0
  24. rasa/privacy/privacy_manager.py +26 -11
  25. rasa/shared/constants.py +2 -0
  26. rasa/shared/utils/llm.py +86 -2
  27. rasa/studio/data_handler.py +27 -13
  28. rasa/studio/download.py +5 -1
  29. rasa/studio/link.py +12 -1
  30. rasa/studio/prompts.py +5 -7
  31. rasa/studio/pull/pull.py +6 -2
  32. rasa/studio/push.py +2 -0
  33. rasa/studio/upload.py +61 -5
  34. rasa/studio/utils.py +33 -0
  35. rasa/tracing/instrumentation/attribute_extractors.py +1 -1
  36. rasa/version.py +1 -1
  37. {rasa_pro-3.13.0rc1.dist-info → rasa_pro-3.13.0rc2.dist-info}/METADATA +1 -1
  38. {rasa_pro-3.13.0rc1.dist-info → rasa_pro-3.13.0rc2.dist-info}/RECORD +41 -40
  39. {rasa_pro-3.13.0rc1.dist-info → rasa_pro-3.13.0rc2.dist-info}/NOTICE +0 -0
  40. {rasa_pro-3.13.0rc1.dist-info → rasa_pro-3.13.0rc2.dist-info}/WHEEL +0 -0
  41. {rasa_pro-3.13.0rc1.dist-info → rasa_pro-3.13.0rc2.dist-info}/entry_points.txt +0 -0
rasa/cli/studio/link.py CHANGED
@@ -2,15 +2,8 @@ import argparse
2
2
  from typing import List, Text
3
3
 
4
4
  from rasa.cli import SubParsersAction
5
- from rasa.cli.arguments.default_arguments import (
6
- add_config_param,
7
- add_data_param,
8
- add_domain_param,
9
- add_endpoint_param,
10
- )
11
5
  from rasa.shared.constants import (
12
6
  DEFAULT_DOMAIN_PATH,
13
- DEFAULT_ENDPOINTS_PATH,
14
7
  )
15
8
  from rasa.studio.link import handle_link
16
9
 
@@ -40,13 +33,4 @@ def add_subparser(
40
33
  type=str,
41
34
  help="Name of the assistant in Rasa Studio.",
42
35
  )
43
-
44
- add_domain_param(link_parser, domain)
45
- add_data_param(link_parser)
46
- add_config_param(link_parser)
47
- add_endpoint_param(
48
- link_parser,
49
- "Configuration file for the model endpoints.",
50
- default=DEFAULT_ENDPOINTS_PATH,
51
- )
52
36
  link_parser.set_defaults(func=handle_link)
rasa/cli/studio/train.py CHANGED
@@ -20,10 +20,7 @@ def add_subparser(
20
20
  parents=parents,
21
21
  conflict_handler="resolve",
22
22
  formatter_class=argparse.ArgumentDefaultsHelpFormatter,
23
- help=(
24
- "Trains a Rasa model using Rasa Studio "
25
- "data and your NLU data and stories."
26
- ),
23
+ help="Trains a Rasa model using Rasa Studio data.",
27
24
  )
28
25
 
29
26
  train_parser.set_defaults(func=handle_train)
rasa/cli/studio/upload.py CHANGED
@@ -25,7 +25,7 @@ def add_subparser(
25
25
  parents=parents,
26
26
  conflict_handler="resolve",
27
27
  formatter_class=argparse.ArgumentDefaultsHelpFormatter,
28
- help="Upload primitives to Rasa Studio. Only works with NLU assistants.",
28
+ help="Upload primitives to Rasa Studio.",
29
29
  )
30
30
 
31
31
  upload_parser.set_defaults(func=handle_upload)
rasa/core/agent.py CHANGED
@@ -238,9 +238,15 @@ async def load_agent(
238
238
  if endpoints.nlu:
239
239
  http_interpreter = RasaNLUHttpInterpreter(endpoints.nlu)
240
240
  if endpoints.privacy:
241
+ in_memory_tracker_store = (
242
+ tracker_store
243
+ if isinstance(tracker_store, InMemoryTrackerStore)
244
+ else None
245
+ )
241
246
  privacy_manager = await BackgroundPrivacyManager.create_instance(
242
247
  endpoints=endpoints,
243
248
  event_loop=loop,
249
+ in_memory_tracker_store=in_memory_tracker_store,
244
250
  )
245
251
  track_privacy_enabled(privacy_manager.config, broker)
246
252
 
@@ -6,6 +6,7 @@ from rasa.core.channels.channel import ( # noqa: F401
6
6
  OutputChannel,
7
7
  UserMessage,
8
8
  CollectingOutputChannel,
9
+ requires_basic_auth,
9
10
  )
10
11
 
11
12
  # this prevents IDE's from optimizing the imports - we need to import the
@@ -17,7 +17,10 @@ from rasa.core.channels.voice_ready.jambonz_protocol import (
17
17
  send_ws_text_message,
18
18
  websocket_message_handler,
19
19
  )
20
- from rasa.core.channels.voice_ready.utils import validate_voice_license_scope
20
+ from rasa.core.channels.voice_ready.utils import (
21
+ validate_username_password_credentials,
22
+ validate_voice_license_scope,
23
+ )
21
24
  from rasa.shared.exceptions import RasaException
22
25
  from rasa.shared.utils.common import mark_as_beta_feature
23
26
  from rasa.utils.io import remove_emojis
@@ -41,11 +44,7 @@ class JambonzVoiceReadyInput(InputChannel):
41
44
 
42
45
  username = credentials.get("username")
43
46
  password = credentials.get("password")
44
- if (username is None) != (password is None):
45
- raise RasaException(
46
- "In Jambonz channel, either both username and password "
47
- "or neither should be provided. "
48
- )
47
+ validate_username_password_credentials(username, password, "Jambonz")
49
48
 
50
49
  return cls(username, password)
51
50
 
@@ -16,9 +16,12 @@ from rasa.core.channels.channel import (
16
16
  create_auth_requested_response_provider,
17
17
  requires_basic_auth,
18
18
  )
19
- from rasa.core.channels.voice_ready.utils import CallParameters
19
+ from rasa.core.channels.voice_ready.utils import (
20
+ CallParameters,
21
+ validate_username_password_credentials,
22
+ )
20
23
  from rasa.shared.core.events import BotUttered
21
- from rasa.shared.exceptions import InvalidConfigException, RasaException
24
+ from rasa.shared.exceptions import InvalidConfigException
22
25
 
23
26
  logger = structlog.get_logger(__name__)
24
27
 
@@ -127,11 +130,7 @@ class TwilioVoiceInput(InputChannel):
127
130
 
128
131
  username = credentials.get("username")
129
132
  password = credentials.get("password")
130
- if (username is None) != (password is None):
131
- raise RasaException(
132
- "In TwilioVoice channel, either both username and password "
133
- "or neither should be provided. "
134
- )
133
+ validate_username_password_credentials(username, password, "TwilioVoice")
135
134
 
136
135
  return cls(
137
136
  credentials.get(
@@ -180,8 +179,9 @@ class TwilioVoiceInput(InputChannel):
180
179
  if self.assistant_voice not in self.SUPPORTED_VOICES:
181
180
  self._raise_invalid_voice_exception()
182
181
 
183
- if (self.username is None) != (self.password is None):
184
- self._raise_invalid_credentials_exception()
182
+ validate_username_password_credentials(
183
+ self.username, self.password, "TwilioVoice"
184
+ )
185
185
 
186
186
  try:
187
187
  int(self.speech_timeout)
@@ -389,9 +389,10 @@ class TwilioVoiceInput(InputChannel):
389
389
  return voice_response
390
390
 
391
391
  def _raise_invalid_credentials_exception(self) -> None:
392
- raise InvalidConfigException(
393
- "In TwilioVoice channel, either both username and password "
394
- "or neither should be provided. "
392
+ # This method is now redundant since we use the shared validation function
393
+ # but keeping it for backward compatibility if any external code calls it
394
+ validate_username_password_credentials(
395
+ self.username, self.password, "TwilioVoice"
395
396
  )
396
397
 
397
398
 
@@ -3,9 +3,31 @@ from typing import Optional
3
3
 
4
4
  import structlog
5
5
 
6
+ from rasa.shared.exceptions import InvalidConfigException
7
+
6
8
  structlogger = structlog.get_logger()
7
9
 
8
10
 
11
+ def validate_username_password_credentials(
12
+ username: Optional[str], password: Optional[str], channel_name: str
13
+ ) -> None:
14
+ """Validate that username and password are both provided or both None.
15
+
16
+ Args:
17
+ username: The username credential, or None
18
+ password: The password credential, or None
19
+ channel_name: The name of the channel for error message
20
+
21
+ Raises:
22
+ InvalidConfigException: If only one of username/password is provided
23
+ """
24
+ if (not username) != (not password):
25
+ raise InvalidConfigException(
26
+ f"In {channel_name} channel, either both username and password "
27
+ "or neither should be provided."
28
+ )
29
+
30
+
9
31
  def validate_voice_license_scope() -> None:
10
32
  from rasa.utils.licensing import (
11
33
  PRODUCT_AREA,
@@ -121,16 +121,10 @@ class AudiocodesVoiceInputChannel(VoiceInputChannel):
121
121
  def from_credentials(
122
122
  cls,
123
123
  credentials: Optional[Dict[str, Any]],
124
- ) -> VoiceInputChannel:
125
- if not credentials:
126
- raise ValueError("No credentials given for Audiocodes voice channel.")
127
-
128
- return cls(
129
- token=credentials.get("token"),
130
- server_url=credentials["server_url"],
131
- asr_config=credentials["asr"],
132
- tts_config=credentials["tts"],
133
- )
124
+ ) -> "AudiocodesVoiceInputChannel":
125
+ channel = super().from_credentials(credentials)
126
+ channel.token = credentials.get("token") # type: ignore[attr-defined, union-attr]
127
+ return channel # type: ignore[return-value]
134
128
 
135
129
  def channel_bytes_to_rasa_audio_bytes(self, input_bytes: bytes) -> RasaAudioBytes:
136
130
  return RasaAudioBytes(base64.b64decode(input_bytes))
@@ -29,6 +29,7 @@ from rasa.core.channels.voice_stream.voice_channel import (
29
29
  VoiceInputChannel,
30
30
  VoiceOutputChannel,
31
31
  )
32
+ from rasa.shared.exceptions import InvalidConfigException
32
33
 
33
34
  """
34
35
  Genesys throws a rate limit error with too many audio messages.
@@ -91,9 +92,14 @@ class GenesysInputChannel(VoiceInputChannel):
91
92
  return "genesys"
92
93
 
93
94
  def __init__(
94
- self, api_key: Text, client_secret: Optional[Text], *args: Any, **kwargs: Any
95
+ self,
96
+ server_url: str,
97
+ asr_config: Dict,
98
+ tts_config: Dict,
99
+ api_key: Optional[Text] = None,
100
+ client_secret: Optional[Text] = None,
95
101
  ) -> None:
96
- super().__init__(*args, **kwargs)
102
+ super().__init__(server_url, asr_config, tts_config)
97
103
  self.api_key = api_key
98
104
  self.client_secret = client_secret
99
105
 
@@ -101,20 +107,33 @@ class GenesysInputChannel(VoiceInputChannel):
101
107
  def from_credentials(
102
108
  cls,
103
109
  credentials: Optional[Dict[str, Any]],
104
- ) -> VoiceInputChannel:
105
- if not credentials:
106
- raise ValueError("No credentials given for Genesys voice channel.")
107
-
108
- if not credentials.get("api_key"):
109
- raise ValueError("No API key given for Genesys voice channel (api_key).")
110
-
111
- return cls(
112
- api_key=credentials["api_key"],
113
- client_secret=credentials.get("client_secret"),
114
- server_url=credentials["server_url"],
115
- asr_config=credentials["asr"],
116
- tts_config=credentials["tts"],
117
- )
110
+ ) -> "GenesysInputChannel":
111
+ """Create a channel from credentials dictionary.
112
+
113
+ Args:
114
+ credentials: Dictionary containing the required credentials:
115
+ - server_url: URL where the server is hosted
116
+ - asr: ASR engine configuration
117
+ - tts: TTS engine configuration
118
+ - api_key: Required API key for Genesys authentication
119
+ - client_secret: Optional client secret for signature verification
120
+
121
+ Returns:
122
+ GenesysInputChannel instance
123
+ """
124
+ channel = super().from_credentials(credentials)
125
+
126
+ # Check required Genesys-specific credentials
127
+ if not credentials.get("api_key"): # type: ignore[union-attr]
128
+ raise InvalidConfigException(
129
+ "No API key given for Genesys voice channel (api_key)."
130
+ )
131
+
132
+ # Update channel with Genesys-specific credentials
133
+ channel.api_key = credentials["api_key"] # type: ignore[index,attr-defined]
134
+ channel.client_secret = credentials.get("client_secret") # type: ignore[union-attr,attr-defined]
135
+
136
+ return channel # type: ignore[return-value]
118
137
 
119
138
  def _ensure_channel_data_initialized(self) -> None:
120
139
  """Initialize Genesys-specific channel data if not already present.
@@ -12,8 +12,11 @@ from sanic import ( # type: ignore[attr-defined]
12
12
  response,
13
13
  )
14
14
 
15
- from rasa.core.channels import UserMessage
16
- from rasa.core.channels.voice_ready.utils import CallParameters
15
+ from rasa.core.channels import UserMessage, requires_basic_auth
16
+ from rasa.core.channels.voice_ready.utils import (
17
+ CallParameters,
18
+ validate_username_password_credentials,
19
+ )
17
20
  from rasa.core.channels.voice_stream.audio_bytes import RasaAudioBytes
18
21
  from rasa.core.channels.voice_stream.call_state import call_state
19
22
  from rasa.core.channels.voice_stream.tts.tts_engine import TTSEngine
@@ -28,6 +31,8 @@ from rasa.core.channels.voice_stream.voice_channel import (
28
31
 
29
32
  logger = structlog.get_logger()
30
33
 
34
+ JAMBONZ_STREAMS_WEBSOCKET_PATH = "webhooks/jambonz_streams/websocket"
35
+
31
36
 
32
37
  def map_call_params(data: Dict[Text, str]) -> CallParameters:
33
38
  """Map the twilio stream parameters to the CallParameters dataclass."""
@@ -68,6 +73,65 @@ class JambonzStreamInputChannel(VoiceInputChannel):
68
73
  def name(cls) -> str:
69
74
  return "jambonz_stream"
70
75
 
76
+ def __init__(
77
+ self,
78
+ server_url: str,
79
+ asr_config: Dict,
80
+ tts_config: Dict,
81
+ username: Optional[Text] = None,
82
+ password: Optional[Text] = None,
83
+ ) -> None:
84
+ """Initialize the channel.
85
+
86
+ Args:
87
+ username: Optional username for basic auth
88
+ password: Optional password for basic auth
89
+ """
90
+ super().__init__(server_url, asr_config, tts_config)
91
+ self.username = username
92
+ self.password = password
93
+
94
+ @classmethod
95
+ def from_credentials(
96
+ cls, credentials: Optional[Dict[Text, Any]]
97
+ ) -> "JambonzStreamInputChannel":
98
+ """Create a channel from credentials dictionary.
99
+
100
+ Args:
101
+ credentials: Dictionary containing the required credentials:
102
+ - server_url: URL where the server is hosted
103
+ - asr: ASR engine configuration
104
+ - tts: TTS engine configuration
105
+ - username: Optional username for basic auth
106
+ - password: Optional password for basic auth
107
+
108
+ Returns:
109
+ JambonzStreamInputChannel instance
110
+ """
111
+ # Get common credentials from parent
112
+ channel = super().from_credentials(credentials)
113
+
114
+ # Check optional basic auth credentials
115
+ username = credentials.get("username") # type: ignore[union-attr]
116
+ password = credentials.get("password") # type: ignore[union-attr]
117
+ validate_username_password_credentials(username, password, "Jambonz Stream")
118
+
119
+ # Update channel with auth credentials
120
+ channel.username = username # type: ignore[attr-defined]
121
+ channel.password = password # type: ignore[attr-defined]
122
+
123
+ return channel # type: ignore[return-value]
124
+
125
+ def _websocket_stream_url(self) -> str:
126
+ """Returns the websocket stream URL."""
127
+ # depending on the config value, the url might contain http as a
128
+ # protocol or not - we'll make sure both work
129
+ if self.server_url.startswith("http"):
130
+ base_url = self.server_url.replace("http", "ws")
131
+ else:
132
+ base_url = f"wss://{self.server_url}"
133
+ return f"{base_url}/{JAMBONZ_STREAMS_WEBSOCKET_PATH}"
134
+
71
135
  def channel_bytes_to_rasa_audio_bytes(self, input_bytes: bytes) -> RasaAudioBytes:
72
136
  """Convert Jambonz audio bytes (L16 PCM) to Rasa audio bytes (μ-law)."""
73
137
  ulaw = audioop.lin2ulaw(input_bytes, 2)
@@ -129,6 +193,7 @@ class JambonzStreamInputChannel(VoiceInputChannel):
129
193
  return response.json({"status": "ok"})
130
194
 
131
195
  @blueprint.route("/call_status", methods=["POST"])
196
+ @requires_basic_auth(self.username, self.password)
132
197
  async def call_status(request: Request) -> HTTPResponse:
133
198
  """Handle call status updates from Jambonz."""
134
199
  data = request.json
@@ -136,6 +201,7 @@ class JambonzStreamInputChannel(VoiceInputChannel):
136
201
  return response.json({"status": "ok"})
137
202
 
138
203
  @blueprint.route("/webhook", methods=["POST"])
204
+ @requires_basic_auth(self.username, self.password)
139
205
  async def webhook(request: Request) -> HTTPResponse:
140
206
  """Handle incoming webhook requests from Jambonz."""
141
207
  data = request.json
@@ -144,7 +210,7 @@ class JambonzStreamInputChannel(VoiceInputChannel):
144
210
  [
145
211
  {
146
212
  "verb": "listen",
147
- "url": f"wss://{self.server_url}/webhooks/jambonz_stream/websocket",
213
+ "url": self._websocket_stream_url(),
148
214
  "sampleRate": 8000,
149
215
  "passDtmf": True,
150
216
  "bidirectionalAudio": {
@@ -19,7 +19,10 @@ from rasa.core.channels.channel import (
19
19
  create_auth_requested_response_provider,
20
20
  requires_basic_auth,
21
21
  )
22
- from rasa.core.channels.voice_ready.utils import CallParameters
22
+ from rasa.core.channels.voice_ready.utils import (
23
+ CallParameters,
24
+ validate_username_password_credentials,
25
+ )
23
26
  from rasa.core.channels.voice_stream.audio_bytes import RasaAudioBytes
24
27
  from rasa.core.channels.voice_stream.call_state import call_state
25
28
  from rasa.core.channels.voice_stream.tts.tts_engine import TTSEngine
@@ -31,7 +34,6 @@ from rasa.core.channels.voice_stream.voice_channel import (
31
34
  VoiceInputChannel,
32
35
  VoiceOutputChannel,
33
36
  )
34
- from rasa.shared.exceptions import RasaException
35
37
 
36
38
  if TYPE_CHECKING:
37
39
  from twilio.twiml.voice_response import VoiceResponse
@@ -122,11 +124,7 @@ class TwilioMediaStreamsInputChannel(VoiceInputChannel):
122
124
 
123
125
  username = credentials.get("username")
124
126
  password = credentials.get("password")
125
- if (username is None) != (password is None):
126
- raise RasaException(
127
- "In TwilioMediaStreams channel, either both username and password "
128
- "or neither should be provided. "
129
- )
127
+ validate_username_password_credentials(username, password, "TwilioMediaStreams")
130
128
 
131
129
  return cls(
132
130
  credentials["server_url"],
@@ -35,7 +35,7 @@ from rasa.core.channels.voice_stream.util import (
35
35
  generate_silence,
36
36
  )
37
37
  from rasa.shared.core.constants import SILENCE_TIMEOUT_SLOT
38
- from rasa.shared.utils.cli import print_error_and_exit
38
+ from rasa.shared.exceptions import InvalidConfigException
39
39
  from rasa.shared.utils.common import (
40
40
  class_from_module_path,
41
41
  mark_as_beta_feature,
@@ -71,6 +71,14 @@ class ContinueConversationAction(VoiceChannelAction):
71
71
 
72
72
 
73
73
  def asr_engine_from_config(asr_config: Dict) -> ASREngine:
74
+ if not asr_config:
75
+ raise ValueError("ASR configuration dictionary cannot be empty")
76
+
77
+ if "name" not in asr_config:
78
+ raise ValueError(
79
+ "ASR configuration must contain 'name' key specifying the engine type"
80
+ )
81
+
74
82
  name = str(asr_config["name"])
75
83
  asr_config = copy.copy(asr_config)
76
84
  asr_config.pop("name")
@@ -84,12 +92,12 @@ def asr_engine_from_config(asr_config: Dict) -> ASREngine:
84
92
  asr_engine_class = class_from_module_path(name)
85
93
  return asr_engine_class.from_config_dict(asr_config)
86
94
  except NameError:
87
- print_error_and_exit(
95
+ raise InvalidConfigException(
88
96
  f"Failed to initialize ASR Engine with type '{name}'. "
89
97
  f"Please make sure the method `from_config_dict`is implemented."
90
98
  )
91
99
  except TypeError as e:
92
- print_error_and_exit(
100
+ raise InvalidConfigException(
93
101
  f"Failed to initialize ASR Engine with type '{name}'. "
94
102
  f"Invalid configuration provided. "
95
103
  f"Error: {e}"
@@ -97,6 +105,14 @@ def asr_engine_from_config(asr_config: Dict) -> ASREngine:
97
105
 
98
106
 
99
107
  def tts_engine_from_config(tts_config: Dict) -> TTSEngine:
108
+ if not tts_config:
109
+ raise ValueError("TTS configuration dictionary cannot be empty")
110
+
111
+ if "name" not in tts_config:
112
+ raise ValueError(
113
+ "TTS configuration must contain 'name' key specifying the engine type"
114
+ )
115
+
100
116
  name = str(tts_config["name"])
101
117
  tts_config = copy.copy(tts_config)
102
118
  tts_config.pop("name")
@@ -110,13 +126,13 @@ def tts_engine_from_config(tts_config: Dict) -> TTSEngine:
110
126
  tts_engine_class = class_from_module_path(name)
111
127
  return tts_engine_class.from_config_dict(tts_config)
112
128
  except NameError:
113
- print_error_and_exit(
129
+ raise InvalidConfigException(
114
130
  f"Failed to initialize TTS Engine with type '{name}'. "
115
131
  f"Please make sure the method `from_config_dict`is implemented."
116
132
  )
117
133
  except TypeError as e:
118
- print_error_and_exit(
119
- f"Failed to initialize ASR Engine with type '{name}'. "
134
+ raise InvalidConfigException(
135
+ f"Failed to initialize TTS Engine with type '{name}'. "
120
136
  f"Invalid configuration provided. "
121
137
  f"Error: {e}"
122
138
  )
@@ -336,11 +352,24 @@ class VoiceInputChannel(InputChannel):
336
352
  cls,
337
353
  credentials: Optional[Dict[str, Any]],
338
354
  ) -> InputChannel:
339
- credentials = credentials or {}
355
+ if not credentials:
356
+ cls.raise_missing_credentials_exception()
357
+
358
+ if not credentials.get("server_url"):
359
+ raise InvalidConfigException("No server_url provided in credentials.")
360
+ if not credentials.get("asr"):
361
+ raise InvalidConfigException(
362
+ "No ASR configuration provided in credentials."
363
+ )
364
+ if not credentials.get("tts"):
365
+ raise InvalidConfigException(
366
+ "No TTS configuration provided in credentials."
367
+ )
368
+
340
369
  return cls(
341
- credentials["server_url"],
342
- credentials["asr"],
343
- credentials["tts"],
370
+ server_url=credentials["server_url"],
371
+ asr_config=credentials["asr"],
372
+ tts_config=credentials["tts"],
344
373
  )
345
374
 
346
375
  def channel_bytes_to_rasa_audio_bytes(self, input_bytes: bytes) -> RasaAudioBytes:
@@ -1,6 +1,8 @@
1
1
  import dataclasses
2
+ import glob
2
3
  import importlib.resources
3
4
  import json
5
+ import os.path
4
6
  import re
5
7
  from typing import TYPE_CHECKING, Any, Dict, List, Optional, Text
6
8
 
@@ -351,9 +353,11 @@ class EnterpriseSearchPolicy(LLMHealthCheckMixin, EmbeddingsHealthCheckMixin, Po
351
353
 
352
354
  if self.vector_store_type == DEFAULT_VECTOR_STORE_TYPE:
353
355
  structlogger.info("enterprise_search_policy.train.faiss")
356
+ docs_folder = self.vector_store_config.get(SOURCE_PROPERTY)
357
+ self._validate_documents_folder(docs_folder)
354
358
  with self._model_storage.write_to(self._resource) as path:
355
359
  self.vector_store = FAISS_Store(
356
- docs_folder=self.vector_store_config.get(SOURCE_PROPERTY),
360
+ docs_folder=docs_folder,
357
361
  embeddings=embeddings,
358
362
  index_path=path,
359
363
  create_index=True,
@@ -773,6 +777,33 @@ class EnterpriseSearchPolicy(LLMHealthCheckMixin, EmbeddingsHealthCheckMixin, Po
773
777
  result[domain.index_for_action(action_name)] = score # type: ignore[assignment]
774
778
  return result
775
779
 
780
+ @classmethod
781
+ def _validate_documents_folder(cls, docs_folder: str) -> None:
782
+ if not os.path.exists(docs_folder) or not os.path.isdir(docs_folder):
783
+ error_message = (
784
+ f"Document source directory does not exist or is not a "
785
+ f"directory: '{docs_folder}'. "
786
+ "Please specify a valid path to the documents source directory in the "
787
+ "vector_store configuration."
788
+ )
789
+ structlogger.error(
790
+ "enterprise_search_policy.train.faiss.invalid_source_directory",
791
+ message=error_message,
792
+ )
793
+ print_error_and_exit(error_message)
794
+
795
+ docs = glob.glob(os.path.join(docs_folder, "*.txt"), recursive=True)
796
+ if not docs or len(docs) < 1:
797
+ error_message = (
798
+ f"Document source directory is empty: '{docs_folder}'. "
799
+ "Please add documents to this directory or specify a different one."
800
+ )
801
+ structlogger.error(
802
+ "enterprise_search_policy.train.faiss.source_directory_empty",
803
+ message=error_message,
804
+ )
805
+ print_error_and_exit(error_message)
806
+
776
807
  @classmethod
777
808
  def load(
778
809
  cls,
@@ -864,7 +895,12 @@ class EnterpriseSearchPolicy(LLMHealthCheckMixin, EmbeddingsHealthCheckMixin, Po
864
895
  e.g. FAISS, to ensure that the graph component is retrained when the knowledge
865
896
  base is updated.
866
897
  """
867
- if store_type != DEFAULT_VECTOR_STORE_TYPE or not source:
898
+ if (
899
+ store_type != DEFAULT_VECTOR_STORE_TYPE
900
+ or not source
901
+ or not os.path.exists(source)
902
+ or not os.path.isdir(source)
903
+ ):
868
904
  return None
869
905
 
870
906
  docs = FAISS_Store.load_documents(source)
rasa/core/processor.py CHANGED
@@ -237,6 +237,12 @@ class MessageProcessor:
237
237
  )
238
238
  return None
239
239
 
240
+ if not self.privacy_manager.event_brokers:
241
+ structlogger.debug(
242
+ "processor.trigger_anonymization.skipping.no_event_brokers",
243
+ )
244
+ return None
245
+
240
246
  structlogger.info(
241
247
  "rasa.core.processor.trigger_anonymization",
242
248
  sender_id=tracker.sender_id,
@@ -21,6 +21,7 @@ from rasa.engine.recipes.default_recipe import DefaultV1Recipe
21
21
  from rasa.engine.storage.resource import Resource
22
22
  from rasa.engine.storage.storage import ModelStorage
23
23
  from rasa.shared.constants import (
24
+ LOGIT_BIAS_CONFIG_KEY,
24
25
  MAX_COMPLETION_TOKENS_CONFIG_KEY,
25
26
  MODEL_CONFIG_KEY,
26
27
  OPENAI_PROVIDER,
@@ -57,12 +58,22 @@ DEFAULT_COMMAND_PROMPT_TEMPLATE = importlib.resources.read_text(
57
58
  )
58
59
  LLM_BASED_ROUTER_CONFIG_FILE_NAME = "config.json"
59
60
 
61
+ # Token ids for gpt-4o corresponding to space + capitalized Letter
62
+ A_TO_C_TOKEN_IDS_CHATGPT = [
63
+ 355, # " A"
64
+ 418, # " B"
65
+ 363, # " C"
66
+ ]
67
+
60
68
  DEFAULT_LLM_CONFIG = {
61
69
  PROVIDER_CONFIG_KEY: OPENAI_PROVIDER,
62
70
  MODEL_CONFIG_KEY: DEFAULT_OPENAI_CHAT_MODEL_NAME,
63
71
  TIMEOUT_CONFIG_KEY: 7,
64
72
  TEMPERATURE_CONFIG_KEY: 0.0,
65
73
  MAX_COMPLETION_TOKENS_CONFIG_KEY: 1,
74
+ LOGIT_BIAS_CONFIG_KEY: {
75
+ str(token_id): 100 for token_id in A_TO_C_TOKEN_IDS_CHATGPT
76
+ },
66
77
  }
67
78
 
68
79
  structlogger = structlog.get_logger()
@@ -125,8 +125,9 @@ class MultiStepLLMCommandGenerator(LLMBasedCommandGenerator):
125
125
  raise_deprecation_warning(
126
126
  message=(
127
127
  "Support for `MultiStepLLMCommandGenerator` will be removed in Rasa "
128
- "`4.0.0`. Please modify your assistant's configuration to use other "
129
- "LLM command generators like the `SingleStepLLMCommandGenerator`."
128
+ "`4.0.0`. Please modify your assistant's configuration to use the "
129
+ "`CompactLLMCommandGenerator` or `SearchReadyLLMCommandGenerator` "
130
+ "instead."
130
131
  )
131
132
  )
132
133