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.

Files changed (77) hide show
  1. rasa/cli/run.py +10 -6
  2. rasa/cli/utils.py +7 -0
  3. rasa/core/channels/channel.py +93 -0
  4. rasa/core/channels/inspector/dist/assets/{arc-f0f8bd46.js → arc-62ea6ecb.js} +1 -1
  5. rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-7162c77d.js → blockDiagram-38ab4fdb-133584f2.js} +1 -1
  6. rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-b1d0d098.js → c4Diagram-3d4e48cf-3fdd847f.js} +1 -1
  7. rasa/core/channels/inspector/dist/assets/channel-6a3b6c3b.js +1 -0
  8. rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-807a1b27.js → classDiagram-70f12bd4-fbbe018c.js} +1 -1
  9. rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-5238dcdb.js → classDiagram-v2-f2320105-a4eb680a.js} +1 -1
  10. rasa/core/channels/inspector/dist/assets/clone-243bdc4d.js +1 -0
  11. rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-75dfaa67.js → createText-2e5e7dd3-a0a4811e.js} +1 -1
  12. rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-df20501d.js → edges-e0da2a9e-d6c66181.js} +1 -1
  13. rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-13cf4797.js → erDiagram-9861fffd-f2062a78.js} +1 -1
  14. rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-a4991264.js → flowDb-956e92f1-1a6bd8c6.js} +1 -1
  15. rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-ccecf773.js → flowDiagram-66a62f08-8c64ef56.js} +1 -1
  16. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-2fc14195.js +1 -0
  17. rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-b5801783.js → flowchart-elk-definition-4a651766-b16259fa.js} +1 -1
  18. rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-161e079a.js → ganttDiagram-c361ad54-3bef87d8.js} +1 -1
  19. rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-f38e86a4.js → gitGraphDiagram-72cf32ee-c0776679.js} +1 -1
  20. rasa/core/channels/inspector/dist/assets/{graph-be6ef5d8.js → graph-af24022c.js} +1 -1
  21. rasa/core/channels/inspector/dist/assets/{index-3862675e-d9ce8994.js → index-3862675e-1f1f2ddf.js} +1 -1
  22. rasa/core/channels/inspector/dist/assets/{index-7794b245.js → index-e799a83e.js} +129 -116
  23. rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-5000a3dc.js → infoDiagram-f8f76790-c5d562c0.js} +1 -1
  24. rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-8ef0a17a.js → journeyDiagram-49397b02-8b3f9070.js} +1 -1
  25. rasa/core/channels/inspector/dist/assets/{layout-d649bc98.js → layout-cc1e3a25.js} +1 -1
  26. rasa/core/channels/inspector/dist/assets/{line-95add810.js → line-7f6d1f25.js} +1 -1
  27. rasa/core/channels/inspector/dist/assets/{linear-f6025094.js → linear-4bacd66e.js} +1 -1
  28. rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-2e8531c4.js → mindmap-definition-fc14e90a-2926a2f0.js} +1 -1
  29. rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-918adfdb.js → pieDiagram-8a3498a8-05bf892e.js} +1 -1
  30. rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-cbd01797.js → quadrantDiagram-120e2f19-f700d7d2.js} +1 -1
  31. rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-6a8b877b.js → requirementDiagram-deff3bca-6eb3541f.js} +1 -1
  32. rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-c377c3fe.js → sankeyDiagram-04a897e0-a47a81ed.js} +1 -1
  33. rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-ab9e9b7f.js → sequenceDiagram-704730f1-cf1ccf9f.js} +1 -1
  34. rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-5e6ae67d.js → stateDiagram-587899a1-405950fc.js} +1 -1
  35. rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-40643476.js → stateDiagram-v2-d93cdb3a-23d8e35b.js} +1 -1
  36. rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-afb8d108.js → styles-6aaf32cf-08c526c0.js} +1 -1
  37. rasa/core/channels/inspector/dist/assets/{styles-9a916d00-7edc9423.js → styles-9a916d00-8062abc7.js} +1 -1
  38. rasa/core/channels/inspector/dist/assets/{styles-c10674c1-c1d8f7e9.js → styles-c10674c1-0c776ed5.js} +1 -1
  39. rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-f494b2ef.js → svgDrawCommon-08f97a94-bb5daba8.js} +1 -1
  40. rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-11c7cdd0.js → timeline-definition-85554ec2-83421f60.js} +1 -1
  41. rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-3f191ec1.js → xychartDiagram-e933f94c-dad4ea79.js} +1 -1
  42. rasa/core/channels/inspector/dist/index.html +1 -1
  43. rasa/core/channels/inspector/src/components/Chat.tsx +23 -2
  44. rasa/core/channels/inspector/src/components/DiagramFlow.tsx +2 -5
  45. rasa/core/channels/inspector/src/helpers/conversation.ts +16 -0
  46. rasa/core/channels/inspector/src/types.ts +1 -1
  47. rasa/core/channels/voice_ready/audiocodes.py +64 -28
  48. rasa/core/channels/voice_ready/jambonz.py +29 -8
  49. rasa/core/channels/voice_ready/jambonz_protocol.py +4 -0
  50. rasa/core/channels/voice_ready/twilio_voice.py +56 -8
  51. rasa/core/channels/voice_stream/asr/asr_event.py +5 -0
  52. rasa/core/channels/voice_stream/tts/azure.py +13 -5
  53. rasa/core/channels/voice_stream/twilio_media_streams.py +110 -32
  54. rasa/core/channels/voice_stream/voice_channel.py +30 -30
  55. rasa/core/policies/intentless_policy.py +5 -59
  56. rasa/dialogue_understanding/generator/nlu_command_adapter.py +1 -1
  57. rasa/dialogue_understanding/processor/command_processor.py +20 -5
  58. rasa/dialogue_understanding/processor/command_processor_component.py +5 -2
  59. rasa/e2e_test/utils/validation.py +3 -3
  60. rasa/engine/validation.py +37 -2
  61. rasa/model_training.py +2 -1
  62. rasa/shared/constants.py +3 -0
  63. rasa/shared/core/domain.py +12 -3
  64. rasa/shared/core/policies/__init__.py +0 -0
  65. rasa/shared/core/policies/utils.py +87 -0
  66. rasa/tracing/instrumentation/attribute_extractors.py +2 -0
  67. rasa/version.py +1 -1
  68. {rasa_pro-3.11.6.dist-info → rasa_pro-3.11.8.dist-info}/METADATA +6 -7
  69. {rasa_pro-3.11.6.dist-info → rasa_pro-3.11.8.dist-info}/RECORD +72 -71
  70. {rasa_pro-3.11.6.dist-info → rasa_pro-3.11.8.dist-info}/WHEEL +1 -1
  71. README.md +0 -41
  72. rasa/core/channels/inspector/dist/assets/channel-e265ea59.js +0 -1
  73. rasa/core/channels/inspector/dist/assets/clone-21f8a43d.js +0 -1
  74. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-5c8ce12d.js +0 -1
  75. rasa/keys +0 -1
  76. {rasa_pro-3.11.6.dist-info → rasa_pro-3.11.8.dist-info}/NOTICE +0 -0
  77. {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 typing import Any, Awaitable, Callable, Dict, Optional, Text, Tuple
7
-
8
- from sanic import Blueprint, HTTPResponse, Request, response
9
- from sanic import Websocket # type: ignore
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
- from twilio.twiml.voice_response import Connect, VoiceResponse
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
- voice_response.append(start)
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 handle_silence_timeout(
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.handle_silence_timeout(
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 consume_asr_events() -> None:
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
- audio_forwarding_task = asyncio.create_task(consume_audio_bytes())
382
- asr_event_task = asyncio.create_task(consume_asr_events())
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
- [audio_forwarding_task, asr_event_task],
375
+ tasks,
385
376
  return_when=asyncio.FIRST_COMPLETED,
386
377
  )
387
- if not audio_forwarding_task.done():
388
- audio_forwarding_task.cancel()
389
- if not asr_event_task.done():
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, Set, TYPE_CHECKING, Text, Tuple
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 = filter_responses(responses, forms, flows or FlowsList([]))
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, command, all_flows, execution_context, story_graph
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 and not has_e2e_stories
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
- rasa.shared.utils.io.raise_warning(
58
+ raise ModelNotFound(
58
59
  f"The provided model path '{model_path}' could not be found. "
59
- f"Using default location '{default}' instead.",
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 FlowsList, Flow
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, model_configuration: GraphModelConfiguration
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"