rasa-pro 3.14.0.dev20250731__py3-none-any.whl → 3.14.0.dev20250818__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 (70) hide show
  1. rasa/core/channels/development_inspector.py +47 -14
  2. rasa/core/channels/inspector/dist/assets/{arc-0b11fe30.js → arc-1ddec37b.js} +1 -1
  3. rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-9eef30a7.js → blockDiagram-38ab4fdb-18af387c.js} +1 -1
  4. rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-03e94f28.js → c4Diagram-3d4e48cf-250127a3.js} +1 -1
  5. rasa/core/channels/inspector/dist/assets/channel-59f6d54b.js +1 -0
  6. rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-95c09eba.js → classDiagram-70f12bd4-c3388b34.js} +1 -1
  7. rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-38e8446c.js → classDiagram-v2-f2320105-9c893a82.js} +1 -1
  8. rasa/core/channels/inspector/dist/assets/clone-26177ddb.js +1 -0
  9. rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-57dc3038.js → createText-2e5e7dd3-c111213b.js} +1 -1
  10. rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-4bac0545.js → edges-e0da2a9e-812a729d.js} +1 -1
  11. rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-81795c90.js → erDiagram-9861fffd-fd5051bc.js} +1 -1
  12. rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-89489ae6.js → flowDb-956e92f1-3287ac02.js} +1 -1
  13. rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-cd152627.js → flowDiagram-66a62f08-692fb0b2.js} +1 -1
  14. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-29c03f5a.js +1 -0
  15. rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-3da369bc.js → flowchart-elk-definition-4a651766-008376f1.js} +1 -1
  16. rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-85ec16f8.js → ganttDiagram-c361ad54-df330a69.js} +1 -1
  17. rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-495bc140.js → gitGraphDiagram-72cf32ee-e03676fb.js} +1 -1
  18. rasa/core/channels/inspector/dist/assets/{graph-1ec4d266.js → graph-46fad2ba.js} +1 -1
  19. rasa/core/channels/inspector/dist/assets/{index-3862675e-0a0e97c9.js → index-3862675e-a484ac55.js} +1 -1
  20. rasa/core/channels/inspector/dist/assets/{index-c804b295.js → index-a003633f.js} +164 -164
  21. rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-4d54bcde.js → infoDiagram-f8f76790-3f9e6ec2.js} +1 -1
  22. rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-dc097114.js → journeyDiagram-49397b02-79f72383.js} +1 -1
  23. rasa/core/channels/inspector/dist/assets/{layout-1a08981e.js → layout-aad098e5.js} +1 -1
  24. rasa/core/channels/inspector/dist/assets/{line-95f7f1d3.js → line-219ab7ae.js} +1 -1
  25. rasa/core/channels/inspector/dist/assets/{linear-97e69543.js → linear-2cddbe62.js} +1 -1
  26. rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-8c71ff03.js → mindmap-definition-fc14e90a-1d41ed99.js} +1 -1
  27. rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-f14c71c7.js → pieDiagram-8a3498a8-cc496ee8.js} +1 -1
  28. rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-f1d3c9ff.js → quadrantDiagram-120e2f19-84d32884.js} +1 -1
  29. rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-bfa2412f.js → requirementDiagram-deff3bca-c0deb984.js} +1 -1
  30. rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-53f2c97b.js → sankeyDiagram-04a897e0-b9d7fd62.js} +1 -1
  31. rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-319d7c0e.js → sequenceDiagram-704730f1-7d517565.js} +1 -1
  32. rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-76a09418.js → stateDiagram-587899a1-98ef9b27.js} +1 -1
  33. rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-a67f15d4.js → stateDiagram-v2-d93cdb3a-cee70748.js} +1 -1
  34. rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-0654e7c3.js → styles-6aaf32cf-3f9d1c96.js} +1 -1
  35. rasa/core/channels/inspector/dist/assets/{styles-9a916d00-1394bb9d.js → styles-9a916d00-67471923.js} +1 -1
  36. rasa/core/channels/inspector/dist/assets/{styles-c10674c1-e4c5bdae.js → styles-c10674c1-bd093fb7.js} +1 -1
  37. rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-50957104.js → svgDrawCommon-08f97a94-675794e8.js} +1 -1
  38. rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-b0885a6a.js → timeline-definition-85554ec2-0ac67617.js} +1 -1
  39. rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-79e6541a.js → xychartDiagram-e933f94c-c018dc37.js} +1 -1
  40. rasa/core/channels/inspector/dist/index.html +2 -2
  41. rasa/core/channels/inspector/index.html +1 -1
  42. rasa/core/channels/inspector/src/App.tsx +53 -7
  43. rasa/core/channels/inspector/src/components/Chat.tsx +3 -2
  44. rasa/core/channels/inspector/src/components/DiagramFlow.tsx +1 -1
  45. rasa/core/channels/inspector/src/components/LatencyDisplay.tsx +268 -0
  46. rasa/core/channels/inspector/src/components/LoadingSpinner.tsx +6 -2
  47. rasa/core/channels/inspector/src/helpers/audio/audiostream.ts +8 -3
  48. rasa/core/channels/inspector/src/types.ts +8 -0
  49. rasa/core/channels/studio_chat.py +59 -15
  50. rasa/core/channels/voice_stream/audiocodes.py +2 -2
  51. rasa/core/channels/voice_stream/browser_audio.py +20 -3
  52. rasa/core/channels/voice_stream/call_state.py +13 -2
  53. rasa/core/channels/voice_stream/genesys.py +2 -2
  54. rasa/core/channels/voice_stream/jambonz.py +2 -2
  55. rasa/core/channels/voice_stream/twilio_media_streams.py +2 -2
  56. rasa/core/channels/voice_stream/voice_channel.py +83 -13
  57. rasa/core/nlg/contextual_response_rephraser.py +13 -2
  58. rasa/dialogue_understanding/processor/command_processor.py +27 -11
  59. rasa/model_manager/socket_bridge.py +1 -2
  60. rasa/studio/upload.py +7 -4
  61. rasa/studio/utils.py +33 -22
  62. rasa/version.py +1 -1
  63. {rasa_pro-3.14.0.dev20250731.dist-info → rasa_pro-3.14.0.dev20250818.dist-info}/METADATA +6 -6
  64. {rasa_pro-3.14.0.dev20250731.dist-info → rasa_pro-3.14.0.dev20250818.dist-info}/RECORD +67 -66
  65. rasa/core/channels/inspector/dist/assets/channel-51d02e9e.js +0 -1
  66. rasa/core/channels/inspector/dist/assets/clone-cc738fa6.js +0 -1
  67. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-0c716443.js +0 -1
  68. {rasa_pro-3.14.0.dev20250731.dist-info → rasa_pro-3.14.0.dev20250818.dist-info}/NOTICE +0 -0
  69. {rasa_pro-3.14.0.dev20250731.dist-info → rasa_pro-3.14.0.dev20250818.dist-info}/WHEEL +0 -0
  70. {rasa_pro-3.14.0.dev20250731.dist-info → rasa_pro-3.14.0.dev20250818.dist-info}/entry_points.txt +0 -0
