rasa-pro 3.13.4__py3-none-any.whl → 3.14.0.dev20250731__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 (76) hide show
  1. rasa/cli/train.py +2 -2
  2. rasa/core/actions/action.py +27 -37
  3. rasa/core/actions/action_run_slot_rejections.py +1 -1
  4. rasa/core/channels/inspector/dist/assets/{arc-371401b1.js → arc-0b11fe30.js} +1 -1
  5. rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-3f126156.js → blockDiagram-38ab4fdb-9eef30a7.js} +1 -1
  6. rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-12f22eb7.js → c4Diagram-3d4e48cf-03e94f28.js} +1 -1
  7. rasa/core/channels/inspector/dist/assets/channel-51d02e9e.js +1 -0
  8. rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-03b1d386.js → classDiagram-70f12bd4-95c09eba.js} +1 -1
  9. rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-84f69d63.js → classDiagram-v2-f2320105-38e8446c.js} +1 -1
  10. rasa/core/channels/inspector/dist/assets/clone-cc738fa6.js +1 -0
  11. rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-ca47fd38.js → createText-2e5e7dd3-57dc3038.js} +1 -1
  12. rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-f837ca8a.js → edges-e0da2a9e-4bac0545.js} +1 -1
  13. rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-8717ac54.js → erDiagram-9861fffd-81795c90.js} +1 -1
  14. rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-94f38b83.js → flowDb-956e92f1-89489ae6.js} +1 -1
  15. rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-b616f9fb.js → flowDiagram-66a62f08-cd152627.js} +1 -1
  16. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-0c716443.js +1 -0
  17. rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-f5d24bb8.js → flowchart-elk-definition-4a651766-3da369bc.js} +1 -1
  18. rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-b43ba8d9.js → ganttDiagram-c361ad54-85ec16f8.js} +1 -1
  19. rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-c3aafaa5.js → gitGraphDiagram-72cf32ee-495bc140.js} +1 -1
  20. rasa/core/channels/inspector/dist/assets/{graph-0d0a2c10.js → graph-1ec4d266.js} +1 -1
  21. rasa/core/channels/inspector/dist/assets/{index-3862675e-58ea0305.js → index-3862675e-0a0e97c9.js} +1 -1
  22. rasa/core/channels/inspector/dist/assets/{index-cce6f8a1.js → index-c804b295.js} +148 -148
  23. rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-b8f60461.js → infoDiagram-f8f76790-4d54bcde.js} +1 -1
  24. rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-95be5545.js → journeyDiagram-49397b02-dc097114.js} +1 -1
  25. rasa/core/channels/inspector/dist/assets/{layout-da885b9b.js → layout-1a08981e.js} +1 -1
  26. rasa/core/channels/inspector/dist/assets/{line-f1c817d3.js → line-95f7f1d3.js} +1 -1
  27. rasa/core/channels/inspector/dist/assets/{linear-d42801e6.js → linear-97e69543.js} +1 -1
  28. rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-a38923a6.js → mindmap-definition-fc14e90a-8c71ff03.js} +1 -1
  29. rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-ca6e71e9.js → pieDiagram-8a3498a8-f14c71c7.js} +1 -1
  30. rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-b290dae9.js → quadrantDiagram-120e2f19-f1d3c9ff.js} +1 -1
  31. rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-03f02ceb.js → requirementDiagram-deff3bca-bfa2412f.js} +1 -1
  32. rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-c49eee40.js → sankeyDiagram-04a897e0-53f2c97b.js} +1 -1
  33. rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-b2cd6a3d.js → sequenceDiagram-704730f1-319d7c0e.js} +1 -1
  34. rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-e53a2028.js → stateDiagram-587899a1-76a09418.js} +1 -1
  35. rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-e1982a03.js → stateDiagram-v2-d93cdb3a-a67f15d4.js} +1 -1
  36. rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-d0226ca5.js → styles-6aaf32cf-0654e7c3.js} +1 -1
  37. rasa/core/channels/inspector/dist/assets/{styles-9a916d00-0e21dc00.js → styles-9a916d00-1394bb9d.js} +1 -1
  38. rasa/core/channels/inspector/dist/assets/{styles-c10674c1-9588494e.js → styles-c10674c1-e4c5bdae.js} +1 -1
  39. rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-be478d4f.js → svgDrawCommon-08f97a94-50957104.js} +1 -1
  40. rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-74631749.js → timeline-definition-85554ec2-b0885a6a.js} +1 -1
  41. rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-a043552f.js → xychartDiagram-e933f94c-79e6541a.js} +1 -1
  42. rasa/core/channels/inspector/dist/index.html +1 -1
  43. rasa/core/channels/inspector/package.json +1 -1
  44. rasa/core/channels/inspector/yarn.lock +4 -4
  45. rasa/core/channels/studio_chat.py +67 -20
  46. rasa/core/channels/voice_ready/twilio_voice.py +1 -1
  47. rasa/core/channels/voice_stream/audiocodes.py +7 -4
  48. rasa/core/channels/voice_stream/browser_audio.py +19 -1
  49. rasa/core/channels/voice_stream/genesys.py +14 -11
  50. rasa/core/channels/voice_stream/jambonz.py +11 -9
  51. rasa/core/channels/voice_stream/twilio_media_streams.py +12 -11
  52. rasa/core/channels/voice_stream/util.py +11 -1
  53. rasa/core/channels/voice_stream/voice_channel.py +18 -16
  54. rasa/core/nlg/contextual_response_rephraser.py +6 -7
  55. rasa/core/nlg/generator.py +21 -5
  56. rasa/core/nlg/response.py +43 -6
  57. rasa/core/nlg/translate.py +8 -0
  58. rasa/dialogue_understanding/commands/correct_slots_command.py +38 -0
  59. rasa/dialogue_understanding/processor/command_processor.py +127 -54
  60. rasa/dialogue_understanding/stack/utils.py +13 -3
  61. rasa/dialogue_understanding_test/du_test_schema.yml +3 -3
  62. rasa/e2e_test/e2e_test_schema.yml +3 -3
  63. rasa/model_manager/model_api.py +1 -1
  64. rasa/model_manager/socket_bridge.py +7 -0
  65. rasa/shared/utils/common.py +2 -1
  66. rasa/utils/licensing.py +21 -10
  67. rasa/utils/plotting.py +1 -1
  68. rasa/version.py +1 -1
  69. {rasa_pro-3.13.4.dist-info → rasa_pro-3.14.0.dev20250731.dist-info}/METADATA +9 -9
  70. {rasa_pro-3.13.4.dist-info → rasa_pro-3.14.0.dev20250731.dist-info}/RECORD +73 -73
  71. rasa/core/channels/inspector/dist/assets/channel-f1efda17.js +0 -1
  72. rasa/core/channels/inspector/dist/assets/clone-fdf164e2.js +0 -1
  73. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-7d7a1629.js +0 -1
  74. {rasa_pro-3.13.4.dist-info → rasa_pro-3.14.0.dev20250731.dist-info}/NOTICE +0 -0
  75. {rasa_pro-3.13.4.dist-info → rasa_pro-3.14.0.dev20250731.dist-info}/WHEEL +0 -0
  76. {rasa_pro-3.13.4.dist-info → rasa_pro-3.14.0.dev20250731.dist-info}/entry_points.txt +0 -0
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import asyncio
2
4
  import audioop
