rasa-pro 3.12.0.dev12__py3-none-any.whl → 3.12.0.dev13__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/cli/inspect.py +20 -1
- rasa/cli/shell.py +3 -3
- rasa/core/actions/action.py +20 -7
- rasa/core/actions/action_handle_digressions.py +142 -0
- rasa/core/actions/forms.py +10 -5
- rasa/core/channels/__init__.py +2 -0
- rasa/core/channels/voice_ready/audiocodes.py +42 -23
- rasa/core/channels/voice_stream/browser_audio.py +1 -0
- rasa/core/channels/voice_stream/call_state.py +7 -1
- rasa/core/channels/voice_stream/genesys.py +331 -0
- rasa/core/channels/voice_stream/tts/azure.py +2 -1
- rasa/core/channels/voice_stream/tts/cartesia.py +16 -3
- rasa/core/channels/voice_stream/twilio_media_streams.py +2 -1
- rasa/core/channels/voice_stream/voice_channel.py +2 -1
- rasa/core/migrate.py +2 -2
- rasa/core/policies/flows/flow_executor.py +36 -42
- rasa/core/run.py +4 -3
- rasa/dialogue_understanding/commands/can_not_handle_command.py +2 -2
- rasa/dialogue_understanding/commands/cancel_flow_command.py +62 -4
- rasa/dialogue_understanding/commands/change_flow_command.py +2 -2
- rasa/dialogue_understanding/commands/chit_chat_answer_command.py +2 -2
- rasa/dialogue_understanding/commands/clarify_command.py +2 -2
- 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 +2 -2
- rasa/dialogue_understanding/commands/knowledge_answer_command.py +2 -2
- rasa/dialogue_understanding/commands/repeat_bot_messages_command.py +2 -2
- rasa/dialogue_understanding/commands/set_slot_command.py +7 -15
- rasa/dialogue_understanding/commands/skip_question_command.py +2 -2
- rasa/dialogue_understanding/commands/start_flow_command.py +43 -2
- rasa/dialogue_understanding/commands/utils.py +1 -1
- rasa/dialogue_understanding/constants.py +1 -0
- rasa/dialogue_understanding/generator/command_generator.py +110 -73
- rasa/dialogue_understanding/generator/command_parser.py +1 -1
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +161 -3
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +10 -2
- rasa/dialogue_understanding/generator/nlu_command_adapter.py +44 -3
- rasa/dialogue_understanding/generator/single_step/command_prompt_template.jinja2 +53 -79
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +11 -19
- rasa/dialogue_understanding/generator/utils.py +32 -1
- rasa/dialogue_understanding/patterns/correction.py +13 -1
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +62 -2
- rasa/dialogue_understanding/patterns/handle_digressions.py +81 -0
- rasa/dialogue_understanding/processor/command_processor.py +115 -28
- rasa/dialogue_understanding/utils.py +31 -0
- rasa/dialogue_understanding_test/README.md +50 -0
- rasa/dialogue_understanding_test/test_case_simulation/test_case_tracker_simulator.py +3 -3
- rasa/model_manager/warm_rasa_process.py +0 -1
- rasa/model_training.py +24 -27
- rasa/shared/core/constants.py +28 -3
- rasa/shared/core/domain.py +13 -20
- rasa/shared/core/events.py +13 -2
- rasa/shared/core/flows/flow.py +17 -0
- rasa/shared/core/flows/flows_yaml_schema.json +38 -0
- rasa/shared/core/flows/steps/collect.py +18 -1
- rasa/shared/core/flows/utils.py +16 -1
- rasa/shared/core/slot_mappings.py +144 -108
- rasa/shared/core/slots.py +23 -2
- rasa/shared/core/trackers.py +3 -1
- rasa/shared/nlu/constants.py +1 -0
- rasa/shared/utils/llm.py +1 -1
- rasa/shared/utils/schemas/domain.yml +0 -1
- rasa/telemetry.py +43 -13
- rasa/utils/common.py +0 -1
- rasa/validator.py +189 -82
- rasa/version.py +1 -1
- {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0.dev13.dist-info}/METADATA +1 -1
- {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0.dev13.dist-info}/RECORD +71 -67
- {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0.dev13.dist-info}/NOTICE +0 -0
- {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0.dev13.dist-info}/WHEEL +0 -0
- {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0.dev13.dist-info}/entry_points.txt +0 -0
rasa/cli/inspect.py
CHANGED
|
@@ -9,6 +9,10 @@ from rasa import telemetry
|
|
|
9
9
|
from rasa.cli import SubParsersAction
|
|
10
10
|
from rasa.cli.arguments import shell as arguments
|
|
11
11
|
from rasa.core import constants
|
|
12
|
+
from rasa.engine.storage.local_model_storage import LocalModelStorage
|
|
13
|
+
from rasa.exceptions import ModelNotFound
|
|
14
|
+
from rasa.model import get_local_model
|
|
15
|
+
from rasa.shared.utils.cli import print_error
|
|
12
16
|
from rasa.utils.cli import remove_argument_from_parser
|
|
13
17
|
|
|
14
18
|
|
|
@@ -55,6 +59,8 @@ async def open_inspector_in_browser(server_url: Text, voice: bool = False) -> No
|
|
|
55
59
|
def inspect(args: argparse.Namespace) -> None:
|
|
56
60
|
"""Inspect the bot using the most recent model."""
|
|
57
61
|
import rasa.cli.run
|
|
62
|
+
from rasa.cli.utils import get_validated_path
|
|
63
|
+
from rasa.shared.constants import DEFAULT_MODELS_PATH
|
|
58
64
|
|
|
59
65
|
async def after_start_hook_open_inspector(_: Sanic, __: AbstractEventLoop) -> None:
|
|
60
66
|
"""Hook to open the browser on server start."""
|
|
@@ -71,5 +77,18 @@ def inspect(args: argparse.Namespace) -> None:
|
|
|
71
77
|
args.credentials = None
|
|
72
78
|
args.server_listeners = [(after_start_hook_open_inspector, "after_server_start")]
|
|
73
79
|
|
|
74
|
-
|
|
80
|
+
model = get_validated_path(args.model, "model", DEFAULT_MODELS_PATH)
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
model = get_local_model(model)
|
|
84
|
+
except ModelNotFound:
|
|
85
|
+
print_error(
|
|
86
|
+
"No model found. Train a model before running the "
|
|
87
|
+
"server using `rasa train`."
|
|
88
|
+
)
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
metadata = LocalModelStorage.metadata_from_archive(model)
|
|
92
|
+
|
|
93
|
+
telemetry.track_inspect_started(args.connector, metadata.assistant_id)
|
|
75
94
|
rasa.cli.run.run(args)
|
rasa/cli/shell.py
CHANGED
|
@@ -95,7 +95,7 @@ def shell_nlu(args: argparse.Namespace) -> None:
|
|
|
95
95
|
)
|
|
96
96
|
return
|
|
97
97
|
|
|
98
|
-
telemetry.track_shell_started("nlu")
|
|
98
|
+
telemetry.track_shell_started("nlu", metadata.assistant_id)
|
|
99
99
|
rasa.nlu.run.run_cmdline(model)
|
|
100
100
|
|
|
101
101
|
|
|
@@ -129,12 +129,12 @@ def shell(args: argparse.Namespace) -> None:
|
|
|
129
129
|
if metadata.training_type == TrainingType.NLU:
|
|
130
130
|
import rasa.nlu.run
|
|
131
131
|
|
|
132
|
-
telemetry.track_shell_started("nlu")
|
|
132
|
+
telemetry.track_shell_started("nlu", metadata.assistant_id)
|
|
133
133
|
|
|
134
134
|
rasa.nlu.run.run_cmdline(model)
|
|
135
135
|
else:
|
|
136
136
|
import rasa.cli.run
|
|
137
137
|
|
|
138
|
-
telemetry.track_shell_started("rasa")
|
|
138
|
+
telemetry.track_shell_started("rasa", metadata.assistant_id)
|
|
139
139
|
|
|
140
140
|
rasa.cli.run.run(args)
|
rasa/core/actions/action.py
CHANGED
|
@@ -73,9 +73,9 @@ from rasa.shared.core.constants import (
|
|
|
73
73
|
ACTION_VALIDATE_SLOT_MAPPINGS,
|
|
74
74
|
DEFAULT_SLOT_NAMES,
|
|
75
75
|
KNOWLEDGE_BASE_SLOT_NAMES,
|
|
76
|
-
MAPPING_TYPE,
|
|
77
76
|
REQUESTED_SLOT,
|
|
78
77
|
USER_INTENT_OUT_OF_SCOPE,
|
|
78
|
+
SetSlotExtractor,
|
|
79
79
|
SlotMappingType,
|
|
80
80
|
)
|
|
81
81
|
from rasa.shared.core.domain import Domain
|
|
@@ -111,6 +111,7 @@ if TYPE_CHECKING:
|
|
|
111
111
|
from rasa.core.channels.channel import OutputChannel
|
|
112
112
|
from rasa.core.nlg import NaturalLanguageGenerator
|
|
113
113
|
from rasa.shared.core.events import IntentPrediction
|
|
114
|
+
from rasa.shared.core.slot_mappings import SlotMapping
|
|
114
115
|
|
|
115
116
|
logger = logging.getLogger(__name__)
|
|
116
117
|
|
|
@@ -118,6 +119,10 @@ logger = logging.getLogger(__name__)
|
|
|
118
119
|
def default_actions(action_endpoint: Optional[EndpointConfig] = None) -> List["Action"]:
|
|
119
120
|
"""List default actions."""
|
|
120
121
|
from rasa.core.actions.action_clean_stack import ActionCleanStack
|
|
122
|
+
from rasa.core.actions.action_handle_digressions import (
|
|
123
|
+
ActionBlockDigressions,
|
|
124
|
+
ActionContinueDigression,
|
|
125
|
+
)
|
|
121
126
|
from rasa.core.actions.action_hangup import ActionHangup
|
|
122
127
|
from rasa.core.actions.action_repeat_bot_messages import ActionRepeatBotMessages
|
|
123
128
|
from rasa.core.actions.action_run_slot_rejections import ActionRunSlotRejections
|
|
@@ -152,6 +157,8 @@ def default_actions(action_endpoint: Optional[EndpointConfig] = None) -> List["A
|
|
|
152
157
|
ActionResetRouting(),
|
|
153
158
|
ActionHangup(),
|
|
154
159
|
ActionRepeatBotMessages(),
|
|
160
|
+
ActionBlockDigressions(),
|
|
161
|
+
ActionContinueDigression(),
|
|
155
162
|
]
|
|
156
163
|
|
|
157
164
|
|
|
@@ -940,7 +947,14 @@ class RemoteAction(Action):
|
|
|
940
947
|
)
|
|
941
948
|
|
|
942
949
|
events = rasa.shared.core.events.deserialise_events(events_json)
|
|
943
|
-
|
|
950
|
+
|
|
951
|
+
processed_events = []
|
|
952
|
+
for event in events:
|
|
953
|
+
if isinstance(event, SlotSet) and event.filled_by is None:
|
|
954
|
+
event.filled_by = SetSlotExtractor.CUSTOM.value
|
|
955
|
+
processed_events.append(event)
|
|
956
|
+
|
|
957
|
+
return cast(List[Event], bot_messages) + processed_events
|
|
944
958
|
|
|
945
959
|
def name(self) -> Text:
|
|
946
960
|
return self._name
|
|
@@ -1208,7 +1222,7 @@ class ActionExtractSlots(Action):
|
|
|
1208
1222
|
|
|
1209
1223
|
async def _execute_custom_action(
|
|
1210
1224
|
self,
|
|
1211
|
-
mapping:
|
|
1225
|
+
mapping: "SlotMapping",
|
|
1212
1226
|
executed_custom_actions: Set[Text],
|
|
1213
1227
|
output_channel: "OutputChannel",
|
|
1214
1228
|
nlg: "NaturalLanguageGenerator",
|
|
@@ -1216,7 +1230,7 @@ class ActionExtractSlots(Action):
|
|
|
1216
1230
|
domain: "Domain",
|
|
1217
1231
|
calm_custom_action_names: Optional[Set[str]] = None,
|
|
1218
1232
|
) -> Tuple[List[Event], Set[Text]]:
|
|
1219
|
-
custom_action = mapping.
|
|
1233
|
+
custom_action = mapping.run_action_every_turn
|
|
1220
1234
|
|
|
1221
1235
|
if not custom_action or custom_action in executed_custom_actions:
|
|
1222
1236
|
return [], executed_custom_actions
|
|
@@ -1317,10 +1331,9 @@ class ActionExtractSlots(Action):
|
|
|
1317
1331
|
slot_events.append(SlotSet(slot.name, slot_value))
|
|
1318
1332
|
|
|
1319
1333
|
for mapping in slot.mappings:
|
|
1320
|
-
|
|
1321
|
-
should_fill_custom_slot = mapping_type == SlotMappingType.CUSTOM
|
|
1334
|
+
should_fill_controlled_slot = mapping.type == SlotMappingType.CONTROLLED
|
|
1322
1335
|
|
|
1323
|
-
if
|
|
1336
|
+
if should_fill_controlled_slot:
|
|
1324
1337
|
(
|
|
1325
1338
|
custom_evts,
|
|
1326
1339
|
executed_custom_actions,
|
|
@@ -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
|
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,6 +32,7 @@ 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
|
|
36
37
|
|
|
37
38
|
input_channel_classes: List[Type[InputChannel]] = [
|
|
@@ -55,6 +56,7 @@ input_channel_classes: List[Type[InputChannel]] = [
|
|
|
55
56
|
JambonzVoiceReadyInput,
|
|
56
57
|
TwilioMediaStreamsInputChannel,
|
|
57
58
|
BrowserAudioInputChannel,
|
|
59
|
+
GenesysInputChannel,
|
|
58
60
|
StudioChatInput,
|
|
59
61
|
]
|
|
60
62
|
|
|
@@ -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"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from contextvars import ContextVar
|
|
3
|
-
from dataclasses import dataclass
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
4
|
from typing import Optional
|
|
5
5
|
|
|
6
6
|
from werkzeug.local import LocalProxy
|
|
@@ -19,6 +19,12 @@ class CallState:
|
|
|
19
19
|
should_hangup: bool = False
|
|
20
20
|
connection_failed: bool = False
|
|
21
21
|
|
|
22
|
+
# Genesys requires the server and client each maintain a
|
|
23
|
+
# monotonically increasing message sequence number.
|
|
24
|
+
client_sequence_number: int = 0
|
|
25
|
+
server_sequence_number: int = 0
|
|
26
|
+
audio_buffer: bytearray = field(default_factory=bytearray)
|
|
27
|
+
|
|
22
28
|
|
|
23
29
|
_call_state: ContextVar[CallState] = ContextVar("call_state")
|
|
24
30
|
call_state = LocalProxy(_call_state)
|