rasa-pro 3.12.0.dev9__py3-none-any.whl → 3.12.0.dev11__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 (72) hide show
  1. rasa/cli/inspect.py +20 -1
  2. rasa/cli/shell.py +3 -3
  3. rasa/core/actions/action.py +20 -7
  4. rasa/core/actions/action_handle_digressions.py +142 -0
  5. rasa/core/actions/forms.py +10 -5
  6. rasa/core/channels/__init__.py +2 -0
  7. rasa/core/channels/voice_ready/audiocodes.py +42 -23
  8. rasa/core/channels/voice_stream/browser_audio.py +1 -0
  9. rasa/core/channels/voice_stream/call_state.py +7 -1
  10. rasa/core/channels/voice_stream/genesys.py +331 -0
  11. rasa/core/channels/voice_stream/tts/azure.py +2 -1
  12. rasa/core/channels/voice_stream/tts/cartesia.py +16 -3
  13. rasa/core/channels/voice_stream/twilio_media_streams.py +2 -1
  14. rasa/core/channels/voice_stream/voice_channel.py +2 -1
  15. rasa/core/migrate.py +2 -2
  16. rasa/core/policies/flows/flow_executor.py +36 -42
  17. rasa/core/run.py +4 -3
  18. rasa/dialogue_understanding/commands/can_not_handle_command.py +2 -2
  19. rasa/dialogue_understanding/commands/cancel_flow_command.py +62 -4
  20. rasa/dialogue_understanding/commands/change_flow_command.py +2 -2
  21. rasa/dialogue_understanding/commands/chit_chat_answer_command.py +2 -2
  22. rasa/dialogue_understanding/commands/clarify_command.py +2 -2
  23. rasa/dialogue_understanding/commands/correct_slots_command.py +11 -2
  24. rasa/dialogue_understanding/commands/handle_digressions_command.py +150 -0
  25. rasa/dialogue_understanding/commands/human_handoff_command.py +2 -2
  26. rasa/dialogue_understanding/commands/knowledge_answer_command.py +2 -2
  27. rasa/dialogue_understanding/commands/repeat_bot_messages_command.py +2 -2
  28. rasa/dialogue_understanding/commands/set_slot_command.py +7 -15
  29. rasa/dialogue_understanding/commands/skip_question_command.py +2 -2
  30. rasa/dialogue_understanding/commands/start_flow_command.py +43 -2
  31. rasa/dialogue_understanding/commands/utils.py +1 -1
  32. rasa/dialogue_understanding/constants.py +1 -0
  33. rasa/dialogue_understanding/generator/command_generator.py +110 -73
  34. rasa/dialogue_understanding/generator/command_parser.py +1 -1
  35. rasa/dialogue_understanding/generator/llm_based_command_generator.py +161 -3
  36. rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +10 -2
  37. rasa/dialogue_understanding/generator/nlu_command_adapter.py +44 -3
  38. rasa/dialogue_understanding/generator/single_step/command_prompt_template.jinja2 +40 -40
  39. rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +11 -19
  40. rasa/dialogue_understanding/generator/utils.py +32 -1
  41. rasa/dialogue_understanding/patterns/correction.py +13 -1
  42. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +62 -2
  43. rasa/dialogue_understanding/patterns/handle_digressions.py +81 -0
  44. rasa/dialogue_understanding/processor/command_processor.py +115 -28
  45. rasa/dialogue_understanding/utils.py +31 -0
  46. rasa/dialogue_understanding_test/README.md +50 -0
  47. rasa/dialogue_understanding_test/test_case_simulation/test_case_tracker_simulator.py +3 -3
  48. rasa/model_service.py +4 -0
  49. rasa/model_training.py +24 -27
  50. rasa/shared/core/constants.py +28 -3
  51. rasa/shared/core/domain.py +13 -20
  52. rasa/shared/core/events.py +13 -2
  53. rasa/shared/core/flows/flow.py +17 -0
  54. rasa/shared/core/flows/flows_yaml_schema.json +38 -0
  55. rasa/shared/core/flows/steps/collect.py +18 -1
  56. rasa/shared/core/flows/utils.py +16 -1
  57. rasa/shared/core/slot_mappings.py +144 -108
  58. rasa/shared/core/slots.py +23 -2
  59. rasa/shared/core/trackers.py +3 -1
  60. rasa/shared/nlu/constants.py +1 -0
  61. rasa/shared/providers/llm/_base_litellm_client.py +0 -40
  62. rasa/shared/utils/llm.py +1 -86
  63. rasa/shared/utils/schemas/domain.yml +0 -1
  64. rasa/telemetry.py +43 -13
  65. rasa/utils/common.py +0 -1
  66. rasa/validator.py +189 -82
  67. rasa/version.py +1 -1
  68. {rasa_pro-3.12.0.dev9.dist-info → rasa_pro-3.12.0.dev11.dist-info}/METADATA +1 -1
  69. {rasa_pro-3.12.0.dev9.dist-info → rasa_pro-3.12.0.dev11.dist-info}/RECORD +72 -68
  70. {rasa_pro-3.12.0.dev9.dist-info → rasa_pro-3.12.0.dev11.dist-info}/NOTICE +0 -0
  71. {rasa_pro-3.12.0.dev9.dist-info → rasa_pro-3.12.0.dev11.dist-info}/WHEEL +0 -0
  72. {rasa_pro-3.12.0.dev9.dist-info → rasa_pro-3.12.0.dev11.dist-info}/entry_points.txt +0 -0
