rasa-pro 3.12.6.dev2__py3-none-any.whl → 3.13.0.dev2__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 (92) hide show
  1. rasa/__init__.py +0 -6
  2. rasa/cli/scaffold.py +1 -1
  3. rasa/core/actions/action.py +38 -34
  4. rasa/core/actions/action_run_slot_rejections.py +1 -1
  5. rasa/core/channels/studio_chat.py +16 -43
  6. rasa/core/channels/voice_ready/audiocodes.py +46 -17
  7. rasa/core/information_retrieval/faiss.py +68 -7
  8. rasa/core/information_retrieval/information_retrieval.py +40 -2
  9. rasa/core/information_retrieval/milvus.py +7 -2
  10. rasa/core/information_retrieval/qdrant.py +7 -2
  11. rasa/core/nlg/contextual_response_rephraser.py +11 -27
  12. rasa/core/nlg/generator.py +5 -21
  13. rasa/core/nlg/response.py +6 -43
  14. rasa/core/nlg/summarize.py +1 -15
  15. rasa/core/nlg/translate.py +0 -8
  16. rasa/core/policies/enterprise_search_policy.py +64 -316
  17. rasa/core/policies/flows/flow_executor.py +3 -38
  18. rasa/core/policies/intentless_policy.py +4 -17
  19. rasa/core/policies/policy.py +0 -2
  20. rasa/core/processor.py +27 -6
  21. rasa/core/utils.py +53 -0
  22. rasa/dialogue_understanding/coexistence/llm_based_router.py +4 -18
  23. rasa/dialogue_understanding/commands/cancel_flow_command.py +4 -59
  24. rasa/dialogue_understanding/commands/knowledge_answer_command.py +2 -2
  25. rasa/dialogue_understanding/commands/start_flow_command.py +0 -41
  26. rasa/dialogue_understanding/generator/command_generator.py +67 -0
  27. rasa/dialogue_understanding/generator/command_parser.py +1 -1
  28. rasa/dialogue_understanding/generator/llm_based_command_generator.py +7 -23
  29. rasa/dialogue_understanding/generator/llm_command_generator.py +1 -3
  30. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_template.jinja2 +1 -1
  31. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_claude_3_5_sonnet_20240620_template.jinja2 +1 -1
  32. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_gpt_4o_2024_11_20_template.jinja2 +24 -2
  33. rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +8 -12
  34. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +0 -61
  35. rasa/dialogue_understanding/processor/command_processor.py +7 -65
  36. rasa/dialogue_understanding/stack/utils.py +0 -38
  37. rasa/dialogue_understanding_test/command_metric_calculation.py +7 -40
  38. rasa/dialogue_understanding_test/command_metrics.py +38 -0
  39. rasa/dialogue_understanding_test/du_test_case.py +58 -25
  40. rasa/dialogue_understanding_test/du_test_result.py +228 -132
  41. rasa/dialogue_understanding_test/du_test_runner.py +10 -1
  42. rasa/dialogue_understanding_test/io.py +48 -16
  43. rasa/document_retrieval/__init__.py +0 -0
  44. rasa/document_retrieval/constants.py +32 -0
  45. rasa/document_retrieval/document_post_processor.py +351 -0
  46. rasa/document_retrieval/document_post_processor_prompt_template.jinja2 +0 -0
  47. rasa/document_retrieval/document_retriever.py +333 -0
  48. rasa/document_retrieval/knowledge_base_connectors/__init__.py +0 -0
  49. rasa/document_retrieval/knowledge_base_connectors/api_connector.py +39 -0
  50. rasa/document_retrieval/knowledge_base_connectors/knowledge_base_connector.py +34 -0
  51. rasa/document_retrieval/knowledge_base_connectors/vector_store_connector.py +226 -0
  52. rasa/document_retrieval/query_rewriter.py +234 -0
  53. rasa/document_retrieval/query_rewriter_prompt_template.jinja2 +8 -0
  54. rasa/engine/recipes/default_components.py +2 -0
  55. rasa/hooks.py +0 -55
  56. rasa/model_manager/model_api.py +1 -1
  57. rasa/model_manager/socket_bridge.py +0 -7
  58. rasa/shared/constants.py +0 -5
  59. rasa/shared/core/constants.py +0 -8
  60. rasa/shared/core/domain.py +12 -3
  61. rasa/shared/core/flows/flow.py +0 -17
  62. rasa/shared/core/flows/flows_yaml_schema.json +3 -38
  63. rasa/shared/core/flows/steps/collect.py +5 -18
  64. rasa/shared/core/flows/utils.py +1 -16
  65. rasa/shared/core/slot_mappings.py +11 -5
  66. rasa/shared/core/slots.py +1 -1
  67. rasa/shared/core/trackers.py +4 -10
  68. rasa/shared/nlu/constants.py +0 -1
  69. rasa/shared/providers/constants.py +0 -9
  70. rasa/shared/providers/llm/_base_litellm_client.py +4 -14
  71. rasa/shared/providers/llm/default_litellm_llm_client.py +2 -2
  72. rasa/shared/providers/llm/litellm_router_llm_client.py +7 -17
  73. rasa/shared/providers/llm/llm_client.py +15 -24
  74. rasa/shared/providers/llm/self_hosted_llm_client.py +2 -10
  75. rasa/shared/utils/common.py +11 -1
  76. rasa/shared/utils/health_check/health_check.py +1 -7
  77. rasa/shared/utils/llm.py +1 -1
  78. rasa/tracing/instrumentation/attribute_extractors.py +50 -17
  79. rasa/tracing/instrumentation/instrumentation.py +12 -12
  80. rasa/tracing/instrumentation/intentless_policy_instrumentation.py +1 -2
  81. rasa/utils/licensing.py +0 -15
  82. rasa/validator.py +1 -123
  83. rasa/version.py +1 -1
  84. {rasa_pro-3.12.6.dev2.dist-info → rasa_pro-3.13.0.dev2.dist-info}/METADATA +2 -3
  85. {rasa_pro-3.12.6.dev2.dist-info → rasa_pro-3.13.0.dev2.dist-info}/RECORD +88 -80
  86. rasa/core/actions/action_handle_digressions.py +0 -164
  87. rasa/dialogue_understanding/commands/handle_digressions_command.py +0 -144
  88. rasa/dialogue_understanding/patterns/handle_digressions.py +0 -81
  89. rasa/monkey_patches.py +0 -91
  90. {rasa_pro-3.12.6.dev2.dist-info → rasa_pro-3.13.0.dev2.dist-info}/NOTICE +0 -0
  91. {rasa_pro-3.12.6.dev2.dist-info → rasa_pro-3.13.0.dev2.dist-info}/WHEEL +0 -0
  92. {rasa_pro-3.12.6.dev2.dist-info → rasa_pro-3.13.0.dev2.dist-info}/entry_points.txt +0 -0