@@ -48,7 +48,24 @@ class BrowserAudioOutputChannel(VoiceOutputChannel):
48
48
 
49
49
  def create_marker_message(self, recipient_id: str) -> Tuple[str, str]:
50
50
  message_id = uuid.uuid4().hex
51
- return json.dumps({"marker": message_id}), message_id
51
+ marker_data = {"marker": message_id}
52
+
53
+ # Include comprehensive latency information if available
54
+ latency_data = {
55
+ "asr_latency_ms": call_state.asr_latency_ms,
56
+ "rasa_processing_latency_ms": call_state.rasa_processing_latency_ms,
57
+ "tts_first_byte_latency_ms": call_state.tts_first_byte_latency_ms,
58
+ "tts_complete_latency_ms": call_state.tts_complete_latency_ms,
59
+ }
60
+
61
+ # Filter out None values from latency data
62
+ latency_data = {k: v for k, v in latency_data.items() if v is not None}
63
+
64
+ # Add latency data to marker if any metrics are available
65
+ if latency_data:
66
+ marker_data["latency"] = latency_data # type: ignore[assignment]
67
+
68
+ return json.dumps(marker_data), message_id
52
69
 
53
70
 
54
71
  class BrowserAudioInputChannel(VoiceInputChannel):
@@ -93,14 +110,14 @@ class BrowserAudioInputChannel(VoiceInputChannel):
93
110
  elif "marker" in data:
94
111
  if data["marker"] == call_state.latest_bot_audio_id:
95
112
  # Just finished streaming last audio bytes
96
- call_state.is_bot_speaking = False # type: ignore[attr-defined]
113
+ call_state.is_bot_speaking = False
97
114
  if call_state.should_hangup:
98
115
  logger.debug(
99
116
  "browser_audio.hangup", marker=call_state.latest_bot_audio_id
100
117
  )
101
118
  return EndConversationAction()
102
119
  else:
103
- call_state.is_bot_speaking = True # type: ignore[attr-defined]
120
+ call_state.is_bot_speaking = True
104
121
  return ContinueConversationAction()
105
122
 
