rasa-pro 3.12.4__py3-none-any.whl → 3.12.6__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 (49) hide show
  1. rasa/core/actions/action.py +0 -6
  2. rasa/core/channels/voice_ready/audiocodes.py +52 -17
  3. rasa/core/channels/voice_stream/audiocodes.py +53 -9
  4. rasa/core/channels/voice_stream/genesys.py +146 -16
  5. rasa/core/policies/flows/flow_executor.py +3 -38
  6. rasa/core/policies/intentless_policy.py +6 -59
  7. rasa/core/processor.py +19 -5
  8. rasa/core/utils.py +53 -0
  9. rasa/dialogue_understanding/commands/cancel_flow_command.py +4 -59
  10. rasa/dialogue_understanding/commands/start_flow_command.py +0 -41
  11. rasa/dialogue_understanding/generator/_jinja_filters.py +9 -0
  12. rasa/dialogue_understanding/generator/command_generator.py +67 -0
  13. rasa/dialogue_understanding/generator/constants.py +4 -0
  14. rasa/dialogue_understanding/generator/llm_based_command_generator.py +22 -16
  15. rasa/dialogue_understanding/generator/nlu_command_adapter.py +1 -1
  16. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_claude_3_5_sonnet_20240620_template.jinja2 +2 -2
  17. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_gpt_4o_2024_11_20_template.jinja2 +2 -2
  18. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +0 -61
  19. rasa/dialogue_understanding/processor/command_processor.py +27 -70
  20. rasa/dialogue_understanding/processor/command_processor_component.py +5 -2
  21. rasa/dialogue_understanding/stack/utils.py +0 -38
  22. rasa/e2e_test/llm_judge_prompts/answer_relevance_prompt_template.jinja2 +1 -1
  23. rasa/engine/validation.py +36 -1
  24. rasa/model_training.py +2 -1
  25. rasa/shared/constants.py +2 -0
  26. rasa/shared/core/constants.py +0 -8
  27. rasa/shared/core/domain.py +12 -3
  28. rasa/shared/core/flows/flow.py +0 -17
  29. rasa/shared/core/flows/flows_yaml_schema.json +3 -38
  30. rasa/shared/core/flows/steps/collect.py +5 -18
  31. rasa/shared/core/flows/utils.py +1 -16
  32. rasa/shared/core/policies/__init__.py +0 -0
  33. rasa/shared/core/policies/utils.py +87 -0
  34. rasa/shared/core/slot_mappings.py +23 -5
  35. rasa/shared/nlu/constants.py +0 -1
  36. rasa/shared/utils/common.py +11 -1
  37. rasa/tracing/instrumentation/attribute_extractors.py +2 -0
  38. rasa/validator.py +1 -123
  39. rasa/version.py +1 -1
  40. {rasa_pro-3.12.4.dist-info → rasa_pro-3.12.6.dist-info}/METADATA +4 -5
  41. {rasa_pro-3.12.4.dist-info → rasa_pro-3.12.6.dist-info}/RECORD +44 -46
  42. {rasa_pro-3.12.4.dist-info → rasa_pro-3.12.6.dist-info}/WHEEL +1 -1
  43. README.md +0 -38
  44. rasa/core/actions/action_handle_digressions.py +0 -164
  45. rasa/dialogue_understanding/commands/handle_digressions_command.py +0 -144
  46. rasa/dialogue_understanding/patterns/handle_digressions.py +0 -81
  47. rasa/keys +0 -1
  48. {rasa_pro-3.12.4.dist-info → rasa_pro-3.12.6.dist-info}/NOTICE +0 -0
  49. {rasa_pro-3.12.4.dist-info → rasa_pro-3.12.6.dist-info}/entry_points.txt +0 -0
@@ -105,10 +105,6 @@ logger = logging.getLogger(__name__)
105
105
  def default_actions(action_endpoint: Optional[EndpointConfig] = None) -> List["Action"]:
106
106
  """List default actions."""
107
107
  from rasa.core.actions.action_clean_stack import ActionCleanStack