@@ -30,10 +30,6 @@ from rasa.graph_components.providers.forms_provider import Forms
30
30
  from rasa.graph_components.providers.responses_provider import Responses
31
31
  from rasa.shared.constants import (
32
32
  EMBEDDINGS_CONFIG_KEY,
33
- LANGFUSE_CUSTOM_METADATA_DICT,
34
- LANGFUSE_METADATA_SESSION_ID,
35
- LANGFUSE_METADATA_USER_ID,
36
- LANGFUSE_TAGS,
37
33
  LLM_CONFIG_KEY,
38
34
  MODEL_CONFIG_KEY,
39
35
  MODEL_GROUP_ID_CONFIG_KEY,
@@ -623,7 +619,6 @@ class IntentlessPolicy(LLMHealthCheckMixin, EmbeddingsHealthCheckMixin, Policy):
623
619
  response_examples: List[str],
624
620
  conversation_samples: List[str],
625
621
  history: str,
626
- sender_id: str,
627
622
  ) -> Optional[str]:
628
623
  """Make the llm call to generate an answer."""
629
624
  llm = llm_factory(self.config.get(LLM_CONFIG_KEY), DEFAULT_LLM_CONFIG)
@@ -639,19 +634,11 @@ class IntentlessPolicy(LLMHealthCheckMixin, EmbeddingsHealthCheckMixin, Policy):
639
634
  log_event="intentless_policy.generate_answer.prompt_rendered",
640
635
  prompt=prompt,
641
636
  )
642
- return await self._generate_llm_answer(llm, prompt, sender_id)
637
+ return await self._generate_llm_answer(llm, prompt)
643
638
 
644
- async def _generate_llm_answer(
645
- self, llm: LLMClient, prompt: str, sender_id: str
646
- ) -> Optional[str]:
647
- metadata = {
648
- LANGFUSE_METADATA_USER_ID: self.user_id,
649
- LANGFUSE_METADATA_SESSION_ID: sender_id,
650
- LANGFUSE_CUSTOM_METADATA_DICT: {"component": self.__class__.__name__},
651
- LANGFUSE_TAGS: [self.__class__.__name__],
652
- }
639
+ async def _generate_llm_answer(self, llm: LLMClient, prompt: str) -> Optional[str]:
653
640
  try:
654
- llm_response = await llm.acompletion(prompt, metadata)
641
+ llm_response = await llm.acompletion(prompt)
655
642
  return llm_response.choices[0]
656
643
  except Exception as e:
657
644
  # unfortunately, langchain does not wrap LLM exceptions which means
@@ -727,7 +714,7 @@ class IntentlessPolicy(LLMHealthCheckMixin, EmbeddingsHealthCheckMixin, Policy):
727
714
  final_response_examples.append(resp)
728
715
 
