rasa-pro 3.11.6__py3-none-any.whl → 3.11.8__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/run.py +10 -6
- rasa/cli/utils.py +7 -0
- rasa/core/channels/channel.py +93 -0
- rasa/core/channels/inspector/dist/assets/{arc-f0f8bd46.js → arc-62ea6ecb.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-7162c77d.js → blockDiagram-38ab4fdb-133584f2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-b1d0d098.js → c4Diagram-3d4e48cf-3fdd847f.js} +1 -1
- rasa/core/channels/inspector/dist/assets/channel-6a3b6c3b.js +1 -0
- rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-807a1b27.js → classDiagram-70f12bd4-fbbe018c.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-5238dcdb.js → classDiagram-v2-f2320105-a4eb680a.js} +1 -1
- rasa/core/channels/inspector/dist/assets/clone-243bdc4d.js +1 -0
- rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-75dfaa67.js → createText-2e5e7dd3-a0a4811e.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-df20501d.js → edges-e0da2a9e-d6c66181.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-13cf4797.js → erDiagram-9861fffd-f2062a78.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-a4991264.js → flowDb-956e92f1-1a6bd8c6.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-ccecf773.js → flowDiagram-66a62f08-8c64ef56.js} +1 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-2fc14195.js +1 -0
- rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-b5801783.js → flowchart-elk-definition-4a651766-b16259fa.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-161e079a.js → ganttDiagram-c361ad54-3bef87d8.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-f38e86a4.js → gitGraphDiagram-72cf32ee-c0776679.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{graph-be6ef5d8.js → graph-af24022c.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-3862675e-d9ce8994.js → index-3862675e-1f1f2ddf.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-7794b245.js → index-e799a83e.js} +129 -116
- rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-5000a3dc.js → infoDiagram-f8f76790-c5d562c0.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-8ef0a17a.js → journeyDiagram-49397b02-8b3f9070.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{layout-d649bc98.js → layout-cc1e3a25.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{line-95add810.js → line-7f6d1f25.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{linear-f6025094.js → linear-4bacd66e.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-2e8531c4.js → mindmap-definition-fc14e90a-2926a2f0.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-918adfdb.js → pieDiagram-8a3498a8-05bf892e.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-cbd01797.js → quadrantDiagram-120e2f19-f700d7d2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-6a8b877b.js → requirementDiagram-deff3bca-6eb3541f.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-c377c3fe.js → sankeyDiagram-04a897e0-a47a81ed.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-ab9e9b7f.js → sequenceDiagram-704730f1-cf1ccf9f.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-5e6ae67d.js → stateDiagram-587899a1-405950fc.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-40643476.js → stateDiagram-v2-d93cdb3a-23d8e35b.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-afb8d108.js → styles-6aaf32cf-08c526c0.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-9a916d00-7edc9423.js → styles-9a916d00-8062abc7.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-c10674c1-c1d8f7e9.js → styles-c10674c1-0c776ed5.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-f494b2ef.js → svgDrawCommon-08f97a94-bb5daba8.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-11c7cdd0.js → timeline-definition-85554ec2-83421f60.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-3f191ec1.js → xychartDiagram-e933f94c-dad4ea79.js} +1 -1
- rasa/core/channels/inspector/dist/index.html +1 -1
- rasa/core/channels/inspector/src/components/Chat.tsx +23 -2
- rasa/core/channels/inspector/src/components/DiagramFlow.tsx +2 -5
- rasa/core/channels/inspector/src/helpers/conversation.ts +16 -0
- rasa/core/channels/inspector/src/types.ts +1 -1
- rasa/core/channels/voice_ready/audiocodes.py +64 -28
- rasa/core/channels/voice_ready/jambonz.py +29 -8
- rasa/core/channels/voice_ready/jambonz_protocol.py +4 -0
- rasa/core/channels/voice_ready/twilio_voice.py +56 -8
- rasa/core/channels/voice_stream/asr/asr_event.py +5 -0
- rasa/core/channels/voice_stream/tts/azure.py +13 -5
- rasa/core/channels/voice_stream/twilio_media_streams.py +110 -32
- rasa/core/channels/voice_stream/voice_channel.py +30 -30
- rasa/core/policies/intentless_policy.py +5 -59
- rasa/dialogue_understanding/generator/nlu_command_adapter.py +1 -1
- rasa/dialogue_understanding/processor/command_processor.py +20 -5
- rasa/dialogue_understanding/processor/command_processor_component.py +5 -2
- rasa/e2e_test/utils/validation.py +3 -3
- rasa/engine/validation.py +37 -2
- rasa/model_training.py +2 -1
- rasa/shared/constants.py +3 -0
- rasa/shared/core/domain.py +12 -3
- rasa/shared/core/policies/__init__.py +0 -0
- rasa/shared/core/policies/utils.py +87 -0
- rasa/tracing/instrumentation/attribute_extractors.py +2 -0
- rasa/version.py +1 -1
- {rasa_pro-3.11.6.dist-info → rasa_pro-3.11.8.dist-info}/METADATA +6 -7
- {rasa_pro-3.11.6.dist-info → rasa_pro-3.11.8.dist-info}/RECORD +72 -71
- {rasa_pro-3.11.6.dist-info → rasa_pro-3.11.8.dist-info}/WHEEL +1 -1
- README.md +0 -41
- rasa/core/channels/inspector/dist/assets/channel-e265ea59.js +0 -1
- rasa/core/channels/inspector/dist/assets/clone-21f8a43d.js +0 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-5c8ce12d.js +0 -1
- rasa/keys +0 -1
- {rasa_pro-3.11.6.dist-info → rasa_pro-3.11.8.dist-info}/NOTICE +0 -0
- {rasa_pro-3.11.6.dist-info → rasa_pro-3.11.8.dist-info}/entry_points.txt +0 -0
|
@@ -1,31 +1,54 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import base64
|
|
2
4
|
import json
|
|
3
5
|
import uuid
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, Optional, Text, Tuple
|
|
4
7
|
|
|
5
8
|
import structlog
|
|
6
|
-
from
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
from sanic import ( # type: ignore[attr-defined]
|
|
10
|
+
Blueprint,
|
|
11
|
+
HTTPResponse,
|
|
12
|
+
Request,
|
|
13
|
+
Websocket,
|
|
14
|
+
response,
|
|
15
|
+
)
|
|
11
16
|
|
|
12
|
-
from rasa.core.channels import UserMessage
|
|
17
|
+
from rasa.core.channels import InputChannel, UserMessage
|
|
18
|
+
from rasa.core.channels.channel import (
|
|
19
|
+
create_auth_requested_response_provider,
|
|
20
|
+
requires_basic_auth,
|
|
21
|
+
)
|
|
13
22
|
from rasa.core.channels.voice_ready.utils import CallParameters
|
|
23
|
+
from rasa.core.channels.voice_stream.audio_bytes import RasaAudioBytes
|
|
14
24
|
from rasa.core.channels.voice_stream.call_state import call_state
|
|
15
25
|
from rasa.core.channels.voice_stream.tts.tts_engine import TTSEngine
|
|
16
|
-
from rasa.core.channels.voice_stream.audio_bytes import RasaAudioBytes
|
|
17
26
|
from rasa.core.channels.voice_stream.voice_channel import (
|
|
27
|
+
ContinueConversationAction,
|
|
18
28
|
EndConversationAction,
|
|
19
29
|
NewAudioAction,
|
|
20
30
|
VoiceChannelAction,
|
|
21
|
-
ContinueConversationAction,
|
|
22
31
|
VoiceInputChannel,
|
|
23
32
|
VoiceOutputChannel,
|
|
24
33
|
)
|
|
34
|
+
from rasa.shared.exceptions import RasaException
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from twilio.twiml.voice_response import VoiceResponse
|
|
25
38
|
|
|
26
39
|
logger = structlog.get_logger(__name__)
|
|
27
40
|
|
|
28
41
|
|
|
42
|
+
TWILIO_MEDIA_STREAMS_WEBHOOK_PATH = "webhooks/twilio_media_streams/webhook"
|
|
43
|
+
TWILIO_MEDIA_STREAMS_WEBSOCKET_PATH = "webhooks/twilio_media_streams/websocket"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
CALL_SID_REQUEST_KEY = "CallSid"
|
|
47
|
+
FROM_NUMBER_REQUEST_KEY = "From"
|
|
48
|
+
TO_NUMBER_REQUEST_KEY = "To"
|
|
49
|
+
DIRECTION_REQUEST_KEY = "Direction"
|
|
50
|
+
|
|
51
|
+
|
|
29
52
|
def map_call_params(data: Dict[Text, Any]) -> CallParameters:
|
|
30
53
|
"""Map the twilio stream parameters to the CallParameters dataclass."""
|
|
31
54
|
stream_sid = data["streamSid"]
|
|
@@ -74,6 +97,40 @@ class TwilioMediaStreamsOutputChannel(VoiceOutputChannel):
|
|
|
74
97
|
|
|
75
98
|
|
|
76
99
|
class TwilioMediaStreamsInputChannel(VoiceInputChannel):
|
|
100
|
+
def __init__(
|
|
101
|
+
self,
|
|
102
|
+
server_url: str,
|
|
103
|
+
asr_config: Dict,
|
|
104
|
+
tts_config: Dict,
|
|
105
|
+
monitor_silence: bool = False,
|
|
106
|
+
username: Optional[Text] = None,
|
|
107
|
+
password: Optional[Text] = None,
|
|
108
|
+
):
|
|
109
|
+
super().__init__(server_url, asr_config, tts_config, monitor_silence)
|
|
110
|
+
self.username = username
|
|
111
|
+
self.password = password
|
|
112
|
+
|
|
113
|
+
@classmethod
|
|
114
|
+
def from_credentials(cls, credentials: Optional[Dict[str, Any]]) -> InputChannel:
|
|
115
|
+
credentials = credentials or {}
|
|
116
|
+
|
|
117
|
+
username = credentials.get("username")
|
|
118
|
+
password = credentials.get("password")
|
|
119
|
+
if (username is None) != (password is None):
|
|
120
|
+
raise RasaException(
|
|
121
|
+
"In TwilioMediaStreams channel, either both username and password "
|
|
122
|
+
"or neither should be provided. "
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
return cls(
|
|
126
|
+
credentials["server_url"],
|
|
127
|
+
credentials["asr"],
|
|
128
|
+
credentials["tts"],
|
|
129
|
+
credentials.get("monitor_silence", False),
|
|
130
|
+
username=username,
|
|
131
|
+
password=password,
|
|
132
|
+
)
|
|
133
|
+
|
|
77
134
|
@classmethod
|
|
78
135
|
def name(cls) -> str:
|
|
79
136
|
return "twilio_media_streams"
|
|
@@ -126,16 +183,6 @@ class TwilioMediaStreamsInputChannel(VoiceInputChannel):
|
|
|
126
183
|
self.tts_cache,
|
|
127
184
|
)
|
|
128
185
|
|
|
129
|
-
def websocket_stream_url(self) -> str:
|
|
130
|
-
"""Returns the websocket stream URL."""
|
|
131
|
-
# depending on the config value, the url might contain http as a
|
|
132
|
-
# protocol or not - we'll make sure both work
|
|
133
|
-
if self.server_url.startswith("http"):
|
|
134
|
-
base_url = self.server_url.replace("http", "ws")
|
|
135
|
-
else:
|
|
136
|
-
base_url = f"wss://{self.server_url}"
|
|
137
|
-
return f"{base_url}/webhooks/twilio_media_streams/websocket"
|
|
138
|
-
|
|
139
186
|
def blueprint(
|
|
140
187
|
self, on_new_message: Callable[[UserMessage], Awaitable[Any]]
|
|
141
188
|
) -> Blueprint:
|
|
@@ -147,22 +194,20 @@ class TwilioMediaStreamsInputChannel(VoiceInputChannel):
|
|
|
147
194
|
return response.json({"status": "ok"})
|
|
148
195
|
|
|
149
196
|
@blueprint.route("/webhook", methods=["POST"])
|
|
197
|
+
@requires_basic_auth(
|
|
198
|
+
username=self.username,
|
|
199
|
+
password=self.password,
|
|
200
|
+
auth_request_provider=create_auth_requested_response_provider(
|
|
201
|
+
realm=TWILIO_MEDIA_STREAMS_WEBHOOK_PATH
|
|
202
|
+
),
|
|
203
|
+
)
|
|
150
204
|
async def receive(request: Request) -> HTTPResponse:
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
voice_response = VoiceResponse()
|
|
154
|
-
start = Connect()
|
|
155
|
-
stream = start.stream(url=self.websocket_stream_url())
|
|
156
|
-
# pass information about the call to the webhook - so we can
|
|
157
|
-
# store it in the input channel
|
|
158
|
-
stream.parameter(name="call_id", value=request.form.get("CallSid", None))
|
|
159
|
-
stream.parameter(name="user_phone", value=request.form.get("From", None))
|
|
160
|
-
stream.parameter(name="bot_phone", value=request.form.get("To", None))
|
|
161
|
-
stream.parameter(
|
|
162
|
-
name="direction", value=request.form.get("Direction", None)
|
|
163
|
-
)
|
|
205
|
+
voice_response = self._build_twilio_response(request)
|
|
164
206
|
|
|
165
|
-
|
|
207
|
+
logger.debug(
|
|
208
|
+
"twilio_media_streams.webhook.twilio_response",
|
|
209
|
+
twilio_response=str(voice_response),
|
|
210
|
+
)
|
|
166
211
|
|
|
167
212
|
return response.text(str(voice_response), content_type="text/xml")
|
|
168
213
|
|
|
@@ -171,3 +216,36 @@ class TwilioMediaStreamsInputChannel(VoiceInputChannel):
|
|
|
171
216
|
await self.run_audio_streaming(on_new_message, ws)
|
|
172
217
|
|
|
173
218
|
return blueprint
|
|
219
|
+
|
|
220
|
+
def _websocket_stream_url(self) -> str:
|
|
221
|
+
"""Returns the websocket stream URL."""
|
|
222
|
+
# depending on the config value, the url might contain http as a
|
|
223
|
+
# protocol or not - we'll make sure both work
|
|
224
|
+
if self.server_url.startswith("http"):
|
|
225
|
+
base_url = self.server_url.replace("http", "ws")
|
|
226
|
+
else:
|
|
227
|
+
base_url = f"wss://{self.server_url}"
|
|
228
|
+
return f"{base_url}/{TWILIO_MEDIA_STREAMS_WEBSOCKET_PATH}"
|
|
229
|
+
|
|
230
|
+
def _build_twilio_response(self, request: Request) -> VoiceResponse:
|
|
231
|
+
from twilio.twiml.voice_response import Connect, VoiceResponse
|
|
232
|
+
|
|
233
|
+
voice_response = VoiceResponse()
|
|
234
|
+
start = Connect()
|
|
235
|
+
stream = start.stream(url=self._websocket_stream_url())
|
|
236
|
+
# pass information about the call to the webhook - so we can
|
|
237
|
+
# store it in the input channel
|
|
238
|
+
stream.parameter(
|
|
239
|
+
name="call_id", value=request.form.get(CALL_SID_REQUEST_KEY, None)
|
|
240
|
+
)
|
|
241
|
+
stream.parameter(
|
|
242
|
+
name="user_phone", value=request.form.get(FROM_NUMBER_REQUEST_KEY, None)
|
|
243
|
+
)
|
|
244
|
+
stream.parameter(
|
|
245
|
+
name="bot_phone", value=request.form.get(TO_NUMBER_REQUEST_KEY, None)
|
|
246
|
+
)
|
|
247
|
+
stream.parameter(
|
|
248
|
+
name="direction", value=request.form.get(DIRECTION_REQUEST_KEY, None)
|
|
249
|
+
)
|
|
250
|
+
voice_response.append(start)
|
|
251
|
+
return voice_response
|
|
@@ -22,6 +22,7 @@ from rasa.core.channels.voice_stream.asr.asr_event import (
|
|
|
22
22
|
ASREvent,
|
|
23
23
|
NewTranscript,
|
|
24
24
|
UserIsSpeaking,
|
|
25
|
+
UserSilence,
|
|
25
26
|
)
|
|
26
27
|
from sanic import Websocket # type: ignore
|
|
27
28
|
|
|
@@ -244,13 +245,7 @@ class VoiceInputChannel(InputChannel):
|
|
|
244
245
|
self.monitor_silence = monitor_silence
|
|
245
246
|
self.tts_cache = TTSCache(tts_config.get("cache_size", 1000))
|
|
246
247
|
|
|
247
|
-
async def
|
|
248
|
-
self,
|
|
249
|
-
voice_websocket: Websocket,
|
|
250
|
-
on_new_message: Callable[[UserMessage], Awaitable[Any]],
|
|
251
|
-
tts_engine: TTSEngine,
|
|
252
|
-
call_parameters: CallParameters,
|
|
253
|
-
) -> None:
|
|
248
|
+
async def monitor_silence_timeout(self, asr_event_queue: asyncio.Queue) -> None:
|
|
254
249
|
timeout = call_state.silence_timeout
|
|
255
250
|
if not timeout:
|
|
256
251
|
return
|
|
@@ -258,16 +253,8 @@ class VoiceInputChannel(InputChannel):
|
|
|
258
253
|
return
|
|
259
254
|
logger.debug("voice_channel.silence_timeout_watch_started", timeout=timeout)
|
|
260
255
|
await asyncio.sleep(timeout)
|
|
256
|
+
await asr_event_queue.put(UserSilence())
|
|
261
257
|
logger.debug("voice_channel.silence_timeout_tripped")
|
|
262
|
-
output_channel = self.create_output_channel(voice_websocket, tts_engine)
|
|
263
|
-
message = UserMessage(
|
|
264
|
-
"/silence_timeout",
|
|
265
|
-
output_channel,
|
|
266
|
-
call_parameters.stream_id,
|
|
267
|
-
input_channel=self.name(),
|
|
268
|
-
metadata=asdict(call_parameters),
|
|
269
|
-
)
|
|
270
|
-
await on_new_message(message)
|
|
271
258
|
|
|
272
259
|
@staticmethod
|
|
273
260
|
def _cancel_silence_timeout_watcher() -> None:
|
|
@@ -328,6 +315,7 @@ class VoiceInputChannel(InputChannel):
|
|
|
328
315
|
_call_state.set(CallState())
|
|
329
316
|
asr_engine = asr_engine_from_config(self.asr_config)
|
|
330
317
|
tts_engine = tts_engine_from_config(self.tts_config)
|
|
318
|
+
asr_event_queue: asyncio.Queue = asyncio.Queue()
|
|
331
319
|
await asr_engine.connect()
|
|
332
320
|
|
|
333
321
|
call_parameters = await self.collect_call_parameters(channel_websocket)
|
|
@@ -354,12 +342,7 @@ class VoiceInputChannel(InputChannel):
|
|
|
354
342
|
self._cancel_silence_timeout_watcher()
|
|
355
343
|
call_state.silence_timeout_watcher = ( # type: ignore[attr-defined]
|
|
356
344
|
asyncio.create_task(
|
|
357
|
-
self.
|
|
358
|
-
channel_websocket,
|
|
359
|
-
on_new_message,
|
|
360
|
-
tts_engine,
|
|
361
|
-
call_parameters,
|
|
362
|
-
)
|
|
345
|
+
self.monitor_silence_timeout(asr_event_queue)
|
|
363
346
|
)
|
|
364
347
|
)
|
|
365
348
|
if isinstance(channel_action, NewAudioAction):
|
|
@@ -368,8 +351,13 @@ class VoiceInputChannel(InputChannel):
|
|
|
368
351
|
# end stream event came from the other side
|
|
369
352
|
break
|
|
370
353
|
|
|
371
|
-
async def
|
|
354
|
+
async def receive_asr_events() -> None:
|
|
372
355
|
async for event in asr_engine.stream_asr_events():
|
|
356
|
+
await asr_event_queue.put(event)
|
|
357
|
+
|
|
358
|
+
async def handle_asr_events() -> None:
|
|
359
|
+
while True:
|
|
360
|
+
event = await asr_event_queue.get()
|
|
373
361
|
await self.handle_asr_event(
|
|
374
362
|
event,
|
|
375
363
|
channel_websocket,
|
|
@@ -378,16 +366,18 @@ class VoiceInputChannel(InputChannel):
|
|
|
378
366
|
call_parameters,
|
|
379
367
|
)
|
|
380
368
|
|
|
381
|
-
|
|
382
|
-
|
|
369
|
+
tasks = [
|
|
370
|
+
asyncio.create_task(consume_audio_bytes()),
|
|
371
|
+
asyncio.create_task(receive_asr_events()),
|
|
372
|
+
asyncio.create_task(handle_asr_events()),
|
|
373
|
+
]
|
|
383
374
|
await asyncio.wait(
|
|
384
|
-
|
|
375
|
+
tasks,
|
|
385
376
|
return_when=asyncio.FIRST_COMPLETED,
|
|
386
377
|
)
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
asr_event_task.cancel()
|
|
378
|
+
for task in tasks:
|
|
379
|
+
if not task.done():
|
|
380
|
+
task.cancel()
|
|
391
381
|
await tts_engine.close_connection()
|
|
392
382
|
await asr_engine.close_connection()
|
|
393
383
|
await channel_websocket.close()
|
|
@@ -425,3 +415,13 @@ class VoiceInputChannel(InputChannel):
|
|
|
425
415
|
elif isinstance(e, UserIsSpeaking):
|
|
426
416
|
self._cancel_silence_timeout_watcher()
|
|
427
417
|
call_state.is_user_speaking = True # type: ignore[attr-defined]
|
|
418
|
+
elif isinstance(e, UserSilence):
|
|
419
|
+
output_channel = self.create_output_channel(voice_websocket, tts_engine)
|
|
420
|
+
message = UserMessage(
|
|
421
|
+
"/silence_timeout",
|
|
422
|
+
output_channel,
|
|
423
|
+
call_parameters.stream_id,
|
|
424
|
+
input_channel=self.name(),
|
|
425
|
+
metadata=asdict(call_parameters),
|
|
426
|
+
)
|
|
427
|
+
await on_new_message(message)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import importlib.resources
|
|
2
2
|
import math
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
|
-
from typing import Any, Dict, List, Optional,
|
|
4
|
+
from typing import Any, Dict, List, Optional, TYPE_CHECKING, Text, Tuple
|
|
5
5
|
|
|
6
6
|
import structlog
|
|
7
7
|
import tiktoken
|
|
@@ -18,7 +18,6 @@ from rasa.core.constants import (
|
|
|
18
18
|
UTTER_SOURCE_METADATA_KEY,
|
|
19
19
|
)
|
|
20
20
|
from rasa.core.policies.policy import Policy, PolicyPrediction, SupportedData
|
|
21
|
-
from rasa.dialogue_understanding.patterns.chitchat import FLOW_PATTERN_CHITCHAT
|
|
22
21
|
from rasa.dialogue_understanding.stack.frames import (
|
|
23
22
|
ChitChatStackFrame,
|
|
24
23
|
DialogueStackFrame,
|
|
@@ -30,7 +29,6 @@ from rasa.engine.storage.storage import ModelStorage
|
|
|
30
29
|
from rasa.graph_components.providers.forms_provider import Forms
|
|
31
30
|
from rasa.graph_components.providers.responses_provider import Responses
|
|
32
31
|
from rasa.shared.constants import (
|
|
33
|
-
REQUIRED_SLOTS_KEY,
|
|
34
32
|
EMBEDDINGS_CONFIG_KEY,
|
|
35
33
|
LLM_CONFIG_KEY,
|
|
36
34
|
MODEL_CONFIG_KEY,
|
|
@@ -42,7 +40,6 @@ from rasa.shared.constants import (
|
|
|
42
40
|
MODEL_GROUP_ID_CONFIG_KEY,
|
|
43
41
|
)
|
|
44
42
|
from rasa.shared.core.constants import ACTION_LISTEN_NAME
|
|
45
|
-
from rasa.shared.core.constants import ACTION_TRIGGER_CHITCHAT
|
|
46
43
|
from rasa.shared.core.domain import KEY_RESPONSES_TEXT, Domain
|
|
47
44
|
from rasa.shared.core.events import (
|
|
48
45
|
ActionExecuted,
|
|
@@ -52,6 +49,7 @@ from rasa.shared.core.events import (
|
|
|
52
49
|
)
|
|
53
50
|
from rasa.shared.core.flows import FlowsList
|
|
54
51
|
from rasa.shared.core.generator import TrackerWithCachedStates
|
|
52
|
+
from rasa.shared.core.policies.utils import filter_responses_for_intentless_policy
|
|
55
53
|
from rasa.shared.core.trackers import DialogueStateTracker
|
|
56
54
|
from rasa.shared.exceptions import FileIOException, RasaCoreException
|
|
57
55
|
from rasa.shared.nlu.constants import PREDICTED_CONFIDENCE_KEY
|
|
@@ -147,59 +145,6 @@ class Conversation:
|
|
|
147
145
|
interactions: List[Interaction] = field(default_factory=list)
|
|
148
146
|
|
|
149
147
|
|
|
150
|
-
def collect_form_responses(forms: Forms) -> Set[Text]:
|
|
151
|
-
"""Collect responses that belong the requested slots in forms.
|
|
152
|
-
|
|
153
|
-
Args:
|
|
154
|
-
forms: the forms from the domain
|
|
155
|
-
Returns:
|
|
156
|
-
all utterances used in forms
|
|
157
|
-
"""
|
|
158
|
-
form_responses = set()
|
|
159
|
-
for _, form_info in forms.data.items():
|
|
160
|
-
for required_slot in form_info.get(REQUIRED_SLOTS_KEY, []):
|
|
161
|
-
form_responses.add(f"utter_ask_{required_slot}")
|
|
162
|
-
return form_responses
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def filter_responses(responses: Responses, forms: Forms, flows: FlowsList) -> Responses:
|
|
166
|
-
"""Filters out responses that are unwanted for the intentless policy.
|
|
167
|
-
|
|
168
|
-
This includes utterances used in flows and forms.
|
|
169
|
-
|
|
170
|
-
Args:
|
|
171
|
-
responses: the responses from the domain
|
|
172
|
-
forms: the forms from the domain
|
|
173
|
-
flows: all flows
|
|
174
|
-
Returns:
|
|
175
|
-
The remaining, relevant responses for the intentless policy.
|
|
176
|
-
"""
|
|
177
|
-
form_responses = collect_form_responses(forms)
|
|
178
|
-
flow_responses = flows.utterances
|
|
179
|
-
combined_responses = form_responses | flow_responses
|
|
180
|
-
filtered_responses = {
|
|
181
|
-
name: variants
|
|
182
|
-
for name, variants in responses.data.items()
|
|
183
|
-
if name not in combined_responses
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
pattern_chitchat = flows.flow_by_id(FLOW_PATTERN_CHITCHAT)
|
|
187
|
-
|
|
188
|
-
# The following condition is highly unlikely, but mypy requires the case
|
|
189
|
-
# of pattern_chitchat == None to be addressed
|
|
190
|
-
if not pattern_chitchat:
|
|
191
|
-
return Responses(data=filtered_responses)
|
|
192
|
-
|
|
193
|
-
# if action_trigger_chitchat, filter out "utter_free_chitchat_response"
|
|
194
|
-
has_action_trigger_chitchat = pattern_chitchat.has_action_step(
|
|
195
|
-
ACTION_TRIGGER_CHITCHAT
|
|
196
|
-
)
|
|
197
|
-
if has_action_trigger_chitchat:
|
|
198
|
-
filtered_responses.pop("utter_free_chitchat_response", None)
|
|
199
|
-
|
|
200
|
-
return Responses(data=filtered_responses)
|
|
201
|
-
|
|
202
|
-
|
|
203
148
|
def action_from_response(
|
|
204
149
|
text: Optional[str], responses: Dict[Text, List[Dict[Text, Any]]]
|
|
205
150
|
) -> Optional[str]:
|
|
@@ -513,7 +458,9 @@ class IntentlessPolicy(LLMHealthCheckMixin, EmbeddingsHealthCheckMixin, Policy):
|
|
|
513
458
|
# Perform health checks of both LLM and embeddings client configs
|
|
514
459
|
self._perform_health_checks(self.config, "intentless_policy.train")
|
|
515
460
|
|
|
516
|
-
responses =
|
|
461
|
+
responses = filter_responses_for_intentless_policy(
|
|
462
|
+
responses, forms, flows or FlowsList([])
|
|
463
|
+
)
|
|
517
464
|
telemetry.track_intentless_policy_train()
|
|
518
465
|
response_texts = [r for r in extract_ai_response_examples(responses.data)]
|
|
519
466
|
|
|
@@ -948,7 +895,6 @@ class IntentlessPolicy(LLMHealthCheckMixin, EmbeddingsHealthCheckMixin, Policy):
|
|
|
948
895
|
**kwargs: Any,
|
|
949
896
|
) -> "IntentlessPolicy":
|
|
950
897
|
"""Loads a trained policy (see parent class for full docstring)."""
|
|
951
|
-
|
|
952
898
|
# Perform health checks of both LLM and embeddings client configs
|
|
953
899
|
cls._perform_health_checks(config, "intentless_policy.load")
|
|
954
900
|
|
|
@@ -139,7 +139,7 @@ class NLUCommandAdapter(GraphComponent, CommandGenerator):
|
|
|
139
139
|
|
|
140
140
|
if commands:
|
|
141
141
|
commands = clean_up_commands(
|
|
142
|
-
commands, tracker, flows, self._execution_context
|
|
142
|
+
commands, tracker, flows, self._execution_context, domain
|
|
143
143
|
)
|
|
144
144
|
log_llm(
|
|
145
145
|
logger=structlogger,
|
|
@@ -41,9 +41,11 @@ from rasa.shared.constants import (
|
|
|
41
41
|
)
|
|
42
42
|
from rasa.shared.core.constants import ACTION_TRIGGER_CHITCHAT, SlotMappingType
|
|
43
43
|
from rasa.shared.core.constants import FLOW_HASHES_SLOT
|
|
44
|
+
from rasa.shared.core.domain import Domain
|
|
44
45
|
from rasa.shared.core.events import Event, SlotSet
|
|
45
46
|
from rasa.shared.core.flows import FlowsList
|
|
46
47
|
from rasa.shared.core.flows.steps.collect import CollectInformationFlowStep
|
|
48
|
+
from rasa.shared.core.policies.utils import contains_intentless_policy_responses
|
|
47
49
|
from rasa.shared.core.slots import Slot
|
|
48
50
|
from rasa.shared.core.trackers import DialogueStateTracker
|
|
49
51
|
from rasa.shared.nlu.constants import COMMANDS
|
|
@@ -182,6 +184,7 @@ def execute_commands(
|
|
|
182
184
|
all_flows: FlowsList,
|
|
183
185
|
execution_context: ExecutionContext,
|
|
184
186
|
story_graph: Optional[StoryGraph] = None,
|
|
187
|
+
domain: Optional[Domain] = None,
|
|
185
188
|
) -> List[Event]:
|
|
186
189
|
"""Executes a list of commands.
|
|
187
190
|
|
|
@@ -191,6 +194,7 @@ def execute_commands(
|
|
|
191
194
|
all_flows: All flows.
|
|
192
195
|
execution_context: Information about the single graph run.
|
|
193
196
|
story_graph: StoryGraph object with stories available for training.
|
|
197
|
+
domain: The domain of the bot.
|
|
194
198
|
|
|
195
199
|
Returns:
|
|
196
200
|
A list of the events that were created.
|
|
@@ -199,7 +203,7 @@ def execute_commands(
|
|
|
199
203
|
original_tracker = tracker.copy()
|
|
200
204
|
|
|
201
205
|
commands = clean_up_commands(
|
|
202
|
-
commands, tracker, all_flows, execution_context, story_graph
|
|
206
|
+
commands, tracker, all_flows, execution_context, story_graph, domain
|
|
203
207
|
)
|
|
204
208
|
|
|
205
209
|
updated_flows = find_updated_flows(tracker, all_flows)
|
|
@@ -333,6 +337,7 @@ def clean_up_commands(
|
|
|
333
337
|
all_flows: FlowsList,
|
|
334
338
|
execution_context: ExecutionContext,
|
|
335
339
|
story_graph: Optional[StoryGraph] = None,
|
|
340
|
+
domain: Optional[Domain] = None,
|
|
336
341
|
) -> List[Command]:
|
|
337
342
|
"""Clean up a list of commands.
|
|
338
343
|
|
|
@@ -348,10 +353,13 @@ def clean_up_commands(
|
|
|
348
353
|
all_flows: All flows.
|
|
349
354
|
execution_context: Information about a single graph run.
|
|
350
355
|
story_graph: StoryGraph object with stories available for training.
|
|
356
|
+
domain: The domain of the bot.
|
|
351
357
|
|
|
352
358
|
Returns:
|
|
353
359
|
The cleaned up commands.
|
|
354
360
|
"""
|
|
361
|
+
domain = domain if domain else Domain.empty()
|
|
362
|
+
|
|
355
363
|
slots_so_far, active_flow = filled_slots_for_active_flow(tracker, all_flows)
|
|
356
364
|
|
|
357
365
|
clean_commands: List[Command] = []
|
|
@@ -394,7 +402,12 @@ def clean_up_commands(
|
|
|
394
402
|
# handle chitchat command differently from other free-form answer commands
|
|
395
403
|
elif isinstance(command, ChitChatAnswerCommand):
|
|
396
404
|
clean_commands = clean_up_chitchat_command(
|
|
397
|
-
clean_commands,
|
|
405
|
+
clean_commands,
|
|
406
|
+
command,
|
|
407
|
+
all_flows,
|
|
408
|
+
execution_context,
|
|
409
|
+
domain,
|
|
410
|
+
story_graph,
|
|
398
411
|
)
|
|
399
412
|
|
|
400
413
|
elif isinstance(command, FreeFormAnswerCommand):
|
|
@@ -590,6 +603,7 @@ def clean_up_chitchat_command(
|
|
|
590
603
|
command: ChitChatAnswerCommand,
|
|
591
604
|
flows: FlowsList,
|
|
592
605
|
execution_context: ExecutionContext,
|
|
606
|
+
domain: Domain,
|
|
593
607
|
story_graph: Optional[StoryGraph] = None,
|
|
594
608
|
) -> List[Command]:
|
|
595
609
|
"""Clean up a chitchat answer command.
|
|
@@ -603,6 +617,8 @@ def clean_up_chitchat_command(
|
|
|
603
617
|
flows: All flows.
|
|
604
618
|
execution_context: Information about a single graph run.
|
|
605
619
|
story_graph: StoryGraph object with stories available for training.
|
|
620
|
+
domain: The domain of the bot.
|
|
621
|
+
|
|
606
622
|
Returns:
|
|
607
623
|
The cleaned up commands.
|
|
608
624
|
"""
|
|
@@ -628,10 +644,9 @@ def clean_up_chitchat_command(
|
|
|
628
644
|
)
|
|
629
645
|
defines_intentless_policy = execution_context.has_node(IntentlessPolicy)
|
|
630
646
|
|
|
631
|
-
has_e2e_stories = True if (story_graph and story_graph.has_e2e_stories()) else False
|
|
632
|
-
|
|
633
647
|
if (has_action_trigger_chitchat and not defines_intentless_policy) or (
|
|
634
|
-
defines_intentless_policy
|
|
648
|
+
defines_intentless_policy
|
|
649
|
+
and not contains_intentless_policy_responses(flows, domain, story_graph)
|
|
635
650
|
):
|
|
636
651
|
resulting_commands.insert(
|
|
637
652
|
0, CannotHandleCommand(RASA_PATTERN_CANNOT_HANDLE_CHITCHAT)
|
|
@@ -6,6 +6,7 @@ import rasa.dialogue_understanding.processor.command_processor
|
|
|
6
6
|
from rasa.engine.graph import ExecutionContext, GraphComponent
|
|
7
7
|
from rasa.engine.storage.resource import Resource
|
|
8
8
|
from rasa.engine.storage.storage import ModelStorage
|
|
9
|
+
from rasa.shared.core.domain import Domain
|
|
9
10
|
from rasa.shared.core.events import Event
|
|
10
11
|
from rasa.shared.core.flows import FlowsList
|
|
11
12
|
from rasa.shared.core.trackers import DialogueStateTracker
|
|
@@ -15,7 +16,8 @@ from rasa.shared.core.training_data.structures import StoryGraph
|
|
|
15
16
|
class CommandProcessorComponent(GraphComponent):
|
|
16
17
|
"""Processes commands by issuing events to modify a tracker.
|
|
17
18
|
|
|
18
|
-
Minimal component that applies commands to a tracker.
|
|
19
|
+
Minimal component that applies commands to a tracker.
|
|
20
|
+
"""
|
|
19
21
|
|
|
20
22
|
def __init__(self, execution_context: ExecutionContext):
|
|
21
23
|
self._execution_context = execution_context
|
|
@@ -36,8 +38,9 @@ class CommandProcessorComponent(GraphComponent):
|
|
|
36
38
|
tracker: DialogueStateTracker,
|
|
37
39
|
flows: FlowsList,
|
|
38
40
|
story_graph: StoryGraph,
|
|
41
|
+
domain: Domain,
|
|
39
42
|
) -> List[Event]:
|
|
40
43
|
"""Execute commands to update tracker state."""
|
|
41
44
|
return rasa.dialogue_understanding.processor.command_processor.execute_commands(
|
|
42
|
-
tracker, flows, self._execution_context, story_graph
|
|
45
|
+
tracker, flows, self._execution_context, story_graph, domain
|
|
43
46
|
)
|
|
@@ -6,6 +6,7 @@ import structlog
|
|
|
6
6
|
|
|
7
7
|
import rasa.shared.utils.io
|
|
8
8
|
from rasa.e2e_test.constants import SCHEMA_FILE_PATH
|
|
9
|
+
from rasa.exceptions import ModelNotFound
|
|
9
10
|
from rasa.shared.utils.yaml import read_schema_file
|
|
10
11
|
|
|
11
12
|
if TYPE_CHECKING:
|
|
@@ -54,10 +55,9 @@ def validate_model_path(model_path: Optional[str], parameter: str, default: str)
|
|
|
54
55
|
return model_path
|
|
55
56
|
|
|
56
57
|
if model_path and not Path(model_path).exists():
|
|
57
|
-
|
|
58
|
+
raise ModelNotFound(
|
|
58
59
|
f"The provided model path '{model_path}' could not be found. "
|
|
59
|
-
|
|
60
|
-
UserWarning,
|
|
60
|
+
"Provide an existing model path."
|
|
61
61
|
)
|
|
62
62
|
|
|
63
63
|
elif model_path is None:
|
rasa/engine/validation.py
CHANGED
|
@@ -84,8 +84,10 @@ from rasa.shared.constants import (
|
|
|
84
84
|
)
|
|
85
85
|
from rasa.shared.core.constants import ACTION_RESET_ROUTING, ACTION_TRIGGER_CHITCHAT
|
|
86
86
|
from rasa.shared.core.domain import Domain
|
|
87
|
-
from rasa.shared.core.flows import
|
|
87
|
+
from rasa.shared.core.flows import Flow, FlowsList
|
|
88
|
+
from rasa.shared.core.policies.utils import contains_intentless_policy_responses
|
|
88
89
|
from rasa.shared.core.slots import Slot
|
|
90
|
+
from rasa.shared.core.training_data.structures import StoryGraph
|
|
89
91
|
from rasa.shared.exceptions import RasaException
|
|
90
92
|
from rasa.shared.nlu.training_data.message import Message
|
|
91
93
|
|
|
@@ -640,11 +642,18 @@ def _recursively_check_required_components(
|
|
|
640
642
|
|
|
641
643
|
|
|
642
644
|
def validate_flow_component_dependencies(
|
|
643
|
-
flows: FlowsList,
|
|
645
|
+
flows: FlowsList,
|
|
646
|
+
domain: Domain,
|
|
647
|
+
story_graph: StoryGraph,
|
|
648
|
+
model_configuration: GraphModelConfiguration,
|
|
644
649
|
) -> None:
|
|
645
650
|
if (pattern_chitchat := flows.flow_by_id(FLOW_PATTERN_CHITCHAT)) is not None:
|
|
646
651
|
_validate_chitchat_dependencies(pattern_chitchat, model_configuration)
|
|
647
652
|
|
|
653
|
+
_validate_intentless_policy_responses(
|
|
654
|
+
flows, domain, story_graph, model_configuration
|
|
655
|
+
)
|
|
656
|
+
|
|
648
657
|
|
|
649
658
|
def _validate_chitchat_dependencies(
|
|
650
659
|
pattern_chitchat: Flow, model_configuration: GraphModelConfiguration
|
|
@@ -672,6 +681,32 @@ def _validate_chitchat_dependencies(
|
|
|
672
681
|
)
|
|
673
682
|
|
|
674
683
|
|
|
684
|
+
def _validate_intentless_policy_responses(
|
|
685
|
+
flows: FlowsList,
|
|
686
|
+
domain: Domain,
|
|
687
|
+
story_graph: StoryGraph,
|
|
688
|
+
model_configuration: GraphModelConfiguration,
|
|
689
|
+
) -> None:
|
|
690
|
+
"""If IntentlessPolicy is configured, validate that it has responses to use:
|
|
691
|
+
either responses from the domain that are not part of any flow, or from
|
|
692
|
+
end-to-end stories.
|
|
693
|
+
"""
|
|
694
|
+
if not model_configuration.predict_schema.has_node(IntentlessPolicy):
|
|
695
|
+
return
|
|
696
|
+
|
|
697
|
+
if not contains_intentless_policy_responses(flows, domain, story_graph):
|
|
698
|
+
structlogger.error(
|
|
699
|
+
"validation.intentless_policy.no_applicable_responses_found",
|
|
700
|
+
event_info=(
|
|
701
|
+
"IntentlessPolicy is configured, but no applicable responses are "
|
|
702
|
+
"found. Please make sure that there are responses defined in the "
|
|
703
|
+
"domain that are not part of any flow, or that there are "
|
|
704
|
+
"end-to-end stories in the training data."
|
|
705
|
+
),
|
|
706
|
+
)
|
|
707
|
+
sys.exit(1)
|
|
708
|
+
|
|
709
|
+
|
|
675
710
|
def get_component_index(schema: GraphSchema, component_class: Type) -> Optional[int]:
|
|
676
711
|
"""Extracts the index of a component of the given class in the schema.
|
|
677
712
|
This function assumes that each component's node name is stored in a way
|
rasa/model_training.py
CHANGED
|
@@ -312,6 +312,7 @@ async def _train_graph(
|
|
|
312
312
|
)
|
|
313
313
|
flows = file_importer.get_flows()
|
|
314
314
|
domain = file_importer.get_domain()
|
|
315
|
+
story_graph = file_importer.get_stories()
|
|
315
316
|
model_configuration = recipe.graph_config_for_recipe(
|
|
316
317
|
config,
|
|
317
318
|
kwargs,
|
|
@@ -327,7 +328,7 @@ async def _train_graph(
|
|
|
327
328
|
config
|
|
328
329
|
)
|
|
329
330
|
rasa.engine.validation.validate_flow_component_dependencies(
|
|
330
|
-
flows, model_configuration
|
|
331
|
+
flows, domain, story_graph, model_configuration
|
|
331
332
|
)
|
|
332
333
|
rasa.engine.validation.validate_command_generator_setup(model_configuration)
|
|
333
334
|
|
rasa/shared/constants.py
CHANGED
|
@@ -98,6 +98,8 @@ UTTER_ASK_PREFIX = "utter_ask_"
|
|
|
98
98
|
ACTION_ASK_PREFIX = "action_ask_"
|
|
99
99
|
FLOW_PREFIX = "flow_"
|
|
100
100
|
|
|
101
|
+
UTTER_FREE_CHITCHAT_RESPONSE = "utter_free_chitchat_response"
|
|
102
|
+
|
|
101
103
|
ASSISTANT_ID_KEY = "assistant_id"
|
|
102
104
|
ASSISTANT_ID_DEFAULT_VALUE = "placeholder_default"
|
|
103
105
|
|
|
@@ -113,6 +115,7 @@ CONFIG_KEYS = CONFIG_KEYS_CORE + CONFIG_KEYS_NLU
|
|
|
113
115
|
CONFIG_MANDATORY_KEYS_CORE: List[Text] = [] + CONFIG_MANDATORY_COMMON_KEYS
|
|
114
116
|
CONFIG_MANDATORY_KEYS_NLU = ["language"] + CONFIG_MANDATORY_COMMON_KEYS
|
|
115
117
|
CONFIG_MANDATORY_KEYS = CONFIG_MANDATORY_KEYS_CORE + CONFIG_MANDATORY_KEYS_NLU
|
|
118
|
+
CONFIG_RECIPE_KEY = "recipe"
|
|
116
119
|
|
|
117
120
|
# Keys related to Forms (in the Domain)
|
|
118
121
|
REQUIRED_SLOTS_KEY = "required_slots"
|