@@ -125,7 +125,7 @@ def _parse_standard_commands(
125
125
  commands: List[Command] = []
126
126
  for command_clz in standard_commands:
127
127
  pattern = _get_compiled_pattern(command_clz.regex_pattern())
128
- if match := pattern.search(action.strip()):
128
+ if match := pattern.search(action):
129
129
  parsed_command = command_clz.from_dsl(match, **kwargs)
130
130
  if _additional_parsing_fn := _get_additional_parsing_logic(command_clz):
131
131
  parsed_command = _additional_parsing_fn(parsed_command, flows, **kwargs)
@@ -1,14 +1,18 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from functools import lru_cache
3
- from typing import Any, Dict, List, Optional, Text, Tuple, Union
3
+ from typing import Any, Dict, List, Optional, Set, Text, Tuple, Union
4
4
 
5
5
  import structlog
6
6
  from jinja2 import Template
7
7
 
8
+ import rasa.dialogue_understanding.generator.utils
8
9
  import rasa.shared.utils.io
9
10
  from rasa.dialogue_understanding.commands import (
10
11
  Command,
12
+ SetSlotCommand,
13
+ StartFlowCommand,
11
14
  )
15
+ from rasa.dialogue_understanding.constants import KEY_MINIMIZE_NUM_CALLS
12
16
  from rasa.dialogue_understanding.generator import CommandGenerator
13
17
  from rasa.dialogue_understanding.generator.constants import (
14
18
  DEFAULT_LLM_CONFIG,
@@ -18,13 +22,18 @@ from rasa.dialogue_understanding.generator.constants import (
18
22
  LLM_CONFIG_KEY,
19
23
  )
20
24
  from rasa.dialogue_understanding.generator.flow_retrieval import FlowRetrieval
25
+ from rasa.dialogue_understanding.stack.utils import top_flow_frame
21
26
  from rasa.engine.graph import ExecutionContext, GraphComponent
22
27
  from rasa.engine.recipes.default_recipe import DefaultV1Recipe
23
28
  from rasa.engine.storage.resource import Resource
24
29
  from rasa.engine.storage.storage import ModelStorage
30
+ from rasa.shared.core.constants import (
31
+ SetSlotExtractor,
32
+ )
25
33
  from rasa.shared.core.domain import Domain
26
34
  from rasa.shared.core.flows import Flow, FlowsList, FlowStep
27
35
  from rasa.shared.core.flows.steps.collect import CollectInformationFlowStep
36
+ from rasa.shared.core.slot_mappings import SlotFillingManager
28
37
  from rasa.shared.core.trackers import DialogueStateTracker
29
38
  from rasa.shared.exceptions import FileIOException, ProviderClientAPIException
30
39
  from rasa.shared.nlu.constants import FLOWS_IN_PROMPT
@@ -357,8 +366,7 @@ class LLMBasedCommandGenerator(
357
366
  "slots": slots_with_info,
358
367
  }
359
368
  )
360
-
361
- return sorted(result, key=lambda x: x["name"])
369
+ return result
362
370
 
363
371
  @staticmethod
364
372
  def is_extractable(
@@ -454,3 +462,153 @@ class LLMBasedCommandGenerator(
454
462
  if isinstance(current_step, CollectInformationFlowStep)
455
463
  else (None, None)
456
464
  )
465
+
466
+ @staticmethod
467
+ def _prior_commands_contain_start_flow(prior_commands: List[Command]) -> bool:
468
+ return any(isinstance(command, StartFlowCommand) for command in prior_commands)
469
+
470
+ @staticmethod
471
+ def _prior_commands_contain_set_slot_for_active_collect_step(
472
+ prior_commands: List[Command],
473
+ flows: FlowsList,
474
+ tracker: DialogueStateTracker,
475
+ ) -> bool:
476
+ latest_user_frame = top_flow_frame(tracker.stack, ignore_call_frames=False)
477
+
478
+ if latest_user_frame is None:
479
+ return False
480
+
481
+ active_flow = latest_user_frame.flow(flows)
482
+ active_step = active_flow.step_by_id(latest_user_frame.step_id)
483
+
484
+ if not isinstance(active_step, CollectInformationFlowStep):
485
+ return False
486
+
487
+ return any(
488
+ command.name == active_step.collect
489
+ for command in prior_commands
490
+ if isinstance(command, SetSlotCommand)
491
+ )
492
+
493
+ def _should_skip_llm_call(
494
+ self,
495
+ prior_commands: List[Command],
496
+ flows: FlowsList,
497
+ tracker: DialogueStateTracker,
498
+ ) -> bool:
499
+ """Skip invoking the LLM.
500
+
501
+ This returns True if the bot builder sets the property
502
+ KEY_MINIMIZE_NUM_CALLS to True and the prior commands
503
+ either contain a StartFlowCommand or a SetSlot command
504
+ for the current collect step.
505
+ """
506
+ return self.config.get(KEY_MINIMIZE_NUM_CALLS, False) and (
507
+ self._prior_commands_contain_start_flow(prior_commands)
508
+ or self._prior_commands_contain_set_slot_for_active_collect_step(
509
+ prior_commands, flows, tracker
510
+ )
511
+ )
512
+
513
+ @staticmethod
514
+ def _check_commands_against_slot_mappings(
515
+ commands: List[Command],
516
+ tracker: DialogueStateTracker,
517
+ domain: Optional[Domain] = None,
518
+ ) -> List[Command]:
519
+ """Check if the LLM-issued slot commands are fillable.
520
+
521
+ The LLM-issued slot commands are fillable if the slot
522
+ mappings are satisfied (in particular the mapping conditions).
523
+ """
524
+ if not domain:
525
+ return commands
526
+
527
+ llm_fillable_slots = [
528
+ tracker.slots.get(command.name)
529
+ for command in commands
530
+ if isinstance(command, SetSlotCommand)
531
+ and command.extractor == SetSlotExtractor.LLM.value
532
+ and tracker.slots.get(command.name) is not None
533
+ ]
534
+
535
+ if not llm_fillable_slots:
536
+ return commands
537
+
538
+ slot_filling_manager = SlotFillingManager(domain, tracker)
539
+ slots_to_be_removed = []
540
+
541
+ structlogger.debug(
542
+ "command_processor.check_commands_against_slot_mappings.active_flow",
543
+ active_flow=tracker.active_flow,
544
+ )
545
+
546
+ for slot in llm_fillable_slots:
547
+ should_fill_slot = False
548
+ for mapping in slot.mappings: # type: ignore[union-attr]
549
+ should_fill_slot = slot_filling_manager.should_fill_slot(
550
+ slot.name, # type: ignore[union-attr]
551
+ mapping,
552
+ )
553
+
554
+ if should_fill_slot:
555
+ break
556
+
557
+ if not should_fill_slot:
558
+ structlogger.debug(
559
+ "command_processor.check_commands_against_slot_mappings.slot_not_fillable",
560
+ slot_name=slot.name, # type: ignore[union-attr]
561
+ )
562
+ slots_to_be_removed.append(slot.name) # type: ignore[union-attr]
563
+
564
+ if not slots_to_be_removed:
565
+ return commands
566
+
567
+ filtered_commands = [
568
+ command
569
+ for command in commands
570
+ if not (
571
+ isinstance(command, SetSlotCommand)
572
+ and command.name in slots_to_be_removed
573
+ )
574
+ ]
575
+
576
+ return filtered_commands
577
+
578
+ def _check_start_flow_command_overlap(
579
+ self,
580
+ prior_commands: List[Command],
581
+ commands: List[Command],
582
+ prior_start_flow_names: Set[str],
583
+ current_start_flow_names: Set[str],
584
+ ) -> List[Command]:
585
+ """Prioritize the prior commands over the LLM-issued commands."""
586
+ different_flow_names = current_start_flow_names.difference(
587
+ prior_start_flow_names
588
+ )
589
+
590
+ if not different_flow_names:
591
+ return prior_commands + commands
592
+
593
+ # discard the flow names that are different to prior start flow commands
594
+ filtered_commands = [
595
+ command
596
+ for command in commands
597
+ if not isinstance(command, StartFlowCommand)
598
+ or command.flow not in different_flow_names
599
+ ]
600
+ return prior_commands + filtered_commands
601
+
602
+ def _filter_slot_commands(
603
+ self,
604
+ prior_commands: List[Command],
605
+ commands: List[Command],
606
+ overlapping_slot_names: Set[str],
607
+ ) -> Tuple[List[Command], List[Command]]:
608
+ """Prioritize prior commands over LLM ones in the case of same slot."""
609
+ filtered_commands = (
610
+ rasa.dialogue_understanding.generator.utils.filter_slot_commands(
611
+ commands, overlapping_slot_names
612
+ )
613
+ )
614
+ return prior_commands, filtered_commands
@@ -190,9 +190,14 @@ class MultiStepLLMCommandGenerator(LLMBasedCommandGenerator):
190
190
  Returns:
191
191
  The commands generated by the llm.
192
192
  """
193
+ prior_commands = self._get_prior_commands(message)
194
+
193
195
  if tracker is None or flows.is_empty():
194
196
  # cannot do anything if there are no flows or no tracker
195
- return []
197
+ return prior_commands
198
+
199
+ if self._should_skip_llm_call(prior_commands, flows, tracker):
200
+ return prior_commands
196
201
 
197
202
  try:
198
203
  commands = await self._predict_commands_with_multi_step(
@@ -221,7 +226,10 @@ class MultiStepLLMCommandGenerator(LLMBasedCommandGenerator):
221
226
  commands=commands,
222
227
  )
223
228
 
224
- return commands
229
+ domain = kwargs.get("domain")
230
+ commands = self._check_commands_against_slot_mappings(commands, tracker, domain)
231
+
232
+ return self._check_commands_overlap(prior_commands, commands)
225
233
 
226
234
  @classmethod
227
235
  def parse_commands(
@@ -1,7 +1,8 @@
1
- from typing import Any, Dict, List, Optional, Text
1
+ from typing import Any, Dict, List, Optional, Set, Text, Tuple
2
2
 
3
3
  import structlog
4
4
 
5
+ import rasa.dialogue_understanding.generator.utils
5
6
  from rasa.dialogue_understanding.commands import (
6
7
  Command,
7
8
  SetSlotCommand,
@@ -98,9 +99,11 @@ class NLUCommandAdapter(GraphComponent, CommandGenerator):
98
99
  Returns:
99
100
  The commands triggered by NLU.
100
101
  """
102
+ prior_commands = self._get_prior_commands(message)
103
+
101
104
  if tracker is None or flows.is_empty():
102
105
  # cannot do anything if there are no flows or no tracker
103
- return []
106
+ return prior_commands
104
107
 
105
108
  domain = kwargs.get("domain", None)
106
109
  commands = self.convert_nlu_to_commands(message, tracker, flows, domain)
@@ -146,7 +149,7 @@ class NLUCommandAdapter(GraphComponent, CommandGenerator):
146
149
  commands=commands,
147
150
  )
148
151
 
149
- return commands
152
+ return self._check_commands_overlap(prior_commands, commands)
150
153
 
151
154
  @staticmethod
152
155
  def convert_nlu_to_commands(
@@ -208,6 +211,44 @@ class NLUCommandAdapter(GraphComponent, CommandGenerator):
208
211
  )
209
212
  return commands
210
213
 
214
+ def _check_start_flow_command_overlap(
215
+ self,
216
+ prior_commands: List[Command],
217
+ commands: List[Command],
218
+ prior_start_flow_names: Set[str],
219
+ current_start_flow_names: Set[str],
220
+ ) -> List[Command]:
221
+ """Prioritize the current NLU commands over the prior commands."""
222
+ different_flow_names = prior_start_flow_names.difference(
223
+ current_start_flow_names
224
+ )
225
+
226
+ if not different_flow_names:
227
+ return prior_commands + commands
228
+
229
+ filtered_commands = [
230
+ command
231
+ for command in prior_commands
232
+ if not isinstance(command, StartFlowCommand)
233
+ or command.flow not in different_flow_names
234
+ ]
235
+
236
+ return filtered_commands + commands
237
+
238
+ def _filter_slot_commands(
239
+ self,
240
+ prior_commands: List[Command],
241
+ commands: List[Command],
242
+ overlapping_slot_names: Set[str],
243
+ ) -> Tuple[List[Command], List[Command]]:
244
+ """Prioritize NLU commands over prior_commands in the case of same slot."""
245
+ filtered_prior_commands = (
246
+ rasa.dialogue_understanding.generator.utils.filter_slot_commands(
247
+ prior_commands, overlapping_slot_names
248
+ )
249
+ )
250
+ return filtered_prior_commands, commands
251
+
211
252
 
212
253
  def _issue_set_slot_commands(
213
254
  message: Message,
@@ -1,58 +1,58 @@
1
1
  Your task is to analyze the current conversation context and generate a list of actions to start new business processes that we call flows, to extract slots, or respond to small talk and knowledge requests.
2
2
 
3
-
4
- ## Available Actions:
5
- * Starting a flow, described by "start flow_name". For example, "start transfer_money" or "start list_contacts"
6
- * Slot setting, described by "set slot_name slot_value". For example, "set transfer_money_recipient Freddy". Can be used to correct and change previously set values.
7
- * Cancelling the current flow, described by "cancel"
8
- * Clarifying which flow should be started in ambiguous cases. For example, "clarify list_contacts add_contact remove_contact" if the user just wrote "contacts" and there are multiple potential candidates.
9
- * Skipping the current question when the user explicitly asks for it, described by "skip".
10
- * Responding to knowledge-oriented user messages, described by "search"
11
- * Responding to a casual, non-task-oriented user message, described by "chat".
12
- * Handing over to a human, in case the user seems frustrated or explicitly asks to speak to one, described by "hand over".
13
-
14
-
15
- ## General Tips
16
- * Do not fill slots with abstract values or placeholders.
17
- * Only use information provided by the user.
18
- * Use clarification in ambiguous cases.
19
- * Multiple flows can be started. If a user wants to digress into a second flow, you do not need to cancel the current flow.
20
- * Strictly adhere to the provided action format.
21
- * For categorical slots try to match the user message with potential slot values. Use "other" if you cannot match it
22
- * Focus on the last message and take it one step at a time.
23
- * Use the previous conversation steps only to aid understanding.
24
-
25
-
26
- ## Available Flows:
3
+ These are the flows that can be started, with their description and slots:
27
4
  {% for flow in available_flows %}
28
- * {{ flow.name }}: {{ flow.description }}
5
+ {{ flow.name }}: {{ flow.description }}
29
6
  {% for slot in flow.slots -%}
30
- * {{ slot.name }}{% if slot.description %} ({{ slot.description }}){% endif %}{% if slot.allowed_values %}, allowed values: {{ slot.allowed_values }}{% endif %}
7
+ slot: {{ slot.name }}{% if slot.description %} ({{ slot.description }}){% endif %}{% if slot.allowed_values %}, allowed values: {{ slot.allowed_values }}{% endif %}
31
8
  {% endfor %}
32
9
  {%- endfor %}
33
10
 
11
+ ===
12
+ Here is what happened previously in the conversation:
13
+ {{ current_conversation }}
34
14
 
35
- ## Current State
15
+ ===
36
16
  {% if current_flow != None %}
37
17
  You are currently in the flow "{{ current_flow }}".
38
18
  You have just asked the user for the slot "{{ current_slot }}"{% if current_slot_description %} ({{ current_slot_description }}){% endif %}.
39
19
 
40
20
  {% if flow_slots|length > 0 %}
41
- Here are the slots of the flow "{{ current_flow }}":
21
+ Here are the slots of the currently active flow:
42
22
  {% for slot in flow_slots -%}
43
- * name: {{ slot.name }}, value: {{ slot.value }}, type: {{ slot.type }}, description: {{ slot.description}}{% if slot.allowed_values %}, allowed values: {{ slot.allowed_values }}{% endif %}
23
+ - name: {{ slot.name }}, value: {{ slot.value }}, type: {{ slot.type }}, description: {{ slot.description}}{% if slot.allowed_values %}, allowed values: {{ slot.allowed_values }}{% endif %}
44
24
  {% endfor %}
45
25
  {% endif %}
46
26
  {% else %}
47
- You are currently not inside any flow.
27
+ You are currently not in any flow and so there are no active slots.
28
+ This means you can only set a slot if you first start a flow that requires that slot.
48
29
  {% endif %}
49
-
50
-
51
- ## Conversation History
52
- {{ current_conversation }}
53
-
54
-
55
- ## Task
56
- Create an action list with one action per line in response to the users last message: """{{ user_message }}""".
57
-
58
- Your action list:
30
+ If you start a flow, first start the flow and then optionally fill that flow's slots with information the user provided in their message.
31
+
32
+ The user just said """{{ user_message }}""".
33
+
34
+ ===
35
+ Based on this information generate a list of actions you want to take. Your job is to start flows and to fill slots where appropriate. Any logic of what happens afterwards is handled by the flow engine. These are your available actions:
36
+ * Slot setting, described by "SetSlot(slot_name, slot_value)". An example would be "SetSlot(recipient, Freddy)"
37
+ * Starting another flow, described by "StartFlow(flow_name)". An example would be "StartFlow(transfer_money)"
38
+ * Cancelling the current flow, described by "CancelFlow()"
39
+ * Clarifying which flow should be started. An example would be Clarify(list_contacts, add_contact, remove_contact) if the user just wrote "contacts" and there are multiple potential candidates. It also works with a single flow name to confirm you understood correctly, as in Clarify(transfer_money).
40
+ * Intercepting and handle user messages with the intent to bypass the current step in the flow, described by "SkipQuestion()". Examples of user skip phrases are: "Go to the next question", "Ask me something else".
41
+ * Responding to knowledge-oriented user messages, described by "SearchAndReply()"
42
+ * Responding to a casual, non-task-oriented user message, described by "ChitChat()".
43
+ * Handing off to a human, in case the user seems frustrated or explicitly asks to speak to one, described by "HumanHandoff()".
44
+ * Repeat the last bot messages, described by "RepeatLastBotMessages()". This is useful when the user asks to repeat the last bot messages.
45
+
46
+ ===
47
+ Write out the actions you want to take, one per line, in the order they should take place.
48
+ Do not fill slots with abstract values or placeholders.
49
+ Only use information provided by the user.
50
+ Only start a flow if it's completely clear what the user wants. Imagine you were a person reading this message. If it's not 100% clear, clarify the next step.
51
+ Don't be overly confident. Take a conservative approach and clarify before proceeding.
52
+ If the user asks for two things which seem contradictory, clarify before starting a flow.
53
+ If it's not clear whether the user wants to skip the step or to cancel the flow, cancel the flow.
54
+ Strictly adhere to the provided action types listed above.
55
+ Focus on the last message and take it one step at a time.
56
+ Use the previous conversation steps only to aid understanding.
57
+
58
+ Your action list:
@@ -51,7 +51,6 @@ from rasa.shared.utils.llm import (
51
51
  sanitize_message_for_prompt,
52
52
  tracker_as_readable_transcript,
53
53
  )
54
- from rasa.utils.beta import BetaNotEnabledException, ensure_beta_feature_is_enabled
55
54
  from rasa.utils.log_utils import log_llm
56
55
 
57
56
  COMMAND_PROMPT_FILE_NAME = "command_prompt.jinja2"
@@ -111,7 +110,6 @@ class SingleStepLLMCommandGenerator(LLMBasedCommandGenerator):
111
110
  )
112
111
 
113
112
  self.trace_prompt_tokens = self.config.get("trace_prompt_tokens", False)
114
- self.repeat_command_enabled = self.is_repeat_command_enabled()
115
113
 
116
114
  ### Implementations of LLMBasedCommandGenerator parent
117
115
  @staticmethod
@@ -198,9 +196,14 @@ class SingleStepLLMCommandGenerator(LLMBasedCommandGenerator):
198
196
  Returns:
199
197
  The commands generated by the llm.
200
198
  """
199
+ prior_commands = self._get_prior_commands(message)
200
+
201
201
  if tracker is None or flows.is_empty():
202
202
  # cannot do anything if there are no flows or no tracker
203
- return []
203
+ return prior_commands
204
+
205
+ if self._should_skip_llm_call(prior_commands, flows, tracker):
206
+ return prior_commands
204
207
 
205
208
  try:
206
209
  commands = await self._predict_commands(message, flows, tracker)
@@ -209,7 +212,7 @@ class SingleStepLLMCommandGenerator(LLMBasedCommandGenerator):
209
212
  # "predict" the ErrorCommand
210
213
  commands = [ErrorCommand()]
211
214
 
212
- if not commands:
215
+ if not commands and not prior_commands:
213
216
  # no commands are parsed or there's an invalid command
214
217
  structlogger.warning(
215
218
  "single_step_llm_command_generator.predict_commands",
@@ -230,7 +233,10 @@ class SingleStepLLMCommandGenerator(LLMBasedCommandGenerator):
230
233
  commands=commands,
231
234
  )
232
235
 
233
- return commands
236
+ domain = kwargs.get("domain")
237
+ commands = self._check_commands_against_slot_mappings(commands, tracker, domain)
238
+
239
+ return self._check_commands_overlap(prior_commands, commands)
234
240
 
235
241
  async def _predict_commands(
236
242
  self,
@@ -406,20 +412,6 @@ class SingleStepLLMCommandGenerator(LLMBasedCommandGenerator):
406
412
  "current_slot": current_slot,
407
413
  "current_slot_description": current_slot_description,
408
414
  "user_message": latest_user_message,
409
- "is_repeat_command_enabled": self.repeat_command_enabled,
410
415
  }
411
416
 
412
417
  return self.compile_template(self.prompt_template).render(**inputs)
413
-
414
- def is_repeat_command_enabled(self) -> bool:
415
- """Check for feature flag"""
416
- RASA_PRO_BETA_REPEAT_COMMAND_ENV_VAR_NAME = "RASA_PRO_BETA_REPEAT_COMMAND"
417
- try:
418
- ensure_beta_feature_is_enabled(
419
- "Repeat Command",
420
- env_flag=RASA_PRO_BETA_REPEAT_COMMAND_ENV_VAR_NAME,
421
- )
422
- except BetaNotEnabledException:
423
- return False
424
-
425
- return True
@@ -1,14 +1,16 @@
1
- from typing import Dict, Type
1
+ from typing import Dict, List, Set, Type
2
2
 
3
3
  from rasa.dialogue_understanding.commands import (
4
4
  CancelFlowCommand,
5
5
  CannotHandleCommand,
6
6
  ChitChatAnswerCommand,
7
7
  Command,
8
+ CorrectSlotsCommand,
8
9
  HumanHandoffCommand,
9
10
  KnowledgeAnswerCommand,
10
11
  RestartCommand,
11
12
  SessionStartCommand,
13
+ SetSlotCommand,
12
14
  SkipQuestionCommand,
13
15
  )
14
16
  from rasa.dialogue_understanding.commands.user_silence_command import UserSilenceCommand
@@ -43,3 +45,32 @@ triggerable_pattern_to_command_class: Dict[str, Type[Command]] = {
43
45
  CannotHandlePatternFlowStackFrame.flow_id: CannotHandleCommand,
44
46
  RestartPatternFlowStackFrame.flow_id: RestartCommand,
45
47
  }
48
+
49
+
50
+ def filter_slot_commands(
51
+ commands: List[Command], overlapping_slot_names: Set[str]
52
+ ) -> List[Command]:
53
+ """Filter out slot commands that set overlapping slots."""
54
+ filtered_commands = []
55
+
56
+ for command in commands:
57
+ if (
58
+ isinstance(command, SetSlotCommand)
59
+ and command.name in overlapping_slot_names
60
+ ):
61
+ continue
62
+
63
+ if isinstance(command, CorrectSlotsCommand):
64
+ allowed_slots = [
65
+ slot
66
+ for slot in command.corrected_slots
67
+ if slot.name not in overlapping_slot_names
68
+ ]
69
+ if not allowed_slots:
70
+ continue
71
+
72
+ command.corrected_slots = allowed_slots
73
+
74
+ filtered_commands.append(command)
75
+
76
+ return filtered_commands
@@ -54,6 +54,8 @@ class CorrectionPatternFlowStackFrame(PatternFlowStackFrame):
54
54
  """The ID of the flow to reset to."""
55
55
  reset_step_id: Optional[str] = None
56
56
  """The ID of the step to reset to."""
57
+ new_slot_values: List[Any] = field(default_factory=list)
58
+ """The new values for the corrected slots."""
57
59
 
58
60
  @classmethod
59
61
  def type(cls) -> str:
@@ -70,6 +72,10 @@ class CorrectionPatternFlowStackFrame(PatternFlowStackFrame):
70
72
  Returns:
71
73
  The created `DialogueStackFrame`.
72
74
  """
75
+ new_slot_values = [
76
+ val.get("value") for _, val in data["corrected_slots"].items()
77
+ ]
78
+
73
79
  return CorrectionPatternFlowStackFrame(
74
80
  frame_id=data["frame_id"],
75
81
  step_id=data["step_id"],
@@ -77,6 +83,7 @@ class CorrectionPatternFlowStackFrame(PatternFlowStackFrame):
77
83
  corrected_slots=data["corrected_slots"],
78
84
  reset_flow_id=data["reset_flow_id"],
79
85
  reset_step_id=data["reset_step_id"],
86
+ new_slot_values=new_slot_values,
80
87
  )
81
88
 
82
89
 
@@ -118,7 +125,12 @@ class ActionCorrectFlowSlot(action.Action):
118
125
  )
