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.
- rasa/__init__.py +0 -6
- rasa/cli/scaffold.py +1 -1
- rasa/core/actions/action.py +38 -34
- rasa/core/actions/action_run_slot_rejections.py +1 -1
- rasa/core/channels/studio_chat.py +16 -43
- rasa/core/channels/voice_ready/audiocodes.py +46 -17
- rasa/core/information_retrieval/faiss.py +68 -7
- rasa/core/information_retrieval/information_retrieval.py +40 -2
- rasa/core/information_retrieval/milvus.py +7 -2
- rasa/core/information_retrieval/qdrant.py +7 -2
- rasa/core/nlg/contextual_response_rephraser.py +11 -27
- rasa/core/nlg/generator.py +5 -21
- rasa/core/nlg/response.py +6 -43
- rasa/core/nlg/summarize.py +1 -15
- rasa/core/nlg/translate.py +0 -8
- rasa/core/policies/enterprise_search_policy.py +64 -316
- rasa/core/policies/flows/flow_executor.py +3 -38
- rasa/core/policies/intentless_policy.py +4 -17
- rasa/core/policies/policy.py +0 -2
- rasa/core/processor.py +27 -6
- rasa/core/utils.py +53 -0
- rasa/dialogue_understanding/coexistence/llm_based_router.py +4 -18
- rasa/dialogue_understanding/commands/cancel_flow_command.py +4 -59
- rasa/dialogue_understanding/commands/knowledge_answer_command.py +2 -2
- rasa/dialogue_understanding/commands/start_flow_command.py +0 -41
- rasa/dialogue_understanding/generator/command_generator.py +67 -0
- rasa/dialogue_understanding/generator/command_parser.py +1 -1
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +7 -23
- rasa/dialogue_understanding/generator/llm_command_generator.py +1 -3
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_template.jinja2 +1 -1
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_claude_3_5_sonnet_20240620_template.jinja2 +1 -1
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_gpt_4o_2024_11_20_template.jinja2 +24 -2
- rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +8 -12
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +0 -61
- rasa/dialogue_understanding/processor/command_processor.py +7 -65
- rasa/dialogue_understanding/stack/utils.py +0 -38
- rasa/dialogue_understanding_test/command_metric_calculation.py +7 -40
- rasa/dialogue_understanding_test/command_metrics.py +38 -0
- rasa/dialogue_understanding_test/du_test_case.py +58 -25
- rasa/dialogue_understanding_test/du_test_result.py +228 -132
- rasa/dialogue_understanding_test/du_test_runner.py +10 -1
- rasa/dialogue_understanding_test/io.py +48 -16
- rasa/document_retrieval/__init__.py +0 -0
- rasa/document_retrieval/constants.py +32 -0
- rasa/document_retrieval/document_post_processor.py +351 -0
- rasa/document_retrieval/document_post_processor_prompt_template.jinja2 +0 -0
- rasa/document_retrieval/document_retriever.py +333 -0
- rasa/document_retrieval/knowledge_base_connectors/__init__.py +0 -0
- rasa/document_retrieval/knowledge_base_connectors/api_connector.py +39 -0
- rasa/document_retrieval/knowledge_base_connectors/knowledge_base_connector.py +34 -0
- rasa/document_retrieval/knowledge_base_connectors/vector_store_connector.py +226 -0
- rasa/document_retrieval/query_rewriter.py +234 -0
- rasa/document_retrieval/query_rewriter_prompt_template.jinja2 +8 -0
- rasa/engine/recipes/default_components.py +2 -0
- rasa/hooks.py +0 -55
- rasa/model_manager/model_api.py +1 -1
- rasa/model_manager/socket_bridge.py +0 -7
- rasa/shared/constants.py +0 -5
- rasa/shared/core/constants.py +0 -8
- rasa/shared/core/domain.py +12 -3
- rasa/shared/core/flows/flow.py +0 -17
- rasa/shared/core/flows/flows_yaml_schema.json +3 -38
- rasa/shared/core/flows/steps/collect.py +5 -18
- rasa/shared/core/flows/utils.py +1 -16
- rasa/shared/core/slot_mappings.py +11 -5
- rasa/shared/core/slots.py +1 -1
- rasa/shared/core/trackers.py +4 -10
- rasa/shared/nlu/constants.py +0 -1
- rasa/shared/providers/constants.py +0 -9
- rasa/shared/providers/llm/_base_litellm_client.py +4 -14
- rasa/shared/providers/llm/default_litellm_llm_client.py +2 -2
- rasa/shared/providers/llm/litellm_router_llm_client.py +7 -17
- rasa/shared/providers/llm/llm_client.py +15 -24
- rasa/shared/providers/llm/self_hosted_llm_client.py +2 -10
- rasa/shared/utils/common.py +11 -1
- rasa/shared/utils/health_check/health_check.py +1 -7
- rasa/shared/utils/llm.py +1 -1
- rasa/tracing/instrumentation/attribute_extractors.py +50 -17
- rasa/tracing/instrumentation/instrumentation.py +12 -12
- rasa/tracing/instrumentation/intentless_policy_instrumentation.py +1 -2
- rasa/utils/licensing.py +0 -15
- rasa/validator.py +1 -123
- rasa/version.py +1 -1
- {rasa_pro-3.12.6.dev2.dist-info → rasa_pro-3.13.0.dev2.dist-info}/METADATA +2 -3
- {rasa_pro-3.12.6.dev2.dist-info → rasa_pro-3.13.0.dev2.dist-info}/RECORD +88 -80
- rasa/core/actions/action_handle_digressions.py +0 -164
- rasa/dialogue_understanding/commands/handle_digressions_command.py +0 -144
- rasa/dialogue_understanding/patterns/handle_digressions.py +0 -81
- rasa/monkey_patches.py +0 -91
- {rasa_pro-3.12.6.dev2.dist-info → rasa_pro-3.13.0.dev2.dist-info}/NOTICE +0 -0
- {rasa_pro-3.12.6.dev2.dist-info → rasa_pro-3.13.0.dev2.dist-info}/WHEEL +0 -0
- {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
|
|
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
|
|
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
|
|
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")
|
rasa/core/policies/policy.py
CHANGED
|
@@ -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
|
-
|
|
773
|
-
|
|
774
|
-
|
|
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
|
|
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
|
|
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
|
|
19
|
-
|
|
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: "
|
|
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]*
|
|
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
|
|
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
|
-
|
|
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
|
|
620
|
-
no prior handle digressions commands.
|
|
610
|
+
This can be done if there are no prior start flow commands.
|
|
621
611
|
"""
|
|
622
|
-
|
|
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)
|
|
@@ -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
|
-
* `
|
|
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
|
|