108
- from rasa.core.actions.action_handle_digressions import (
109
- ActionBlockDigressions,
110
- ActionContinueDigression,
111
- )
112
108
  from rasa.core.actions.action_hangup import ActionHangup
113
109
  from rasa.core.actions.action_repeat_bot_messages import ActionRepeatBotMessages
114
110
  from rasa.core.actions.action_run_slot_rejections import ActionRunSlotRejections
@@ -143,8 +139,6 @@ def default_actions(action_endpoint: Optional[EndpointConfig] = None) -> List["A
143
139
  ActionResetRouting(),
144
140
  ActionHangup(),
145
141
  ActionRepeatBotMessages(),
146
- ActionBlockDigressions(),
147
- ActionContinueDigression(),
148
142
  ]
149
143
 
150
144
 
@@ -1,5 +1,6 @@
1
1
  import asyncio
2
2
  import copy
3
+ import hmac
3
4
  import json
4
5
  import uuid
5
6
  from collections import defaultdict
@@ -114,11 +115,21 @@ class Conversation:
114
115
  async def handle_activities(
115
116
  self,
116
117
  message: Dict[Text, Any],
118
+ input_channel_name: str,
117
119
  output_channel: OutputChannel,
118
120
  on_new_message: Callable[[UserMessage], Awaitable[Any]],
119
121
  ) -> None:
120
122
  """Handle activities sent by Audiocodes."""
121
123
  structlogger.debug("audiocodes.handle.activities")
124
+ if input_channel_name == "":
125
+ structlogger.warning(
126
+ "audiocodes.handle.activities.empty_input_channel_name",
127
+ event_info=(
128
+ "Audiocodes input channel name is empty "
129
+ f"for conversation {self.conversation_id}"
130
+ ),
131
+ )
132
+
122
133
  for activity in message["activities"]:
123
134
  text = None
124
135
  if activity[ACTIVITY_ID_KEY] in self.activity_ids:
@@ -142,6 +153,7 @@ class Conversation:
142
153
  metadata = self.get_metadata(activity)