119
126
  events.extend(tracker.create_stack_updated_events(updated_stack))
120
127
 
121
- events.extend([SlotSet(k, v) for k, v in top.corrected_slots.items()])
128
+ events.extend(
129
+ [
130
+ SlotSet(name, value=val.get("value"), filled_by=val.get("filled_by"))
131
+ for name, val in top.corrected_slots.items()
132
+ ]
133
+ )
122
134
  return events
123
135
 
124
136
 
@@ -1,6 +1,17 @@
1
1
  version: "3.1"
2
2
  responses:
3
3
 
4
+ utter_ask_continue_previous_flow:
5
+ - text: "Confirm if you would like to continue with the initial topic: {{context.interrupted_flow_id}}?"
6
+ metadata:
7
+ rephrase: True
8
+ template: jinja
9
+ buttons:
10
+ - title: Continue with the previous topic.
11
+ payload: /SetSlots(continue_previous_flow=True)
12
+ - title: Switch to new topic.
13
+ payload: /SetSlots(continue_previous_flow=False)
14
+
4
15
  utter_ask_rephrase:
5
16
  - text: I’m sorry I am unable to understand you, could you please rephrase?
6
17
 
@@ -9,6 +20,20 @@ responses:
9
20
  metadata:
10
21
  rephrase: True