729
716
  llm_response = await self.generate_answer(
730
- final_response_examples, conversation_samples, history, tracker.sender_id
717
+ final_response_examples, conversation_samples, history
731
718
  )
732
719
  if not llm_response:
733
720
  structlogger.debug("intentless_policy.prediction.skip_llm_fail")
@@ -39,7 +39,6 @@ from rasa.shared.core.generator import TrackerWithCachedStates
39
39
  from rasa.shared.core.trackers import DialogueStateTracker
40
40
  from rasa.shared.exceptions import FileIOException, RasaException
41
41
  from rasa.shared.nlu.constants import ACTION_NAME, ACTION_TEXT, ENTITIES, INTENT, TEXT
42
- from rasa.utils.licensing import get_human_readable_licence_owner
43
42
 
44
43
  if TYPE_CHECKING:
45
44
  from rasa.core.featurizers.tracker_featurizers import (
@@ -173,7 +172,6 @@ class Policy(GraphComponent):
173
172
 
174
173
  self._model_storage = model_storage
175
174
  self._resource = resource
176
- self.user_id = get_human_readable_licence_owner()
177
175
 
178
176
  @classmethod
179
177
  def create(
rasa/core/processor.py CHANGED
@@ -76,6 +76,7 @@ from rasa.shared.core.constants import (
76
76
  SLOT_SILENCE_TIMEOUT,
77
77
  USER_INTENT_RESTART,
78
78
  USER_INTENT_SILENCE_TIMEOUT,
79
+ SetSlotExtractor,
79
80
  )
80
81
  from rasa.shared.core.events import (
81
82
  ActionExecuted,
@@ -766,13 +767,26 @@ class MessageProcessor:
766
767
  if self.http_interpreter:
767
768
  parse_data = await self.http_interpreter.parse(message)
768
769
  else:
769
- regex_reader = create_regex_pattern_reader(message, self.domain)
770
-
771
770
  processed_message = Message({TEXT: message.text})
772
- if regex_reader:
773
- processed_message = regex_reader.unpack_regex_message(
774
- message=processed_message, domain=self.domain
771
+
772
+ all_flows = await self.get_flows()
773
+ should_force_slot_command, slot_name = (
774
+ rasa.core.utils.should_force_slot_filling(tracker, all_flows)
775
+ )
776
+
777
+ if should_force_slot_command:
778
+ command = SetSlotCommand(
779
+ name=slot_name,
780
+ value=message.text,
781
+ extractor=SetSlotExtractor.COMMAND_PAYLOAD_READER.value,
775
782
  )
783
+ processed_message.set(COMMANDS, [command.as_dict()], add_to_output=True)
784
+ else:
785
+ regex_reader = create_regex_pattern_reader(message, self.domain)
786
+ if regex_reader:
787
+ processed_message = regex_reader.unpack_regex_message(
788
+ message=processed_message, domain=self.domain
789
+ )
776
790
 
777
791
  # Invalid use of slash syntax, sanitize the message before passing
778
792
  # it to the graph
@@ -1009,7 +1023,14 @@ class MessageProcessor:
1009
1023
 
1010
1024
  @staticmethod
1011
1025
  def _should_handle_message(tracker: DialogueStateTracker) -> bool:
1012
- return not tracker.is_paused() or (
1026
+ return not tracker.is_paused() or MessageProcessor._last_user_intent_is_restart(
1027
+ tracker
1028
+ )
1029
+
1030
+ @staticmethod
1031
+ def _last_user_intent_is_restart(tracker: DialogueStateTracker) -> bool:
1032
+ """Check if the last user intent is a restart intent."""
1033
+ return (
1013
1034
  tracker.latest_message is not None
1014
1035
  and tracker.latest_message.intent.get(INTENT_NAME_KEY)
1015
1036
  == USER_INTENT_RESTART
rasa/core/utils.py CHANGED
@@ -19,6 +19,7 @@ from rasa.core.constants import (
19
19
  )
20
20
  from rasa.core.lock_store import InMemoryLockStore, LockStore, RedisLockStore
21
21
  from rasa.shared.constants import DEFAULT_ENDPOINTS_PATH, TCP_PROTOCOL
22
+ from rasa.shared.core.constants import SlotMappingType
22
23
  from rasa.shared.core.trackers import DialogueStateTracker
23
24
  from rasa.utils.endpoints import (
24
25
  EndpointConfig,
@@ -30,6 +31,7 @@ from rasa.utils.io import write_yaml
30
31
  if TYPE_CHECKING:
31
32
  from rasa.core.nlg import NaturalLanguageGenerator
32
33
  from rasa.shared.core.domain import Domain
34
+ from rasa.shared.core.flows.flows_list import FlowsList
33
35
 
34
36
  structlogger = structlog.get_logger()
35
37
 
@@ -364,3 +366,54 @@ def add_bot_utterance_metadata(
364
366
  ]
365
367
 
366
368
  return message
369
+
370
+
371
+ def should_force_slot_filling(
372
+ tracker: Optional[DialogueStateTracker], flows: "FlowsList"
373
+ ) -> Tuple[bool, Optional[str]]:
374
+ """Check if the flow should force slot filling.
375
+
376
+ This is only valid when the flow is at a collect information step which
377
+ has set `force_slot_filling` to true and the slot has a valid `from_text` mapping.
378
+
379
+ Args:
380
+ tracker: The dialogue state tracker.
381
+ flows: The list of flows.
382
+
383
+ Returns:
384
+ A tuple of a boolean indicating if the flow should force slot filling
385
+ and the name of the slot if applicable.
386
+ """
387
+ from rasa.dialogue_understanding.processor.command_processor import (
388
+ get_current_collect_step,
389
+ )
390
+
391
+ if tracker is None:
392
+ structlogger.error(
393
+ "slot.force_slot_filling.error",
394
+ event_info="Tracker is None. Cannot force slot filling.",
395
+ )
396
+ return False, None
397
+
398
+ stack = tracker.stack
399
+ step = get_current_collect_step(stack, flows)
400
+ if step is None or not step.force_slot_filling:
401
+ return False, None
402
+
403
+ slot_name = step.collect
404
+ slot = tracker.slots.get(slot_name)
405
+
406
+ if not slot:
407
+ structlogger.debug(
408
+ "slot.force_slot_filling.error",
409
+ event_info=f"Slot '{slot_name}' not found in tracker. "
410
+ f"Cannot force slot filling. "
411
+ f"Please check if the slot is defined in the domain.",
412
+ )
413
+ return False, None
414
+
415
+ for slot_mapping in slot.mappings:
416
+ if slot_mapping.type == SlotMappingType.FROM_TEXT:
417
+ return True, slot_name
418
+
419
+ return False, None
@@ -23,10 +23,6 @@ from rasa.engine.recipes.default_recipe import DefaultV1Recipe
23
23
  from rasa.engine.storage.resource import Resource
24
24
  from rasa.engine.storage.storage import ModelStorage
25
25
  from rasa.shared.constants import (
26
- LANGFUSE_CUSTOM_METADATA_DICT,
27
- LANGFUSE_METADATA_SESSION_ID,
28
- LANGFUSE_METADATA_USER_ID,
29
- LANGFUSE_TAGS,
30
26
  MODEL_CONFIG_KEY,
31
27
  OPENAI_PROVIDER,
32
28
  PROMPT_CONFIG_KEY,
@@ -47,7 +43,6 @@ from rasa.shared.utils.llm import (
47
43
  llm_factory,
48
44
  resolve_model_client_config,
49
45
  )
50
- from rasa.utils.licensing import get_human_readable_licence_owner
51
46
  from rasa.utils.log_utils import log_llm
52
47
 
53
48
  LLM_BASED_ROUTER_PROMPT_FILE_NAME = "llm_based_router_prompt.jinja2"
@@ -118,7 +113,6 @@ class LLMBasedRouter(LLMHealthCheckMixin, GraphComponent):
118
113
  self._model_storage = model_storage
119
114
  self._resource = resource
120
115
  self.validate_config()
121
- self.user_id = get_human_readable_licence_owner()
122
116
 
123
117
  def validate_config(self) -> None:
124
118
  """Validate the config of the router."""
@@ -166,6 +160,7 @@ class LLMBasedRouter(LLMHealthCheckMixin, GraphComponent):
166
160
  **kwargs: Any,
167
161
  ) -> "LLMBasedRouter":
168
162
  """Loads trained component (see parent class for full docstring)."""
163
+
169
164
  # Perform health check on the resolved LLM client config
170
165
  llm_config = resolve_model_client_config(config.get(LLM_CONFIG_KEY, {}))
171
166
  cls.perform_llm_health_check(
@@ -237,7 +232,7 @@ class LLMBasedRouter(LLMHealthCheckMixin, GraphComponent):
237
232
  prompt=prompt,
238
233
  )
239
234
  # generating answer
240
- answer = await self._generate_answer_using_llm(prompt, tracker.sender_id)
235
+ answer = await self._generate_answer_using_llm(prompt)
241
236
  log_llm(
242
237
  logger=structlogger,
243
238
  log_module="LLMBasedRouter",
@@ -297,9 +292,7 @@ class LLMBasedRouter(LLMHealthCheckMixin, GraphComponent):
297
292
 
298
293
  return Template(self.prompt_template).render(**inputs)
299
294
 
300
- async def _generate_answer_using_llm(
301
- self, prompt: str, sender_id: str
302
- ) -> Optional[str]:
295
+ async def _generate_answer_using_llm(self, prompt: str) -> Optional[str]:
303
296
  """Use LLM to generate a response.
304
297
 
305
298
  Args:
@@ -310,15 +303,8 @@ class LLMBasedRouter(LLMHealthCheckMixin, GraphComponent):
310
303
  """
311
304
  llm = llm_factory(self.config.get(LLM_CONFIG_KEY), DEFAULT_LLM_CONFIG)
312
305
 
313
- metadata = {
314
- LANGFUSE_METADATA_USER_ID: self.user_id,
315
- LANGFUSE_METADATA_SESSION_ID: sender_id,
316
- LANGFUSE_CUSTOM_METADATA_DICT: {"component": self.__class__.__name__},
317
- LANGFUSE_TAGS: [self.__class__.__name__],
318
- }
319
-
320
306
  try:
321
- llm_response = await llm.acompletion(prompt, metadata)
307
+ llm_response = await llm.acompletion(prompt)
322
308
  return llm_response.choices[0]
323
309
  except Exception as e:
324
310
  # unfortunately, langchain does not wrap LLM exceptions which means
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import copy
4
3
  import re
5
4
  from dataclasses import dataclass
6
5
  from typing import Any, Dict, List
@@ -13,10 +12,11 @@ from rasa.dialogue_understanding.commands.command_syntax_manager import (
13
12
  CommandSyntaxVersion,
14
13
  )
15
14
  from rasa.dialogue_understanding.patterns.cancel import CancelPatternFlowStackFrame
16
- from rasa.dialogue_understanding.patterns.clarify import ClarifyPatternFlowStackFrame
17
15
  from rasa.dialogue_understanding.stack.dialogue_stack import DialogueStack
18
- from rasa.dialogue_understanding.stack.frames import UserFlowStackFrame
19
- from rasa.dialogue_understanding.stack.frames.flow_stack_frame import FlowStackFrameType
16
+ from rasa.dialogue_understanding.stack.frames.flow_stack_frame import (
17
+ FlowStackFrameType,
18
+ UserFlowStackFrame,
19
+ )
20
20
  from rasa.dialogue_understanding.stack.utils import top_user_flow_frame
21
21
  from rasa.shared.core.events import Event, FlowCancelled
22
22
  from rasa.shared.core.flows import FlowsList
@@ -95,8 +95,6 @@ class CancelFlowCommand(Command):
95
95
  original_stack = original_tracker.stack
96
96
 
97
97
  applied_events: List[Event] = []
98
- # capture the top frame before we push new frames onto the stack
99
- initial_top_frame = stack.top()
100
98
  user_frame = top_user_flow_frame(original_stack)
101
99
  current_flow = user_frame.flow(all_flows) if user_frame else None
102
100
 
@@ -123,21 +121,6 @@ class CancelFlowCommand(Command):
123
121
  if user_frame:
124
122
  applied_events.append(FlowCancelled(user_frame.flow_id, user_frame.step_id))
125
123
 
126
- if initial_top_frame and isinstance(
127
- initial_top_frame, ClarifyPatternFlowStackFrame
128
- ):
129
- structlogger.debug(
130
- "command_executor.cancel_flow.cancel_clarification_options",
131
- clarification_options=initial_top_frame.clarification_options,
132
- )
133
- applied_events += cancel_all_pending_clarification_options(
134
- initial_top_frame,
135
- original_stack,
136
- canceled_frames,
137
- all_flows,
138
- stack,
139
- )
140
-
141
124
  return applied_events + tracker.create_stack_updated_events(stack)
142
125
 
143
126
  def __hash__(self) -> int:
@@ -172,41 +155,3 @@ class CancelFlowCommand(Command):
172
155
  CommandSyntaxManager.get_syntax_version(),
173
156
  mapper[CommandSyntaxManager.get_default_syntax_version()],
174
157
  )
175
-
176
-
177
- def cancel_all_pending_clarification_options(
178
- initial_top_frame: ClarifyPatternFlowStackFrame,
179
- original_stack: DialogueStack,
180
- canceled_frames: List[str],
181
- all_flows: FlowsList,
182
- stack: DialogueStack,
183
- ) -> List[FlowCancelled]:
184
- """Cancel all pending clarification options.
185
-
186
- This is a special case when the assistant asks the user to clarify
187
- which pending digression flow to start after the completion of an active flow.
188
- If the user chooses to cancel all options, this function takes care of
189
- updating the stack by removing all pending flow stack frames
190
- listed as clarification options.
191
- """
192
- clarification_names = set(initial_top_frame.names)
193
- to_be_canceled_frames = []
194
- applied_events = []
195
- for frame in reversed(original_stack.frames):
196
- if frame.frame_id in canceled_frames:
197
- continue
198
-
199
- to_be_canceled_frames.append(frame.frame_id)
200
- if isinstance(frame, UserFlowStackFrame):
201
- readable_flow_name = frame.flow(all_flows).readable_name()
202
- if readable_flow_name in clarification_names:
203
- stack.push(
204
- CancelPatternFlowStackFrame(
205
- canceled_name=readable_flow_name,
206
- canceled_frames=copy.deepcopy(to_be_canceled_frames),
207
- )
208
- )
209
- applied_events.append(FlowCancelled(frame.flow_id, frame.step_id))
210
- to_be_canceled_frames.clear()
211
-
212
- return applied_events
@@ -65,7 +65,7 @@ class KnowledgeAnswerCommand(FreeFormAnswerCommand):
65
65
  """Converts the command to a DSL string."""
66
66
  mapper = {
67
67
  CommandSyntaxVersion.v1: "SearchAndReply()",
68
- CommandSyntaxVersion.v2: "provide info",
68
+ CommandSyntaxVersion.v2: "search and reply",
69
69
  }
70
70
  return mapper.get(
71
71
  CommandSyntaxManager.get_syntax_version(),
@@ -81,7 +81,7 @@ class KnowledgeAnswerCommand(FreeFormAnswerCommand):
81
81
  def regex_pattern() -> str:
82
82
  mapper = {
83
83
  CommandSyntaxVersion.v1: r"SearchAndReply\(\)",
84
- CommandSyntaxVersion.v2: r"""^[\s\W\d]*provide info['"`]*$""",
84
+ CommandSyntaxVersion.v2: r"""^[\s\W\d]*search and reply['"`]*$""",
85
85
  }
86
86
  return mapper.get(
87
87
  CommandSyntaxManager.get_syntax_version(),
@@ -11,11 +11,6 @@ from rasa.dialogue_understanding.commands.command_syntax_manager import (
11
11
  CommandSyntaxManager,
12
12
  CommandSyntaxVersion,
13
13
  )
14
- from rasa.dialogue_understanding.patterns.clarify import FLOW_PATTERN_CLARIFICATION
15
- from rasa.dialogue_understanding.patterns.continue_interrupted import (
16
- ContinueInterruptedPatternFlowStackFrame,
17
- )
18
- from rasa.dialogue_understanding.stack.dialogue_stack import DialogueStack
19
14
  from rasa.dialogue_understanding.stack.frames.flow_stack_frame import (
20
15
  FlowStackFrameType,
21
16
  UserFlowStackFrame,
@@ -77,10 +72,6 @@ class StartFlowCommand(Command):
77
72
  applied_events: List[Event] = []
78
73
 
79
74
  if self.flow in user_flows_on_the_stack(stack):
80
- top_frame = stack.top()
81
- if top_frame is not None and top_frame.type() == FLOW_PATTERN_CLARIFICATION:
82
- return self.change_flow_frame_position_in_the_stack(stack, tracker)
83
-
84
75
  structlogger.debug(
85
76
  "command_executor.skip_command.already_started_flow", command=self
86
77
  )
@@ -149,35 +140,3 @@ class StartFlowCommand(Command):
149
140
  CommandSyntaxManager.get_syntax_version(),
150
141
  mapper[CommandSyntaxManager.get_default_syntax_version()],
151
142
  )
152
-
153
- def change_flow_frame_position_in_the_stack(
154
- self, stack: DialogueStack, tracker: DialogueStateTracker
155
- ) -> List[Event]:
156
- """Changes the position of the flow frame in the stack.
157
-
158
- This is a special case when pattern clarification is the active flow and
159
- the same flow is selected to start. In this case, the existing flow frame
160
- should be moved up in the stack.
161
- """
162
- frames = stack.frames[:]
163
-
164
- for idx, frame in enumerate(frames):
165
- if isinstance(frame, UserFlowStackFrame) and frame.flow_id == self.flow:
166
- structlogger.debug(
167
- "command_executor.change_flow_position_during_clarification",
168
- command=self,
169
- index=idx,
170
- )
171
- # pop the continue interrupted flow frame if it exists
172
- next_frame = frames[idx + 1] if idx + 1 < len(frames) else None
173
- if (
174
- isinstance(next_frame, ContinueInterruptedPatternFlowStackFrame)
175
- and next_frame.previous_flow_name == self.flow
176
- ):
177
- stack.frames.pop(idx + 1)
178
- # move up the existing flow from the stack
179
- stack.frames.pop(idx)
180
- stack.push(frame)
181
- return tracker.create_stack_updated_events(stack)
182
-
183
- return []
@@ -4,6 +4,7 @@ from typing import Any, Dict, List, Optional, Set, Text, Tuple
4
4
  import structlog
5
5
 
6
6
  from rasa.dialogue_understanding.commands import (
7
+ CannotHandleCommand,
7
8
  Command,
8
9
  CorrectSlotsCommand,
9
10
  ErrorCommand,
@@ -107,6 +108,14 @@ class CommandGenerator:
107
108
  commands = self._check_commands_against_startable_flows(
108
109
  commands, startable_flows
109
110
  )
111
+
112
+ # During force slot filling, keep only the command that sets the
113
+ # slot asked by the active collect step.
114
+ # Or return a CannotHandleCommand if no matching command is found.
115
+ commands = self._filter_commands_during_force_slot_filling(
116
+ commands, available_flows, tracker
117
+ )
118
+
110
119
  commands_dicts = [command.as_dict() for command in commands]
111
120
  message.set(COMMANDS, commands_dicts, add_to_output=True)
112
121
 
@@ -370,6 +379,64 @@ class CommandGenerator:
370
379
  Command.command_from_json(command) for command in message.get(COMMANDS, [])
371
380
  ]
372
381
 
382
+ @staticmethod
383
+ def _filter_commands_during_force_slot_filling(
384
+ commands: List[Command],
385
+ available_flows: FlowsList,
386
+ tracker: Optional[DialogueStateTracker] = None,
387
+ ) -> List[Command]:
388
+ """Filter commands during a collect step that has set `force_slot_filling`.
389
+
390
+ Args:
391
+ commands: The commands to filter.
392
+ available_flows: The available flows.
393
+ tracker: The tracker.
394
+
395
+ Returns:
396
+ The filtered commands.
397
+ """
398
+ from rasa.dialogue_understanding.processor.command_processor import (
399
+ get_current_collect_step,
400
+ )
401
+
402
+ if tracker is None:
403
+ structlogger.error(
404
+ "command_generator.filter_commands_during_force_slot_filling.tracker_not_found",
405
+ )
406
+ return commands
407
+
408
+ stack = tracker.stack
409
+ step = get_current_collect_step(stack, available_flows)
410
+
411
+ if step is None or not step.force_slot_filling:
412
+ return commands
413
+
414
+ # Retain only the command that sets the slot asked by
415
+ # the active collect step
416
+ filtered_commands: List[Command] = [
417
+ command
418
+ for command in commands
419
+ if (isinstance(command, SetSlotCommand) and command.name == step.collect)
420
+ ]
421
+
422
+ if not filtered_commands:
423
+ # If no commands were predicted, we need to return a CannotHandleCommand
424
+ structlogger.debug(
425
+ "command_generator.filter_commands_during_force_slot_filling.no_commands",
426
+ event_info=f"The command generator did not find any SetSlot "
427
+ f"command at the collect step for the slot '{step.collect}'. "
428
+ f"Returning a CannotHandleCommand instead.",
429
+ )
430
+ return [CannotHandleCommand()]
431
+
432
+ structlogger.debug(
433
+ "command_generator.filter_commands_during_force_slot_filling.filtered_commands",
434
+ slot_name=step.collect,
435
+ filtered_commands=filtered_commands,
436
+ )
437
+
438
+ return filtered_commands
439
+
373
440
 
374
441
  def gather_slot_names(commands: List[Command]) -> Set[str]:
375
442
  """Gather all slot names from the commands."""
@@ -169,7 +169,7 @@ def _parse_standard_commands(
169
169
  commands: List[Command] = []
170
170
  for command_clz in standard_commands:
171
171
  pattern = _get_compiled_pattern(command_clz.regex_pattern())
172
- if match := pattern.search(action):
172
+ if match := pattern.search(action.strip()):
173
173
  parsed_command = command_clz.from_dsl(match, **kwargs)
174
174
  if _additional_parsing_fn := _get_additional_parsing_logic(command_clz):
175
175
  parsed_command = _additional_parsing_fn(parsed_command, flows, **kwargs)
@@ -12,9 +12,6 @@ from rasa.dialogue_understanding.commands import (
12
12
  SetSlotCommand,
13
13
  StartFlowCommand,
14
14
  )
15
- from rasa.dialogue_understanding.commands.handle_digressions_command import (
16
- HandleDigressionsCommand,
17
- )
18
15
  from rasa.dialogue_understanding.constants import KEY_MINIMIZE_NUM_CALLS
19
16
  from rasa.dialogue_understanding.generator import CommandGenerator
20
17
  from rasa.dialogue_understanding.generator._jinja_filters import to_json_escaped_string
@@ -49,7 +46,6 @@ from rasa.shared.utils.llm import (
49
46
  llm_factory,
50
47
  resolve_model_client_config,
51
48
  )
52
- from rasa.utils.licensing import get_human_readable_licence_owner
53
49
  from rasa.utils.log_utils import log_llm
54
50
 
55
51
  structlogger = structlog.get_logger()
@@ -93,8 +89,6 @@ class LLMBasedCommandGenerator(
93
89
  else:
94
90
  self.flow_retrieval = None
95
91
 
96
- self.user_id = get_human_readable_licence_owner()
97
-
98
92
  ### Abstract methods
99
93
  @staticmethod
100
94
  @abstractmethod
@@ -233,19 +227,18 @@ class LLMBasedCommandGenerator(
233
227
  def compile_template(self, template: str) -> Template:
234
228
  """
235
229
  Compile the prompt template and register custom filters.
230
+
236
231
  Compiling the template is an expensive operation,
237
232
  so we cache the result.
238
233
  """
239
234
  # Create an environment
240
235
  # Autoescaping disabled explicitly for LLM prompt templates rendered from
241
-
242
236
  # strings (safe, not HTML)
243
237
  env = Environment(
244
238
  autoescape=select_autoescape(
245
239
  disabled_extensions=["jinja2"], default_for_string=False, default=True
246
240
  )
247
241
  )
248
-
249
242
  # Register filters
250
243
  env.filters[TO_JSON_ESCAPED_STRING_JINJA_FILTER] = to_json_escaped_string
251
244
 
@@ -334,9 +327,7 @@ class LLMBasedCommandGenerator(
334
327
 
335
328
  @measure_llm_latency
336
329
  async def invoke_llm(
337
- self,
338
- prompt: Union[List[dict], List[str], str],
339
- metadata: Optional[Dict[str, Any]] = None,
330
+ self, prompt: Union[List[dict], List[str], str]
340
331
  ) -> Optional[LLMResponse]:
341
332
  """Use LLM to generate a response.
342
333
 
@@ -349,7 +340,6 @@ class LLMBasedCommandGenerator(
349
340
  - a list of messages. Each message is a string and will be formatted
350
341
  as a user message.
351
342
  - a single message as a string which will be formatted as user message.
352
- metadata: Optional metadata to be passed to the LLM call.
353
343
 
354
344
  Returns:
355
345
  An LLMResponse object.
@@ -361,7 +351,7 @@ class LLMBasedCommandGenerator(
361
351
  self.config.get(LLM_CONFIG_KEY), self.get_default_llm_config()
362
352
  )
363
353
  try:
364
- return await llm.acompletion(prompt, metadata)
354
+ return await llm.acompletion(prompt)
365
355
  except Exception as e:
366
356
  # unfortunately, langchain does not wrap LLM exceptions which means
367
357
  # we have to catch all exceptions here
@@ -400,7 +390,8 @@ class LLMBasedCommandGenerator(
400
390
  "slots": slots_with_info,
401
391
  }
402
392
  )
403
- return result
393
+
394
+ return sorted(result, key=lambda x: x["name"])
404
395
 
405
396
  @staticmethod
406
397
  def is_extractable(
@@ -616,16 +607,9 @@ class LLMBasedCommandGenerator(
616
607
  ) -> bool:
617
608
  """Check if the LLM current commands should be merged with the prior commands.
618
609
 
619
- This can be done if there are no prior start flow commands and
620
- no prior handle digressions commands.
610
+ This can be done if there are no prior start flow commands.
621
611
  """
622
- prior_handle_digressions = [
623
- command
624
- for command in prior_commands
625
- if isinstance(command, HandleDigressionsCommand)
626
- ]
627
-
628
- return not prior_start_flow_names and not prior_handle_digressions
612
+ return not prior_start_flow_names
629
613
 
630
614
  def _check_start_flow_command_overlap(
631
615
  self,
@@ -55,9 +55,7 @@ class LLMCommandGenerator(SingleStepLLMCommandGenerator):
55
55
  )
56
56
 
57
57
  async def invoke_llm(
58
- self,
59
- prompt: Union[List[dict], List[str], str],
60
- metadata: Optional[Dict[str, Any]] = None,
58
+ self, prompt: Union[List[dict], List[str], str]
61
59
  ) -> Optional[LLMResponse]:
62
60
  try:
63
61
  return await super().invoke_llm(prompt)
@@ -57,4 +57,4 @@ Strictly adhere to the provided action types listed above.
57
57
  Focus on the last message and take it one step at a time.
58
58
  Use the previous conversation steps only to aid understanding.
59
59
 
60
- Your action list:
60
+ Your action list:
@@ -8,7 +8,7 @@ Your task is to analyze the current conversation context and generate a list of
8
8
  * `set slot slot_name slot_value`: Slot setting. For example, `set slot transfer_money_recipient Freddy`. Can be used to correct and change previously set values.
9
9
  * `cancel flow`: Cancelling the current flow.
10
10
  * `disambiguate flows flow_name1 flow_name2 ... flow_name_n`: Disambiguate which flow should be started when user input is ambiguous by listing the potential flows as options. For example, `disambiguate flows list_contacts add_contact remove_contact ...` if the user just wrote "contacts".
11
- * `provide info`: Responding to the user's questions by supplying relevant information, such as answering FAQs or explaining services.
11
+ * `search and reply`: Responding to the user's questions by supplying relevant information, such as answering FAQs or explaining services.
12
12
  * `offtopic reply`: Responding to casual or social user messages that are unrelated to any flows, engaging in friendly conversation and addressing off-topic remarks.
13
13
  * `hand over`: Handing over to a human, in case the user seems frustrated or explicitly asks to speak to one.
14
14