106
123
  def create_output_channel(
@@ -1,7 +1,7 @@
1
1
  import asyncio
2
2
  from contextvars import ContextVar
3
3
  from dataclasses import dataclass, field
4
- from typing import Any, Dict, Optional
4
+ from typing import Any, Dict, Optional, cast
5
5
 
6
6
  from werkzeug.local import LocalProxy
7
7
 
@@ -19,9 +19,20 @@ class CallState:
19
19
  should_hangup: bool = False
20
20
  connection_failed: bool = False
21
21
 
22
+ # Latency tracking - start times only
23
+ user_speech_start_time: Optional[float] = None
24
+ rasa_processing_start_time: Optional[float] = None
25
+ tts_start_time: Optional[float] = None
26
+
27
+ # Calculated latencies (used by channels like browser_audio)
28
+ asr_latency_ms: Optional[float] = None
29
+ rasa_processing_latency_ms: Optional[float] = None
30
+ tts_first_byte_latency_ms: Optional[float] = None
31
+ tts_complete_latency_ms: Optional[float] = None
32
+
22
33
  # Generic field for channel-specific state data
23
34
  channel_data: Dict[str, Any] = field(default_factory=dict)
24
35
 
25
36
 
26
37
  _call_state: ContextVar[CallState] = ContextVar("call_state")
27
- call_state = LocalProxy(_call_state)
38
+ call_state: CallState = cast(CallState, LocalProxy(_call_state))
@@ -219,10 +219,10 @@ class GenesysInputChannel(VoiceInputChannel):
219
219
  self.handle_ping(ws, data)
220
220
  elif msg_type == "playback_started":
221
221
  logger.debug("genesys.handle_playback_started", message=data)
222
- call_state.is_bot_speaking = True # type: ignore[attr-defined]
222
+ call_state.is_bot_speaking = True
223
223
  elif msg_type == "playback_completed":
224
224
  logger.debug("genesys.handle_playback_completed", message=data)
225
- call_state.is_bot_speaking = False # type: ignore[attr-defined]
225
+ call_state.is_bot_speaking = False
226
226
  if call_state.should_hangup:
227
227
  logger.info("genesys.hangup")
228
228
  self.disconnect(ws, data)
@@ -160,14 +160,14 @@ class JambonzStreamInputChannel(VoiceInputChannel):
160
160
  if data["type"] == "mark":
161
161
  if data["data"]["name"] == call_state.latest_bot_audio_id:
162
162
  # Just finished streaming last audio bytes
163
- call_state.is_bot_speaking = False # type: ignore[attr-defined]
163
+ call_state.is_bot_speaking = False
164
164
  if call_state.should_hangup:
165
165
  logger.debug(
166
166
  "jambonz.hangup", marker=call_state.latest_bot_audio_id
167
167
  )
168
168
  return EndConversationAction()
169
169
  else:
170
- call_state.is_bot_speaking = True # type: ignore[attr-defined]
170
+ call_state.is_bot_speaking = True
171
171
  elif data["event"] == "dtmf":
172
172
  # TODO: handle DTMF input
173
173
  logger.debug("jambonz.dtmf.received", dtmf=data["dtmf"])
@@ -176,14 +176,14 @@ class TwilioMediaStreamsInputChannel(VoiceInputChannel):
176
176
  elif data["event"] == "mark":
177
177
  if data["mark"]["name"] == call_state.latest_bot_audio_id:
178
178
  # Just finished streaming last audio bytes
179
- call_state.is_bot_speaking = False # type: ignore[attr-defined]
179
+ call_state.is_bot_speaking = False
180
180
  if call_state.should_hangup:
181
181
  logger.debug(
182
182
  "twilio_streams.hangup", marker=call_state.latest_bot_audio_id
183
183
  )
184
184
  return EndConversationAction()
185
185
  else:
186
- call_state.is_bot_speaking = True # type: ignore[attr-defined]
186
+ call_state.is_bot_speaking = True
187
187
  return ContinueConversationAction()
188
188
 
189
189
  def create_output_channel(
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import copy
5
+ import time
5
6
  from dataclasses import asdict, dataclass
6
7
  from typing import Any, AsyncIterator, Awaitable, Callable, Dict, List, Optional, Tuple
7
8
 
@@ -191,7 +192,7 @@ class VoiceOutputChannel(OutputChannel):
191
192
  def update_silence_timeout(self) -> None:
192
193
  """Updates the silence timeout for the session."""
193
194
  if self.tracker_state:
194
- call_state.silence_timeout = self.tracker_state["slots"][ # type: ignore[attr-defined]
195
+ call_state.silence_timeout = self.tracker_state["slots"][
195
196
  SILENCE_TIMEOUT_SLOT
196
197
  ]
197
198
  logger.debug(
@@ -209,22 +210,63 @@ class VoiceOutputChannel(OutputChannel):
209
210
  """Uses the concise button output format for voice channels."""
210
211
  await self.send_text_with_buttons_concise(recipient_id, text, buttons, **kwargs)
211
212
 
213
+ def _track_rasa_processing_latency(self) -> None:
214
+ """Track and log Rasa processing completion latency."""
215
+ if call_state.rasa_processing_start_time:
216
+ call_state.rasa_processing_latency_ms = (
217
+ time.time() - call_state.rasa_processing_start_time
218
+ ) * 1000
219
+ logger.debug(
220
+ "voice_channel.rasa_processing_latency",
221
+ latency_ms=call_state.rasa_processing_latency_ms,
222
+ )
223
+
224
+ def _track_tts_first_byte_latency(self) -> None:
225
+ """Track and log TTS first byte latency."""
226
+ if call_state.tts_start_time:
227
+ call_state.tts_first_byte_latency_ms = (
228
+ time.time() - call_state.tts_start_time
229
+ ) * 1000
230
+ logger.debug(
231
+ "voice_channel.tts_first_byte_latency",
232
+ latency_ms=call_state.tts_first_byte_latency_ms,
233
+ )
234
+
235
+ def _track_tts_complete_latency(self) -> None:
236
+ """Track and log TTS completion latency."""
237
+ if call_state.tts_start_time:
238
+ call_state.tts_complete_latency_ms = (
239
+ time.time() - call_state.tts_start_time
240
+ ) * 1000
241
+ logger.debug(
242
+ "voice_channel.tts_complete_latency",
243
+ latency_ms=call_state.tts_complete_latency_ms,
244
+ )
245
+
212
246
  async def send_text_message(
213
247
  self, recipient_id: str, text: str, **kwargs: Any
214
248
  ) -> None:
215
249
  text = remove_emojis(text)
216
250
  self.update_silence_timeout()
251
+
252
+ # Track Rasa processing completion
253
+ self._track_rasa_processing_latency()
254
+
255
+ # Track TTS start time
256
+ call_state.tts_start_time = time.time()
257
+
217
258
  cached_audio_bytes = self.tts_cache.get(text)
218
259
  collected_audio_bytes = RasaAudioBytes(b"")
219
260
  seconds_marker = -1
220
261
  last_sent_offset = 0
262
+ first_audio_sent = False
221
263
  logger.debug("voice_channel.sending_audio", text=text)
222
264
 
223
265
  # Send start marker before first chunk
224
266
  try:
225
267
  await self.send_start_marker(recipient_id)
226
268
  except (WebsocketClosed, ServerError):
227
- call_state.connection_failed = True # type: ignore[attr-defined]
269
+ call_state.connection_failed = True
228
270
 
229
271
  if cached_audio_bytes:
230
272
  audio_stream = self.chunk_audio(cached_audio_bytes)
@@ -246,6 +288,11 @@ class VoiceOutputChannel(OutputChannel):
246
288
 
247
289
  if should_send:
248
290
  try:
291
+ # Track TTS first byte time
292
+ if not first_audio_sent:
293
+ self._track_tts_first_byte_latency()
294
+ first_audio_sent = True
295
+
249
296
  # Send only the new bytes since last send
250
297
  new_bytes = RasaAudioBytes(collected_audio_bytes[last_sent_offset:])
251
298
  await self.send_audio_bytes(recipient_id, new_bytes)
@@ -258,24 +305,31 @@ class VoiceOutputChannel(OutputChannel):
258
305
 
259
306
  except (WebsocketClosed, ServerError):
260
307
  # ignore sending error, and keep collecting and caching audio bytes
261
- call_state.connection_failed = True # type: ignore[attr-defined]
308
+ call_state.connection_failed = True
262
309
 
263
310
  # Send any remaining audio not yet sent
264
311
  remaining_bytes = len(collected_audio_bytes) - last_sent_offset
265
312
  if remaining_bytes > 0:
266
313
  try:
314
+ # Track TTS first byte time if not already tracked
315
+ if not first_audio_sent:
316
+ self._track_tts_first_byte_latency()
317
+
267
318
  new_bytes = RasaAudioBytes(collected_audio_bytes[last_sent_offset:])
268
319
  await self.send_audio_bytes(recipient_id, new_bytes)
269
320
  except (WebsocketClosed, ServerError):
270
321
  # ignore sending error
271
- call_state.connection_failed = True # type: ignore[attr-defined]
322
+ call_state.connection_failed = True
323
+
324
+ # Track TTS completion time
325
+ self._track_tts_complete_latency()
272
326
 
273
327
  try:
274
328
  await self.send_end_marker(recipient_id)
275
329
  except (WebsocketClosed, ServerError):
276
330
  # ignore sending error
277
331
  pass
278
- call_state.latest_bot_audio_id = self.latest_message_id # type: ignore[attr-defined]
332
+ call_state.latest_bot_audio_id = self.latest_message_id
279
333
 
280
334
  if not cached_audio_bytes:
281
335
  self.tts_cache.put(text, collected_audio_bytes)
@@ -300,7 +354,7 @@ class VoiceOutputChannel(OutputChannel):
300
354
  return
301
355
 
302
356
  async def hangup(self, recipient_id: str, **kwargs: Any) -> None:
303
- call_state.should_hangup = True # type: ignore[attr-defined]
357
+ call_state.should_hangup = True
304
358
 
305
359
 
306
360
  class VoiceInputChannel(InputChannel):
@@ -347,7 +401,7 @@ class VoiceInputChannel(InputChannel):
347
401
  if call_state.silence_timeout_watcher:
348
402
  logger.debug("voice_channel.cancelling_current_timeout_watcher_task")
349
403
  call_state.silence_timeout_watcher.cancel()
350
- call_state.silence_timeout_watcher = None # type: ignore[attr-defined]
404
+ call_state.silence_timeout_watcher = None
351
405
 
352
406
  @classmethod
353
407
  def validate_basic_credentials(cls, credentials: Optional[Dict[str, Any]]) -> None:
@@ -441,10 +495,8 @@ class VoiceInputChannel(InputChannel):
441
495
  if was_bot_speaking_before and not is_bot_speaking_after:
442
496
  logger.debug("voice_channel.bot_stopped_speaking")
443
497
  self._cancel_silence_timeout_watcher()
444
- call_state.silence_timeout_watcher = ( # type: ignore[attr-defined]
445
- asyncio.create_task(
446
- self.monitor_silence_timeout(asr_event_queue)
447
- )
498
+ call_state.silence_timeout_watcher = asyncio.create_task(
499
+ self.monitor_silence_timeout(asr_event_queue)
448
500
  )
449
501
  if isinstance(channel_action, NewAudioAction):
450
502
  await asr_engine.send_audio_chunks(channel_action.audio_bytes)
@@ -500,6 +552,16 @@ class VoiceInputChannel(InputChannel):
500
552
  """Create a matching voice output channel for this voice input channel."""
501
553
  raise NotImplementedError
502
554
 
555
+ def _track_asr_latency(self) -> None:
556
+ """Track and log ASR processing latency."""
557
+ if call_state.user_speech_start_time:
558
+ call_state.asr_latency_ms = (
559
+ time.time() - call_state.user_speech_start_time
560
+ ) * 1000
561
+ logger.debug(
562
+ "voice_channel.asr_latency", latency_ms=call_state.asr_latency_ms
563
+ )
564
+
503
565
  async def handle_asr_event(
504
566
  self,
505
567
  e: ASREvent,
@@ -513,7 +575,12 @@ class VoiceInputChannel(InputChannel):
513
575
  logger.debug(
514
576
  "VoiceInputChannel.handle_asr_event.new_transcript", transcript=e.text
515
577
  )
516
- call_state.is_user_speaking = False # type: ignore[attr-defined]
578
+ call_state.is_user_speaking = False
579
+
580
+ # Track ASR and Rasa latencies
581
+ self._track_asr_latency()
582
+ call_state.rasa_processing_start_time = time.time()
583
+
517
584
  output_channel = self.create_output_channel(voice_websocket, tts_engine)
518
585
  message = UserMessage(
519
586
  text=e.text,
@@ -524,8 +591,11 @@ class VoiceInputChannel(InputChannel):
524
591
  )
525
592
  await on_new_message(message)
526
593
  elif isinstance(e, UserIsSpeaking):
594
+ # Track when user starts speaking for ASR latency calculation
595
+ if not call_state.is_user_speaking:
596
+ call_state.user_speech_start_time = time.time()
527
597
  self._cancel_silence_timeout_watcher()
528
- call_state.is_user_speaking = True # type: ignore[attr-defined]
598
+ call_state.is_user_speaking = True
529
599
  elif isinstance(e, UserSilence):
530
600
  output_channel = self.create_output_channel(voice_websocket, tts_engine)
531
601
  message = UserMessage(
@@ -24,6 +24,7 @@ from rasa.shared.constants import (
24
24
  )
25
25
  from rasa.shared.core.domain import KEY_RESPONSES_TEXT, Domain
26
26
  from rasa.shared.core.events import BotUttered, UserUttered
27
+ from rasa.shared.core.flows.constants import KEY_TRANSLATION
27
28
  from rasa.shared.core.trackers import DialogueStateTracker
28
29
  from rasa.shared.nlu.constants import (
29
30
  KEY_COMPONENT_NAME,
@@ -307,7 +308,12 @@ class ContextualResponseRephraser(
307
308
  Returns:
308
309
  The response with the rephrased text.
309
310
  """
310
- if not (response_text := response.get(KEY_RESPONSES_TEXT)):
311
+ translation_response = response.get(KEY_TRANSLATION) or {}
312
+ lang_code = getattr(tracker.current_language, "code", None)
313
+ response_text = translation_response.get(
314
+ lang_code, response.get(KEY_RESPONSES_TEXT)
315
+ )
316
+ if not response_text:
311
317
  return response
312
318
 
313
319
  prompt_template_text = self._template_for_response_rephrasing(response)
@@ -369,12 +375,17 @@ class ContextualResponseRephraser(
369
375
  return response
370
376
 
371
377
  updated_text = llm_response.choices[0]
378
+
379
+ if lang_code in translation_response:
380
+ response[KEY_TRANSLATION][lang_code] = updated_text
381
+ else:
382
+ response[KEY_RESPONSES_TEXT] = updated_text
383
+
372
384
  structlogger.debug(
373
385
  "nlg.rewrite.complete",
374
386
  response_text=response_text,
375
387
  updated_text=updated_text,
376
388
  )
377
- response[KEY_RESPONSES_TEXT] = updated_text
378
389
  return response
379
390
 
380
391
  def does_response_allow_rephrasing(self, template: Dict[Text, Any]) -> bool:
@@ -398,19 +398,12 @@ def clean_up_commands(
398
398
  """
399
399
  domain = domain if domain else Domain.empty()
400
400
 
401
- # we consider all slots that were set in the tracker for potential corrections
402
- # in the correct_slot_command we will check if a slot should actually be
403
- # corrected
404
- slots_so_far = set(
405
- [event.key for event in tracker.events if isinstance(event, SlotSet)]
406
- )
407
-
408
401
  clean_commands: List[Command] = []
409
402
 
410
403
  for command in commands:
411
404
  if isinstance(command, SetSlotCommand):
412
405
  clean_commands = clean_up_slot_command(
413
- clean_commands, command, tracker, all_flows, slots_so_far
406
+ clean_commands, command, tracker, all_flows
414
407
  )
415
408
 
416
409
  elif isinstance(command, CancelFlowCommand) and contains_command(
@@ -501,6 +494,25 @@ def clean_up_commands(
501
494
  return clean_commands
502
495
 
503
496
 
497
+ def _get_slots_eligible_for_correction(tracker: DialogueStateTracker) -> Set[str]:
498
+ """Get all slots that are eligible for correction.
499
+
500
+ # We consider all slots, which are not None, that were set in the tracker
501
+ # eligible for correction.
502
+ # In the correct_slot_command we will check if a slot should actually be
503
+ # corrected.
504
+ """
505
+ # get all slots that were set in the tracker
506
+ slots_so_far = set(
507
+ [event.key for event in tracker.events if isinstance(event, SlotSet)]
508
+ )
509
+
510
+ # filter out slots that are set to None (None = empty value)
511
+ slots_so_far = {slot for slot in slots_so_far if tracker.get_slot(slot) is not None}
512
+
513
+ return slots_so_far
514
+
515
+
504
516
  def ensure_max_number_of_command_type(
505
517
  commands: List[Command], command_type: Type[Command], n: int
506
518
  ) -> List[Command]:
@@ -560,7 +572,6 @@ def clean_up_slot_command(
560
572
  command: SetSlotCommand,
561
573
  tracker: DialogueStateTracker,
562
574
  all_flows: FlowsList,
563
- slots_so_far: Set[str],
564
575
  ) -> List[Command]:
565
576
  """Clean up a slot command.
566
577
 
@@ -573,7 +584,6 @@ def clean_up_slot_command(
573
584
  command: The command to clean up.
574
585
  tracker: The dialogue state tracker.
575
586
  all_flows: All flows.
576
- slots_so_far: The slots that have been filled so far.
577
587
 
578
588
  Returns:
579
589
  The cleaned up commands.
@@ -642,7 +652,13 @@ def clean_up_slot_command(
642
652
  )
643
653
  return resulting_commands
644
654
 
645
- if command.name in slots_so_far and command.name != ROUTE_TO_CALM_SLOT:
655
+ # get all slots that were set in the tracker and are eligible for correction
656
+ slots_eligible_for_correction = _get_slots_eligible_for_correction(tracker)
657
+
658
+ if (
659
+ command.name in slots_eligible_for_correction
660
+ and command.name != ROUTE_TO_CALM_SLOT
661
+ ):
646
662
  current_collect_info = get_current_collect_step(stack, all_flows)
647
663
 
648
664
  if current_collect_info and current_collect_info.collect == command.name:
@@ -2,8 +2,7 @@ import json
2
2
  from typing import Any, Dict, Optional
3
3
 
4
4
  import structlog
5
- from socketio import AsyncServer
6
- from socketio.asyncio_client import AsyncClient
5
+ from socketio import AsyncClient, AsyncServer # type: ignore[attr-defined]
7
6
  from socketio.exceptions import ConnectionRefusedError
8
7
 
9
8
  from rasa.model_manager.runner_service import BotSession
rasa/studio/upload.py CHANGED
@@ -115,9 +115,10 @@ def run_validation(args: argparse.Namespace) -> None:
115
115
  """
116
116
  from rasa.validator import Validator
117
117
 
118
+ training_data_paths = args.data if isinstance(args.data, list) else [args.data]
118
119
  training_data_importer = TrainingDataImporter.load_from_dict(
119
120
  domain_path=args.domain,
120
- training_data_paths=[args.data],
121
+ training_data_paths=training_data_paths,
121
122
  config_path=args.config,
122
123
  expand_env_vars=False,
123
124
  )
@@ -263,8 +264,9 @@ def build_calm_import_parts(
263
264
  domain_from_files = importer.get_user_domain().as_dict()
264
265
  domain = extract_values(domain_from_files, DOMAIN_KEYS)
265
266
 
267
+ training_data_paths = data_path if isinstance(data_path, list) else [str(data_path)]
266
268
  flow_importer = FlowSyncImporter.load_from_dict(
267
- training_data_paths=[str(data_path)], expand_env_vars=False
269
+ training_data_paths=training_data_paths, expand_env_vars=False
268
270
  )
269
271
 
270
272
  flows = list(flow_importer.get_user_flows())
@@ -272,7 +274,7 @@ def build_calm_import_parts(
272
274
  flows = read_yaml(flows_yaml, expand_env_vars=False)
273
275
 
274
276
  nlu_importer = TrainingDataImporter.load_from_dict(
275
- training_data_paths=[str(data_path)], expand_env_vars=False
277
+ training_data_paths=training_data_paths, expand_env_vars=False
276
278
  )
277
279
  nlu_data = nlu_importer.get_nlu_data()
278
280
  nlu_examples = nlu_data.filter_training_examples(
@@ -349,9 +351,10 @@ def upload_nlu_assistant(
349
351
  "rasa.studio.upload.nlu_data_read",
350
352
  event_info="Found DM1 assistant data, parsing...",
351
353
  )
354
+ training_data_paths = args.data if isinstance(args.data, list) else [args.data]
352
355
  importer = TrainingDataImporter.load_from_dict(
353
356
  domain_path=args.domain,
354
- training_data_paths=[args.data],
357
+ training_data_paths=training_data_paths,
355
358
  config_path=args.config,
356
359
  expand_env_vars=False,
357
360
  )
rasa/studio/utils.py CHANGED
@@ -1,33 +1,44 @@
1
1
  import argparse
2
2
  from pathlib import Path
3
+ from typing import List
3
4
 
4
5
  import rasa.shared.utils.cli
5
- from rasa.shared.constants import (
6
- DEFAULT_CONFIG_PATH,
7
- DEFAULT_DATA_PATH,
8
- DEFAULT_ENDPOINTS_PATH,
9
- )
10
- from rasa.studio.constants import DOMAIN_FILENAME
6
+
7
+ DOMAIN_FILENAME = "domain.yml"
8
+ DEFAULT_CONFIG_PATH = "config.yml"
9
+ DEFAULT_ENDPOINTS_PATH = "endpoints.yml"
10
+ DEFAULT_DATA_PATH = "data"
11
11
 
12
12
 
13
13
  def validate_argument_paths(args: argparse.Namespace) -> None:
14
- """Validates the paths provided in the command line arguments.
14
+ """Validate every path passed via CLI arguments.
15
15
 
16
16
  Args:
17
- args: The command line arguments containing paths to validate.
17
+ args: CLI arguments containing paths to validate.
18
+
19
+ Raises:
20
+ rasa.shared.utils.cli.PrintErrorAndExit: If any path does not exist.
18
21
  """
22
+ invalid_paths: List[str] = []
23
+
24
+ def collect_invalid_paths(arg_name: str, default: str) -> None:
25
+ value = getattr(args, arg_name, None)
26
+ path_values = value if isinstance(value, list) else [value]
27
+ for path_value in path_values:
28
+ if not path_value or path_value == default:
29
+ continue
30
+
31
+ if not Path(path_value).resolve().exists():
32
+ invalid_paths.append(f"{arg_name}: '{path_value}'")
33
+
34
+ collect_invalid_paths("domain", DOMAIN_FILENAME)
35
+ collect_invalid_paths("config", DEFAULT_CONFIG_PATH)
36
+ collect_invalid_paths("endpoints", DEFAULT_ENDPOINTS_PATH)
37
+ collect_invalid_paths("data", DEFAULT_DATA_PATH)
19
38
 
20
- def validate_path(arg_name: str, default: str) -> None:
21
- path_value = getattr(args, arg_name, None)
22
- if path_value and path_value != default:
23
- resolved_path = Path(path_value).resolve()
24
- if not resolved_path.exists():
25
- rasa.shared.utils.cli.print_error_and_exit(
26
- f"{arg_name.capitalize()} file or directory "
27
- f"'{path_value}' does not exist."
28
- )
29
-
30
- validate_path("domain", DOMAIN_FILENAME)
31
- validate_path("config", DEFAULT_CONFIG_PATH)
32
- validate_path("endpoints", DEFAULT_ENDPOINTS_PATH)
33
- validate_path("data", DEFAULT_DATA_PATH)
39
+ if invalid_paths:
40
+ message = (
41
+ "The following files or directories do not exist:\n - "
42
+ + "\n - ".join(invalid_paths)
43
+ )
44
+ rasa.shared.utils.cli.print_error_and_exit(message)
rasa/version.py CHANGED
@@ -1,3 +1,3 @@
1
1
  # this file will automatically be changed,
2
2
  # do not add anything but the version number here!
3
- __version__ = "3.14.0.dev20250731"
3
+ __version__ = "3.14.0.dev20250818"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: rasa-pro
3
- Version: 3.14.0.dev20250731
3
+ Version: 3.14.0.dev20250818
4
4
  Summary: State-of-the-art open-core Conversational AI framework for Enterprises that natively leverages generative AI for effortless assistant development.
5
5
  Keywords: nlp,machine-learning,machine-learning-library,bot,bots,botkit,rasa conversational-agents,conversational-ai,chatbot,chatbot-framework,bot-framework
6
6
  Author: Rasa Technologies GmbH
@@ -23,7 +23,7 @@ Provides-Extra: spacy
23
23
  Provides-Extra: transformers
24
24
  Requires-Dist: CacheControl (>=0.14.2,<0.15.0)
25
25
  Requires-Dist: PyJWT[crypto] (>=2.8.0,<3.0.0)
26
- Requires-Dist: SQLAlchemy (>=2.0.41,<2.1.0)
26
+ Requires-Dist: SQLAlchemy (>=2.0.42,<2.1.0)
27
27
  Requires-Dist: absl-py (>=2.0,<2.1)
28
28
  Requires-Dist: aio-pika (>=8.2.3,<9.4.4)
29
29
  Requires-Dist: aiogram (>=3.15,<3.16)
@@ -38,7 +38,7 @@ Requires-Dist: colorama (>=0.4.6,<0.5.0) ; sys_platform == "win32"
38
38
  Requires-Dist: colorclass (>=2.2,<2.3)
39
39
  Requires-Dist: coloredlogs (>=15,<16)
40
40
  Requires-Dist: colorhash (>=2.0,<2.1.0)
41
- Requires-Dist: confluent-kafka (>=2.10.0,<3.0.0)
41
+ Requires-Dist: confluent-kafka (>=2.11.0,<3.0.0)
42
42
  Requires-Dist: cryptography (>=44.0.1)
43
43
  Requires-Dist: cvg-python-sdk (>=0.5.1,<0.6.0)
44
44
  Requires-Dist: dask (>=2024.8.0,<2024.9.0)
@@ -93,9 +93,9 @@ Requires-Dist: python-dateutil (>=2.8.2,<2.9.0)
93
93
  Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
94
94
  Requires-Dist: python-engineio (>=4.12.2,<4.13.0)
95
95
  Requires-Dist: python-keycloak (>=3.12.0,<4.0.0)
96
- Requires-Dist: python-socketio (>=5.8,<6)
96
+ Requires-Dist: python-socketio (>=5.13,<6)
97
97
  Requires-Dist: pytz (>=2022.7.1,<2023.0)
98
- Requires-Dist: pyyaml (>=6.0)
98
+ Requires-Dist: pyyaml (>=6.0.2,<6.1.0)
99
99
  Requires-Dist: qdrant-client (>=1.9.1,<1.10.0)
100
100
  Requires-Dist: questionary (>=1.10.0,<2.1.0)
101
101
  Requires-Dist: randomname (>=0.2.1,<0.3.0)
@@ -120,7 +120,7 @@ Requires-Dist: sklearn-crfsuite (>=0.5.0,<0.6.0)
120
120
  Requires-Dist: skops (>=0.11.0,<0.12.0)
121
121
  Requires-Dist: slack-sdk (>=3.27.1,<3.28.0)
122
122
  Requires-Dist: spacy (>=3.5.4,<4.0.0) ; extra == "spacy" or extra == "full"
123
- Requires-Dist: structlog (>=23.1.0,<23.2.0)
123
+ Requires-Dist: structlog (>=25.4.0,<25.5.0)
124
124
  Requires-Dist: structlog-sentry (>=2.0.3,<2.1.0)
125
125
  Requires-Dist: tarsafe (>=0.0.5,<0.0.6)
126
126
  Requires-Dist: tenacity (>=8.4.1,<8.5.0)