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.
- rasa/cli/train.py +2 -2
- rasa/core/actions/action.py +27 -37
- rasa/core/actions/action_run_slot_rejections.py +1 -1
- rasa/core/channels/inspector/dist/assets/{arc-371401b1.js → arc-0b11fe30.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-3f126156.js → blockDiagram-38ab4fdb-9eef30a7.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-12f22eb7.js → c4Diagram-3d4e48cf-03e94f28.js} +1 -1
- rasa/core/channels/inspector/dist/assets/channel-51d02e9e.js +1 -0
- rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-03b1d386.js → classDiagram-70f12bd4-95c09eba.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-84f69d63.js → classDiagram-v2-f2320105-38e8446c.js} +1 -1
- rasa/core/channels/inspector/dist/assets/clone-cc738fa6.js +1 -0
- rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-ca47fd38.js → createText-2e5e7dd3-57dc3038.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-f837ca8a.js → edges-e0da2a9e-4bac0545.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-8717ac54.js → erDiagram-9861fffd-81795c90.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-94f38b83.js → flowDb-956e92f1-89489ae6.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-b616f9fb.js → flowDiagram-66a62f08-cd152627.js} +1 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-0c716443.js +1 -0
- rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-f5d24bb8.js → flowchart-elk-definition-4a651766-3da369bc.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-b43ba8d9.js → ganttDiagram-c361ad54-85ec16f8.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-c3aafaa5.js → gitGraphDiagram-72cf32ee-495bc140.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{graph-0d0a2c10.js → graph-1ec4d266.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-3862675e-58ea0305.js → index-3862675e-0a0e97c9.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-cce6f8a1.js → index-c804b295.js} +148 -148
- rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-b8f60461.js → infoDiagram-f8f76790-4d54bcde.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-95be5545.js → journeyDiagram-49397b02-dc097114.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{layout-da885b9b.js → layout-1a08981e.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{line-f1c817d3.js → line-95f7f1d3.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{linear-d42801e6.js → linear-97e69543.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-a38923a6.js → mindmap-definition-fc14e90a-8c71ff03.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-ca6e71e9.js → pieDiagram-8a3498a8-f14c71c7.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-b290dae9.js → quadrantDiagram-120e2f19-f1d3c9ff.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-03f02ceb.js → requirementDiagram-deff3bca-bfa2412f.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-c49eee40.js → sankeyDiagram-04a897e0-53f2c97b.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-b2cd6a3d.js → sequenceDiagram-704730f1-319d7c0e.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-e53a2028.js → stateDiagram-587899a1-76a09418.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-e1982a03.js → stateDiagram-v2-d93cdb3a-a67f15d4.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-d0226ca5.js → styles-6aaf32cf-0654e7c3.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-9a916d00-0e21dc00.js → styles-9a916d00-1394bb9d.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-c10674c1-9588494e.js → styles-c10674c1-e4c5bdae.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-be478d4f.js → svgDrawCommon-08f97a94-50957104.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-74631749.js → timeline-definition-85554ec2-b0885a6a.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-a043552f.js → xychartDiagram-e933f94c-79e6541a.js} +1 -1
- rasa/core/channels/inspector/dist/index.html +1 -1
- rasa/core/channels/inspector/package.json +1 -1
- rasa/core/channels/inspector/yarn.lock +4 -4
- rasa/core/channels/studio_chat.py +67 -20
- rasa/core/channels/voice_ready/twilio_voice.py +1 -1
- rasa/core/channels/voice_stream/audiocodes.py +7 -4
- rasa/core/channels/voice_stream/browser_audio.py +19 -1
- rasa/core/channels/voice_stream/genesys.py +14 -11
- rasa/core/channels/voice_stream/jambonz.py +11 -9
- rasa/core/channels/voice_stream/twilio_media_streams.py +12 -11
- rasa/core/channels/voice_stream/util.py +11 -1
- rasa/core/channels/voice_stream/voice_channel.py +18 -16
- rasa/core/nlg/contextual_response_rephraser.py +6 -7
- rasa/core/nlg/generator.py +21 -5
- rasa/core/nlg/response.py +43 -6
- rasa/core/nlg/translate.py +8 -0
- rasa/dialogue_understanding/commands/correct_slots_command.py +38 -0
- rasa/dialogue_understanding/processor/command_processor.py +127 -54
- rasa/dialogue_understanding/stack/utils.py +13 -3
- rasa/dialogue_understanding_test/du_test_schema.yml +3 -3
- rasa/e2e_test/e2e_test_schema.yml +3 -3
- rasa/model_manager/model_api.py +1 -1
- rasa/model_manager/socket_bridge.py +7 -0
- rasa/shared/utils/common.py +2 -1
- rasa/utils/licensing.py +21 -10
- rasa/utils/plotting.py +1 -1
- rasa/version.py +1 -1
- {rasa_pro-3.13.4.dist-info → rasa_pro-3.14.0.dev20250731.dist-info}/METADATA +9 -9
- {rasa_pro-3.13.4.dist-info → rasa_pro-3.14.0.dev20250731.dist-info}/RECORD +73 -73
- rasa/core/channels/inspector/dist/assets/channel-f1efda17.js +0 -1
- rasa/core/channels/inspector/dist/assets/clone-fdf164e2.js +0 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-7d7a1629.js +0 -1
- {rasa_pro-3.13.4.dist-info → rasa_pro-3.14.0.dev20250731.dist-info}/NOTICE +0 -0
- {rasa_pro-3.13.4.dist-info → rasa_pro-3.14.0.dev20250731.dist-info}/WHEEL +0 -0
- {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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
255
|
-
tracker = DialogueStateTracker.from_dict(
|
|
256
|
-
data["sender_id"], data["events"], domain.slots
|
|
257
|
-
)
|
|
281
|
+
tracker: Optional[DialogueStateTracker] = None
|
|
258
282
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
) ->
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return
|
|
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
|
|
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
|
-
) ->
|
|
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
|
-
|
|
127
|
+
cls.validate_credentials(credentials)
|
|
128
|
+
new_creds = repack_voice_credentials(credentials)
|
|
129
|
+
return cls(**new_creds)
|
|
125
130
|
|
|
126
|
-
|
|
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
|
-
|
|
133
|
-
|
|
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
|
|
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
|
-
) ->
|
|
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
|
-
|
|
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
|
|
124
|
+
cls.validate_credentials(credentials)
|
|
125
|
+
new_creds = repack_voice_credentials(credentials)
|
|
126
|
+
return cls(**new_creds)
|
|
124
127
|
|
|
125
|
-
|
|
126
|
-
|
|
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
|
|
352
|
-
|
|
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
|
-
"
|
|
359
|
+
"Credentials must be a dictionary for voice channel."
|
|
363
360
|
)
|
|
364
|
-
|
|
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
|
-
"
|
|
367
|
+
f"Missing required fields in credentials: {', '.join(missing_fields)} "
|
|
368
|
+
f"for channel {cls.name()}"
|
|
367
369
|
)
|
|
368
370
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
|
228
|
-
|
|
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
|
-
|
|
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
|
-
|
|
414
|
-
stack_context=stack_context,
|
|
413
|
+
tracker=tracker,
|
|
415
414
|
output_channel=output_channel,
|
|
416
415
|
**kwargs,
|
|
417
416
|
)
|
rasa/core/nlg/generator.py
CHANGED
|
@@ -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.
|
|
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.
|
|
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,
|
|
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
|
-
|
|
79
|
-
utter_action,
|
|
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(
|
|
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:
|