rasa-pro 3.13.0rc1__py3-none-any.whl → 3.13.0rc3__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/cli/studio/link.py +0 -16
- rasa/cli/studio/train.py +1 -4
- rasa/cli/studio/upload.py +1 -1
- rasa/core/agent.py +6 -0
- rasa/core/channels/__init__.py +1 -0
- rasa/core/channels/voice_ready/jambonz.py +5 -6
- rasa/core/channels/voice_ready/twilio_voice.py +13 -12
- rasa/core/channels/voice_ready/utils.py +22 -0
- rasa/core/channels/voice_stream/audiocodes.py +5 -11
- rasa/core/channels/voice_stream/genesys.py +35 -16
- rasa/core/channels/voice_stream/jambonz.py +69 -3
- rasa/core/channels/voice_stream/twilio_media_streams.py +5 -7
- rasa/core/channels/voice_stream/voice_channel.py +39 -10
- rasa/core/policies/enterprise_search_policy.py +197 -68
- 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/processor.py +6 -0
- rasa/core/tracker_stores/redis_tracker_store.py +15 -5
- rasa/dialogue_understanding/coexistence/llm_based_router.py +11 -0
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +3 -2
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +9 -0
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +5 -2
- rasa/dialogue_understanding/processor/command_processor.py +12 -10
- rasa/e2e_test/constants.py +1 -1
- rasa/llm_fine_tuning/annotation_module.py +43 -11
- rasa/llm_fine_tuning/paraphrasing/conversation_rephraser.py +1 -1
- rasa/model_manager/runner_service.py +20 -4
- rasa/model_manager/trainer_service.py +6 -0
- rasa/privacy/privacy_filter.py +57 -4
- rasa/privacy/privacy_manager.py +31 -16
- rasa/shared/constants.py +2 -0
- rasa/shared/core/constants.py +1 -0
- rasa/shared/utils/llm.py +86 -2
- rasa/studio/data_handler.py +27 -13
- rasa/studio/download.py +5 -1
- rasa/studio/link.py +12 -1
- rasa/studio/prompts.py +5 -7
- rasa/studio/pull/domains.py +14 -3
- rasa/studio/pull/pull.py +6 -2
- rasa/studio/push.py +2 -0
- rasa/studio/upload.py +61 -5
- rasa/studio/utils.py +33 -0
- rasa/tracing/instrumentation/attribute_extractors.py +1 -1
- rasa/version.py +1 -1
- {rasa_pro-3.13.0rc1.dist-info → rasa_pro-3.13.0rc3.dist-info}/METADATA +1 -1
- {rasa_pro-3.13.0rc1.dist-info → rasa_pro-3.13.0rc3.dist-info}/RECORD +49 -48
- {rasa_pro-3.13.0rc1.dist-info → rasa_pro-3.13.0rc3.dist-info}/NOTICE +0 -0
- {rasa_pro-3.13.0rc1.dist-info → rasa_pro-3.13.0rc3.dist-info}/WHEEL +0 -0
- {rasa_pro-3.13.0rc1.dist-info → rasa_pro-3.13.0rc3.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.
|
|
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
|
|
rasa/core/channels/__init__.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
184
|
-
self.
|
|
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
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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,
|
|
@@ -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__(
|
|
@@ -121,16 +121,10 @@ class AudiocodesVoiceInputChannel(VoiceInputChannel):
|
|
|
121
121
|
def from_credentials(
|
|
122
122
|
cls,
|
|
123
123
|
credentials: Optional[Dict[str, Any]],
|
|
124
|
-
) ->
|
|
125
|
-
|
|
126
|
-
|
|
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,
|
|
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__(
|
|
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
|
-
) ->
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
|
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":
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
119
|
-
f"Failed to initialize
|
|
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
|
-
|
|
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:
|