3
5
  import base64
@@ -32,6 +34,7 @@ from rasa.core.channels.voice_stream.voice_channel import (
32
34
  VoiceInputChannel,
33
35
  VoiceOutputChannel,
34
36
  )
37
+ from rasa.core.exceptions import AgentNotReady
35
38
  from rasa.hooks import hookimpl
36
39
  from rasa.plugin import plugin_manager
37
40
  from rasa.shared.core.constants import ACTION_LISTEN_NAME
@@ -42,7 +45,7 @@ if TYPE_CHECKING:
42
45
  from sanic import Sanic, Websocket # type: ignore[attr-defined]
43
46
  from socketio import AsyncServer
44
47
 
45
- from rasa.core.channels.channel import InputChannel, UserMessage
48
+ from rasa.core.channels.channel import UserMessage
46
49
  from rasa.shared.core.trackers import DialogueStateTracker
47
50
 
48
51
 
@@ -179,7 +182,9 @@ class StudioChatInput(SocketIOInput, VoiceInputChannel):
179
182
  self._register_tracker_update_hook()
180
183
 
181
184
  @classmethod
182
- def from_credentials(cls, credentials: Optional[Dict[Text, Any]]) -> "InputChannel":
185
+ def from_credentials(
186
+ cls, credentials: Optional[Dict[Text, Any]]
187
+ ) -> "StudioChatInput":
183
188
  """Creates a StudioChatInput channel from credentials."""
184
189
  credentials = credentials or {}
185
190
 
@@ -199,6 +204,13 @@ class StudioChatInput(SocketIOInput, VoiceInputChannel):
199
204
  metadata_key=credentials.get("metadata_key", "metadata"),
200
205
  )
201
206
 
207
+ async def emit(self, event: str, data: Dict, room: str) -> None:
208
+ """Emits an event to the websocket."""
209
+ if not self.sio:
210
+ structlogger.error("studio_chat.emit.sio_not_initialized")
211
+ return
212
+ await self.sio.emit(event, data, room=room)
213
+
202
214
  def _register_tracker_update_hook(self) -> None:
