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.
- rasa/core/channels/development_inspector.py +47 -14
- rasa/core/channels/inspector/dist/assets/{arc-0b11fe30.js → arc-1ddec37b.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-9eef30a7.js → blockDiagram-38ab4fdb-18af387c.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-03e94f28.js → c4Diagram-3d4e48cf-250127a3.js} +1 -1
- rasa/core/channels/inspector/dist/assets/channel-59f6d54b.js +1 -0
- rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-95c09eba.js → classDiagram-70f12bd4-c3388b34.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-38e8446c.js → classDiagram-v2-f2320105-9c893a82.js} +1 -1
- rasa/core/channels/inspector/dist/assets/clone-26177ddb.js +1 -0
- rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-57dc3038.js → createText-2e5e7dd3-c111213b.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-4bac0545.js → edges-e0da2a9e-812a729d.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-81795c90.js → erDiagram-9861fffd-fd5051bc.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-89489ae6.js → flowDb-956e92f1-3287ac02.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-cd152627.js → flowDiagram-66a62f08-692fb0b2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-29c03f5a.js +1 -0
- rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-3da369bc.js → flowchart-elk-definition-4a651766-008376f1.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-85ec16f8.js → ganttDiagram-c361ad54-df330a69.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-495bc140.js → gitGraphDiagram-72cf32ee-e03676fb.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{graph-1ec4d266.js → graph-46fad2ba.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-3862675e-0a0e97c9.js → index-3862675e-a484ac55.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-c804b295.js → index-a003633f.js} +164 -164
- rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-4d54bcde.js → infoDiagram-f8f76790-3f9e6ec2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-dc097114.js → journeyDiagram-49397b02-79f72383.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{layout-1a08981e.js → layout-aad098e5.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{line-95f7f1d3.js → line-219ab7ae.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{linear-97e69543.js → linear-2cddbe62.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-8c71ff03.js → mindmap-definition-fc14e90a-1d41ed99.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-f14c71c7.js → pieDiagram-8a3498a8-cc496ee8.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-f1d3c9ff.js → quadrantDiagram-120e2f19-84d32884.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-bfa2412f.js → requirementDiagram-deff3bca-c0deb984.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-53f2c97b.js → sankeyDiagram-04a897e0-b9d7fd62.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-319d7c0e.js → sequenceDiagram-704730f1-7d517565.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-76a09418.js → stateDiagram-587899a1-98ef9b27.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-a67f15d4.js → stateDiagram-v2-d93cdb3a-cee70748.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-0654e7c3.js → styles-6aaf32cf-3f9d1c96.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-9a916d00-1394bb9d.js → styles-9a916d00-67471923.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-c10674c1-e4c5bdae.js → styles-c10674c1-bd093fb7.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-50957104.js → svgDrawCommon-08f97a94-675794e8.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-b0885a6a.js → timeline-definition-85554ec2-0ac67617.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-79e6541a.js → xychartDiagram-e933f94c-c018dc37.js} +1 -1
- rasa/core/channels/inspector/dist/index.html +2 -2
- rasa/core/channels/inspector/index.html +1 -1
- rasa/core/channels/inspector/src/App.tsx +53 -7
- rasa/core/channels/inspector/src/components/Chat.tsx +3 -2
- rasa/core/channels/inspector/src/components/DiagramFlow.tsx +1 -1
- rasa/core/channels/inspector/src/components/LatencyDisplay.tsx +268 -0
- rasa/core/channels/inspector/src/components/LoadingSpinner.tsx +6 -2
- rasa/core/channels/inspector/src/helpers/audio/audiostream.ts +8 -3
- rasa/core/channels/inspector/src/types.ts +8 -0
- rasa/core/channels/studio_chat.py +59 -15
- rasa/core/channels/voice_stream/audiocodes.py +2 -2
- rasa/core/channels/voice_stream/browser_audio.py +20 -3
- rasa/core/channels/voice_stream/call_state.py +13 -2
- rasa/core/channels/voice_stream/genesys.py +2 -2
- rasa/core/channels/voice_stream/jambonz.py +2 -2
- rasa/core/channels/voice_stream/twilio_media_streams.py +2 -2
- rasa/core/channels/voice_stream/voice_channel.py +83 -13
- rasa/core/nlg/contextual_response_rephraser.py +13 -2
- rasa/dialogue_understanding/processor/command_processor.py +27 -11
- rasa/model_manager/socket_bridge.py +1 -2
- rasa/studio/upload.py +7 -4
- rasa/studio/utils.py +33 -22
- rasa/version.py +1 -1
- {rasa_pro-3.14.0.dev20250731.dist-info → rasa_pro-3.14.0.dev20250818.dist-info}/METADATA +6 -6
- {rasa_pro-3.14.0.dev20250731.dist-info → rasa_pro-3.14.0.dev20250818.dist-info}/RECORD +67 -66
- rasa/core/channels/inspector/dist/assets/channel-51d02e9e.js +0 -1
- rasa/core/channels/inspector/dist/assets/clone-cc738fa6.js +0 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-0c716443.js +0 -1
- {rasa_pro-3.14.0.dev20250731.dist-info → rasa_pro-3.14.0.dev20250818.dist-info}/NOTICE +0 -0
- {rasa_pro-3.14.0.dev20250731.dist-info → rasa_pro-3.14.0.dev20250818.dist-info}/WHEEL +0 -0
- {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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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"][
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 = (
|
|
445
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
"""
|
|
14
|
+
"""Validate every path passed via CLI arguments.
|
|
15
15
|
|
|
16
16
|
Args:
|
|
17
|
-
args:
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: rasa-pro
|
|
3
|
-
Version: 3.14.0.
|
|
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.
|
|
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.
|
|
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.
|
|
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 (>=
|
|
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)
|