143
154
  user_msg = UserMessage(
144
155
  text=text,
156
+ input_channel=input_channel_name,
145
157
  output_channel=output_channel,
146
158
  sender_id=self.conversation_id,
147
159
  metadata=metadata,
@@ -245,8 +257,13 @@ class AudiocodesInput(InputChannel):
245
257
 
246
258
  def _check_token(self, token: Optional[Text]) -> None:
247
259
  if not token:
260
+ structlogger.error("audiocodes.token_not_provided")
248
261
  raise HttpUnauthorized("Authentication token required.")
249
262
 
263
+ if not hmac.compare_digest(str(token), str(self.token)):
264
+ structlogger.error("audiocodes.invalid_token", invalid_token=token)
265
+ raise HttpUnauthorized("Invalid authentication token.")
266
+
250
267
  def _get_conversation(
251
268
  self, token: Optional[Text], conversation_id: Text
252
269
  ) -> Conversation:
@@ -388,7 +405,12 @@ class AudiocodesInput(InputChannel):
388
405
  # start a background task to handle activities
389
406
  self._create_task(
390
407
  conversation_id,
391
- conversation.handle_activities(request.json, ac_output, on_new_message),
408
+ conversation.handle_activities(
409
+ request.json,
410
+ input_channel_name=self.name(),
411
+ output_channel=ac_output,
412
+ on_new_message=on_new_message,
413
+ ),
392
414
  )
393
415
  return response.json(response_json)
394
416
 
@@ -401,23 +423,9 @@ class AudiocodesInput(InputChannel):
401
423
  Example of payload:
402
424
  {"conversation": <conversation_id>, "reason": Optional[Text]}.
403
425
  """
404
- self._get_conversation(request.token, conversation_id)
405
- reason = {"reason": request.json.get("reason")}
406
- await on_new_message(
407
- UserMessage(
408
- text=f"{INTENT_MESSAGE_PREFIX}session_end",
409
- output_channel=None,
410
- sender_id=conversation_id,
411
- metadata=reason,
412
- )
413
- )
414
- del self.conversations[conversation_id]
415
- structlogger.debug(
416
- "audiocodes.disconnect",
417
- conversation=conversation_id,
418
- request=request.json,
426
+ return await self._handle_disconnect(
427
+ request, conversation_id, on_new_message
419
428
  )
420
- return response.json({})
421
429
 
422
430
  @ac_webhook.route("/conversation/<conversation_id>/keepalive", methods=["POST"])
423
431
  async def keepalive(request: Request, conversation_id: Text) -> HTTPResponse:
@@ -432,6 +440,32 @@ class AudiocodesInput(InputChannel):
432
440
 
433
441
  return ac_webhook
434
442
 
443
+ async def _handle_disconnect(
444
+ self,
445
+ request: Request,
446
+ conversation_id: Text,
447
+ on_new_message: Callable[[UserMessage], Awaitable[Any]],
448
+ ) -> HTTPResponse:
449
+ """Triggered when the call is disconnected."""
450
+ self._get_conversation(request.token, conversation_id)
451
+ reason = {"reason": request.json.get("reason")}
452
+ await on_new_message(
453
+ UserMessage(
454
+ text=f"{INTENT_MESSAGE_PREFIX}session_end",
455
+ input_channel=self.name(),
456
+ output_channel=None,
457
+ sender_id=conversation_id,
458
+ metadata=reason,
459
+ )
460
+ )
461
+ del self.conversations[conversation_id]
462
+ structlogger.debug(
463
+ "audiocodes.disconnect",
464
+ conversation=conversation_id,
465
+ request=request.json,
466
+ )
467
+ return response.json({})
468
+
435
469
 
436
470
  class AudiocodesOutput(OutputChannel):
437
471
  @classmethod
@@ -439,6 +473,7 @@ class AudiocodesOutput(OutputChannel):
439
473
  return CHANNEL_NAME
440
474
 
441
475
  def __init__(self) -> None:
476
+ super().__init__()
442
477
  self.messages: List[Dict] = []
443
478
 
444
479
  async def add_message(self, message: Dict) -> None:
@@ -1,5 +1,6 @@
1
1
  import asyncio
2
2
  import base64
3
+ import hmac
3
4
  import json
4
5
  from typing import Any, Awaitable, Callable, Dict, Optional, Text
5
6
 
@@ -103,6 +104,7 @@ class AudiocodesVoiceInputChannel(VoiceInputChannel):
103
104
 
104
105
  def __init__(
105
106
  self,
107
+ token: Optional[Text],
106
108
  server_url: str,
107
109
  asr_config: Dict,
108
110
  tts_config: Dict,
@@ -110,6 +112,22 @@ class AudiocodesVoiceInputChannel(VoiceInputChannel):
110
112
  ):
111
113
  mark_as_beta_feature("Audiocodes (audiocodes_stream) Channel")
112
114
  super().__init__(server_url, asr_config, tts_config, monitor_silence)
115
+ self.token = token
116
+
117
+ @classmethod
118
+ def from_credentials(
119
+ cls, credentials: Optional[Dict[str, Any]]
120
+ ) -> VoiceInputChannel:
121
+ if not credentials:
122
+ raise ValueError("No credentials given for Audiocodes voice channel.")
123
+
124
+ return cls(
125
+ token=credentials.get("token"),
126
+ server_url=credentials["server_url"],
127
+ asr_config=credentials["asr"],
128
+ tts_config=credentials["tts"],
129
+ monitor_silence=credentials.get("monitor_silence", False),
130
+ )
113
131
 
114
132
  def channel_bytes_to_rasa_audio_bytes(self, input_bytes: bytes) -> RasaAudioBytes:
115
133
  return RasaAudioBytes(base64.b64decode(input_bytes))
@@ -135,6 +153,13 @@ class AudiocodesVoiceInputChannel(VoiceInputChannel):
135
153
  )
136
154
  if activity["name"] == "start":
137
155
  return map_call_params(activity["parameters"])
156
+ elif data["type"] == "connection.validate":
157
+ # not part of call flow; only sent when integration is created
158
+ logger.info(
159
+ "audiocodes_stream.collect_call_parameters.connection.validate",
160
+ event_info="received request to validate integration",
161
+ )
162
+ self._send_validated(channel_websocket, data)
138
163
  else:
139
164
  logger.warning("audiocodes_stream.unknown_message", data=data)
140
165
  return None
@@ -158,7 +183,7 @@ class AudiocodesVoiceInputChannel(VoiceInputChannel):
158
183
  elif activity["name"] == "playFinished":
159
184
  logger.debug("audiocodes_stream.playFinished", data=activity)
160
185
  if call_state.should_hangup:
161
- logger.info("audiocodes.hangup")
186
+ logger.info("audiocodes_stream.hangup")
162
187
  self._send_hangup(ws, data)
163
188
  # the conversation should continue until
164
189
  # we receive a end message from audiocodes
@@ -180,11 +205,10 @@ class AudiocodesVoiceInputChannel(VoiceInputChannel):
180
205
  elif data["type"] == "session.end":
181
206
  logger.debug("audiocodes_stream.end", data=data)
182
207
  return EndConversationAction()
183
- elif data["type"] == "connection.validate":
184
- # not part of call flow; only sent when integration is created
185
- self._send_validated(ws, data)
186
208
  else:
187
- logger.warning("audiocodes_stream.unknown_message", data=data)
209
+ logger.warning(
210
+ "audiocodes_stream.map_input_message.unknown_message", data=data
211
+ )
188
212
 
189
213
  return ContinueConversationAction()
190
214
 
@@ -254,6 +278,17 @@ class AudiocodesVoiceInputChannel(VoiceInputChannel):
254
278
  self.tts_cache,
255
279
  )
256
280
 
281
+ def _is_token_valid(self, token: Optional[Text]) -> bool:
282
+ # If no token is set, always return True
283
+ if not self.token:
284
+ return True
285
+
286
+ # Token is required, but not provided
287
+ if not token:
288
+ return False
289
+
290
+ return hmac.compare_digest(str(self.token), str(token))
291
+
257
292
  def blueprint(
258
293
  self, on_new_message: Callable[[UserMessage], Awaitable[Any]]
259
294
  ) -> Blueprint:
@@ -266,17 +301,26 @@ class AudiocodesVoiceInputChannel(VoiceInputChannel):
266
301
 
267
302
  @blueprint.websocket("/websocket") # type: ignore
268
303
  async def receive(request: Request, ws: Websocket) -> None:
269
- # TODO: validate API key header
270
- logger.info("audiocodes.receive", message="Starting audio streaming")
304
+ if not self._is_token_valid(request.token):
305
+ logger.error(
306
+ "audiocodes_stream.invalid_token",
307
+ invalid_token=request.token,
308
+ )
309
+ await ws.close(code=1008, reason="Invalid token")
310
+ return
311
+
312
+ logger.info(
313
+ "audiocodes_stream.receive", event_info="Started websocket connection"
314
+ )
271
315
  try:
272
316
  await self.run_audio_streaming(on_new_message, ws)
273
317
  except Exception as e:
274
318
  logger.exception(
275
- "audiocodes.receive",
319
+ "audiocodes_stream.receive",
276
320
  message="Error during audio streaming",
277
321
  error=e,
278
322
  )
279
- # return 500 error
323
+ await ws.close(code=1011, reason="Error during audio streaming")
280
324
  raise
281
325
 
282
326
  return blueprint
@@ -1,4 +1,7 @@
1
1
  import asyncio
2
+ import base64
3
+ import hashlib
4
+ import hmac
2
5
  import json
3
6
  from typing import Any, Awaitable, Callable, Dict, Optional, Text
4
7
 
@@ -45,6 +48,7 @@ in the documentation but observed in their example app
45
48
  https://github.com/GenesysCloudBlueprints/audioconnector-server-reference-implementation
46
49
  """
47
50
  MAXIMUM_BINARY_MESSAGE_SIZE = 64000 # 64KB
51
+ HEADER_API_KEY = "X-Api-Key"
48
52
  logger = structlog.get_logger(__name__)
49
53
 
50
54
 
@@ -86,8 +90,31 @@ class GenesysInputChannel(VoiceInputChannel):
86
90
  def name(cls) -> str:
87
91
  return "genesys"
88
92
 
89
- def __init__(self, *args: Any, **kwargs: Any) -> None:
93
+ def __init__(
94
+ self, api_key: Text, client_secret: Optional[Text], *args: Any, **kwargs: Any
95
+ ) -> None:
90
96
  super().__init__(*args, **kwargs)
97
+ self.api_key = api_key
98
+ self.client_secret = client_secret
99
+
100
+ @classmethod
101
+ def from_credentials(
102
+ cls, credentials: Optional[Dict[str, Any]]
103
+ ) -> VoiceInputChannel:
104
+ if not credentials:
105
+ raise ValueError("No credentials given for Genesys voice channel.")
106
+
107
+ if not credentials.get("api_key"):
108
+ raise ValueError("No API key given for Genesys voice channel (api_key).")
109
+
110
+ return cls(
111
+ api_key=credentials["api_key"],
112
+ client_secret=credentials.get("client_secret"),
113
+ server_url=credentials["server_url"],
114
+ asr_config=credentials["asr"],
115
+ tts_config=credentials["tts"],
116
+ monitor_silence=credentials.get("monitor_silence", False),
117
+ )
91
118
 
92
119
  def _ensure_channel_data_initialized(self) -> None:
93
120
  """Initialize Genesys-specific channel data if not already present.
@@ -273,6 +300,93 @@ class GenesysInputChannel(VoiceInputChannel):
273
300
  logger.debug("genesys.disconnect", message=message)
274
301
  _schedule_ws_task(ws.send(json.dumps(message)))
275
302
 
303
+ def _calculate_signature(self, request: Request) -> str:
304
+ """Calculate the signature using request data."""
305
+ org_id = request.headers.get("Audiohook-Organization-Id")
306
+ session_id = request.headers.get("Audiohook-Session-Id")
307
+ correlation_id = request.headers.get("Audiohook-Correlation-Id")
308
+ api_key = request.headers.get(HEADER_API_KEY)
309
+
310
+ # order of components is important!
311
+ components = [
312
+ ("@request-target", "/webhooks/genesys/websocket"),
313
+ ("audiohook-session-id", session_id),
314
+ ("audiohook-organization-id", org_id),
315
+ ("audiohook-correlation-id", correlation_id),
316
+ (HEADER_API_KEY.lower(), api_key),
317
+ ("@authority", self.server_url),
318
+ ]
319
+
320
+ # Create signature base string
321
+ signing_string = ""
322
+ for name, value in components:
323
+ signing_string += f'"{name}": {value}\n'
324
+
325
+ # Add @signature-params
326
+ signature_input = request.headers["Signature-Input"]
327
+ _, params_str = signature_input.split("=", 1)
328
+ signing_string += f'"@signature-params": {params_str}'
329
+
330
+ # Calculate the HMAC signature
331
+ key_bytes = base64.b64decode(self.client_secret)
332
+ signature = hmac.new(
333
+ key_bytes, signing_string.encode("utf-8"), hashlib.sha256
334
+ ).digest()
335
+ return base64.b64encode(signature).decode("utf-8")
336
+
337
+ async def _verify_signature(self, request: Request) -> bool:
338
+ """Verify the HTTP message signature from Genesys."""
339
+ if not self.client_secret:
340
+ logger.info(
341
+ "genesys.verify_signature.no_client_secret",
342
+ event_info="Signature verification skipped",
343
+ )
344
+ return True # Skip verification if no client secret
345
+
346
+ signature = request.headers.get("Signature")
347
+ signature_input = request.headers.get("Signature-Input")
348
+ if not signature or not signature_input:
349
+ logger.error("genesys.signature.missing_signature_header")
350
+ return False
351
+
352
+ try:
353
+ actual_signature = signature.split("=", 1)[1].strip(':"')
354
+ expected_signature = self._calculate_signature(request)
355
+ return hmac.compare_digest(
356
+ expected_signature.encode("utf-8"), actual_signature.encode("utf-8")
357
+ )
358
+ except Exception as e:
359
+ logger.exception("genesys.signature.verification_error", error=e)
360
+ return False
361
+
362
+ def _ensure_required_headers(self, request: Request) -> bool:
363
+ """Ensure required headers are present in the request."""
364
+ required_headers = [
365
+ "Audiohook-Organization-Id",
366
+ "Audiohook-Correlation-Id",
367
+ "Audiohook-Session-Id",
368
+ HEADER_API_KEY,
369
+ ]
370
+
371
+ missing_headers = [
372
+ header for header in required_headers if header not in request.headers
373
+ ]
374
+
375
+ if missing_headers:
376
+ logger.error(
377
+ "genesys.missing_required_headers",
378
+ missing_headers=missing_headers,
379
+ )
380
+ return False
381
+ return True
382
+
383
+ def _ensure_api_key(self, request: Request) -> bool:
384
+ """Ensure the API key is present in the request."""
385
+ api_key = request.headers.get(HEADER_API_KEY)
386
+ if not hmac.compare_digest(str(self.api_key), str(api_key)):
387
+ return False
388
+ return True
389
+
276
390
  def blueprint(
277
391
  self, on_new_message: Callable[[UserMessage], Awaitable[Any]]
278
392
  ) -> Blueprint:
@@ -289,23 +403,39 @@ class GenesysInputChannel(VoiceInputChannel):
289
403
  "genesys.receive",
290
404
  audiohook_session_id=request.headers.get("audiohook-session-id"),
291
405
  )
292
- # validate required headers
293
- required_headers = [
294
- "audiohook-organization-id",
295
- "audiohook-correlation-id",
296
- "audiohook-session-id",
297
- "x-api-key",
298
- ]
299
-
300
- for header in required_headers:
301
- if header not in request.headers:
302
- await ws.close(1008, f"Missing required header: {header}")
303
- return
304
-
305
- # TODO: validate API key header
406
+
407
+ # verify signature
408
+ if not await self._verify_signature(request):
409
+ logger.error("genesys.receive.invalid_signature")
410
+ await ws.close(code=1008, reason="Invalid signature")
411
+ return
412
+
413
+ # ensure required headers are present
414
+ if not self._ensure_required_headers(request):
415
+ await ws.close(code=1002, reason="Missing required headers")
416
+ return
417
+
418
+ # ensure API key is correct
419
+ if not self._ensure_api_key(request):
420
+ logger.error(
421
+ "genesys.receive.invalid_api_key",
422
+ invalid_api_key=request.headers.get(HEADER_API_KEY),
423
+ )
424
+ await ws.close(code=1008, reason="Invalid API key")
425
+ return
426
+
306
427
  # process audio streaming
307
428
  logger.info("genesys.receive", message="Starting audio streaming")
308
- await self.run_audio_streaming(on_new_message, ws)
429
+ try:
430
+ await self.run_audio_streaming(on_new_message, ws)
431
+ except Exception as e:
432
+ logger.exception(
433
+ "genesys.receive",
434
+ message="Error during audio streaming",
435
+ error=e,
436
+ )
437
+ await ws.close(code=1011, reason="Error during audio streaming")
438
+ raise
309
439
 
310
440
  return blueprint
311
441
 
@@ -23,7 +23,6 @@ from rasa.core.policies.flows.flow_step_result import (
23
23
  )
24
24
  from rasa.dialogue_understanding.commands import CancelFlowCommand
25
25
  from rasa.dialogue_understanding.patterns.cancel import CancelPatternFlowStackFrame
26
- from rasa.dialogue_understanding.patterns.clarify import ClarifyPatternFlowStackFrame
27
26
  from rasa.dialogue_understanding.patterns.collect_information import (
28
27
  CollectInformationPatternFlowStackFrame,
29
28
  )
@@ -51,7 +50,6 @@ from rasa.dialogue_understanding.stack.frames.flow_stack_frame import (
51
50
  )
52
51
  from rasa.dialogue_understanding.stack.utils import (
53
52
  top_user_flow_frame,
54
- user_flows_on_the_stack,
55
53
  )
56
54
  from rasa.shared.constants import RASA_PATTERN_HUMAN_HANDOFF
57
55
  from rasa.shared.core.constants import (
@@ -280,33 +278,6 @@ def trigger_pattern_continue_interrupted(
280
278
  return events
281
279
 
282
280
 
283
- def trigger_pattern_clarification(
284
- current_frame: DialogueStackFrame, stack: DialogueStack, flows: FlowsList
285
- ) -> None:
286
- """Trigger the pattern to clarify which topic to continue if needed."""
287
- if not isinstance(current_frame, UserFlowStackFrame):
288
- return None
289
-
290
- if current_frame.frame_type in [
291
- FlowStackFrameType.CALL,
292
- FlowStackFrameType.INTERRUPT,
293
- ]:
294
- # we want to return to the flow that called
295
- # the current flow or the flow that was interrupted
296
- # by the current flow
297
- return None
298
-
299
- pending_flows = [
300
- flows.flow_by_id(frame.flow_id)
301
- for frame in stack.frames
302
- if isinstance(frame, UserFlowStackFrame)
303
- and frame.flow_id != current_frame.flow_id
304
- ]
305
-
306
- flow_names = [flow.readable_name() for flow in pending_flows if flow is not None]
307
- stack.push(ClarifyPatternFlowStackFrame(names=flow_names))
308
-
309
-
310
281
  def trigger_pattern_completed(
311
282
  current_frame: DialogueStackFrame, stack: DialogueStack, flows: FlowsList
312
283
  ) -> None:
@@ -675,15 +646,9 @@ def _run_end_step(
675
646
  structlogger.debug("flow.step.run.flow_end")
676
647
  current_frame = stack.pop()
677
648
  trigger_pattern_completed(current_frame, stack, flows)
678
- resumed_events = []
679
- if len(user_flows_on_the_stack(stack)) > 1:
680
- # if there are more user flows on the stack,
681
- # we need to trigger the pattern clarify
682
- trigger_pattern_clarification(current_frame, stack, flows)
683
- else:
684
- resumed_events = trigger_pattern_continue_interrupted(
685
- current_frame, stack, flows, tracker
686
- )
649
+ resumed_events = trigger_pattern_continue_interrupted(
650
+ current_frame, stack, flows, tracker
651
+ )
687
652
  reset_events: List[Event] = reset_scoped_slots(current_frame, flow, tracker)
688
653
  return ContinueFlowWithNextStep(
689
654
  events=initial_events + reset_events + resumed_events, has_flow_ended=True
@@ -1,7 +1,7 @@
1
1
  import importlib.resources
2
2
  import math
3
3
  from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Text, Tuple
4
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, 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,
@@ -38,10 +37,9 @@ from rasa.shared.constants import (
38
37
  OPENAI_PROVIDER,
39
38
  PROMPT_CONFIG_KEY,
40
39
  PROVIDER_CONFIG_KEY,
41
- REQUIRED_SLOTS_KEY,
42
40
  TIMEOUT_CONFIG_KEY,
43
41
  )
44
- from rasa.shared.core.constants import ACTION_LISTEN_NAME, ACTION_TRIGGER_CHITCHAT
42
+ from rasa.shared.core.constants import ACTION_LISTEN_NAME
45
43
  from rasa.shared.core.domain import KEY_RESPONSES_TEXT, Domain
46
44
  from rasa.shared.core.events import (
47
45
  ActionExecuted,
@@ -51,6 +49,7 @@ from rasa.shared.core.events import (
51
49
  )
52
50
  from rasa.shared.core.flows import FlowsList
53
51
  from rasa.shared.core.generator import TrackerWithCachedStates
52
+ from rasa.shared.core.policies.utils import filter_responses_for_intentless_policy
54
53
  from rasa.shared.core.trackers import DialogueStateTracker
55
54
  from rasa.shared.exceptions import FileIOException, RasaCoreException
56
55
  from rasa.shared.nlu.constants import PREDICTED_CONFIDENCE_KEY
@@ -146,59 +145,6 @@ class Conversation:
146
145
  interactions: List[Interaction] = field(default_factory=list)
147
146
 
148
147
 
149
- def collect_form_responses(forms: Forms) -> Set[Text]:
150
- """Collect responses that belong the requested slots in forms.
151
-
152
- Args:
153
- forms: the forms from the domain
154
- Returns:
155
- all utterances used in forms
156
- """
157
- form_responses = set()
158
- for _, form_info in forms.data.items():
159
- for required_slot in form_info.get(REQUIRED_SLOTS_KEY, []):
160
- form_responses.add(f"utter_ask_{required_slot}")
161
- return form_responses
162
-
163
-
164
- def filter_responses(responses: Responses, forms: Forms, flows: FlowsList) -> Responses:
165
- """Filters out responses that are unwanted for the intentless policy.
166
-
167
- This includes utterances used in flows and forms.
168
-
169
- Args:
170
- responses: the responses from the domain
171
- forms: the forms from the domain
172
- flows: all flows
173
- Returns:
174
- The remaining, relevant responses for the intentless policy.
175
- """
176
- form_responses = collect_form_responses(forms)
177
- flow_responses = flows.utterances
178
- combined_responses = form_responses | flow_responses
179
- filtered_responses = {
180
- name: variants
181
- for name, variants in responses.data.items()
182
- if name not in combined_responses
183
- }
184
-
185
- pattern_chitchat = flows.flow_by_id(FLOW_PATTERN_CHITCHAT)
186
-
187
- # The following condition is highly unlikely, but mypy requires the case
188
- # of pattern_chitchat == None to be addressed
189
- if not pattern_chitchat:
190
- return Responses(data=filtered_responses)
191
-
192
- # if action_trigger_chitchat, filter out "utter_free_chitchat_response"
193
- has_action_trigger_chitchat = pattern_chitchat.has_action_step(
194
- ACTION_TRIGGER_CHITCHAT
195
- )
196
- if has_action_trigger_chitchat:
197
- filtered_responses.pop("utter_free_chitchat_response", None)
198
-
199
- return Responses(data=filtered_responses)
200
-
201
-
202
148
  def action_from_response(
203
149
  text: Optional[str], responses: Dict[Text, List[Dict[Text, Any]]]
204
150
  ) -> Optional[str]:
@@ -512,7 +458,9 @@ class IntentlessPolicy(LLMHealthCheckMixin, EmbeddingsHealthCheckMixin, Policy):
512
458
  # Perform health checks of both LLM and embeddings client configs
513
459
  self._perform_health_checks(self.config, "intentless_policy.train")
514
460
 
515
- responses = filter_responses(responses, forms, flows or FlowsList([]))
461
+ responses = filter_responses_for_intentless_policy(
462
+ responses, forms, flows or FlowsList([])
463
+ )
516
464
  telemetry.track_intentless_policy_train()
517
465
  response_texts = [r for r in extract_ai_response_examples(responses.data)]
518
466
 
@@ -947,7 +895,6 @@ class IntentlessPolicy(LLMHealthCheckMixin, EmbeddingsHealthCheckMixin, Policy):
947
895
  **kwargs: Any,
948
896
  ) -> "IntentlessPolicy":
949
897
  """Loads a trained policy (see parent class for full docstring)."""
950
-
951
898
  # Perform health checks of both LLM and embeddings client configs
952
899
  cls._perform_health_checks(config, "intentless_policy.load")
953
900