203
215
  plugin_manager().register(StudioTrackerUpdatePlugin(self))
204
216
 
@@ -208,10 +220,7 @@ class StudioChatInput(SocketIOInput, VoiceInputChannel):
208
220
 
209
221
  async def publish_tracker_update(self, sender_id: str, tracker_dump: Dict) -> None:
210
222
  """Publishes a tracker update notification to the websocket."""
211
- if not self.sio:
212
- structlogger.error("studio_chat.on_tracker_updated.sio_not_initialized")
213
- return
214
- await self.sio.emit("tracker", tracker_dump, room=sender_id)
223
+ await self.emit("tracker", tracker_dump, room=sender_id)
215
224
 
216
225
  async def on_message_proxy(
217
226
  self,
@@ -224,8 +233,15 @@ class StudioChatInput(SocketIOInput, VoiceInputChannel):
224
233
  """
225
234
  await on_new_message(message)
226
235
 
227
- if not self.agent:
236
+ if not self.agent or not self.agent.is_ready():
228
237
  structlogger.error("studio_chat.on_message_proxy.agent_not_initialized")
238
+ await self.emit_error(
239
+ "The Rasa Pro model could not be loaded. "
240
+ "Please check the training and deployment logs "
241
+ "for more information.",
242
+ message.sender_id,
243
+ AgentNotReady("The Rasa Pro model could not be loaded."),
244
+ )
229
245
  return
230
246
 
231
247
  tracker = await self.agent.tracker_store.retrieve(message.sender_id)
@@ -235,6 +251,17 @@ class StudioChatInput(SocketIOInput, VoiceInputChannel):
235
251
 
236
252
  await self.on_tracker_updated(tracker)
237
253
 
254
+ async def emit_error(self, message: str, room: str, e: Exception) -> None:
255
+ await self.emit(
256
+ "error",
257
+ {
258
+ "message": message,
259
+ "error": str(e),
260
+ "exception": str(type(e).__name__),
261
+ },
262
+ room=room,
263
+ )
264
+
238
265
  async def handle_tracker_update(self, sid: str, data: Dict) -> None:
239
266
  from rasa.shared.core.trackers import DialogueStateTracker
240
267
 
@@ -251,21 +278,41 @@ class StudioChatInput(SocketIOInput, VoiceInputChannel):
251
278
  structlogger.error("studio_chat.sio.domain_not_initialized")
252
279
  return None
253
280
 
254
- async with self.agent.lock_store.lock(data["sender_id"]):
255
- tracker = DialogueStateTracker.from_dict(
256
- data["sender_id"], data["events"], domain.slots
257
- )
281
+ tracker: Optional[DialogueStateTracker] = None
258
282
 
259
- # will override an existing tracker with the same id!
260
- await self.agent.tracker_store.save(tracker)
261
-
262
- processor = self.agent.processor
263
- if processor and does_need_action_prediction(tracker):
264
- output_channel = self.get_output_channel()
283
+ async with self.agent.lock_store.lock(data["sender_id"]):
284
+ try:
285
+ tracker = DialogueStateTracker.from_dict(
286
+ data["sender_id"], data["events"], domain.slots
287
+ )
265
288
 
266
- await processor._run_prediction_loop(output_channel, tracker)
289
+ # will override an existing tracker with the same id!
267
290
  await self.agent.tracker_store.save(tracker)
268
291
 
292
+ processor = self.agent.processor
293
+ if processor and does_need_action_prediction(tracker):
294
+ output_channel = self.get_output_channel()
295
+
296
+ await processor._run_prediction_loop(output_channel, tracker)
297
+ await self.agent.tracker_store.save(tracker)
298
+ except Exception as e:
299
+ structlogger.error(
300
+ "studio_chat.sio.handle_tracker_update.error",
301
+ error=e,
302
+ sender_id=data["sender_id"],
303
+ )
304
+ await self.emit_error(
305
+ "An error occurred while updating the conversation.",
306
+ data["sender_id"],
307
+ e,
308
+ )
309
+
310
+ if not tracker:
311
+ # in case the tracker couldn't be updated, we retrieve the prior
312
+ # version and use that to populate the update
313
+ tracker = await self.agent.tracker_store.get_or_create_tracker(
314
+ data["sender_id"]
315
+ )
269
316
  await self.on_tracker_updated(tracker)
270
317
 
271
318
  def channel_bytes_to_rasa_audio_bytes(self, input_bytes: bytes) -> RasaAudioBytes:
@@ -275,7 +322,7 @@ class StudioChatInput(SocketIOInput, VoiceInputChannel):
275
322
  async def collect_call_parameters(
276
323
  self, channel_websocket: "Websocket"
277
324
  ) -> Optional[CallParameters]:
278
- """Voice method to collect call parameters"""
325
+ """Voice method to collect call parameters."""
279
326
  session_id = channel_websocket.session_id
280
327
  return CallParameters(session_id, "local", "local", stream_id=session_id)
281
328
 
@@ -305,7 +352,7 @@ class StudioChatInput(SocketIOInput, VoiceInputChannel):
305
352
  def create_output_channel(
306
353
  self, voice_websocket: "Websocket", tts_engine: TTSEngine
307
354
  ) -> VoiceOutputChannel:
308
- """Create a voice output channel"""
355
+ """Create a voice output channel."""
309
356
  return StudioVoiceOutputChannel(
310
357
  voice_websocket,
311
358
  tts_engine,
@@ -30,7 +30,7 @@ TWILIO_VOICE_PATH = "webhooks/twilio_voice/webhook"
30
30
 
31
31
 
32
32
  def map_call_params(form: RequestParameters) -> CallParameters:
33
- """Map the Audiocodes parameters to the CallParameters dataclass."""
33
+ """Map the Twilio Voice parameters to the CallParameters dataclass."""
34
34
  return CallParameters(
35
35
  call_id=form.get("CallSid"),
36
36
  user_phone=form.get("Caller"),
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import asyncio
2
4
  import base64
3
5
  import hmac
@@ -21,6 +23,7 @@ from rasa.core.channels.voice_stream.call_state import (
21
23
  call_state,
22
24
  )
23
25
  from rasa.core.channels.voice_stream.tts.tts_engine import TTSEngine
26
+ from rasa.core.channels.voice_stream.util import repack_voice_credentials
24
27
  from rasa.core.channels.voice_stream.voice_channel import (
25
28
  ContinueConversationAction,
26
29
  EndConversationAction,
@@ -127,10 +130,10 @@ class AudiocodesVoiceInputChannel(VoiceInputChannel):
127
130
  def from_credentials(
128
131
  cls,
129
132
  credentials: Optional[Dict[str, Any]],
130
- ) -> "AudiocodesVoiceInputChannel":
131
- channel = super().from_credentials(credentials)
132
- channel.token = credentials.get("token") # type: ignore[attr-defined, union-attr]
133
- return channel # type: ignore[return-value]
133
+ ) -> AudiocodesVoiceInputChannel:
134
+ cls.validate_basic_credentials(credentials)
135
+ new_creds = repack_voice_credentials(credentials)
136
+ return cls(**new_creds)
134
137
 
135
138
  def channel_bytes_to_rasa_audio_bytes(self, input_bytes: bytes) -> RasaAudioBytes:
136
139
  return RasaAudioBytes(base64.b64decode(input_bytes))
@@ -1,8 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  import audioop
2
4
  import base64
3
5
  import json
4
6
  import uuid
5
- from typing import Any, Awaitable, Callable, Optional, Tuple
7
+ from typing import Any, Awaitable, Callable, Dict, Optional, Tuple
6
8
 
7
9
  import structlog
8
10
  from sanic import ( # type: ignore[attr-defined]
@@ -18,6 +20,7 @@ from rasa.core.channels.voice_ready.utils import CallParameters
18
20
  from rasa.core.channels.voice_stream.audio_bytes import RasaAudioBytes
19
21
  from rasa.core.channels.voice_stream.call_state import call_state
20
22
  from rasa.core.channels.voice_stream.tts.tts_engine import TTSEngine
23
+ from rasa.core.channels.voice_stream.util import repack_voice_credentials
21
24
  from rasa.core.channels.voice_stream.voice_channel import (
22
25
  ContinueConversationAction,
23
26
  EndConversationAction,
@@ -49,6 +52,12 @@ class BrowserAudioOutputChannel(VoiceOutputChannel):
49
52
 
50
53
 
51
54
  class BrowserAudioInputChannel(VoiceInputChannel):
55
+ def __init__(
56
+ self, server_url: str, asr_config: Dict[str, Any], tts_config: Dict[str, Any]
57
+ ) -> None:
58
+ """Initializes the browser audio input channel."""
59
+ super().__init__(server_url, asr_config, tts_config)
60
+
52
61
  @classmethod
53
62
  def name(cls) -> str:
54
63
  return "browser_audio"
@@ -62,6 +71,15 @@ class BrowserAudioInputChannel(VoiceInputChannel):
62
71
  call_id = f"inspect-{uuid.uuid4()}"
63
72
  return CallParameters(call_id, "local", "local", stream_id=call_id)
64
73
 
74
+ @classmethod
75
+ def from_credentials(
76
+ cls,
77
+ credentials: Optional[Dict[str, Any]],
78
+ ) -> BrowserAudioInputChannel:
79
+ cls.validate_basic_credentials(credentials)
80
+ new_creds = repack_voice_credentials(credentials)
81
+ return cls(**new_creds)
82
+
65
83
  def map_input_message(
66
84
  self,
67
85
  message: Any,
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import asyncio
2
4
  import base64
3
5
  import hashlib
@@ -21,6 +23,7 @@ from rasa.core.channels.voice_stream.call_state import (
21
23
  call_state,
22
24
  )
23
25
  from rasa.core.channels.voice_stream.tts.tts_engine import TTSEngine
26
+ from rasa.core.channels.voice_stream.util import repack_voice_credentials
24
27
  from rasa.core.channels.voice_stream.voice_channel import (
25
28
  ContinueConversationAction,
26
29
  EndConversationAction,
@@ -54,7 +57,7 @@ logger = structlog.get_logger(__name__)
54
57
 
55
58
 
56
59
  def map_call_params(data: Dict[Text, Any]) -> CallParameters:
57
- """Map the twilio stream parameters to the CallParameters dataclass."""
60
+ """Map the Genesys parameters to the CallParameters dataclass."""
58
61
  parameters = data["parameters"]
59
62
  participant = parameters["participant"]
60
63
  # sent as {"ani": "tel:+491604697810"}
@@ -107,7 +110,7 @@ class GenesysInputChannel(VoiceInputChannel):
107
110
  def from_credentials(
108
111
  cls,
109
112
  credentials: Optional[Dict[str, Any]],
110
- ) -> "GenesysInputChannel":
113
+ ) -> GenesysInputChannel:
111
114
  """Create a channel from credentials dictionary.
112
115
 
113
116
  Args:
@@ -121,21 +124,21 @@ class GenesysInputChannel(VoiceInputChannel):
121
124
  Returns:
122
125
  GenesysInputChannel instance
123
126
  """
124
- channel = super().from_credentials(credentials)
127
+ cls.validate_credentials(credentials)
128
+ new_creds = repack_voice_credentials(credentials)
129
+ return cls(**new_creds)
125
130
 
126
- # Check required Genesys-specific credentials
131
+ @classmethod
132
+ def validate_credentials(cls, credentials: Optional[Dict[str, Any]]) -> None:
133
+ """Validate the credentials for the Genesys voice channel."""
134
+ cls.validate_basic_credentials(credentials)
127
135
  if not credentials.get("api_key"): # type: ignore[union-attr]
128
136
  raise InvalidConfigException(
129
137
  "No API key given for Genesys voice channel (api_key)."
130
138
  )
131
139
 
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]
137
-
138
- def _ensure_channel_data_initialized(self) -> None:
140
+ @staticmethod
141
+ def _ensure_channel_data_initialized() -> None:
139
142
  """Initialize Genesys-specific channel data if not already present.
140
143
 
141
144
  Genesys requires the server and client each maintain a
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import audioop
2
4
  import json
3
5
  import uuid
@@ -20,6 +22,7 @@ from rasa.core.channels.voice_ready.utils import (
20
22
  from rasa.core.channels.voice_stream.audio_bytes import RasaAudioBytes
21
23
  from rasa.core.channels.voice_stream.call_state import call_state
22
24
  from rasa.core.channels.voice_stream.tts.tts_engine import TTSEngine
25
+ from rasa.core.channels.voice_stream.util import repack_voice_credentials
23
26
  from rasa.core.channels.voice_stream.voice_channel import (
24
27
  ContinueConversationAction,
25
28
  EndConversationAction,
@@ -35,7 +38,7 @@ JAMBONZ_STREAMS_WEBSOCKET_PATH = "webhooks/jambonz_stream/websocket"
35
38
 
36
39
 
37
40
  def map_call_params(data: Dict[Text, str]) -> CallParameters:
38
- """Map the twilio stream parameters to the CallParameters dataclass."""
41
+ """Map the Jambonz stream parameters to the CallParameters dataclass."""
39
42
  call_sid = data.get("callSid", "None")
40
43
  from_number = data.get("from", "Unknown")
41
44
  to_number = data.get("to")
@@ -94,7 +97,7 @@ class JambonzStreamInputChannel(VoiceInputChannel):
94
97
  @classmethod
95
98
  def from_credentials(
96
99
  cls, credentials: Optional[Dict[Text, Any]]
97
- ) -> "JambonzStreamInputChannel":
100
+ ) -> JambonzStreamInputChannel:
98
101
  """Create a channel from credentials dictionary.
99
102
 
100
103
  Args:
@@ -109,19 +112,18 @@ class JambonzStreamInputChannel(VoiceInputChannel):
109
112
  JambonzStreamInputChannel instance
110
113
  """
111
114
  # Get common credentials from parent
112
- channel = super().from_credentials(credentials)
115
+ cls.validate_credentials(credentials)
116
+ new_creds = repack_voice_credentials(credentials)
117
+ return cls(**new_creds)
113
118
 
119
+ @classmethod
120
+ def validate_credentials(cls, credentials: Optional[Dict[Text, Any]]) -> None:
121
+ cls.validate_basic_credentials(credentials)
114
122
  # Check optional basic auth credentials
115
123
  username = credentials.get("username") # type: ignore[union-attr]
116
124
  password = credentials.get("password") # type: ignore[union-attr]
117
125
  validate_username_password_credentials(username, password, "Jambonz Stream")
118
126
 
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
127
  def _websocket_stream_url(self) -> str:
126
128
  """Returns the websocket stream URL."""
127
129
  # depending on the config value, the url might contain http as a
@@ -26,6 +26,7 @@ from rasa.core.channels.voice_ready.utils import (
26
26
  from rasa.core.channels.voice_stream.audio_bytes import RasaAudioBytes
27
27
  from rasa.core.channels.voice_stream.call_state import call_state
28
28
  from rasa.core.channels.voice_stream.tts.tts_engine import TTSEngine
29
+ from rasa.core.channels.voice_stream.util import repack_voice_credentials
29
30
  from rasa.core.channels.voice_stream.voice_channel import (
30
31
  ContinueConversationAction,
31
32
  EndConversationAction,
@@ -120,20 +121,20 @@ class TwilioMediaStreamsInputChannel(VoiceInputChannel):
120
121
  cls,
121
122
  credentials: Optional[Dict[str, Any]],
122
123
  ) -> VoiceInputChannel:
123
- credentials = credentials or {}
124
+ cls.validate_credentials(credentials)
125
+ new_creds = repack_voice_credentials(credentials)
126
+ return cls(**new_creds)
124
127
 
125
- username = credentials.get("username")
126
- password = credentials.get("password")
128
+ @classmethod
129
+ def validate_credentials(
130
+ cls,
131
+ credentials: Optional[Dict[str, Any]],
132
+ ) -> None:
133
+ cls.validate_basic_credentials(credentials)
134
+ username = credentials.get("username") if credentials else None
135
+ password = credentials.get("password") if credentials else None
127
136
  validate_username_password_credentials(username, password, "TwilioMediaStreams")
128
137
 
129
- return cls(
130
- credentials["server_url"],
131
- credentials["asr"],
132
- credentials["tts"],
133
- username=username,
134
- password=password,
135
- )
136
-
137
138
  @classmethod
138
139
  def name(cls) -> str:
139
140
  return "twilio_media_streams"
@@ -1,7 +1,7 @@
1
1
  import audioop
2
2
  import wave
3
3
  from dataclasses import asdict, dataclass
4
- from typing import Optional, Type, TypeVar
4
+ from typing import Dict, Optional, Type, TypeVar
5
5
 
6
6
  import structlog
7
7
 
@@ -55,3 +55,13 @@ class MergeableConfig:
55
55
  @classmethod
56
56
  def from_dict(cls: Type[T], data: dict[str, Optional[str]]) -> T:
57
57
  return cls(**data)
58
+
59
+
60
+ def repack_voice_credentials(
61
+ credentials: Dict[str, str],
62
+ ) -> Dict[str, str]:
63
+ """Repack voice credentials to ensure they are in the correct format."""
64
+ new_creds = {**credentials}
65
+ new_creds["asr_config"] = new_creds.pop("asr", None)
66
+ new_creds["tts_config"] = new_creds.pop("tts", None)
67
+ return new_creds
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import asyncio
2
4
  import copy
3
5
  from dataclasses import asdict, dataclass
@@ -348,29 +350,29 @@ class VoiceInputChannel(InputChannel):
348
350
  call_state.silence_timeout_watcher = None # type: ignore[attr-defined]
349
351
 
350
352
  @classmethod
351
- def from_credentials(
352
- cls,
353
- credentials: Optional[Dict[str, Any]],
354
- ) -> InputChannel:
353
+ def validate_basic_credentials(cls, credentials: Optional[Dict[str, Any]]) -> None:
354
+ """Validate the basic credentials for the voice channel."""
355
355
  if not credentials:
356
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"):
357
+ if not isinstance(credentials, dict):
361
358
  raise InvalidConfigException(
362
- "No ASR configuration provided in credentials."
359
+ "Credentials must be a dictionary for voice channel."
363
360
  )
364
- if not credentials.get("tts"):
361
+
362
+ required_keys = {"server_url", "asr", "tts"}
363
+ credentials_keys = set(credentials.keys())
364
+ if not required_keys.issubset(credentials_keys):
365
+ missing_fields = required_keys - credentials_keys
365
366
  raise InvalidConfigException(
366
- "No TTS configuration provided in credentials."
367
+ f"Missing required fields in credentials: {', '.join(missing_fields)} "
368
+ f"for channel {cls.name()}"
367
369
  )
368
370
 
369
- return cls(
370
- server_url=credentials["server_url"],
371
- asr_config=credentials["asr"],
372
- tts_config=credentials["tts"],
373
- )
371
+ @classmethod
372
+ def from_credentials(
373
+ cls, credentials: Optional[Dict[str, Any]]
374
+ ) -> VoiceInputChannel:
375
+ raise NotImplementedError
374
376
 
375
377
  def channel_bytes_to_rasa_audio_bytes(self, input_bytes: bytes) -> RasaAudioBytes:
376
378
  raise NotImplementedError
@@ -224,8 +224,10 @@ class ContextualResponseRephraser(
224
224
 
225
225
  @measure_llm_latency
226
226
  async def _generate_llm_response(self, prompt: str) -> Optional[LLMResponse]:
227
- """Use LLM to generate a response, returning an LLMResponse object
228
- containing both the generated text (choices) and metadata.
227
+ """Use LLM to generate a response.
228
+
229
+ Returns an LLMResponse object containing both the generated text
230
+ (choices) and metadata.
229
231
 
230
232
  Args:
231
233
  prompt: The prompt to send to the LLM.
@@ -406,12 +408,9 @@ class ContextualResponseRephraser(
406
408
  Returns:
407
409
  The generated response.
408
410
  """
409
- filled_slots = tracker.current_slot_values()
410
- stack_context = tracker.stack.current_context()
411
- templated_response = self.generate_from_slots(
411
+ templated_response = await super().generate(
412
412
  utter_action=utter_action,
413
- filled_slots=filled_slots,
414
- stack_context=stack_context,
413
+ tracker=tracker,
415
414
  output_channel=output_channel,
416
415
  **kwargs,
417
416
  )
@@ -6,6 +6,8 @@ from pypred import Predicate
6
6
 
7
7
  import rasa.shared.utils.common
8
8
  import rasa.shared.utils.io
9
+ from rasa.core.nlg.translate import has_translation
10
+ from rasa.engine.language import Language
9
11
  from rasa.shared.constants import CHANNEL, RESPONSE_CONDITION
10
12
  from rasa.shared.core.domain import Domain
11
13
  from rasa.shared.core.trackers import DialogueStateTracker
@@ -131,11 +133,23 @@ class ResponseVariationFilter:
131
133
 
132
134
  return True
133
135
 
136
+ def _filter_by_language(
137
+ self, responses: List[Dict[Text, Any]], language: Optional[Language] = None
138
+ ) -> List[Dict[Text, Any]]:
139
+ if not language:
140
+ return responses
141
+
142
+ if filtered := [r for r in responses if has_translation(r, language)]:
143
+ return filtered
144
+ # if no translation is found, return the original response variations
145
+ return responses
146
+
134
147
  def responses_for_utter_action(
135
148
  self,
136
149
  utter_action: Text,
137
150
  output_channel: Text,
138
151
  filled_slots: Dict[Text, Any],
152
+ language: Optional[Language] = None,
139
153
  ) -> List[Dict[Text, Any]]:
140
154
  """Returns array of responses that fit the channel, action and condition."""
141
155
  # filter responses without a condition
@@ -176,16 +190,16 @@ class ResponseVariationFilter:
176
190
  )
177
191
 
178
192
  if conditional_channel:
179
- return conditional_channel
193
+ return self._filter_by_language(conditional_channel, language)
180
194
 
181
195
  if default_channel:
182
- return default_channel
196
+ return self._filter_by_language(default_channel, language)
183
197
 
184
198
  if conditional_no_channel:
185
- return conditional_no_channel
199
+ return self._filter_by_language(conditional_no_channel, language)
186
200
 
187
201
  if default_no_channel:
188
- return default_no_channel
202
+ return self._filter_by_language(default_no_channel, language)
189
203
 
190
204
  # if there is no response variation selected,
191
205
  # return the internal error response to prevent
@@ -198,7 +212,9 @@ class ResponseVariationFilter:
198
212
  f"a default variation and that all the conditions are valid. "
199
213
  f"Returning the internal error response.",
200
214
  )
201
- return self.responses.get("utter_internal_error_rasa", [])
215
+ return self._filter_by_language(
216
+ self.responses.get("utter_internal_error_rasa", []), language
217
+ )
202
218
 
203
219
  def get_response_variation_id(
204
220
  self,
rasa/core/nlg/response.py CHANGED
@@ -5,8 +5,11 @@ from typing import Any, Dict, List, Optional, Text
5
5
  from rasa.core.constants import DEFAULT_TEMPLATE_ENGINE, TEMPLATE_ENGINE_CONFIG_KEY
6
6
  from rasa.core.nlg import interpolator
7
7
  from rasa.core.nlg.generator import NaturalLanguageGenerator, ResponseVariationFilter
8
- from rasa.shared.constants import RESPONSE_CONDITION
8
+ from rasa.core.nlg.translate import get_translated_buttons, get_translated_text
9
+ from rasa.engine.language import Language
10
+ from rasa.shared.constants import BUTTONS, RESPONSE_CONDITION, TEXT
9
11
  from rasa.shared.core.domain import RESPONSE_KEYS_TO_INTERPOLATE
12
+ from rasa.shared.core.flows.constants import KEY_TRANSLATION
10
13
  from rasa.shared.core.trackers import DialogueStateTracker
11
14
  from rasa.shared.nlu.constants import METADATA
12
15
 
@@ -30,7 +33,11 @@ class TemplatedNaturalLanguageGenerator(NaturalLanguageGenerator):
30
33
 
31
34
  # noinspection PyUnusedLocal
32
35
  def _random_response_for(
33
- self, utter_action: Text, output_channel: Text, filled_slots: Dict[Text, Any]
36
+ self,
37
+ utter_action: Text,
38
+ output_channel: Text,
39
+ filled_slots: Dict[Text, Any],
40
+ language: Optional[Language] = None,
34
41
  ) -> Optional[Dict[Text, Any]]:
35
42
  """Select random response for the utter action from available ones.
36
43
 
@@ -42,7 +49,7 @@ class TemplatedNaturalLanguageGenerator(NaturalLanguageGenerator):
42
49
  if utter_action in self.responses:
43
50
  response_filter = ResponseVariationFilter(self.responses)
44
51
  suitable_responses = response_filter.responses_for_utter_action(
45
- utter_action, output_channel, filled_slots
52
+ utter_action, output_channel, filled_slots, language
46
53
  )
47
54
 
48
55
  if suitable_responses:
@@ -75,9 +82,36 @@ class TemplatedNaturalLanguageGenerator(NaturalLanguageGenerator):
75
82
  """Generate a response for the requested utter action."""
76
83
  filled_slots = tracker.current_slot_values()
77
84
  stack_context = tracker.stack.current_context()
78
- return self.generate_from_slots(
79
- utter_action, filled_slots, stack_context, output_channel, **kwargs
85
+ response = self.generate_from_slots(
86
+ utter_action,
87
+ filled_slots,
88
+ stack_context,
89
+ output_channel,
90
+ tracker.current_language,
91
+ **kwargs,
80
92
  )
93
+ if response is not None:
94
+ return self.translate_response(response, tracker.current_language)
95
+ return None
96
+
97
+ def translate_response(
98
+ self, response: Dict[Text, Any], language: Optional[Language] = None
99
+ ) -> Dict[Text, Any]:
100
+ message_copy = copy.deepcopy(response)
101
+
102
+ text = get_translated_text(
103
+ text=message_copy.pop(TEXT, None),
104
+ translation=message_copy.pop(KEY_TRANSLATION, {}),
105
+ language=language,
106
+ )
107
+
108
+ buttons = get_translated_buttons(
109
+ buttons=message_copy.pop(BUTTONS, None), language=language
110
+ )
111
+ message_copy[TEXT] = text
112
+ if buttons:
113
+ message_copy[BUTTONS] = buttons
114
+ return message_copy
81
115
 
82
116
  def generate_from_slots(
83
117
  self,
@@ -85,12 +119,15 @@ class TemplatedNaturalLanguageGenerator(NaturalLanguageGenerator):
85
119
  filled_slots: Dict[Text, Any],
86
120
  stack_context: Dict[Text, Any],
87
121
  output_channel: Text,
122
+ language: Optional[Language] = None,
88
123
  **kwargs: Any,
89
124
  ) -> Optional[Dict[Text, Any]]:
90
125
  """Generate a response for the requested utter action."""
91
126
  # Fetching a random response for the passed utter action
92
127
  r = copy.deepcopy(
93
- self._random_response_for(utter_action, output_channel, filled_slots)
128
+ self._random_response_for(
129
+ utter_action, output_channel, filled_slots, language
130
+ )
94
131
  )
95
132
  # Filling the slots in the response with placeholders and returning the response
96
133
  if r is not None: