rasa-pro 3.12.0.dev12__py3-none-any.whl → 3.12.0rc1__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/anonymization/anonymization_rule_executor.py +16 -10
- rasa/cli/data.py +16 -0
- rasa/cli/inspect.py +20 -1
- rasa/cli/project_templates/calm/config.yml +2 -2
- rasa/cli/project_templates/calm/endpoints.yml +2 -2
- rasa/cli/shell.py +3 -3
- rasa/cli/utils.py +12 -0
- rasa/core/actions/action.py +99 -193
- rasa/core/actions/action_handle_digressions.py +142 -0
- rasa/core/actions/action_run_slot_rejections.py +16 -4
- rasa/core/actions/forms.py +10 -5
- rasa/core/channels/__init__.py +4 -0
- rasa/core/channels/studio_chat.py +19 -0
- rasa/core/channels/telegram.py +42 -24
- rasa/core/channels/voice_ready/audiocodes.py +42 -23
- rasa/core/channels/voice_ready/utils.py +1 -1
- rasa/core/channels/voice_stream/asr/asr_engine.py +10 -4
- rasa/core/channels/voice_stream/asr/azure.py +14 -1
- rasa/core/channels/voice_stream/asr/deepgram.py +20 -4
- rasa/core/channels/voice_stream/audiocodes.py +264 -0
- rasa/core/channels/voice_stream/browser_audio.py +5 -1
- rasa/core/channels/voice_stream/call_state.py +10 -1
- rasa/core/channels/voice_stream/genesys.py +335 -0
- rasa/core/channels/voice_stream/tts/azure.py +11 -2
- rasa/core/channels/voice_stream/tts/cartesia.py +29 -10
- rasa/core/channels/voice_stream/twilio_media_streams.py +2 -1
- rasa/core/channels/voice_stream/voice_channel.py +25 -3
- rasa/core/constants.py +2 -0
- rasa/core/migrate.py +2 -2
- rasa/core/nlg/contextual_response_rephraser.py +18 -1
- rasa/core/nlg/generator.py +83 -15
- rasa/core/nlg/response.py +6 -3
- rasa/core/nlg/translate.py +55 -0
- rasa/core/policies/enterprise_search_prompt_with_citation_template.jinja2 +1 -1
- rasa/core/policies/flows/flow_executor.py +47 -46
- rasa/core/processor.py +72 -9
- rasa/core/run.py +4 -3
- rasa/dialogue_understanding/commands/can_not_handle_command.py +20 -2
- rasa/dialogue_understanding/commands/cancel_flow_command.py +80 -4
- rasa/dialogue_understanding/commands/change_flow_command.py +20 -2
- rasa/dialogue_understanding/commands/chit_chat_answer_command.py +20 -2
- rasa/dialogue_understanding/commands/clarify_command.py +29 -3
- rasa/dialogue_understanding/commands/command.py +1 -16
- rasa/dialogue_understanding/commands/command_syntax_manager.py +55 -0
- rasa/dialogue_understanding/commands/correct_slots_command.py +11 -2
- rasa/dialogue_understanding/commands/handle_digressions_command.py +150 -0
- rasa/dialogue_understanding/commands/human_handoff_command.py +20 -2
- rasa/dialogue_understanding/commands/knowledge_answer_command.py +20 -2
- rasa/dialogue_understanding/commands/prompt_command.py +94 -0
- rasa/dialogue_understanding/commands/repeat_bot_messages_command.py +20 -2
- rasa/dialogue_understanding/commands/set_slot_command.py +29 -15
- rasa/dialogue_understanding/commands/skip_question_command.py +20 -2
- rasa/dialogue_understanding/commands/start_flow_command.py +61 -2
- rasa/dialogue_understanding/commands/utils.py +98 -4
- rasa/dialogue_understanding/constants.py +1 -0
- rasa/dialogue_understanding/generator/__init__.py +2 -0
- rasa/dialogue_understanding/generator/command_generator.py +110 -73
- rasa/dialogue_understanding/generator/command_parser.py +16 -13
- rasa/dialogue_understanding/generator/constants.py +3 -0
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +170 -5
- rasa/dialogue_understanding/generator/llm_command_generator.py +5 -3
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +26 -4
- rasa/dialogue_understanding/generator/nlu_command_adapter.py +44 -3
- rasa/dialogue_understanding/generator/prompt_templates/__init__.py +0 -0
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_template.jinja2 +60 -0
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_claude_3_5_sonnet_20240620_template.jinja2 +77 -0
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_default.jinja2 +68 -0
- rasa/dialogue_understanding/generator/{single_step/command_prompt_template.jinja2 → prompt_templates/command_prompt_v2_gpt_4o_2024_11_20_template.jinja2} +1 -1
- rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +460 -0
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +12 -318
- rasa/dialogue_understanding/generator/utils.py +32 -1
- rasa/dialogue_understanding/patterns/collect_information.py +1 -1
- rasa/dialogue_understanding/patterns/correction.py +13 -1
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +78 -2
- rasa/dialogue_understanding/patterns/handle_digressions.py +81 -0
- rasa/dialogue_understanding/patterns/validate_slot.py +65 -0
- rasa/dialogue_understanding/processor/command_processor.py +154 -28
- rasa/dialogue_understanding/utils.py +31 -0
- rasa/dialogue_understanding_test/README.md +50 -0
- rasa/dialogue_understanding_test/du_test_case.py +28 -8
- rasa/dialogue_understanding_test/du_test_result.py +13 -9
- rasa/dialogue_understanding_test/io.py +14 -0
- rasa/dialogue_understanding_test/test_case_simulation/test_case_tracker_simulator.py +3 -3
- rasa/e2e_test/utils/io.py +0 -37
- rasa/engine/graph.py +1 -0
- rasa/engine/language.py +140 -0
- rasa/engine/recipes/config_files/default_config.yml +4 -0
- rasa/engine/recipes/default_recipe.py +2 -0
- rasa/engine/recipes/graph_recipe.py +2 -0
- rasa/engine/storage/local_model_storage.py +1 -0
- rasa/engine/storage/storage.py +4 -1
- rasa/model_manager/runner_service.py +7 -4
- rasa/model_manager/socket_bridge.py +7 -6
- rasa/model_manager/warm_rasa_process.py +0 -1
- rasa/model_training.py +24 -27
- rasa/shared/constants.py +15 -13
- rasa/shared/core/constants.py +30 -3
- rasa/shared/core/domain.py +13 -20
- rasa/shared/core/events.py +13 -2
- rasa/shared/core/flows/constants.py +11 -0
- rasa/shared/core/flows/flow.py +100 -19
- rasa/shared/core/flows/flows_yaml_schema.json +69 -3
- rasa/shared/core/flows/steps/collect.py +19 -37
- rasa/shared/core/flows/utils.py +43 -4
- rasa/shared/core/flows/validation.py +1 -1
- rasa/shared/core/slot_mappings.py +350 -111
- rasa/shared/core/slots.py +154 -3
- rasa/shared/core/trackers.py +77 -2
- rasa/shared/importers/importer.py +50 -2
- rasa/shared/nlu/constants.py +1 -0
- rasa/shared/nlu/training_data/schemas/responses.yml +19 -12
- rasa/shared/providers/_configs/azure_entra_id_config.py +541 -0
- rasa/shared/providers/_configs/azure_openai_client_config.py +138 -3
- rasa/shared/providers/_configs/client_config.py +3 -1
- rasa/shared/providers/_configs/default_litellm_client_config.py +3 -1
- rasa/shared/providers/_configs/huggingface_local_embedding_client_config.py +3 -1
- rasa/shared/providers/_configs/litellm_router_client_config.py +3 -1
- rasa/shared/providers/_configs/model_group_config.py +4 -2
- rasa/shared/providers/_configs/oauth_config.py +33 -0
- rasa/shared/providers/_configs/openai_client_config.py +3 -1
- rasa/shared/providers/_configs/rasa_llm_client_config.py +3 -1
- rasa/shared/providers/_configs/self_hosted_llm_client_config.py +3 -1
- rasa/shared/providers/constants.py +6 -0
- rasa/shared/providers/embedding/azure_openai_embedding_client.py +28 -3
- rasa/shared/providers/embedding/litellm_router_embedding_client.py +3 -1
- rasa/shared/providers/llm/_base_litellm_client.py +42 -17
- rasa/shared/providers/llm/azure_openai_llm_client.py +81 -25
- rasa/shared/providers/llm/default_litellm_llm_client.py +3 -1
- rasa/shared/providers/llm/litellm_router_llm_client.py +29 -8
- rasa/shared/providers/llm/llm_client.py +23 -7
- rasa/shared/providers/llm/openai_llm_client.py +9 -3
- rasa/shared/providers/llm/rasa_llm_client.py +11 -2
- rasa/shared/providers/llm/self_hosted_llm_client.py +30 -11
- rasa/shared/providers/router/_base_litellm_router_client.py +3 -1
- rasa/shared/providers/router/router_client.py +3 -1
- rasa/shared/utils/constants.py +3 -0
- rasa/shared/utils/llm.py +31 -8
- rasa/shared/utils/pykwalify_extensions.py +24 -0
- rasa/shared/utils/schemas/domain.yml +26 -1
- rasa/telemetry.py +45 -14
- rasa/tracing/config.py +2 -0
- rasa/tracing/constants.py +12 -0
- rasa/tracing/instrumentation/instrumentation.py +36 -0
- rasa/tracing/instrumentation/metrics.py +41 -0
- rasa/tracing/metric_instrument_provider.py +40 -0
- rasa/utils/common.py +0 -1
- rasa/validator.py +561 -89
- rasa/version.py +1 -1
- {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0rc1.dist-info}/METADATA +2 -1
- {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0rc1.dist-info}/RECORD +153 -134
- {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0rc1.dist-info}/NOTICE +0 -0
- {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0rc1.dist-info}/WHEEL +0 -0
- {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0rc1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
import structlog
|
|
6
|
+
|
|
7
|
+
from rasa.core.actions.action import Action, create_bot_utterance
|
|
8
|
+
from rasa.core.channels import OutputChannel
|
|
9
|
+
from rasa.core.nlg import NaturalLanguageGenerator
|
|
10
|
+
from rasa.core.utils import add_bot_utterance_metadata
|
|
11
|
+
from rasa.dialogue_understanding.patterns.continue_interrupted import (
|
|
12
|
+
ContinueInterruptedPatternFlowStackFrame,
|
|
13
|
+
)
|
|
14
|
+
from rasa.dialogue_understanding.patterns.handle_digressions import (
|
|
15
|
+
HandleDigressionsPatternFlowStackFrame,
|
|
16
|
+
)
|
|
17
|
+
from rasa.dialogue_understanding.stack.frames.flow_stack_frame import (
|
|
18
|
+
FlowStackFrameType,
|
|
19
|
+
UserFlowStackFrame,
|
|
20
|
+
)
|
|
21
|
+
from rasa.shared.core.constants import (
|
|
22
|
+
ACTION_BLOCK_DIGRESSION,
|
|
23
|
+
ACTION_CONTINUE_DIGRESSION,
|
|
24
|
+
)
|
|
25
|
+
from rasa.shared.core.domain import Domain
|
|
26
|
+
from rasa.shared.core.events import Event, FlowInterrupted
|
|
27
|
+
from rasa.shared.core.trackers import DialogueStateTracker
|
|
28
|
+
|
|
29
|
+
structlogger = structlog.get_logger()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ActionBlockDigressions(Action):
|
|
33
|
+
"""Action which blocks an interruption and continues the current flow."""
|
|
34
|
+
|
|
35
|
+
def name(self) -> str:
|
|
36
|
+
"""Return the action name."""
|
|
37
|
+
return ACTION_BLOCK_DIGRESSION
|
|
38
|
+
|
|
39
|
+
async def run(
|
|
40
|
+
self,
|
|
41
|
+
output_channel: OutputChannel,
|
|
42
|
+
nlg: NaturalLanguageGenerator,
|
|
43
|
+
tracker: DialogueStateTracker,
|
|
44
|
+
domain: Domain,
|
|
45
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
46
|
+
) -> List[Event]:
|
|
47
|
+
"""Update the stack."""
|
|
48
|
+
structlogger.debug("action_block_digressions.run")
|
|
49
|
+
top_frame = tracker.stack.top()
|
|
50
|
+
|
|
51
|
+
if not isinstance(top_frame, HandleDigressionsPatternFlowStackFrame):
|
|
52
|
+
return []
|
|
53
|
+
|
|
54
|
+
blocked_flow_id = top_frame.interrupting_flow_id
|
|
55
|
+
frame_type = FlowStackFrameType.REGULAR
|
|
56
|
+
|
|
57
|
+
stack = tracker.stack
|
|
58
|
+
stack.push(
|
|
59
|
+
UserFlowStackFrame(flow_id=blocked_flow_id, frame_type=frame_type), 0
|
|
60
|
+
)
|
|
61
|
+
stack.push(
|
|
62
|
+
ContinueInterruptedPatternFlowStackFrame(
|
|
63
|
+
previous_flow_name=blocked_flow_id
|
|
64
|
+
),
|
|
65
|
+
1,
|
|
66
|
+
)
|
|
67
|
+
events = tracker.create_stack_updated_events(stack)
|
|
68
|
+
|
|
69
|
+
utterance = "utter_block_digressions"
|
|
70
|
+
message = await nlg.generate(
|
|
71
|
+
utterance,
|
|
72
|
+
tracker,
|
|
73
|
+
output_channel.name(),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if message is None:
|
|
77
|
+
structlogger.error(
|
|
78
|
+
"action_block_digressions.run.failed.finding.utter",
|
|
79
|
+
utterance=utterance,
|
|
80
|
+
)
|
|
81
|
+
else:
|
|
82
|
+
message = add_bot_utterance_metadata(
|
|
83
|
+
message, utterance, nlg, domain, tracker
|
|
84
|
+
)
|
|
85
|
+
events.append(create_bot_utterance(message))
|
|
86
|
+
|
|
87
|
+
return events
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class ActionContinueDigression(Action):
|
|
91
|
+
"""Action which continues with an interruption."""
|
|
92
|
+
|
|
93
|
+
def name(self) -> str:
|
|
94
|
+
"""Return the action name."""
|
|
95
|
+
return ACTION_CONTINUE_DIGRESSION
|
|
96
|
+
|
|
97
|
+
async def run(
|
|
98
|
+
self,
|
|
99
|
+
output_channel: OutputChannel,
|
|
100
|
+
nlg: NaturalLanguageGenerator,
|
|
101
|
+
tracker: DialogueStateTracker,
|
|
102
|
+
domain: Domain,
|
|
103
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
104
|
+
) -> List[Event]:
|
|
105
|
+
"""Update the stack."""
|
|
106
|
+
structlogger.debug("action_continue_digression.run")
|
|
107
|
+
top_frame = tracker.stack.top()
|
|
108
|
+
|
|
109
|
+
if not isinstance(top_frame, HandleDigressionsPatternFlowStackFrame):
|
|
110
|
+
return []
|
|
111
|
+
|
|
112
|
+
blocked_flow_id = top_frame.interrupting_flow_id
|
|
113
|
+
frame_type = FlowStackFrameType.INTERRUPT
|
|
114
|
+
stack = tracker.stack
|
|
115
|
+
stack.push(UserFlowStackFrame(flow_id=blocked_flow_id, frame_type=frame_type))
|
|
116
|
+
|
|
117
|
+
events = [
|
|
118
|
+
FlowInterrupted(
|
|
119
|
+
flow_id=top_frame.interrupted_flow_id,
|
|
120
|
+
step_id=top_frame.interrupted_step_id,
|
|
121
|
+
)
|
|
122
|
+
] + tracker.create_stack_updated_events(stack)
|
|
123
|
+
|
|
124
|
+
utterance = "utter_continue_interruption"
|
|
125
|
+
message = await nlg.generate(
|
|
126
|
+
utterance,
|
|
127
|
+
tracker,
|
|
128
|
+
output_channel.name(),
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
if message is None:
|
|
132
|
+
structlogger.error(
|
|
133
|
+
"action_continue_digression.run.failed.finding.utter",
|
|
134
|
+
utterance=utterance,
|
|
135
|
+
)
|
|
136
|
+
else:
|
|
137
|
+
message = add_bot_utterance_metadata(
|
|
138
|
+
message, utterance, nlg, domain, tracker
|
|
139
|
+
)
|
|
140
|
+
events.append(create_bot_utterance(message))
|
|
141
|
+
|
|
142
|
+
return events
|
|
@@ -9,14 +9,17 @@ from rasa.core.utils import add_bot_utterance_metadata
|
|
|
9
9
|
from rasa.dialogue_understanding.patterns.collect_information import (
|
|
10
10
|
CollectInformationPatternFlowStackFrame,
|
|
11
11
|
)
|
|
12
|
+
from rasa.dialogue_understanding.patterns.validate_slot import (
|
|
13
|
+
ValidateSlotPatternFlowStackFrame,
|
|
14
|
+
)
|
|
12
15
|
from rasa.shared.core.constants import ACTION_RUN_SLOT_REJECTIONS_NAME
|
|
13
16
|
from rasa.shared.core.events import Event, SlotSet
|
|
14
|
-
from rasa.shared.core.flows.steps.collect import SlotRejection
|
|
15
17
|
from rasa.shared.core.slots import (
|
|
16
18
|
BooleanSlot,
|
|
17
19
|
CategoricalSlot,
|
|
18
20
|
FloatSlot,
|
|
19
21
|
Slot,
|
|
22
|
+
SlotRejection,
|
|
20
23
|
)
|
|
21
24
|
|
|
22
25
|
if TYPE_CHECKING:
|
|
@@ -138,10 +141,19 @@ class ActionRunSlotRejections(Action):
|
|
|
138
141
|
"""Run the predicate checks."""
|
|
139
142
|
utterance = None
|
|
140
143
|
top_frame = tracker.stack.top()
|
|
141
|
-
if not isinstance(
|
|
144
|
+
if not isinstance(
|
|
145
|
+
top_frame,
|
|
146
|
+
(
|
|
147
|
+
CollectInformationPatternFlowStackFrame,
|
|
148
|
+
ValidateSlotPatternFlowStackFrame,
|
|
149
|
+
),
|
|
150
|
+
):
|
|
142
151
|
return []
|
|
152
|
+
elif isinstance(top_frame, CollectInformationPatternFlowStackFrame):
|
|
153
|
+
slot_name = top_frame.collect
|
|
154
|
+
elif isinstance(top_frame, ValidateSlotPatternFlowStackFrame):
|
|
155
|
+
slot_name = top_frame.validate
|
|
143
156
|
|
|
144
|
-
slot_name = top_frame.collect
|
|
145
157
|
slot_instance = tracker.slots.get(slot_name)
|
|
146
158
|
if slot_instance and not slot_instance.has_been_set:
|
|
147
159
|
# this is the first time the assistant asks for the slot value,
|
|
@@ -205,6 +217,6 @@ class ActionRunSlotRejections(Action):
|
|
|
205
217
|
message = add_bot_utterance_metadata(
|
|
206
218
|
message, utterance, nlg, domain, tracker
|
|
207
219
|
)
|
|
208
|
-
events.append(create_bot_utterance(message))
|
|
220
|
+
events.append(create_bot_utterance(message, tracker.current_language))
|
|
209
221
|
|
|
210
222
|
return events
|
rasa/core/actions/forms.py
CHANGED
|
@@ -2,7 +2,7 @@ import copy
|
|
|
2
2
|
import itertools
|
|
3
3
|
import json
|
|
4
4
|
import logging
|
|
5
|
-
from typing import Any, Dict, List, Optional, Set, Text, Union
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Text, Union
|
|
6
6
|
|
|
7
7
|
import structlog
|
|
8
8
|
|
|
@@ -16,7 +16,7 @@ from rasa.shared.constants import UTTER_PREFIX
|
|
|
16
16
|
from rasa.shared.core.constants import (
|
|
17
17
|
ACTION_EXTRACT_SLOTS,
|
|
18
18
|
ACTION_LISTEN_NAME,
|
|
19
|
-
|
|
19
|
+
KEY_MAPPING_TYPE,
|
|
20
20
|
REQUESTED_SLOT,
|
|
21
21
|
SLOT_MAPPINGS,
|
|
22
22
|
SlotMappingType,
|
|
@@ -35,6 +35,9 @@ from rasa.shared.core.slots import ListSlot
|
|
|
35
35
|
from rasa.shared.core.trackers import DialogueStateTracker
|
|
36
36
|
from rasa.utils.endpoints import EndpointConfig
|
|
37
37
|
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from rasa.shared.core.slot_mappings import SlotMapping
|
|
40
|
+
|
|
38
41
|
logger = logging.getLogger(__name__)
|
|
39
42
|
structlogger = structlog.get_logger()
|
|
40
43
|
|
|
@@ -158,7 +161,9 @@ class FormAction(LoopAction):
|
|
|
158
161
|
domain_slots = domain.as_dict().get(KEY_SLOTS, {})
|
|
159
162
|
for slot in domain.required_slots_for_form(self.name()):
|
|
160
163
|
for slot_mapping in domain_slots.get(slot, {}).get(SLOT_MAPPINGS, []):
|
|
161
|
-
if slot_mapping.get(
|
|
164
|
+
if slot_mapping.get(KEY_MAPPING_TYPE) == str(
|
|
165
|
+
SlotMappingType.FROM_ENTITY
|
|
166
|
+
):
|
|
162
167
|
mapping_as_string = json.dumps(slot_mapping, sort_keys=True)
|
|
163
168
|
if mapping_as_string in unique_entity_slot_mappings:
|
|
164
169
|
unique_entity_slot_mappings.remove(mapping_as_string)
|
|
@@ -169,7 +174,7 @@ class FormAction(LoopAction):
|
|
|
169
174
|
return unique_entity_slot_mappings
|
|
170
175
|
|
|
171
176
|
def entity_mapping_is_unique(
|
|
172
|
-
self, slot_mapping:
|
|
177
|
+
self, slot_mapping: "SlotMapping", domain: Domain
|
|
173
178
|
) -> bool:
|
|
174
179
|
"""Verifies if the from_entity mapping is unique."""
|
|
175
180
|
if not self._have_unique_entity_mappings_been_initialized:
|
|
@@ -177,7 +182,7 @@ class FormAction(LoopAction):
|
|
|
177
182
|
self._unique_entity_mappings = self._create_unique_entity_mappings(domain)
|
|
178
183
|
self._have_unique_entity_mappings_been_initialized = True
|
|
179
184
|
|
|
180
|
-
mapping_as_string = json.dumps(slot_mapping, sort_keys=True)
|
|
185
|
+
mapping_as_string = json.dumps(slot_mapping.as_dict(), sort_keys=True)
|
|
181
186
|
return mapping_as_string in self._unique_entity_mappings
|
|
182
187
|
|
|
183
188
|
@staticmethod
|
rasa/core/channels/__init__.py
CHANGED
|
@@ -32,7 +32,9 @@ from rasa.core.channels.vier_cvg import CVGInput
|
|
|
32
32
|
from rasa.core.channels.voice_stream.twilio_media_streams import (
|
|
33
33
|
TwilioMediaStreamsInputChannel,
|
|
34
34
|
)
|
|
35
|
+
from rasa.core.channels.voice_stream.genesys import GenesysInputChannel
|
|
35
36
|
from rasa.core.channels.studio_chat import StudioChatInput
|
|
37
|
+
from rasa.core.channels.voice_stream.audiocodes import AudiocodesVoiceInputChannel
|
|
36
38
|
|
|
37
39
|
input_channel_classes: List[Type[InputChannel]] = [
|
|
38
40
|
CmdlineInput,
|
|
@@ -55,7 +57,9 @@ input_channel_classes: List[Type[InputChannel]] = [
|
|
|
55
57
|
JambonzVoiceReadyInput,
|
|
56
58
|
TwilioMediaStreamsInputChannel,
|
|
57
59
|
BrowserAudioInputChannel,
|
|
60
|
+
GenesysInputChannel,
|
|
58
61
|
StudioChatInput,
|
|
62
|
+
AudiocodesVoiceInputChannel,
|
|
59
63
|
]
|
|
60
64
|
|
|
61
65
|
# Mapping from an input channel name to its class to allow name based lookup.
|
|
@@ -18,6 +18,8 @@ from sanic import Sanic
|
|
|
18
18
|
from rasa.core.channels.socketio import SocketBlueprint, SocketIOInput
|
|
19
19
|
from rasa.hooks import hookimpl
|
|
20
20
|
from rasa.plugin import plugin_manager
|
|
21
|
+
from rasa.shared.core.constants import ACTION_LISTEN_NAME
|
|
22
|
+
from rasa.shared.core.events import ActionExecuted
|
|
21
23
|
from rasa.shared.core.trackers import EventVerbosity
|
|
22
24
|
|
|
23
25
|
if TYPE_CHECKING:
|
|
@@ -43,6 +45,15 @@ def tracker_as_dump(tracker: "DialogueStateTracker") -> str:
|
|
|
43
45
|
return json.dumps(state)
|
|
44
46
|
|
|
45
47
|
|
|
48
|
+
def does_need_action_prediction(tracker: "DialogueStateTracker") -> bool:
|
|
49
|
+
"""Check if the tracker needs an action prediction."""
|
|
50
|
+
return (
|
|
51
|
+
len(tracker.events) == 0
|
|
52
|
+
or not isinstance(tracker.events[-1], ActionExecuted)
|
|
53
|
+
or tracker.events[-1].action_name != ACTION_LISTEN_NAME
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
46
57
|
class StudioTrackerUpdatePlugin:
|
|
47
58
|
"""Plugin for publishing tracker updates a socketio channel."""
|
|
48
59
|
|
|
@@ -169,6 +180,14 @@ class StudioChatInput(SocketIOInput):
|
|
|
169
180
|
# will override an existing tracker with the same id!
|
|
170
181
|
await self.agent.tracker_store.save(tracker)
|
|
171
182
|
|
|
183
|
+
processor = self.agent.processor
|
|
184
|
+
if processor and does_need_action_prediction(tracker):
|
|
185
|
+
output_channel = self.get_output_channel()
|
|
186
|
+
|
|
187
|
+
await processor._run_prediction_loop(output_channel, tracker)
|
|
188
|
+
await processor.run_anonymization_pipeline(tracker)
|
|
189
|
+
await self.agent.tracker_store.save(tracker)
|
|
190
|
+
|
|
172
191
|
await self.on_tracker_updated(tracker)
|
|
173
192
|
|
|
174
193
|
def blueprint(
|
rasa/core/channels/telegram.py
CHANGED
|
@@ -1,21 +1,9 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
|
+
import typing
|
|
3
4
|
from copy import deepcopy
|
|
4
5
|
from typing import Any, Awaitable, Callable, Dict, List, Optional, Text
|
|
5
6
|
|
|
6
|
-
from aiogram import Bot
|
|
7
|
-
from aiogram.exceptions import TelegramAPIError
|
|
8
|
-
from aiogram.types import (
|
|
9
|
-
InlineKeyboardButton,
|
|
10
|
-
KeyboardButton,
|
|
11
|
-
Message,
|
|
12
|
-
Update,
|
|
13
|
-
)
|
|
14
|
-
from aiogram.utils.keyboard import (
|
|
15
|
-
InlineKeyboardBuilder,
|
|
16
|
-
KeyboardBuilder,
|
|
17
|
-
ReplyKeyboardBuilder,
|
|
18
|
-
)
|
|
19
7
|
from sanic import Blueprint, response
|
|
20
8
|
from sanic.request import Request
|
|
21
9
|
from sanic.response import HTTPResponse
|
|
@@ -27,8 +15,11 @@ from rasa.shared.exceptions import RasaException
|
|
|
27
15
|
|
|
28
16
|
logger = logging.getLogger(__name__)
|
|
29
17
|
|
|
18
|
+
if typing.TYPE_CHECKING:
|
|
19
|
+
from aiogram.types import Message, Update
|
|
30
20
|
|
|
31
|
-
|
|
21
|
+
|
|
22
|
+
class TelegramOutput(OutputChannel):
|
|
32
23
|
"""Output channel for Telegram."""
|
|
33
24
|
|
|
34
25
|
# skipcq: PYL-W0236
|
|
@@ -37,20 +28,28 @@ class TelegramOutput(Bot, OutputChannel):
|
|
|
37
28
|
return "telegram"
|
|
38
29
|
|
|
39
30
|
def __init__(self, access_token: Optional[Text]) -> None:
|
|
40
|
-
|
|
31
|
+
try:
|
|
32
|
+
from aiogram import Bot
|
|
33
|
+
|
|
34
|
+
self.bot = Bot(access_token)
|
|
35
|
+
except ImportError:
|
|
36
|
+
raise ImportError(
|
|
37
|
+
"To use the Telegram channel, please install the aiogram package "
|
|
38
|
+
"with 'pip install aiogram'"
|
|
39
|
+
)
|
|
41
40
|
|
|
42
41
|
async def send_text_message(
|
|
43
42
|
self, recipient_id: Text, text: Text, **kwargs: Any
|
|
44
43
|
) -> None:
|
|
45
44
|
"""Sends text message."""
|
|
46
45
|
for message_part in text.strip().split("\n\n"):
|
|
47
|
-
await self.send_message(recipient_id, message_part)
|
|
46
|
+
await self.bot.send_message(recipient_id, message_part)
|
|
48
47
|
|
|
49
48
|
async def send_image_url(
|
|
50
49
|
self, recipient_id: Text, image: Text, **kwargs: Any
|
|
51
50
|
) -> None:
|
|
52
51
|
"""Sends an image."""
|
|
53
|
-
await self.send_photo(recipient_id, image)
|
|
52
|
+
await self.bot.send_photo(recipient_id, image)
|
|
54
53
|
|
|
55
54
|
async def send_text_with_buttons(
|
|
56
55
|
self,
|
|
@@ -70,8 +69,15 @@ class TelegramOutput(Bot, OutputChannel):
|
|
|
70
69
|
|
|
71
70
|
:button_type reply: reply keyboard
|
|
72
71
|
"""
|
|
72
|
+
from aiogram.types import InlineKeyboardButton, KeyboardButton
|
|
73
|
+
from aiogram.utils.keyboard import (
|
|
74
|
+
InlineKeyboardBuilder,
|
|
75
|
+
KeyboardBuilder,
|
|
76
|
+
ReplyKeyboardBuilder,
|
|
77
|
+
)
|
|
78
|
+
|
|
73
79
|
if button_type == "inline":
|
|
74
|
-
reply_markup_builder: KeyboardBuilder = InlineKeyboardBuilder()
|
|
80
|
+
reply_markup_builder: "KeyboardBuilder" = InlineKeyboardBuilder()
|
|
75
81
|
button_list = [
|
|
76
82
|
InlineKeyboardButton(text=s["title"], callback_data=s["payload"])
|
|
77
83
|
for s in buttons
|
|
@@ -110,7 +116,7 @@ class TelegramOutput(Bot, OutputChannel):
|
|
|
110
116
|
)
|
|
111
117
|
return
|
|
112
118
|
|
|
113
|
-
await self.send_message(recipient_id, text, reply_markup=reply_markup)
|
|
119
|
+
await self.bot.send_message(recipient_id, text, reply_markup=reply_markup)
|
|
114
120
|
|
|
115
121
|
async def send_custom_json(
|
|
116
122
|
self, recipient_id: Text, json_message: Dict[Text, Any], **kwargs: Any
|
|
@@ -150,9 +156,17 @@ class TelegramOutput(Bot, OutputChannel):
|
|
|
150
156
|
for params in send_functions.keys():
|
|
151
157
|
if all(json_message.get(p) is not None for p in params):
|
|
152
158
|
args = [json_message.pop(p) for p in params]
|
|
153
|
-
api_call = getattr(self, send_functions[params])
|
|
159
|
+
api_call = getattr(self.bot, send_functions[params])
|
|
154
160
|
await api_call(recipient_id, *args, **json_message)
|
|
155
161
|
|
|
162
|
+
async def get_me(self) -> Any:
|
|
163
|
+
"""Get information about the bot itself."""
|
|
164
|
+
return await self.bot.get_me()
|
|
165
|
+
|
|
166
|
+
async def set_webhook(self, url: Text) -> None:
|
|
167
|
+
"""Set the webhook URL for telegram."""
|
|
168
|
+
await self.bot.set_webhook(url=url)
|
|
169
|
+
|
|
156
170
|
|
|
157
171
|
class TelegramInput(InputChannel):
|
|
158
172
|
"""Telegram input channel."""
|
|
@@ -185,19 +199,19 @@ class TelegramInput(InputChannel):
|
|
|
185
199
|
self.debug_mode = debug_mode
|
|
186
200
|
|
|
187
201
|
@staticmethod
|
|
188
|
-
def _is_location(message: Message) -> bool:
|
|
202
|
+
def _is_location(message: "Message") -> bool:
|
|
189
203
|
return message.location is not None
|
|
190
204
|
|
|
191
205
|
@staticmethod
|
|
192
|
-
def _is_user_message(message: Message) -> bool:
|
|
206
|
+
def _is_user_message(message: "Message") -> bool:
|
|
193
207
|
return message.text is not None
|
|
194
208
|
|
|
195
209
|
@staticmethod
|
|
196
|
-
def _is_edited_message(message: Update) -> bool:
|
|
210
|
+
def _is_edited_message(message: "Update") -> bool:
|
|
197
211
|
return message.edited_message is not None
|
|
198
212
|
|
|
199
213
|
@staticmethod
|
|
200
|
-
def _is_button(message: Update) -> bool:
|
|
214
|
+
def _is_button(message: "Update") -> bool:
|
|
201
215
|
return message.callback_query is not None
|
|
202
216
|
|
|
203
217
|
def blueprint(
|
|
@@ -223,6 +237,8 @@ class TelegramInput(InputChannel):
|
|
|
223
237
|
|
|
224
238
|
@telegram_webhook.route("/webhook", methods=["GET", "POST"])
|
|
225
239
|
async def message(request: Request) -> Any:
|
|
240
|
+
from aiogram.types import Update
|
|
241
|
+
|
|
226
242
|
if request.method == "POST":
|
|
227
243
|
request_dict = request.json
|
|
228
244
|
if isinstance(request_dict, Text):
|
|
@@ -322,6 +338,8 @@ class TelegramInput(InputChannel):
|
|
|
322
338
|
return TelegramOutput(self.access_token)
|
|
323
339
|
|
|
324
340
|
async def set_webhook(self, channel: TelegramOutput) -> None:
|
|
341
|
+
from aiogram.exceptions import TelegramAPIError
|
|
342
|
+
|
|
325
343
|
try:
|
|
326
344
|
await channel.set_webhook(url=self.webhook_url)
|
|
327
345
|
except TelegramAPIError as error:
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import copy
|
|
2
3
|
import json
|
|
3
4
|
import uuid
|
|
5
|
+
from collections import defaultdict
|
|
4
6
|
from dataclasses import asdict
|
|
5
7
|
from datetime import datetime, timedelta, timezone
|
|
6
|
-
from typing import Any, Awaitable, Callable, Dict, List, Optional, Text, Union
|
|
8
|
+
from typing import Any, Awaitable, Callable, Dict, List, Optional, Set, Text, Union
|
|
7
9
|
|
|
8
10
|
import structlog
|
|
9
11
|
from jsonschema import ValidationError, validate
|
|
@@ -223,6 +225,16 @@ class AudiocodesInput(InputChannel):
|
|
|
223
225
|
self.scheduler_job = None
|
|
224
226
|
self.keep_alive = keep_alive
|
|
225
227
|
self.keep_alive_expiration_factor = keep_alive_expiration_factor
|
|
228
|
+
self.background_tasks: Dict[Text, Set[asyncio.Task]] = defaultdict(set)
|
|
229
|
+
|
|
230
|
+
def _create_task(self, conversation_id: Text, coro: Awaitable[Any]) -> asyncio.Task:
|
|
231
|
+
"""Create and track an asyncio task for a conversation."""
|
|
232
|
+
task: asyncio.Task = asyncio.create_task(coro)
|
|
233
|
+
self.background_tasks[conversation_id].add(task)
|
|
234
|
+
task.add_done_callback(
|
|
235
|
+
lambda t: self.background_tasks[conversation_id].discard(t)
|
|
236
|
+
)
|
|
237
|
+
return task
|
|
226
238
|
|
|
227
239
|
async def _set_scheduler_job(self) -> None:
|
|
228
240
|
if self.scheduler_job:
|
|
@@ -251,11 +263,20 @@ class AudiocodesInput(InputChannel):
|
|
|
251
263
|
)
|
|
252
264
|
now = datetime.now(timezone.utc)
|
|
253
265
|
delta = timedelta(seconds=self.keep_alive * self.keep_alive_expiration_factor)
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
266
|
+
|
|
267
|
+
# clean up conversations
|
|
268
|
+
inactive = [
|
|
269
|
+
conv_id
|
|
270
|
+
for conv_id, conv in self.conversations.items()
|
|
271
|
+
if not conv.is_active_conversation(now, delta)
|
|
272
|
+
]
|
|
273
|
+
|
|
274
|
+
# cancel tasks and remove conversations
|
|
275
|
+
for conv_id in inactive:
|
|
276
|
+
for task in self.background_tasks[conv_id]:
|
|
277
|
+
task.cancel()
|
|
278
|
+
self.background_tasks.pop(conv_id, None)
|
|
279
|
+
self.conversations.pop(conv_id, None)
|
|
259
280
|
|
|
260
281
|
def handle_start_conversation(self, body: Dict[Text, Any]) -> Dict[Text, Any]:
|
|
261
282
|
conversation_id = body["conversation"]
|
|
@@ -347,31 +368,29 @@ class AudiocodesInput(InputChannel):
|
|
|
347
368
|
structlogger.debug("audiocodes.on_activities", conversation=conversation_id)
|
|
348
369
|
conversation = self._get_conversation(request.token, conversation_id)
|
|
349
370
|
if conversation is None:
|
|
371
|
+
structlogger.warning(
|
|
372
|
+
"audiocodes.on_activities.no_conversation", request=request.json
|
|
373
|
+
)
|
|
350
374
|
return response.json({})
|
|
351
375
|
elif conversation.ws:
|
|
352
376
|
ac_output: Union[WebsocketOutput, AudiocodesOutput] = WebsocketOutput(
|
|
353
377
|
conversation.ws, conversation_id
|
|
354
378
|
)
|
|
355
|
-
|
|
356
|
-
request.json,
|
|
357
|
-
output_channel=ac_output,
|
|
358
|
-
on_new_message=on_new_message,
|
|
359
|
-
)
|
|
360
|
-
return response.json({})
|
|
379
|
+
response_json = {}
|
|
361
380
|
else:
|
|
362
381
|
# handle non websocket case where messages get returned in json
|
|
363
382
|
ac_output = AudiocodesOutput()
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
383
|
+
response_json = {
|
|
384
|
+
"conversation": conversation_id,
|
|
385
|
+
"activities": ac_output.messages,
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
# start a background task to handle activities
|
|
389
|
+
self._create_task(
|
|
390
|
+
conversation_id,
|
|
391
|
+
conversation.handle_activities(request.json, ac_output, on_new_message),
|
|
392
|
+
)
|
|
393
|
+
return response.json(response_json)
|
|
375
394
|
|
|
376
395
|
@ac_webhook.route(
|
|
377
396
|
"/conversation/<conversation_id>/disconnect", methods=["POST"]
|
|
@@ -10,6 +10,7 @@ from typing import (
|
|
|
10
10
|
TypeVar,
|
|
11
11
|
)
|
|
12
12
|
|
|
13
|
+
import structlog
|
|
13
14
|
from websockets.legacy.client import WebSocketClientProtocol
|
|
14
15
|
|
|
15
16
|
from rasa.core.channels.voice_stream.asr.asr_event import ASREvent
|
|
@@ -20,6 +21,7 @@ from rasa.shared.utils.common import validate_environment
|
|
|
20
21
|
|
|
21
22
|
T = TypeVar("T", bound="ASREngineConfig")
|
|
22
23
|
E = TypeVar("E", bound="ASREngine")
|
|
24
|
+
logger = structlog.get_logger(__name__)
|
|
23
25
|
|
|
24
26
|
|
|
25
27
|
@dataclass
|
|
@@ -74,10 +76,14 @@ class ASREngine(Generic[T]):
|
|
|
74
76
|
"""Stream the events returned by the ASR system as it is fed audio bytes."""
|
|
75
77
|
if self.asr_socket is None:
|
|
76
78
|
raise ConnectionException("Websocket not connected.")
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
async for message in self.asr_socket:
|
|
82
|
+
asr_event = self.engine_event_to_asr_event(message)
|
|
83
|
+
if asr_event:
|
|
84
|
+
yield asr_event
|
|
85
|
+
except Exception as e:
|
|
86
|
+
logger.warning(f"Error while streaming ASR events: {e}")
|
|
81
87
|
|
|
82
88
|
def engine_event_to_asr_event(self, e: Any) -> Optional[ASREvent]:
|
|
83
89
|
"""Translate an engine event to a common ASREvent."""
|
|
@@ -18,6 +18,8 @@ from rasa.shared.exceptions import ConnectionException
|
|
|
18
18
|
class AzureASRConfig(ASREngineConfig):
|
|
19
19
|
language: Optional[str] = None
|
|
20
20
|
speech_region: Optional[str] = None
|
|
21
|
+
speech_host: Optional[str] = None
|
|
22
|
+
speech_endpoint: Optional[str] = None
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
class AzureASR(ASREngine[AzureASRConfig]):
|
|
@@ -52,9 +54,18 @@ class AzureASR(ASREngine[AzureASRConfig]):
|
|
|
52
54
|
async def connect(self) -> None:
|
|
53
55
|
import azure.cognitiveservices.speech as speechsdk
|
|
54
56
|
|
|
57
|
+
# connecting to eastus by default
|
|
58
|
+
if (
|
|
59
|
+
self.config.speech_region is None
|
|
60
|
+
and self.config.speech_host is None
|
|
61
|
+
and self.config.speech_endpoint is None
|
|
62
|
+
):
|
|
63
|
+
self.config.speech_region = "eastus"
|
|
55
64
|
speech_config = speechsdk.SpeechConfig(
|
|
56
65
|
subscription=os.environ[AZURE_SPEECH_API_KEY_ENV_VAR],
|
|
57
66
|
region=self.config.speech_region,
|
|
67
|
+
endpoint=self.config.speech_endpoint,
|
|
68
|
+
host=self.config.speech_host,
|
|
58
69
|
)
|
|
59
70
|
audio_format = speechsdk.audio.AudioStreamFormat(
|
|
60
71
|
samples_per_second=HERTZ,
|
|
@@ -123,7 +134,9 @@ class AzureASR(ASREngine[AzureASRConfig]):
|
|
|
123
134
|
|
|
124
135
|
@staticmethod
|
|
125
136
|
def get_default_config() -> AzureASRConfig:
|
|
126
|
-
return AzureASRConfig(
|
|
137
|
+
return AzureASRConfig(
|
|
138
|
+
language=None, speech_region=None, speech_host=None, speech_endpoint=None
|
|
139
|
+
)
|
|
127
140
|
|
|
128
141
|
@classmethod
|
|
129
142
|
def from_config_dict(cls, config: Dict) -> "AzureASR":
|