11
22
 
23
+ utter_block_digressions:
24
+ - text: "We can look into {{ context.interrupting_flow_id }} later. Let's focus on the current topic: {{ context.interrupted_flow_id }}."
25
+ metadata:
26
+ rephrase: True
27
+ template: jinja
28
+ - text: "Let's continue with the current topic: {{ context.interrupted_flow_id }}."
29
+ condition:
30
+ - type: slot
31
+ name: continue_previous_flow
32
+ value: True
33
+ metadata:
34
+ rephrase: True
35
+ template: jinja
36
+
12
37
  utter_boolean_slot_rejection:
13
38
  - text: "Sorry, the value you provided, `{{value}}`, is not valid. Please respond with a valid value."
14
39
  metadata:
@@ -35,8 +60,14 @@ responses:
35
60
  rephrase: True
36
61
  template: jinja
37
62
 
63
+ utter_continue_interruption:
64
+ - text: "Let's continue with the chosen topic instead: {{ context.interrupting_flow_id }}."
65
+ metadata:
66
+ rephrase: True
67
+ template: jinja
68
+
38
69
  utter_corrected_previous_input:
39
- - text: "Ok, I am updating {{ context.corrected_slots.keys()|join(', ') }} to {{ context.corrected_slots.values()|join(', ') }} respectively."
70
+ - text: "Ok, I am updating {{ context.corrected_slots.keys()|join(', ') }} to {{ context.new_slot_values | join(', ') }} respectively."
40
71
  metadata:
41
72
  rephrase: True
42
73
  template: jinja
@@ -119,7 +150,10 @@ slots:
119
150
  type: float
120
151
  initial_value: 0.0
121
152
  max_value: 1000000
122
-
153
+ continue_previous_flow:
154
+ type: bool
155
+ mappings:
156
+ - type: from_llm
123
157
 
124
158
  flows:
125
159
  pattern_cancel_flow:
@@ -163,6 +197,7 @@ flows:
163
197
  steps:
164
198
  - action: action_clarify_flows
165
199
  - action: utter_clarification_options_rasa
200
+ - action: action_listen
166
201
 
167
202
  pattern_code_change:
168
203
  description: Conversation repair flow for cleaning the stack after an assistant update
@@ -212,6 +247,31 @@ flows:
212
247
  next: END
213
248
  - else: END
214
249
 
250
+ pattern_handle_digressions:
251
+ description: Conversation repair flow for handling digressions
252
+ name: pattern handle digressions
253
+ steps:
254
+ - noop: true
255
+ id: branching
256
+ next:
257
+ - if: context.ask_confirm_digressions contains context.interrupting_flow_id
258
+ then: continue_previous_flow
259
+ - if: context.block_digressions contains context.interrupting_flow_id
260
+ then: block_digression
261
+ - else: continue_digression
262
+ - id: continue_previous_flow
263
+ collect: continue_previous_flow
264
+ next:
265
+ - if: slots.continue_previous_flow
266
+ then: block_digression
267
+ - else: continue_digression
268
+ - id: block_digression
269
+ action: action_block_digression
270
+ next: END
271
+ - id: continue_digression
272
+ action: action_continue_digression
273
+ next: END
274
+
215
275
  pattern_human_handoff:
216
276
  description: Conversation repair flow for switching users to a human agent if their request can't be handled
217
277
  name: pattern human handoff